# HG changeset patch # User pmuthuswamy # Date 1553056092 -19800 # Node ID 6ba98ff894999540f20588d4a9686d6930997d31 # Parent ddfb658c8ce3f7e8a0eac60234407e1be2fcb4b9 8219958: Automatically load taglets from a jar file Reviewed-by: jjg diff -r ddfb658c8ce3 -r 6ba98ff89499 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.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 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 args : customTagStrs) { + if (args.get(0).equals("-taglet")) { + tagletManager.addCustomTag(args.get(1), fileManager); + continue; + } + List 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 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()); } } diff -r ddfb658c8ce3 -r 6ba98ff89499 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties --- 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: diff -r ddfb658c8ce3 -r 6ba98ff89499 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java --- 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 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 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 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 location = ((StandardJavaFileManager)fileManager).getLocation(TAGLET_PATH); + if (location != null && location.iterator().hasNext()) { + ServiceLoader serviceLoader = + fileManager.getServiceLoader(TAGLET_PATH, jdk.javadoc.doclet.Taglet.class); + Iterator 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 diff -r ddfb658c8ce3 -r 6ba98ff89499 test/langtools/jdk/javadoc/doclet/testAutoLoadTaglets/TestAutoLoadTaglets.java --- /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 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 tags, Element " + + "element) {\n" + + " return \"user taglet taglet" + i + "\";\n" + + " }\n" + + "}\n"); + } + } + +}