1 /*
2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.doclets.internal.toolkit;
27 
28 import java.io.*;
29 import java.util.*;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import javax.tools.JavaFileManager;
33 
34 import com.sun.javadoc.*;
35 import com.sun.tools.javac.sym.Profiles;
36 import com.sun.tools.javac.jvm.Profile;
37 import com.sun.tools.doclets.internal.toolkit.builders.BuilderFactory;
38 import com.sun.tools.doclets.internal.toolkit.taglets.*;
39 import com.sun.tools.doclets.internal.toolkit.util.*;
40 import com.sun.tools.javac.util.StringUtils;
41 
42 /**
43  * Configure the output based on the options. Doclets should sub-class
44  * Configuration, to configure and add their own options. This class contains
45  * all user options which are supported by the 1.1 doclet and the standard
46  * doclet.
47  *
48  *  <p><b>This is NOT part of any supported API.
49  *  If you write code that depends on this, you do so at your own risk.
50  *  This code and its internal interfaces are subject to change or
51  *  deletion without notice.</b>
52  *
53  * @author Robert Field.
54  * @author Atul Dambalkar.
55  * @author Jamie Ho
56  */
57 public abstract class Configuration {
58 
59     /**
60      * Exception used to report a problem during setOptions.
61      */
62     public static class Fault extends Exception {
63         private static final long serialVersionUID = 0;
64 
Fault(String msg)65         Fault(String msg) {
66             super(msg);
67         }
68 
Fault(String msg, Exception cause)69         Fault(String msg, Exception cause) {
70             super(msg, cause);
71         }
72     }
73 
74     /**
75      * The factory for builders.
76      */
77     protected BuilderFactory builderFactory;
78 
79     /**
80      * The taglet manager.
81      */
82     public TagletManager tagletManager;
83 
84     /**
85      * The path to the builder XML input file.
86      */
87     public String builderXMLPath;
88 
89     /**
90      * The default path to the builder XML.
91      */
92     private static final String DEFAULT_BUILDER_XML = "resources/doclet.xml";
93 
94     /**
95      * The path to Taglets
96      */
97     public String tagletpath = "";
98 
99     /**
100      * This is true if option "-serialwarn" is used. Defualt value is false to
101      * suppress excessive warnings about serial tag.
102      */
103     public boolean serialwarn = false;
104 
105     /**
106      * The specified amount of space between tab stops.
107      */
108     public int sourcetab;
109 
110     public String tabSpaces;
111 
112     /**
113      * True if we should generate browsable sources.
114      */
115     public boolean linksource = false;
116 
117     /**
118      * True if command line option "-nosince" is used. Default value is
119      * false.
120      */
121     public boolean nosince = false;
122 
123     /**
124      * True if we should recursively copy the doc-file subdirectories
125      */
126     public boolean copydocfilesubdirs = false;
127 
128     /**
129      * The META charset tag used for cross-platform viewing.
130      */
131     public String charset = "";
132 
133     /**
134      * True if user wants to add member names as meta keywords.
135      * Set to false because meta keywords are ignored in general
136      * by most Internet search engines.
137      */
138     public boolean keywords = false;
139 
140     /**
141      * The meta tag keywords instance.
142      */
143     public final MetaKeywords metakeywords = new MetaKeywords(this);
144 
145     /**
146      * The list of doc-file subdirectories to exclude
147      */
148     protected Set<String> excludedDocFileDirs;
149 
150     /**
151      * The list of qualifiers to exclude
152      */
153     protected Set<String> excludedQualifiers;
154 
155     /**
156      * The Root of the generated Program Structure from the Doclet API.
157      */
158     public RootDoc root;
159 
160     /**
161      * Destination directory name, in which doclet will generate the entire
162      * documentation. Default is current directory.
163      */
164     public String destDirName = "";
165 
166     /**
167      * Destination directory name, in which doclet will copy the doc-files to.
168      */
169     public String docFileDestDirName = "";
170 
171     /**
172      * Encoding for this document. Default is default encoding for this
173      * platform.
174      */
175     public String docencoding = null;
176 
177     /**
178      * True if user wants to suppress descriptions and tags.
179      */
180     public boolean nocomment = false;
181 
182     /**
183      * Encoding for this document. Default is default encoding for this
184      * platform.
185      */
186     public String encoding = null;
187 
188     /**
189      * Generate author specific information for all the classes if @author
190      * tag is used in the doc comment and if -author option is used.
191      * <code>showauthor</code> is set to true if -author option is used.
192      * Default is don't show author information.
193      */
194     public boolean showauthor = false;
195 
196     /**
197      * Generate documentation for JavaFX getters and setters automatically
198      * by copying it from the appropriate property definition.
199      */
200     public boolean javafx = false;
201 
202     /**
203      * Generate version specific information for the all the classes
204      * if @version tag is used in the doc comment and if -version option is
205      * used. <code>showversion</code> is set to true if -version option is
206      * used.Default is don't show version information.
207      */
208     public boolean showversion = false;
209 
210     /**
211      * Sourcepath from where to read the source files. Default is classpath.
212      *
213      */
214     public String sourcepath = "";
215 
216     /**
217      * Argument for command line option "-Xprofilespath".
218      */
219     public String profilespath = "";
220 
221     /**
222      * Generate profiles documentation if profilespath is set and valid profiles
223      * are present.
224      */
225     public boolean showProfiles = false;
226 
227     /**
228      * Don't generate deprecated API information at all, if -nodeprecated
229      * option is used. <code>nodepracted</code> is set to true if
230      * -nodeprecated option is used. Default is generate deprected API
231      * information.
232      */
233     public boolean nodeprecated = false;
234 
235     /**
236      * The catalog of classes specified on the command-line
237      */
238     public ClassDocCatalog classDocCatalog;
239 
240     /**
241      * Message Retriever for the doclet, to retrieve message from the resource
242      * file for this Configuration, which is common for 1.1 and standard
243      * doclets.
244      *
245      * TODO:  Make this private!!!
246      */
247     public MessageRetriever message = null;
248 
249     /**
250      * True if user wants to suppress time stamp in output.
251      * Default is false.
252      */
253     public boolean notimestamp= false;
254 
255     /**
256      * The package grouping instance.
257      */
258     public final Group group = new Group(this);
259 
260     /**
261      * The tracker of external package links.
262      */
263     public final Extern extern = new Extern(this);
264 
265     /**
266      * Return the build date for the doclet.
267      */
getDocletSpecificBuildDate()268     public abstract String getDocletSpecificBuildDate();
269 
270     /**
271      * This method should be defined in all those doclets(configurations),
272      * which want to derive themselves from this Configuration. This method
273      * can be used to set its own command line options.
274      *
275      * @param options The array of option names and values.
276      * @throws DocletAbortException
277      */
setSpecificDocletOptions(String[][] options)278     public abstract void setSpecificDocletOptions(String[][] options) throws Fault;
279 
280     /**
281      * Return the doclet specific {@link MessageRetriever}
282      * @return the doclet specific MessageRetriever.
283      */
getDocletSpecificMsg()284     public abstract MessageRetriever getDocletSpecificMsg();
285 
286     /**
287      * A profiles object used to access profiles across various pages.
288      */
289     public Profiles profiles;
290 
291     /**
292      * An map of the profiles to packages.
293      */
294     public Map<String,PackageDoc[]> profilePackages;
295 
296     /**
297      * An array of the packages specified on the command-line merged
298      * with the array of packages that contain the classes specified on the
299      * command-line.  The array is sorted.
300      */
301     public PackageDoc[] packages;
302 
303     /**
304      * Constructor. Constructs the message retriever with resource file.
305      */
Configuration()306     public Configuration() {
307         message =
308             new MessageRetriever(this,
309             "com.sun.tools.doclets.internal.toolkit.resources.doclets");
310         excludedDocFileDirs = new HashSet<String>();
311         excludedQualifiers = new HashSet<String>();
312         setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH);
313     }
314 
315     /**
316      * Return the builder factory for this doclet.
317      *
318      * @return the builder factory for this doclet.
319      */
getBuilderFactory()320     public BuilderFactory getBuilderFactory() {
321         if (builderFactory == null) {
322             builderFactory = new BuilderFactory(this);
323         }
324         return builderFactory;
325     }
326 
327     /**
328      * This method should be defined in all those doclets
329      * which want to inherit from this Configuration. This method
330      * should return the number of arguments to the command line
331      * option (including the option name).  For example,
332      * -notimestamp is a single-argument option, so this method would
333      * return 1.
334      *
335      * @param option Command line option under consideration.
336      * @return number of arguments to option (including the
337      * option name). Zero return means option not known.
338      * Negative value means error occurred.
339      */
optionLength(String option)340     public int optionLength(String option) {
341         option = StringUtils.toLowerCase(option);
342         if (option.equals("-author") ||
343             option.equals("-docfilessubdirs") ||
344             option.equals("-javafx") ||
345             option.equals("-keywords") ||
346             option.equals("-linksource") ||
347             option.equals("-nocomment") ||
348             option.equals("-nodeprecated") ||
349             option.equals("-nosince") ||
350             option.equals("-notimestamp") ||
351             option.equals("-quiet") ||
352             option.equals("-xnodate") ||
353             option.equals("-version")) {
354             return 1;
355         } else if (option.equals("-d") ||
356                    option.equals("-docencoding") ||
357                    option.equals("-encoding") ||
358                    option.equals("-excludedocfilessubdir") ||
359                    option.equals("-link") ||
360                    option.equals("-sourcetab") ||
361                    option.equals("-noqualifier") ||
362                    option.equals("-output") ||
363                    option.equals("-sourcepath") ||
364                    option.equals("-tag") ||
365                    option.equals("-taglet") ||
366                    option.equals("-tagletpath") ||
367                    option.equals("-xprofilespath")) {
368             return 2;
369         } else if (option.equals("-group") ||
370                    option.equals("-linkoffline")) {
371             return 3;
372         } else {
373             return -1;  // indicate we don't know about it
374         }
375     }
376 
377     /**
378      * Perform error checking on the given options.
379      *
380      * @param options  the given options to check.
381      * @param reporter the reporter used to report errors.
382      */
validOptions(String options[][], DocErrorReporter reporter)383     public abstract boolean validOptions(String options[][],
384         DocErrorReporter reporter);
385 
initProfiles()386     private void initProfiles() throws IOException {
387         if (profilespath.isEmpty())
388             return;
389 
390         profiles = Profiles.read(new File(profilespath));
391 
392         // Group the packages to be documented by the lowest profile (if any)
393         // in which each appears
394         Map<Profile, List<PackageDoc>> interimResults =
395                 new EnumMap<Profile, List<PackageDoc>>(Profile.class);
396         for (Profile p: Profile.values())
397             interimResults.put(p, new ArrayList<PackageDoc>());
398 
399         for (PackageDoc pkg: packages) {
400             if (nodeprecated && Util.isDeprecated(pkg)) {
401                 continue;
402             }
403             // the getProfile method takes a type name, not a package name,
404             // but isn't particularly fussy about the simple name -- so just use *
405             int i = profiles.getProfile(pkg.name().replace(".", "/") + "/*");
406             Profile p = Profile.lookup(i);
407             if (p != null) {
408                 List<PackageDoc> pkgs = interimResults.get(p);
409                 pkgs.add(pkg);
410             }
411         }
412 
413         // Build the profilePackages structure used by the doclet
414         profilePackages = new HashMap<String,PackageDoc[]>();
415         List<PackageDoc> prev = Collections.<PackageDoc>emptyList();
416         int size;
417         for (Map.Entry<Profile,List<PackageDoc>> e: interimResults.entrySet()) {
418             Profile p = e.getKey();
419             List<PackageDoc> pkgs =  e.getValue();
420             pkgs.addAll(prev); // each profile contains all lower profiles
421             Collections.sort(pkgs);
422             size = pkgs.size();
423             // For a profile, if there are no packages to be documented, do not add
424             // it to profilePackages map.
425             if (size > 0)
426                 profilePackages.put(p.name, pkgs.toArray(new PackageDoc[pkgs.size()]));
427             prev = pkgs;
428         }
429 
430         // Generate profiles documentation if any profile contains any
431         // of the packages to be documented.
432         showProfiles = !prev.isEmpty();
433     }
434 
initPackageArray()435     private void initPackageArray() {
436         Set<PackageDoc> set = new HashSet<PackageDoc>(Arrays.asList(root.specifiedPackages()));
437         ClassDoc[] classes = root.specifiedClasses();
438         for (int i = 0; i < classes.length; i++) {
439             set.add(classes[i].containingPackage());
440         }
441         ArrayList<PackageDoc> results = new ArrayList<PackageDoc>(set);
442         Collections.sort(results);
443         packages = results.toArray(new PackageDoc[] {});
444     }
445 
446     /**
447      * Set the command line options supported by this configuration.
448      *
449      * @param options the two dimensional array of options.
450      */
setOptions(String[][] options)451     public void setOptions(String[][] options) throws Fault {
452         LinkedHashSet<String[]> customTagStrs = new LinkedHashSet<String[]>();
453 
454         // Some options, specifically -link and -linkoffline, require that
455         // the output directory has already been created: so do that first.
456         for (int oi = 0; oi < options.length; ++oi) {
457             String[] os = options[oi];
458             String opt = StringUtils.toLowerCase(os[0]);
459             if (opt.equals("-d")) {
460                 destDirName = addTrailingFileSep(os[1]);
461                 docFileDestDirName = destDirName;
462                 ensureOutputDirExists();
463                 break;
464             }
465         }
466 
467         for (int oi = 0; oi < options.length; ++oi) {
468             String[] os = options[oi];
469             String opt = StringUtils.toLowerCase(os[0]);
470             if (opt.equals("-docfilessubdirs")) {
471                 copydocfilesubdirs = true;
472             } else if (opt.equals("-docencoding")) {
473                 docencoding = os[1];
474             } else if (opt.equals("-encoding")) {
475                 encoding = os[1];
476             } else if (opt.equals("-author")) {
477                 showauthor = true;
478             } else  if (opt.equals("-javafx")) {
479                 javafx = true;
480             } else if (opt.equals("-nosince")) {
481                 nosince = true;
482             } else if (opt.equals("-version")) {
483                 showversion = true;
484             } else if (opt.equals("-nodeprecated")) {
485                 nodeprecated = true;
486             } else if (opt.equals("-sourcepath")) {
487                 sourcepath = os[1];
488             } else if ((opt.equals("-classpath") || opt.equals("-cp")) &&
489                        sourcepath.length() == 0) {
490                 sourcepath = os[1];
491             } else if (opt.equals("-excludedocfilessubdir")) {
492                 addToSet(excludedDocFileDirs, os[1]);
493             } else if (opt.equals("-noqualifier")) {
494                 addToSet(excludedQualifiers, os[1]);
495             } else if (opt.equals("-linksource")) {
496                 linksource = true;
497             } else if (opt.equals("-sourcetab")) {
498                 linksource = true;
499                 try {
500                     setTabWidth(Integer.parseInt(os[1]));
501                 } catch (NumberFormatException e) {
502                     //Set to -1 so that warning will be printed
503                     //to indicate what is valid argument.
504                     sourcetab = -1;
505                 }
506                 if (sourcetab <= 0) {
507                     message.warning("doclet.sourcetab_warning");
508                     setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH);
509                 }
510             } else if (opt.equals("-notimestamp")) {
511                 notimestamp = true;
512             } else if (opt.equals("-nocomment")) {
513                 nocomment = true;
514             } else if (opt.equals("-tag") || opt.equals("-taglet")) {
515                 customTagStrs.add(os);
516             } else if (opt.equals("-tagletpath")) {
517                 tagletpath = os[1];
518             }  else if (opt.equals("-xprofilespath")) {
519                 profilespath = os[1];
520             } else if (opt.equals("-keywords")) {
521                 keywords = true;
522             } else if (opt.equals("-serialwarn")) {
523                 serialwarn = true;
524             } else if (opt.equals("-group")) {
525                 group.checkPackageGroups(os[1], os[2]);
526             } else if (opt.equals("-link")) {
527                 String url = os[1];
528                 extern.link(url, url, root, false);
529             } else if (opt.equals("-linkoffline")) {
530                 String url = os[1];
531                 String pkglisturl = os[2];
532                 extern.link(url, pkglisturl, root, true);
533             }
534         }
535         if (sourcepath.length() == 0) {
536             sourcepath = System.getProperty("env.class.path") == null ? "" :
537                 System.getProperty("env.class.path");
538         }
539         if (docencoding == null) {
540             docencoding = encoding;
541         }
542 
543         classDocCatalog = new ClassDocCatalog(root.specifiedClasses(), this);
544         initTagletManager(customTagStrs);
545     }
546 
547     /**
548      * Set the command line options supported by this configuration.
549      *
550      * @throws DocletAbortException
551      */
setOptions()552     public void setOptions() throws Fault {
553         initPackageArray();
554         setOptions(root.options());
555         try {
556             initProfiles();
557         } catch (Exception e) {
558             throw new DocletAbortException(e);
559         }
560         setSpecificDocletOptions(root.options());
561     }
562 
ensureOutputDirExists()563     private void ensureOutputDirExists() throws Fault {
564         DocFile destDir = DocFile.createFileForDirectory(this, destDirName);
565         if (!destDir.exists()) {
566             //Create the output directory (in case it doesn't exist yet)
567             root.printNotice(getText("doclet.dest_dir_create", destDirName));
568             destDir.mkdirs();
569         } else if (!destDir.isDirectory()) {
570             throw new Fault(getText(
571                 "doclet.destination_directory_not_directory_0",
572                 destDir.getPath()));
573         } else if (!destDir.canWrite()) {
574             throw new Fault(getText(
575                 "doclet.destination_directory_not_writable_0",
576                 destDir.getPath()));
577         }
578     }
579 
580 
581     /**
582      * Initialize the taglet manager.  The strings to initialize the simple custom tags should
583      * be in the following format:  "[tag name]:[location str]:[heading]".
584      * @param customTagStrs the set two dimensional arrays of strings.  These arrays contain
585      * either -tag or -taglet arguments.
586      */
initTagletManager(Set<String[]> customTagStrs)587     private void initTagletManager(Set<String[]> customTagStrs) {
588         tagletManager = tagletManager == null ?
589             new TagletManager(nosince, showversion, showauthor, javafx, message) :
590             tagletManager;
591         String[] args;
592         for (Iterator<String[]> it = customTagStrs.iterator(); it.hasNext(); ) {
593             args = it.next();
594             if (args[0].equals("-taglet")) {
595                 tagletManager.addCustomTag(args[1], getFileManager(), tagletpath);
596                 continue;
597             }
598             String[] tokens = tokenize(args[1],
599                 TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3);
600             if (tokens.length == 1) {
601                 String tagName = args[1];
602                 if (tagletManager.isKnownCustomTag(tagName)) {
603                     //reorder a standard tag
604                     tagletManager.addNewSimpleCustomTag(tagName, null, "");
605                 } else {
606                     //Create a simple tag with the heading that has the same name as the tag.
607                     StringBuilder heading = new StringBuilder(tagName + ":");
608                     heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0)));
609                     tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a");
610                 }
611             } else if (tokens.length == 2) {
612                 //Add simple taglet without heading, probably to excluding it in the output.
613                 tagletManager.addNewSimpleCustomTag(tokens[0], tokens[1], "");
614             } else if (tokens.length >= 3) {
615                 tagletManager.addNewSimpleCustomTag(tokens[0], tokens[2], tokens[1]);
616             } else {
617                 message.error("doclet.Error_invalid_custom_tag_argument", args[1]);
618             }
619         }
620     }
621 
622     /**
623      * Given a string, return an array of tokens.  The separator can be escaped
624      * with the '\' character.  The '\' character may also be escaped by the
625      * '\' character.
626      *
627      * @param s         the string to tokenize.
628      * @param separator the separator char.
629      * @param maxTokens the maximum number of tokens returned.  If the
630      *                  max is reached, the remaining part of s is appended
631      *                  to the end of the last token.
632      *
633      * @return an array of tokens.
634      */
tokenize(String s, char separator, int maxTokens)635     private String[] tokenize(String s, char separator, int maxTokens) {
636         List<String> tokens = new ArrayList<String>();
637         StringBuilder  token = new StringBuilder ();
638         boolean prevIsEscapeChar = false;
639         for (int i = 0; i < s.length(); i += Character.charCount(i)) {
640             int currentChar = s.codePointAt(i);
641             if (prevIsEscapeChar) {
642                 // Case 1:  escaped character
643                 token.appendCodePoint(currentChar);
644                 prevIsEscapeChar = false;
645             } else if (currentChar == separator && tokens.size() < maxTokens-1) {
646                 // Case 2:  separator
647                 tokens.add(token.toString());
648                 token = new StringBuilder();
649             } else if (currentChar == '\\') {
650                 // Case 3:  escape character
651                 prevIsEscapeChar = true;
652             } else {
653                 // Case 4:  regular character
654                 token.appendCodePoint(currentChar);
655             }
656         }
657         if (token.length() > 0) {
658             tokens.add(token.toString());
659         }
660         return tokens.toArray(new String[] {});
661     }
662 
addToSet(Set<String> s, String str)663     private void addToSet(Set<String> s, String str){
664         StringTokenizer st = new StringTokenizer(str, ":");
665         String current;
666         while(st.hasMoreTokens()){
667             current = st.nextToken();
668             s.add(current);
669         }
670     }
671 
672     /**
673      * Add a trailing file separator, if not found. Remove superfluous
674      * file separators if any. Preserve the front double file separator for
675      * UNC paths.
676      *
677      * @param path Path under consideration.
678      * @return String Properly constructed path string.
679      */
addTrailingFileSep(String path)680     public static String addTrailingFileSep(String path) {
681         String fs = System.getProperty("file.separator");
682         String dblfs = fs + fs;
683         int indexDblfs;
684         while ((indexDblfs = path.indexOf(dblfs, 1)) >= 0) {
685             path = path.substring(0, indexDblfs) +
686                 path.substring(indexDblfs + fs.length());
687         }
688         if (!path.endsWith(fs))
689             path += fs;
690         return path;
691     }
692 
693     /**
694      * This checks for the validity of the options used by the user.
695      * This works exactly like
696      * {@link com.sun.javadoc.Doclet#validOptions(String[][],
697      * DocErrorReporter)}. This will validate the options which are shared
698      * by our doclets. For example, this method will flag an error using
699      * the DocErrorReporter if user has used "-nohelp" and "-helpfile" option
700      * together.
701      *
702      * @param options  options used on the command line.
703      * @param reporter used to report errors.
704      * @return true if all the options are valid.
705      */
generalValidOptions(String options[][], DocErrorReporter reporter)706     public boolean generalValidOptions(String options[][],
707             DocErrorReporter reporter) {
708         boolean docencodingfound = false;
709         String encoding = "";
710         for (int oi = 0; oi < options.length; oi++) {
711             String[] os = options[oi];
712             String opt = StringUtils.toLowerCase(os[0]);
713             if (opt.equals("-docencoding")) {
714                 docencodingfound = true;
715                 if (!checkOutputFileEncoding(os[1], reporter)) {
716                     return false;
717                 }
718             } else if (opt.equals("-encoding")) {
719                 encoding = os[1];
720             }
721         }
722         if (!docencodingfound && encoding.length() > 0) {
723             if (!checkOutputFileEncoding(encoding, reporter)) {
724                 return false;
725             }
726         }
727         return true;
728     }
729 
730     /**
731      * Check the validity of the given profile. Return false if there are no
732      * valid packages to be documented for the profile.
733      *
734      * @param profileName the profile that needs to be validated.
735      * @return true if the profile has valid packages to be documented.
736      */
shouldDocumentProfile(String profileName)737     public boolean shouldDocumentProfile(String profileName) {
738         return profilePackages.containsKey(profileName);
739     }
740 
741     /**
742      * Check the validity of the given Source or Output File encoding on this
743      * platform.
744      *
745      * @param docencoding output file encoding.
746      * @param reporter    used to report errors.
747      */
checkOutputFileEncoding(String docencoding, DocErrorReporter reporter)748     private boolean checkOutputFileEncoding(String docencoding,
749             DocErrorReporter reporter) {
750         OutputStream ost= new ByteArrayOutputStream();
751         OutputStreamWriter osw = null;
752         try {
753             osw = new OutputStreamWriter(ost, docencoding);
754         } catch (UnsupportedEncodingException exc) {
755             reporter.printError(getText("doclet.Encoding_not_supported",
756                 docencoding));
757             return false;
758         } finally {
759             try {
760                 if (osw != null) {
761                     osw.close();
762                 }
763             } catch (IOException exc) {
764             }
765         }
766         return true;
767     }
768 
769     /**
770      * Return true if the given doc-file subdirectory should be excluded and
771      * false otherwise.
772      * @param docfilesubdir the doc-files subdirectory to check.
773      */
shouldExcludeDocFileDir(String docfilesubdir)774     public boolean shouldExcludeDocFileDir(String docfilesubdir){
775         if (excludedDocFileDirs.contains(docfilesubdir)) {
776             return true;
777         } else {
778             return false;
779         }
780     }
781 
782     /**
783      * Return true if the given qualifier should be excluded and false otherwise.
784      * @param qualifier the qualifier to check.
785      */
shouldExcludeQualifier(String qualifier)786     public boolean shouldExcludeQualifier(String qualifier){
787         if (excludedQualifiers.contains("all") ||
788             excludedQualifiers.contains(qualifier) ||
789             excludedQualifiers.contains(qualifier + ".*")) {
790             return true;
791         } else {
792             int index = -1;
793             while ((index = qualifier.indexOf(".", index + 1)) != -1) {
794                 if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) {
795                     return true;
796                 }
797             }
798             return false;
799         }
800     }
801 
802     /**
803      * Return the qualified name of the <code>ClassDoc</code> if it's qualifier is not excluded.  Otherwise,
804      * return the unqualified <code>ClassDoc</code> name.
805      * @param cd the <code>ClassDoc</code> to check.
806      */
getClassName(ClassDoc cd)807     public String getClassName(ClassDoc cd) {
808         PackageDoc pd = cd.containingPackage();
809         if (pd != null && shouldExcludeQualifier(cd.containingPackage().name())) {
810             return cd.name();
811         } else {
812             return cd.qualifiedName();
813         }
814     }
815 
getText(String key)816     public String getText(String key) {
817         try {
818             //Check the doclet specific properties file.
819             return getDocletSpecificMsg().getText(key);
820         } catch (Exception e) {
821             //Check the shared properties file.
822             return message.getText(key);
823         }
824     }
825 
getText(String key, String a1)826     public String getText(String key, String a1) {
827         try {
828             //Check the doclet specific properties file.
829             return getDocletSpecificMsg().getText(key, a1);
830         } catch (Exception e) {
831             //Check the shared properties file.
832             return message.getText(key, a1);
833         }
834     }
835 
getText(String key, String a1, String a2)836     public String getText(String key, String a1, String a2) {
837         try {
838             //Check the doclet specific properties file.
839             return getDocletSpecificMsg().getText(key, a1, a2);
840         } catch (Exception e) {
841             //Check the shared properties file.
842             return message.getText(key, a1, a2);
843         }
844     }
845 
getText(String key, String a1, String a2, String a3)846     public String getText(String key, String a1, String a2, String a3) {
847         try {
848             //Check the doclet specific properties file.
849             return getDocletSpecificMsg().getText(key, a1, a2, a3);
850         } catch (Exception e) {
851             //Check the shared properties file.
852             return message.getText(key, a1, a2, a3);
853         }
854     }
855 
newContent()856     public abstract Content newContent();
857 
858     /**
859      * Get the configuration string as a content.
860      *
861      * @param key the key to look for in the configuration file
862      * @return a content tree for the text
863      */
getResource(String key)864     public Content getResource(String key) {
865         Content c = newContent();
866         c.addContent(getText(key));
867         return c;
868     }
869 
870     /**
871      * Get the configuration string as a content.
872      *
873      * @param key the key to look for in the configuration file
874      * @param o   string or content argument added to configuration text
875      * @return a content tree for the text
876      */
getResource(String key, Object o)877     public Content getResource(String key, Object o) {
878         return getResource(key, o, null, null);
879     }
880 
881     /**
882      * Get the configuration string as a content.
883      *
884      * @param key the key to look for in the configuration file
885      * @param o   string or content argument added to configuration text
886      * @return a content tree for the text
887      */
getResource(String key, Object o1, Object o2)888     public Content getResource(String key, Object o1, Object o2) {
889         return getResource(key, o1, o2, null);
890     }
891 
892     /**
893      * Get the configuration string as a content.
894      *
895      * @param key the key to look for in the configuration file
896      * @param o1  string or content argument added to configuration text
897      * @param o2  string or content argument added to configuration text
898      * @return a content tree for the text
899      */
getResource(String key, Object o0, Object o1, Object o2)900     public Content getResource(String key, Object o0, Object o1, Object o2) {
901         Content c = newContent();
902         Pattern p = Pattern.compile("\\{([012])\\}");
903         String text = getText(key);
904         Matcher m = p.matcher(text);
905         int start = 0;
906         while (m.find(start)) {
907             c.addContent(text.substring(start, m.start()));
908 
909             Object o = null;
910             switch (m.group(1).charAt(0)) {
911                 case '0': o = o0; break;
912                 case '1': o = o1; break;
913                 case '2': o = o2; break;
914             }
915 
916             if (o == null) {
917                 c.addContent("{" + m.group(1) + "}");
918             } else if (o instanceof String) {
919                 c.addContent((String) o);
920             } else if (o instanceof Content) {
921                 c.addContent((Content) o);
922             }
923 
924             start = m.end();
925         }
926 
927         c.addContent(text.substring(start));
928         return c;
929     }
930 
931 
932     /**
933      * Return true if the ClassDoc element is getting documented, depending upon
934      * -nodeprecated option and the deprecation information. Return true if
935      * -nodeprecated is not used. Return false if -nodeprecated is used and if
936      * either ClassDoc element is deprecated or the containing package is deprecated.
937      *
938      * @param cd the ClassDoc for which the page generation is checked
939      */
isGeneratedDoc(ClassDoc cd)940     public boolean isGeneratedDoc(ClassDoc cd) {
941         if (!nodeprecated) {
942             return true;
943         }
944         return !(Util.isDeprecated(cd) || Util.isDeprecated(cd.containingPackage()));
945     }
946 
947     /**
948      * Return the doclet specific instance of a writer factory.
949      * @return the {@link WriterFactory} for the doclet.
950      */
getWriterFactory()951     public abstract WriterFactory getWriterFactory();
952 
953     /**
954      * Return the input stream to the builder XML.
955      *
956      * @return the input steam to the builder XML.
957      * @throws FileNotFoundException when the given XML file cannot be found.
958      */
getBuilderXML()959     public InputStream getBuilderXML() throws IOException {
960         return builderXMLPath == null ?
961             Configuration.class.getResourceAsStream(DEFAULT_BUILDER_XML) :
962             DocFile.createFileForInput(this, builderXMLPath).openInputStream();
963     }
964 
965     /**
966      * Return the Locale for this document.
967      */
getLocale()968     public abstract Locale getLocale();
969 
970     /**
971      * Return the current file manager.
972      */
getFileManager()973     public abstract JavaFileManager getFileManager();
974 
975     /**
976      * Return the comparator that will be used to sort member documentation.
977      * To no do any sorting, return null.
978      *
979      * @return the {@link java.util.Comparator} used to sort members.
980      */
getMemberComparator()981     public abstract Comparator<ProgramElementDoc> getMemberComparator();
982 
setTabWidth(int n)983     private void setTabWidth(int n) {
984         sourcetab = n;
985         tabSpaces = String.format("%" + n + "s", "");
986     }
987 
showMessage(SourcePosition pos, String key)988     public abstract boolean showMessage(SourcePosition pos, String key);
989 }
990