8219958: Automatically load taglets from a jar file
authorpmuthuswamy
Wed, 20 Mar 2019 09:58:12 +0530
changeset 54198 6ba98ff89499
parent 54197 ddfb658c8ce3
child 54199 40296a51aeb0
8219958: Automatically load taglets from a jar file Reviewed-by: jjg
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java
test/langtools/jdk/javadoc/doclet/testAutoLoadTaglets/TestAutoLoadTaglets.java
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Tue Mar 19 15:18:35 2019 -0400
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Wed Mar 20 09:58:12 2019 +0530
@@ -837,39 +837,47 @@
         tagletManager = tagletManager == null ?
                 new TagletManager(nosince, showversion, showauthor, javafx, this) :
                 tagletManager;
-        for (List<String> args : customTagStrs) {
-            if (args.get(0).equals("-taglet")) {
-                tagletManager.addCustomTag(args.get(1), getFileManager(), tagletpath);
-                continue;
+        JavaFileManager fileManager = getFileManager();
+        Messages messages = getMessages();
+        try {
+            tagletManager.initTagletPath(fileManager, tagletpath);
+            tagletManager.loadTaglets(fileManager);
+
+            for (List<String> args : customTagStrs) {
+                if (args.get(0).equals("-taglet")) {
+                    tagletManager.addCustomTag(args.get(1), fileManager);
+                    continue;
+                }
+                List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3);
+                switch (tokens.size()) {
+                    case 1:
+                        String tagName = args.get(1);
+                        if (tagletManager.isKnownCustomTag(tagName)) {
+                            //reorder a standard tag
+                            tagletManager.addNewSimpleCustomTag(tagName, null, "");
+                        } else {
+                            //Create a simple tag with the heading that has the same name as the tag.
+                            StringBuilder heading = new StringBuilder(tagName + ":");
+                            heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0)));
+                            tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a");
+                        }
+                        break;
+
+                    case 2:
+                        //Add simple taglet without heading, probably to excluding it in the output.
+                        tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), "");
+                        break;
+
+                    case 3:
+                        tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1));
+                        break;
+
+                    default:
+                        messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1));
+                }
             }
-            List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3);
-            switch (tokens.size()) {
-                case 1:
-                    String tagName = args.get(1);
-                    if (tagletManager.isKnownCustomTag(tagName)) {
-                        //reorder a standard tag
-                        tagletManager.addNewSimpleCustomTag(tagName, null, "");
-                    } else {
-                        //Create a simple tag with the heading that has the same name as the tag.
-                        StringBuilder heading = new StringBuilder(tagName + ":");
-                        heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0)));
-                        tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a");
-                    }
-                    break;
-
-                case 2:
-                    //Add simple taglet without heading, probably to excluding it in the output.
-                    tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), "");
-                    break;
-
-                case 3:
-                    tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1));
-                    break;
-
-                default:
-                    Messages messages = getMessages();
-                    messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1));
-            }
+        } catch (IOException e) {
+            messages.error("doclet.taglet_could_not_set_location", e.toString());
         }
     }
 
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Tue Mar 19 15:18:35 2019 -0400
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Wed Mar 20 09:58:12 2019 +0530
@@ -56,6 +56,8 @@
 doclet.Notice_taglet_conflict_warn=Note: Custom tags that could override future standard tags: {0}. To avoid potential overrides, use at least one period character (.) in custom tag names.
 doclet.Error_taglet_not_registered=Error - Exception {0} thrown while trying to register Taglet {1}...
 doclet.Error_invalid_custom_tag_argument=Error - {0} is an invalid argument to the -tag option...
+doclet.taglet_could_not_set_location = Could not set the taglet path: {0}
+doclet.not_standard_file_manager = Cannot set taglet path; the file manager is not a StandardJavaFileManager
 doclet.Author=Author:
 doclet.DefaultValue=Default value:
 doclet.PropertyDescription=Property description:
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java	Tue Mar 19 15:18:35 2019 -0400
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java	Wed Mar 20 09:58:12 2019 +0530
@@ -204,45 +204,78 @@
     }
 
     /**
-     * Add a new {@code Taglet}.  Print a message to indicate whether or not
+     * Initializes the location TAGLET_PATH which is used to locate the custom taglets.
+     * @param fileManager the filemanager to load classes and resources.
+     * @param tagletPath the path to the custom taglet.
+     * @throws IOException if an error occurs while setting the location.
+     */
+    public void initTagletPath(JavaFileManager fileManager, String tagletPath) throws IOException {
+        if (fileManager instanceof StandardJavaFileManager) {
+            StandardJavaFileManager sfm = (StandardJavaFileManager)fileManager;
+            if (tagletPath != null) {
+                List<File> paths = new ArrayList<>();
+                for (String pathname : tagletPath.split(File.pathSeparator)) {
+                    paths.add(new File(pathname));
+                }
+                sfm.setLocation(TAGLET_PATH, paths);
+            } else if (!sfm.hasLocation(TAGLET_PATH)) {
+                sfm.setLocation(TAGLET_PATH, Collections.emptyList());
+            }
+        } else if (tagletPath != null) {
+            messages.error("doclet.not_standard_file_manager");
+        }
+    }
+
+    /**
+     * Adds a new {@code Taglet}.  Print a message to indicate whether or not
      * the Taglet was registered properly.
      * @param classname  the name of the class representing the custom tag.
      * @param fileManager the filemanager to load classes and resources.
-     * @param tagletPath  the path to the class representing the custom tag.
      */
-    public void addCustomTag(String classname, JavaFileManager fileManager, String tagletPath) {
+    public void addCustomTag(String classname, JavaFileManager fileManager) {
         try {
             ClassLoader tagClassLoader;
-            if (!fileManager.hasLocation(TAGLET_PATH)) {
-                List<File> paths = new ArrayList<>();
-                if (tagletPath != null) {
-                    for (String pathname : tagletPath.split(File.pathSeparator)) {
-                        paths.add(new File(pathname));
-                    }
-                }
-                if (fileManager instanceof StandardJavaFileManager) {
-                    ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, paths);
-                }
-            }
             tagClassLoader = fileManager.getClassLoader(TAGLET_PATH);
             Class<? extends jdk.javadoc.doclet.Taglet> customTagClass =
                     tagClassLoader.loadClass(classname).asSubclass(jdk.javadoc.doclet.Taglet.class);
             jdk.javadoc.doclet.Taglet instance = customTagClass.getConstructor().newInstance();
-            instance.init(docEnv, doclet);
-            Taglet newLegacy = new UserTaglet(instance);
-            String tname = newLegacy.getName();
-            Taglet t = allTaglets.get(tname);
-            if (t != null) {
-                allTaglets.remove(tname);
+            registerTaglet(instance);
+        } catch (ReflectiveOperationException exc) {
+            messages.error("doclet.Error_taglet_not_registered", exc.getClass().getName(),
+                    classname);
+        }
+    }
+
+    /**
+     * Loads taglets from a taglet path using service loader.
+     * @param fileManager the filemanager to load the taglets.
+     * @throws IOException if an error occurs while getting the service loader.
+     */
+    public void loadTaglets(JavaFileManager fileManager) throws IOException {
+        Iterable<? extends File> location = ((StandardJavaFileManager)fileManager).getLocation(TAGLET_PATH);
+        if (location != null && location.iterator().hasNext()) {
+            ServiceLoader<jdk.javadoc.doclet.Taglet> serviceLoader =
+                    fileManager.getServiceLoader(TAGLET_PATH, jdk.javadoc.doclet.Taglet.class);
+            Iterator<jdk.javadoc.doclet.Taglet> iterator = serviceLoader.iterator();
+            while (iterator.hasNext()) {
+                jdk.javadoc.doclet.Taglet taglet = iterator.next();
+                registerTaglet(taglet);
             }
-            allTaglets.put(tname, newLegacy);
-            messages.notice("doclet.Notice_taglet_registered", classname);
-        } catch (Exception exc) {
-            messages.error("doclet.Error_taglet_not_registered", exc.getClass().getName(), classname);
         }
     }
 
     /**
+     * Registers the {@code Taglet}. Prints a message if a {@code Taglet} got registered properly.
+     * @param instance the {@code Taglet} instance.
+     */
+    private void registerTaglet(jdk.javadoc.doclet.Taglet instance) {
+        instance.init(docEnv, doclet);
+        Taglet newLegacy = new UserTaglet(instance);
+        allTaglets.put(newLegacy.getName(), newLegacy);
+        messages.notice("doclet.Notice_taglet_registered", instance.getClass().getName());
+    }
+
+    /**
      * Add a new {@code SimpleTaglet}.  If this tag already exists
      * and the header passed as an argument is null, move tag to the back of the
      * list. If this tag already exists and the header passed as an argument is
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/jdk/javadoc/doclet/testAutoLoadTaglets/TestAutoLoadTaglets.java	Wed Mar 20 09:58:12 2019 +0530
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8219958
+ * @summary Automatically load taglets from a jar file
+ * @library /tools/lib ../../lib
+ * @modules
+ *     jdk.javadoc/jdk.javadoc.internal.tool
+ *     jdk.compiler/com.sun.tools.javac.api
+ *     jdk.compiler/com.sun.tools.javac.main
+ * @build javadoc.tester.* toolbox.ToolBox builder.ClassBuilder toolbox.JarTask
+ * @run main/othervm TestAutoLoadTaglets
+ */
+
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import builder.ClassBuilder;
+import toolbox.JarTask;
+import toolbox.JavacTask;
+import toolbox.ToolBox;
+
+import javadoc.tester.JavadocTester;
+
+public class TestAutoLoadTaglets extends JavadocTester {
+
+    final ToolBox tb;
+
+    public static void main(String... args) throws Exception {
+        TestAutoLoadTaglets tester = new TestAutoLoadTaglets();
+        tester.runTests(m -> new Object[]{Paths.get(m.getName())});
+    }
+
+    TestAutoLoadTaglets() {
+        tb = new ToolBox();
+    }
+
+    @Test
+    public void test(Path base) throws Exception {
+        Path srcDir = base.resolve("src");
+        Path outDir = base.resolve("out");
+
+        createTagletsJar(base, srcDir);
+
+        new ClassBuilder(tb, "pkg.A")
+                .setComments("test {@taglet1} and {@taglet2}")
+                .setModifiers("public", "class")
+                .write(srcDir);
+
+        javadoc("-d", outDir.toString(),
+                "-sourcepath", srcDir.toString(),
+                "-tagletpath", "taglets.jar",
+                "pkg");
+
+        checkExit(Exit.OK);
+
+        checkOutput("pkg/A.html", true,
+                "test user taglet taglet1 and user taglet taglet2");
+    }
+
+    private void createTagletsJar(Path base, Path srcDir) throws Exception {
+        Path classes = base.resolve("classes");
+        tb.createDirectories(classes);
+        createTaglets(srcDir);
+
+        new JavacTask(tb).files(srcDir.resolve("Taglet1.java"), srcDir.resolve("Taglet2.java"))
+                .outdir(classes).run();
+
+        Path services = classes.resolve("META-INF").resolve("services").resolve("jdk.javadoc.doclet.Taglet");
+        tb.writeFile(services,
+                "Taglet1\n"
+                + "Taglet2");
+
+        new JarTask(tb, srcDir).run("cf", "taglets.jar", "-C", classes.toString(), ".");
+    }
+
+    private void createTaglets(Path srcDir) throws Exception {
+        for (int i = 1; i < 3; i++) {
+            tb.writeJavaFiles(srcDir,
+                    "import com.sun.source.doctree.DocTree;\n"
+                    + "import jdk.javadoc.doclet.Taglet;\n"
+                    + "import javax.lang.model.element.Element;\n"
+                    + "import java.util.List;\n"
+                    + "import java.util.Set;\n"
+                    + "public class Taglet" + i + " implements Taglet {\n"
+                    + "    @Override\n"
+                    + "    public Set<Location> getAllowedLocations() {\n"
+                    + "        return null;\n"
+                    + "    }\n"
+                    + "    @Override\n"
+                    + "    public boolean isInlineTag() {\n"
+                    + "        return true;\n"
+                    + "    }\n"
+                    + "    @Override\n"
+                    + "    public String getName() {\n"
+                    + "        return \"taglet" + i + "\";\n"
+                    + "    }\n"
+                    + "    @Override\n"
+                    + "    public String toString(List<? extends DocTree> tags, Element "
+                    + "element) {\n"
+                    + "        return \"user taglet taglet" + i + "\";\n"
+                    + "    }\n"
+                    + "}\n");
+        }
+    }
+
+}