8184969: Cannot specify multiple -link to jdk9 javadoc
authorksrini
Fri, 28 Jul 2017 15:00:53 -0700
changeset 46079 059faa5e1267
parent 46078 35994b07ed5e
child 46080 65ccd412049b
8184969: Cannot specify multiple -link to jdk9 javadoc Reviewed-by: jjg
langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java
langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Extern.java
langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java
langtools/test/jdk/javadoc/doclet/testLinkOption/TestLinkOption.java
langtools/test/jdk/javadoc/doclet/testLinkOption/pkg3/A.java
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Fri Jul 28 14:29:29 2017 -0700
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Fri Jul 28 15:00:53 2017 -0700
@@ -55,6 +55,7 @@
 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException;
 import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog;
 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
+import jdk.javadoc.internal.doclets.toolkit.util.Utils.Pair;
 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap;
 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.GetterSetter;
 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.Kind;
@@ -66,11 +67,11 @@
  * BaseConfiguration, to configure and add their own options. This class contains
  * all user options which are supported by the 1.1 doclet and the standard
  * doclet.
- *
- *  <p><b>This is NOT part of any supported API.
- *  If you write code that depends on this, you do so at your own risk.
- *  This code and its internal interfaces are subject to change or
- *  deletion without notice.</b>
+ * <p>
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
  *
  * @author Robert Field.
  * @author Atul Dambalkar.
@@ -240,7 +241,6 @@
 
     /**
      * Sourcepath from where to read the source files. Default is classpath.
-     *
      */
     public String sourcepath = "";
 
@@ -266,7 +266,7 @@
      * True if user wants to suppress time stamp in output.
      * Default is false.
      */
-    public boolean notimestamp= false;
+    public boolean notimestamp = false;
 
     /**
      * The package grouping instance.
@@ -278,7 +278,7 @@
      */
     public final Extern extern = new Extern(this);
 
-    public  Reporter reporter;
+    public Reporter reporter;
 
     public Locale locale;
 
@@ -287,21 +287,21 @@
      */
     public boolean quiet = false;
 
-    private String urlForLink;
-
-    private String pkglistUrlForLink;
+    // A list containing urls
+    private final List<String> linkList = new ArrayList<>();
 
-    private String urlForLinkOffline;
+     // A list of pairs containing urls and package list
+    private final List<Pair<String, String>> linkOfflineList = new ArrayList<>();
 
-    private String pkglistUrlForLinkOffline;
 
     public boolean dumpOnError = false;
 
-    private List<GroupContainer> groups;
+    private List<Pair<String, String>> groupPairs;
 
     private final Map<TypeElement, EnumMap<Kind, Reference<VisibleMemberMap>>> typeElementMemberCache;
 
     public abstract Messages getMessages();
+
     public abstract Resources getResources();
 
     /**
@@ -342,15 +342,17 @@
      */
     public SortedMap<ModuleElement, Set<PackageElement>> modulePackages;
 
-   /**
-    * The list of known modules, that should be documented.
-    */
+    /**
+     * The list of known modules, that should be documented.
+     */
     public SortedSet<ModuleElement> modules;
 
     protected static final String sharedResourceBundleName =
             "jdk.javadoc.internal.doclets.toolkit.resources.doclets";
+
     /**
      * Constructs the configurations needed by the doclet.
+     *
      * @param doclet the doclet that created this configuration
      */
     public BaseConfiguration(Doclet doclet) {
@@ -359,7 +361,7 @@
         excludedQualifiers = new HashSet<>();
         setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH);
         metakeywords = new MetaKeywords(this);
-        groups = new ArrayList<>(0);
+        groupPairs = new ArrayList<>(0);
         typeElementMemberCache = new HashMap<>();
     }
 
@@ -400,31 +402,37 @@
     }
 
     private Set<ModuleElement> specifiedModuleElements;
+
     public Set<ModuleElement> getSpecifiedModuleElements() {
         return specifiedModuleElements;
     }
 
     private Set<PackageElement> specifiedPackageElements;
+
     public Set<PackageElement> getSpecifiedPackageElements() {
         return specifiedPackageElements;
     }
 
     private Set<TypeElement> specifiedTypeElements;
+
     public Set<TypeElement> getSpecifiedTypeElements() {
         return specifiedTypeElements;
     }
 
     private Set<ModuleElement> includedModuleElements;
+
     public Set<ModuleElement> getIncludedModuleElements() {
         return includedModuleElements;
     }
 
     private Set<PackageElement> includedPackageElements;
+
     public Set<PackageElement> getIncludedPackageElements() {
         return includedPackageElements;
     }
 
     private Set<TypeElement> includedTypeElements;
+
     public Set<TypeElement> getIncludedTypeElements() {
         return includedTypeElements;
     }
@@ -435,7 +443,7 @@
         modules.addAll(getSpecifiedModuleElements());
 
         modulePackages = new TreeMap<>(utils.makeModuleComparator());
-        for (PackageElement p: packages) {
+        for (PackageElement p : packages) {
             ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p);
             if (mdle != null && !mdle.isUnnamed()) {
                 Set<PackageElement> s = modulePackages
@@ -444,7 +452,7 @@
             }
         }
 
-        for (PackageElement p: getIncludedPackageElements()) {
+        for (PackageElement p : getIncludedPackageElements()) {
             ModuleElement mdle = docEnv.getElementUtils().getModuleOf(p);
             if (mdle != null && !mdle.isUnnamed()) {
                 Set<PackageElement> s = modulePackages
@@ -474,207 +482,205 @@
     public Set<Doclet.Option> getSupportedOptions() {
         Resources resources = getResources();
         Doclet.Option[] options = {
-            new Option(resources, "-author") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    showauthor = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-d", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    destDirName = addTrailingFileSep(args.get(0));
-                    return true;
-                }
-            },
-            new Option(resources, "-docencoding", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    docencoding = args.get(0);
-                    return true;
-                }
-            },
-            new Option(resources, "-docfilessubdirs") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    copydocfilesubdirs = true;
-                    return true;
-                }
-            },
-            new Hidden(resources, "-encoding", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    encoding = args.get(0);
-                    return true;
-                }
-            },
-            new Option(resources, "-excludedocfilessubdir", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    addToSet(excludedDocFileDirs, args.get(0));
-                    return true;
-                }
-            },
-            new Option(resources, "-group", 2) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    groups.add(new GroupContainer(args.get(0), args.get(1)));
-                    return true;
-                }
-            },
-            new Option(resources, "--javafx -javafx") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    javafx = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-keywords") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    keywords = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-link", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    urlForLink = args.get(0);
-                    pkglistUrlForLink = urlForLink;
-                    return true;
-                }
-            },
-            new Option(resources, "-linksource") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    linksource = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-linkoffline", 2) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    urlForLinkOffline = args.get(0);
-                    pkglistUrlForLinkOffline = args.get(1);
-                    return true;
-                }
-            },
-            new Option(resources, "-nocomment") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    nocomment = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-nodeprecated") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    nodeprecated = true;
-                    return true;
+                new Option(resources, "-author") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        showauthor = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-d", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        destDirName = addTrailingFileSep(args.get(0));
+                        return true;
+                    }
+                },
+                new Option(resources, "-docencoding", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        docencoding = args.get(0);
+                        return true;
+                    }
+                },
+                new Option(resources, "-docfilessubdirs") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        copydocfilesubdirs = true;
+                        return true;
+                    }
+                },
+                new Hidden(resources, "-encoding", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        encoding = args.get(0);
+                        return true;
+                    }
+                },
+                new Option(resources, "-excludedocfilessubdir", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        addToSet(excludedDocFileDirs, args.get(0));
+                        return true;
+                    }
+                },
+                new Option(resources, "-group", 2) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        groupPairs.add(new Pair<>(args.get(0), args.get(1)));
+                        return true;
+                    }
+                },
+                new Option(resources, "--javafx -javafx") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        javafx = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-keywords") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        keywords = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-link", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        linkList.add(args.get(0));
+                        return true;
+                    }
+                },
+                new Option(resources, "-linksource") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        linksource = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-linkoffline", 2) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        linkOfflineList.add(new Pair<String, String>(args.get(0), args.get(1)));
+                        return true;
+                    }
+                },
+                new Option(resources, "-nocomment") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        nocomment = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-nodeprecated") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        nodeprecated = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-nosince") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        nosince = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-notimestamp") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        notimestamp = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-noqualifier", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        addToSet(excludedQualifiers, args.get(0));
+                        return true;
+                    }
+                },
+                new Hidden(resources, "-quiet") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        quiet = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-serialwarn") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        serialwarn = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "-sourcetab", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        linksource = true;
+                        try {
+                            setTabWidth(Integer.parseInt(args.get(0)));
+                        } catch (NumberFormatException e) {
+                            //Set to -1 so that warning will be printed
+                            //to indicate what is valid argument.
+                            sourcetab = -1;
+                        }
+                        if (sourcetab <= 0) {
+                            getMessages().warning("doclet.sourcetab_warning");
+                            setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH);
+                        }
+                        return true;
+                    }
+                },
+                new Option(resources, "-tag", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        ArrayList<String> list = new ArrayList<>();
+                        list.add(opt);
+                        list.add(args.get(0));
+                        customTagStrs.add(list);
+                        return true;
+                    }
+                },
+                new Option(resources, "-taglet", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        ArrayList<String> list = new ArrayList<>();
+                        list.add(opt);
+                        list.add(args.get(0));
+                        customTagStrs.add(list);
+                        return true;
+                    }
+                },
+                new Option(resources, "-tagletpath", 1) {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        tagletpath = args.get(0);
+                        return true;
+                    }
+                },
+                new Option(resources, "-version") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        showversion = true;
+                        return true;
+                    }
+                },
+                new Hidden(resources, "--dump-on-error") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        dumpOnError = true;
+                        return true;
+                    }
+                },
+                new Option(resources, "--allow-script-in-comments") {
+                    @Override
+                    public boolean process(String opt, List<String> args) {
+                        allowScriptInComments = true;
+                        return true;
+                    }
                 }
-            },
-            new Option(resources, "-nosince") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    nosince = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-notimestamp") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    notimestamp = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-noqualifier", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    addToSet(excludedQualifiers, args.get(0));
-                    return true;
-                }
-            },
-            new Hidden(resources, "-quiet") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    quiet = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-serialwarn") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    serialwarn = true;
-                    return true;
-                }
-            },
-            new Option(resources, "-sourcetab", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    linksource = true;
-                    try {
-                        setTabWidth(Integer.parseInt(args.get(0)));
-                    } catch (NumberFormatException e) {
-                             //Set to -1 so that warning will be printed
-                        //to indicate what is valid argument.
-                        sourcetab = -1;
-                    }
-                    if (sourcetab <= 0) {
-                        getMessages().warning("doclet.sourcetab_warning");
-                        setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH);
-                    }
-                    return true;
-                }
-            },
-            new Option(resources, "-tag", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    ArrayList<String> list = new ArrayList<>();
-                    list.add(opt);
-                    list.add(args.get(0));
-                    customTagStrs.add(list);
-                    return true;
-                }
-            },
-             new Option(resources, "-taglet", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    ArrayList<String> list = new ArrayList<>();
-                    list.add(opt);
-                    list.add(args.get(0));
-                    customTagStrs.add(list);
-                    return true;
-                }
-            },
-            new Option(resources, "-tagletpath", 1) {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    tagletpath = args.get(0);
-                    return true;
-                }
-            },
-            new Option(resources, "-version") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    showversion = true;
-                    return true;
-                }
-            },
-            new Hidden(resources, "--dump-on-error") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    dumpOnError = true;
-                    return true;
-                }
-            },
-            new Option(resources, "--allow-script-in-comments") {
-                @Override
-                public boolean process(String opt, List<String> args) {
-                    allowScriptInComments = true;
-                    return true;
-                }
-            }
         };
         Set<Doclet.Option> set = new TreeSet<>();
         set.addAll(Arrays.asList(options));
@@ -689,20 +695,22 @@
      */
     private void finishOptionSettings0() throws DocletException {
         initDestDirectory();
-        if (urlForLink != null && pkglistUrlForLink != null)
-            extern.link(urlForLink, pkglistUrlForLink, reporter, false);
-        if (urlForLinkOffline != null && pkglistUrlForLinkOffline != null)
-            extern.link(urlForLinkOffline, pkglistUrlForLinkOffline, reporter, true);
+        for (String link : linkList) {
+            extern.link(link, reporter);
+        }
+        for (Pair<String, String> linkOfflinePair : linkOfflineList) {
+            extern.link(linkOfflinePair.first, linkOfflinePair.second, reporter);
+        }
         if (docencoding == null) {
             docencoding = encoding;
         }
         typeElementCatalog = new TypeElementCatalog(includedTypeElements, this);
         initTagletManager(customTagStrs);
-        groups.stream().forEach((grp) -> {
+        groupPairs.stream().forEach((grp) -> {
             if (showModules) {
-                group.checkModuleGroups(grp.value1, grp.value2);
+                group.checkModuleGroups(grp.first, grp.second);
             } else {
-                group.checkPackageGroups(grp.value1, grp.value2);
+                group.checkPackageGroups(grp.first, grp.second);
             }
         });
     }
@@ -748,12 +756,12 @@
      * be in the following format:  "[tag name]:[location str]:[heading]".
      *
      * @param customTagStrs the set two dimensional arrays of strings.  These arrays contain
-     * either -tag or -taglet arguments.
+     *                      either -tag or -taglet arguments.
      */
     private void initTagletManager(Set<List<String>> customTagStrs) {
         tagletManager = tagletManager == null ?
-            new TagletManager(nosince, showversion, showauthor, javafx, this) :
-            tagletManager;
+                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);
@@ -793,12 +801,11 @@
      * @param maxTokens the maximum number of tokens returned.  If the
      *                  max is reached, the remaining part of s is appended
      *                  to the end of the last token.
-     *
      * @return an array of tokens.
      */
     private List<String> tokenize(String s, char separator, int maxTokens) {
         List<String> tokens = new ArrayList<>();
-        StringBuilder  token = new StringBuilder ();
+        StringBuilder token = new StringBuilder();
         boolean prevIsEscapeChar = false;
         for (int i = 0; i < s.length(); i += Character.charCount(i)) {
             int currentChar = s.codePointAt(i);
@@ -806,7 +813,7 @@
                 // Case 1:  escaped character
                 token.appendCodePoint(currentChar);
                 prevIsEscapeChar = false;
-            } else if (currentChar == separator && tokens.size() < maxTokens-1) {
+            } else if (currentChar == separator && tokens.size() < maxTokens - 1) {
                 // Case 2:  separator
                 tokens.add(token.toString());
                 token = new StringBuilder();
@@ -824,10 +831,10 @@
         return tokens;
     }
 
-    private void addToSet(Set<String> s, String str){
+    private void addToSet(Set<String> s, String str) {
         StringTokenizer st = new StringTokenizer(str, ":");
         String current;
-        while(st.hasMoreTokens()){
+        while (st.hasMoreTokens()) {
             current = st.nextToken();
             s.add(current);
         }
@@ -847,7 +854,7 @@
         int indexDblfs;
         while ((indexDblfs = path.indexOf(dblfs, 1)) >= 0) {
             path = path.substring(0, indexDblfs) +
-                path.substring(indexDblfs + fs.length());
+                    path.substring(indexDblfs + fs.length());
         }
         if (!path.endsWith(fs))
             path += fs;
@@ -855,7 +862,6 @@
     }
 
     /**
-     *
      * This checks for the validity of the options used by the user.
      * As of this writing, this checks only docencoding.
      *
@@ -883,7 +889,7 @@
      * @param reporter    used to report errors.
      */
     private boolean checkOutputFileEncoding(String docencoding) {
-        OutputStream ost= new ByteArrayOutputStream();
+        OutputStream ost = new ByteArrayOutputStream();
         OutputStreamWriter osw = null;
         try {
             osw = new OutputStreamWriter(ost, docencoding);
@@ -908,7 +914,7 @@
      * @param docfilesubdir the doc-files subdirectory to check.
      * @return true if the directory is excluded.
      */
-    public boolean shouldExcludeDocFileDir(String docfilesubdir){
+    public boolean shouldExcludeDocFileDir(String docfilesubdir) {
         return excludedDocFileDirs.contains(docfilesubdir);
     }
 
@@ -918,10 +924,10 @@
      * @param qualifier the qualifier to check.
      * @return true if the qualifier should be excluded
      */
-    public boolean shouldExcludeQualifier(String qualifier){
+    public boolean shouldExcludeQualifier(String qualifier) {
         if (excludedQualifiers.contains("all") ||
-            excludedQualifiers.contains(qualifier) ||
-            excludedQualifiers.contains(qualifier + ".*")) {
+                excludedQualifiers.contains(qualifier) ||
+                excludedQualifiers.contains(qualifier + ".*")) {
             return true;
         } else {
             int index = -1;
@@ -956,7 +962,7 @@
      * @param key the key for the desired string
      * @return the string for the given key
      * @throws MissingResourceException if the key is not found in either
-     *  bundle.
+     *                                  bundle.
      */
     public abstract String getText(String key);
 
@@ -965,11 +971,11 @@
      * {@link Resources resources}.
      * Equivalent to <code>getResources.getText(key, args);</code>.
      *
-     * @param key the key for the desired string
+     * @param key  the key for the desired string
      * @param args values to be substituted into the resulting string
      * @return the string for the given key
      * @throws MissingResourceException if the key is not found in either
-     *  bundle.
+     *                                  bundle.
      */
     public abstract String getText(String key, String... args);
 
@@ -997,8 +1003,8 @@
      * {@link Resources resources} as a {@code Content} object.
      *
      * @param key the key for the desired string
-     * @param o1 resource argument
-     * @param o2 resource argument
+     * @param o1  resource argument
+     * @param o2  resource argument
      * @return a content tree for the text
      */
     public abstract Content getContent(String key, Object o1, Object o2);
@@ -1045,8 +1051,8 @@
      */
     public InputStream getBuilderXML() throws DocFileIOException {
         return builderXMLPath == null ?
-            BaseConfiguration.class.getResourceAsStream(DEFAULT_BUILDER_XML) :
-            DocFile.createFileForInput(this, builderXMLPath).openInputStream();
+                BaseConfiguration.class.getResourceAsStream(DEFAULT_BUILDER_XML) :
+                DocFile.createFileForInput(this, builderXMLPath).openInputStream();
     }
 
     /**
@@ -1203,18 +1209,6 @@
     }
 
     /*
-     * Stores a pair of Strings.
-     */
-    protected static class GroupContainer {
-        final String value1;
-        final String value2;
-        public GroupContainer(String value1, String value2) {
-            this.value1 = value1;
-            this.value2 = value2;
-        }
-    }
-
-    /*
      * Splits the elements in a collection to its individual
      * collection.
      */
@@ -1267,6 +1261,7 @@
     /**
      * Returns whether or not to allow JavaScript in comments.
      * Default is off; can be set true from a command line option.
+     *
      * @return the allowScriptInComments
      */
     public boolean isAllowScriptInComments() {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Extern.java	Fri Jul 28 14:29:29 2017 -0700
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Extern.java	Fri Jul 28 15:00:53 2017 -0700
@@ -170,6 +170,36 @@
     }
 
     /**
+     * Build the extern package list from given URL or the directory path,
+     * as specified with the "-link" flag.
+     * Flag error if the "-link" or "-linkoffline" option is already used.
+     *
+     * @param url        URL or Directory path.
+     * @param reporter   The <code>DocErrorReporter</code> used to report errors.
+     * @return true if successful, false otherwise
+     * @throws DocFileIOException if there is a problem reading a package list file
+     */
+    public boolean link(String url, Reporter reporter) throws DocFileIOException {
+        return link(url, url, reporter, false);
+    }
+
+    /**
+     * Build the extern package list from given URL or the directory path,
+     * as specified with the "-linkoffline" flag.
+     * Flag error if the "-link" or "-linkoffline" option is already used.
+     *
+     * @param url        URL or Directory path.
+     * @param pkglisturl This can be another URL for "package-list" or ordinary
+     *                   file.
+     * @param reporter   The <code>DocErrorReporter</code> used to report errors.
+     * @return true if successful, false otherwise
+     * @throws DocFileIOException if there is a problem reading a package list file
+     */
+    public boolean link(String url, String pkglisturl, Reporter reporter) throws DocFileIOException {
+        return link(url, pkglisturl, reporter, true);
+    }
+
+    /*
      * Build the extern package list from given URL or the directory path.
      * Flag error if the "-link" or "-linkoffline" option is already used.
      *
@@ -181,7 +211,7 @@
      * @return true if successful, false otherwise
      * @throws DocFileIOException if there is a problem reading a package list file
      */
-    public boolean link(String url, String pkglisturl, Reporter reporter, boolean linkoffline)
+    private boolean link(String url, String pkglisturl, Reporter reporter, boolean linkoffline)
                 throws DocFileIOException {
         this.linkoffline = linkoffline;
         try {
@@ -245,8 +275,7 @@
             readPackageList(link.openStream(), urlpath, false);
         } catch (URISyntaxException | MalformedURLException exc) {
             throw new Fault(configuration.getText("doclet.MalformedURL", pkglisturlpath.toString()), exc);
-        }
-        catch (IOException exc) {
+        } catch (IOException exc) {
             throw new Fault(configuration.getText("doclet.URL_error", pkglisturlpath.toString()), exc);
         }
     }
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java	Fri Jul 28 14:29:29 2017 -0700
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java	Fri Jul 28 15:00:53 2017 -0700
@@ -3328,4 +3328,19 @@
             return out;
         }
     }
+
+    /**
+     * A simple pair container.
+     * @param <K> first a value
+     * @param <L> second another value
+     */
+    public static class Pair<K, L> {
+        public final K first;
+        public final L second;
+
+        public Pair(K first, L second) {
+            this.first = first;
+            this.second = second;
+        }
+    }
 }
--- a/langtools/test/jdk/javadoc/doclet/testLinkOption/TestLinkOption.java	Fri Jul 28 14:29:29 2017 -0700
+++ b/langtools/test/jdk/javadoc/doclet/testLinkOption/TestLinkOption.java	Fri Jul 28 15:00:53 2017 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2017, 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
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 4720957 5020118 8026567 8038976
+ * @bug 4720957 5020118 8026567 8038976 8184969
  * @summary Test to make sure that -link and -linkoffline link to
  * right files, and URLs with and without trailing slash are accepted.
  * @author jamieh
@@ -135,6 +135,46 @@
         // this is the text that is given when there is a problem with a URL
         checkOutput(Output.OUT, false,
                 "warning - Error fetching URL");
+
+        // check multiple link options
+        javadoc("-d", "out5",
+                "-sourcepath", testSrc,
+                "-link", "../" + "out1",
+                "-link", "../" + "out2",
+                "pkg3");
+        checkExit(Exit.OK);
+        checkOutput("pkg3/A.html", true,
+                "<pre>public class <span class=\"typeNameLabel\">A</span>\n"
+                + "extends java.lang.Object</pre>\n"
+                + "<div class=\"block\">Test links.\n"
+                + " <br>\n"
+                + " <a href=\"../../out2/pkg2/C2.html?is-external=true\" "
+                + "title=\"class or interface in pkg2\"><code>link to pkg2.C2</code></a>\n"
+                + " <br>\n"
+                + " <a href=\"../../out1/mylib/lang/StringBuilderChild.html?is-external=true\" "
+                + "title=\"class or interface in mylib.lang\">"
+                + "<code>link to mylib.lang.StringBuilderChild</code></a>.</div>\n"
+        );
+
+        // check multiple linkoffline options
+        javadoc("-d", "out6",
+                "-sourcepath", testSrc,
+                "-linkoffline", "../copy/out1", "out1",
+                "-linkoffline", "../copy/out2", "out2",
+                "pkg3");
+        checkExit(Exit.OK);
+        checkOutput("pkg3/A.html", true,
+                "<pre>public class <span class=\"typeNameLabel\">A</span>\n"
+                        + "extends java.lang.Object</pre>\n"
+                        + "<div class=\"block\">Test links.\n"
+                        + " <br>\n"
+                        + " <a href=\"../../copy/out2/pkg2/C2.html?is-external=true\" "
+                        + "title=\"class or interface in pkg2\"><code>link to pkg2.C2</code></a>\n"
+                        + " <br>\n"
+                        + " <a href=\"../../copy/out1/mylib/lang/StringBuilderChild.html?is-external=true\" "
+                        + "title=\"class or interface in mylib.lang\">"
+                        + "<code>link to mylib.lang.StringBuilderChild</code></a>.</div>\n"
+        );
     }
 
     /*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/javadoc/doclet/testLinkOption/pkg3/A.java	Fri Jul 28 15:00:53 2017 -0700
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+package pkg3;
+
+/**
+ * Test links.
+ * <br>
+ * {@link pkg2.C2 link to pkg2.C2}
+ * <br>
+ * {@link mylib.lang.StringBuilderChild link to mylib.lang.StringBuilderChild}.
+ */
+
+public class A {}