001    /*
002     * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.doclets.internal.toolkit;
027    
028    import com.sun.tools.doclets.internal.toolkit.taglets.*;
029    import com.sun.tools.doclets.internal.toolkit.util.*;
030    import com.sun.tools.doclets.internal.toolkit.builders.BuilderFactory;
031    import com.sun.javadoc.*;
032    import java.util.*;
033    import java.io.*;
034    
035    /**
036     * Configure the output based on the options. Doclets should sub-class
037     * Configuration, to configure and add their own options. This class contains
038     * all user options which are supported by the 1.1 doclet and the standard
039     * doclet.
040     *
041     * This code is not part of an API.
042     * It is implementation that is subject to change.
043     * Do not use it as an API
044     *
045     * @author Robert Field.
046     * @author Atul Dambalkar.
047     * @author Jamie Ho
048     */
049    public abstract class Configuration {
050    
051        /**
052         * The factory for builders.
053         */
054        protected BuilderFactory builderFactory;
055    
056        /**
057         * The taglet manager.
058         */
059        public TagletManager tagletManager;
060    
061        /**
062         * The path to the builder XML input file.
063         */
064        public String builderXMLPath;
065    
066        /**
067         * The default path to the builder XML.
068         */
069        private static final String DEFAULT_BUILDER_XML = "resources/doclet.xml";
070    
071        /**
072         * The path to Taglets
073         */
074        public String tagletpath = "";
075    
076        /**
077         * This is true if option "-serialwarn" is used. Defualt value is false to
078         * supress excessive warnings about serial tag.
079         */
080        public boolean serialwarn = false;
081    
082        /**
083         * The specified amount of space between tab stops.
084         */
085        public int sourcetab = DocletConstants.DEFAULT_TAB_STOP_LENGTH;
086    
087        /**
088         * True if we should generate browsable sources.
089         */
090        public boolean linksource = false;
091    
092        /**
093         * True if command line option "-nosince" is used. Default value is
094         * false.
095         */
096        public boolean nosince = false;
097    
098        /**
099         * True if we should recursively copy the doc-file subdirectories
100         */
101        public boolean copydocfilesubdirs = false;
102    
103        /**
104         * The META charset tag used for cross-platform viewing.
105         */
106        public String charset = "";
107    
108        /**
109         * True if user wants to add member names as meta keywords.
110         * Set to false because meta keywords are ignored in general
111         * by most Internet search engines.
112         */
113        public boolean keywords = false;
114    
115        /**
116         * The meta tag keywords instance.
117         */
118        public final MetaKeywords metakeywords = new MetaKeywords(this);
119    
120        /**
121         * The list of doc-file subdirectories to exclude
122         */
123        protected Set<String> excludedDocFileDirs;
124    
125        /**
126         * The list of qualifiers to exclude
127         */
128        protected Set<String> excludedQualifiers;
129    
130        /**
131         * The Root of the generated Program Structure from the Doclet API.
132         */
133        public RootDoc root;
134    
135        /**
136         * Destination directory name, in which doclet will generate the entire
137         * documentation. Default is current directory.
138         */
139        public String destDirName = "";
140    
141        /**
142         * Destination directory name, in which doclet will copy the doc-files to.
143         */
144        public String docFileDestDirName = "";
145    
146        /**
147         * Encoding for this document. Default is default encoding for this
148         * platform.
149         */
150        public String docencoding = null;
151    
152        /**
153         * True if user wants to suppress descriptions and tags.
154         */
155        public boolean nocomment = false;
156    
157        /**
158         * Encoding for this document. Default is default encoding for this
159         * platform.
160         */
161        public String encoding = null;
162    
163        /**
164         * Generate author specific information for all the classes if @author
165         * tag is used in the doc comment and if -author option is used.
166         * <code>showauthor</code> is set to true if -author option is used.
167         * Default is don't show author information.
168         */
169        public boolean showauthor = false;
170    
171        /**
172         * Generate version specific information for the all the classes
173         * if @version tag is used in the doc comment and if -version option is
174         * used. <code>showversion</code> is set to true if -version option is
175         * used.Default is don't show version information.
176         */
177        public boolean showversion = false;
178    
179        /**
180         * Sourcepath from where to read the source files. Default is classpath.
181         *
182         */
183        public String sourcepath = "";
184    
185        /**
186         * Don't generate deprecated API information at all, if -nodeprecated
187         * option is used. <code>nodepracted</code> is set to true if
188         * -nodeprecated option is used. Default is generate deprected API
189         * information.
190         */
191        public boolean nodeprecated = false;
192    
193        /**
194         * The catalog of classes specified on the command-line
195         */
196        public ClassDocCatalog classDocCatalog;
197    
198        /**
199         * Message Retriever for the doclet, to retrieve message from the resource
200         * file for this Configuration, which is common for 1.1 and standard
201         * doclets.
202         *
203         * TODO:  Make this private!!!
204         */
205        public MessageRetriever message = null;
206    
207        /**
208         * True if user wants to suppress time stamp in output.
209         * Default is false.
210         */
211        public boolean notimestamp= false;
212    
213        /**
214         * The package grouping instance.
215         */
216        public final Group group = new Group(this);
217    
218        /**
219         * The tracker of external package links.
220         */
221        public final Extern extern = new Extern(this);
222    
223        /**
224         * Return the build date for the doclet.
225         */
226        public abstract String getDocletSpecificBuildDate();
227    
228        /**
229         * This method should be defined in all those doclets(configurations),
230         * which want to derive themselves from this Configuration. This method
231         * can be used to set its own command line options.
232         *
233         * @param options The array of option names and values.
234         * @throws DocletAbortException
235         */
236        public abstract void setSpecificDocletOptions(String[][] options);
237    
238        /**
239         * Return the doclet specific {@link MessageRetriever}
240         * @return the doclet specific MessageRetriever.
241         */
242        public abstract MessageRetriever getDocletSpecificMsg();
243    
244        /**
245         * An array of the packages specified on the command-line merged
246         * with the array of packages that contain the classes specified on the
247         * command-line.  The array is sorted.
248         */
249        public PackageDoc[] packages;
250    
251        /**
252         * Constructor. Constructs the message retriever with resource file.
253         */
254        public Configuration() {
255            message =
256                new MessageRetriever(this,
257                "com.sun.tools.doclets.internal.toolkit.resources.doclets");
258            excludedDocFileDirs = new HashSet<String>();
259            excludedQualifiers = new HashSet<String>();
260        }
261    
262        /**
263         * Return the builder factory for this doclet.
264         *
265         * @return the builder factory for this doclet.
266         */
267        public BuilderFactory getBuilderFactory() {
268            if (builderFactory == null) {
269                builderFactory = new BuilderFactory(this);
270            }
271            return builderFactory;
272        }
273    
274        /**
275         * This method should be defined in all those doclets
276         * which want to inherit from this Configuration. This method
277         * should return the number of arguments to the command line
278         * option (including the option name).  For example,
279         * -notimestamp is a single-argument option, so this method would
280         * return 1.
281         *
282         * @param option Command line option under consideration.
283         * @return number of arguments to option (including the
284         * option name). Zero return means option not known.
285         * Negative value means error occurred.
286         */
287        public int optionLength(String option) {
288            option = option.toLowerCase();
289            if (option.equals("-author") ||
290                option.equals("-docfilessubdirs") ||
291                option.equals("-keywords") ||
292                option.equals("-linksource") ||
293                option.equals("-nocomment") ||
294                option.equals("-nodeprecated") ||
295                option.equals("-nosince") ||
296                option.equals("-notimestamp") ||
297                option.equals("-quiet") ||
298                option.equals("-xnodate") ||
299                option.equals("-version")) {
300                return 1;
301            } else if (option.equals("-d") ||
302                       option.equals("-docencoding") ||
303                       option.equals("-encoding") ||
304                       option.equals("-excludedocfilessubdir") ||
305                       option.equals("-link") ||
306                       option.equals("-sourcetab") ||
307                       option.equals("-noqualifier") ||
308                       option.equals("-output") ||
309                       option.equals("-sourcepath") ||
310                       option.equals("-tag") ||
311                       option.equals("-taglet") ||
312                       option.equals("-tagletpath")) {
313                return 2;
314            } else if (option.equals("-group") ||
315                       option.equals("-linkoffline")) {
316                return 3;
317            } else {
318                return -1;  // indicate we don't know about it
319            }
320        }
321    
322        /**
323         * Perform error checking on the given options.
324         *
325         * @param options  the given options to check.
326         * @param reporter the reporter used to report errors.
327         */
328        public abstract boolean validOptions(String options[][],
329            DocErrorReporter reporter);
330    
331        private void initPackageArray() {
332            Set<PackageDoc> set = new HashSet<PackageDoc>(Arrays.asList(root.specifiedPackages()));
333            ClassDoc[] classes = root.specifiedClasses();
334            for (int i = 0; i < classes.length; i++) {
335                set.add(classes[i].containingPackage());
336            }
337            ArrayList<PackageDoc> results = new ArrayList<PackageDoc>(set);
338            Collections.sort(results);
339            packages = results.toArray(new PackageDoc[] {});
340        }
341    
342        /**
343         * Set the command line options supported by this configuration.
344         *
345         * @param options the two dimensional array of options.
346         */
347        public void setOptions(String[][] options) {
348            LinkedHashSet<String[]> customTagStrs = new LinkedHashSet<String[]>();
349            for (int oi = 0; oi < options.length; ++oi) {
350                String[] os = options[oi];
351                String opt = os[0].toLowerCase();
352                if (opt.equals("-d")) {
353                    destDirName = addTrailingFileSep(os[1]);
354                    docFileDestDirName = destDirName;
355                } else  if (opt.equals("-docfilessubdirs")) {
356                    copydocfilesubdirs = true;
357                } else  if (opt.equals("-docencoding")) {
358                    docencoding = os[1];
359                } else  if (opt.equals("-encoding")) {
360                    encoding = os[1];
361                } else  if (opt.equals("-author")) {
362                    showauthor = true;
363                } else  if (opt.equals("-version")) {
364                    showversion = true;
365                } else  if (opt.equals("-nodeprecated")) {
366                    nodeprecated = true;
367                } else  if (opt.equals("-sourcepath")) {
368                    sourcepath = os[1];
369                } else if (opt.equals("-classpath") &&
370                           sourcepath.length() == 0) {
371                    sourcepath = os[1];
372                } else if (opt.equals("-excludedocfilessubdir")) {
373                    addToSet(excludedDocFileDirs, os[1]);
374                } else if (opt.equals("-noqualifier")) {
375                    addToSet(excludedQualifiers, os[1]);
376                } else if (opt.equals("-linksource")) {
377                    linksource = true;
378                } else if (opt.equals("-sourcetab")) {
379                    linksource = true;
380                    try {
381                        sourcetab = Integer.parseInt(os[1]);
382                    } catch (NumberFormatException e) {
383                        //Set to -1 so that warning will be printed
384                        //to indicate what is valid argument.
385                        sourcetab = -1;
386                    }
387                    if (sourcetab <= 0) {
388                        message.warning("doclet.sourcetab_warning");
389                        sourcetab = DocletConstants.DEFAULT_TAB_STOP_LENGTH;
390                    }
391                } else  if (opt.equals("-notimestamp")) {
392                    notimestamp = true;
393                } else  if (opt.equals("-nocomment")) {
394                    nocomment = true;
395                } else if (opt.equals("-tag") || opt.equals("-taglet")) {
396                    customTagStrs.add(os);
397                } else if (opt.equals("-tagletpath")) {
398                    tagletpath = os[1];
399                } else  if (opt.equals("-keywords")) {
400                    keywords = true;
401                } else  if (opt.equals("-serialwarn")) {
402                    serialwarn = true;
403                } else if (opt.equals("-group")) {
404                    group.checkPackageGroups(os[1], os[2]);
405                } else if (opt.equals("-link")) {
406                    String url = os[1];
407                    extern.url(url, url, root, false);
408                } else if (opt.equals("-linkoffline")) {
409                    String url = os[1];
410                    String pkglisturl = os[2];
411                    extern.url(url, pkglisturl, root, true);
412                }
413            }
414            if (sourcepath.length() == 0) {
415                sourcepath = System.getProperty("env.class.path") == null ? "" :
416                    System.getProperty("env.class.path");
417            }
418            if (docencoding == null) {
419                docencoding = encoding;
420            }
421    
422            classDocCatalog = new ClassDocCatalog(root.specifiedClasses());
423            initTagletManager(customTagStrs);
424        }
425    
426        /**
427         * Set the command line options supported by this configuration.
428         *
429         * @throws DocletAbortException
430         */
431        public void setOptions() {
432            initPackageArray();
433            setOptions(root.options());
434            setSpecificDocletOptions(root.options());
435        }
436    
437    
438        /**
439         * Initialize the taglet manager.  The strings to initialize the simple custom tags should
440         * be in the following format:  "[tag name]:[location str]:[heading]".
441         * @param customTagStrs the set two dimentional arrays of strings.  These arrays contain
442         * either -tag or -taglet arguments.
443         */
444        private void initTagletManager(Set<String[]> customTagStrs) {
445            tagletManager = tagletManager == null ?
446                new TagletManager(nosince, showversion, showauthor, message) :
447                tagletManager;
448            String[] args;
449            for (Iterator<String[]> it = customTagStrs.iterator(); it.hasNext(); ) {
450                args = it.next();
451                if (args[0].equals("-taglet")) {
452                    tagletManager.addCustomTag(args[1], tagletpath);
453                    continue;
454                }
455                String[] tokens = Util.tokenize(args[1],
456                    TagletManager.SIMPLE_TAGLET_OPT_SEPERATOR, 3);
457                if (tokens.length == 1) {
458                    String tagName = args[1];
459                    if (tagletManager.isKnownCustomTag(tagName)) {
460                        //reorder a standard tag
461                        tagletManager.addNewSimpleCustomTag(tagName, null, "");
462                    } else {
463                        //Create a simple tag with the heading that has the same name as the tag.
464                        StringBuffer heading = new StringBuffer(tagName + ":");
465                        heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0)));
466                        tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a");
467                    }
468                } else if (tokens.length == 2) {
469                    //Add simple taglet without heading, probably to excluding it in the output.
470                    tagletManager.addNewSimpleCustomTag(tokens[0], tokens[1], "");
471                } else if (tokens.length >= 3) {
472                    tagletManager.addNewSimpleCustomTag(tokens[0], tokens[2], tokens[1]);
473                } else {
474                    message.error("doclet.Error_invalid_custom_tag_argument", args[1]);
475                }
476            }
477        }
478    
479        private void addToSet(Set<String> s, String str){
480            StringTokenizer st = new StringTokenizer(str, ":");
481            String current;
482            while(st.hasMoreTokens()){
483                current = st.nextToken();
484                s.add(current);
485            }
486        }
487    
488        /**
489         * Add a traliling file separator, if not found or strip off extra trailing
490         * file separators if any.
491         *
492         * @param path Path under consideration.
493         * @return String Properly constructed path string.
494         */
495        String addTrailingFileSep(String path) {
496            String fs = System.getProperty("file.separator");
497            String dblfs = fs + fs;
498            int indexDblfs;
499            while ((indexDblfs = path.indexOf(dblfs)) >= 0) {
500                path = path.substring(0, indexDblfs) +
501                    path.substring(indexDblfs + fs.length());
502            }
503            if (!path.endsWith(fs))
504                path += fs;
505            return path;
506        }
507    
508        /**
509         * This checks for the validity of the options used by the user.
510         * This works exactly like
511         * {@link com.sun.javadoc.Doclet#validOptions(String[][],
512         * DocErrorReporter)}. This will validate the options which are shared
513         * by our doclets. For example, this method will flag an error using
514         * the DocErrorReporter if user has used "-nohelp" and "-helpfile" option
515         * together.
516         *
517         * @param options  options used on the command line.
518         * @param reporter used to report errors.
519         * @return true if all the options are valid.
520         */
521        public boolean generalValidOptions(String options[][],
522                DocErrorReporter reporter) {
523            boolean docencodingfound = false;
524            String encoding = "";
525            for (int oi = 0; oi < options.length; oi++) {
526                String[] os = options[oi];
527                String opt = os[0].toLowerCase();
528                if (opt.equals("-d")) {
529                    String destdirname = addTrailingFileSep(os[1]);
530                    File destDir = new File(destdirname);
531                    if (!destDir.exists()) {
532                        //Create the output directory (in case it doesn't exist yet)
533                        reporter.printNotice(getText("doclet.dest_dir_create",
534                            destdirname));
535                        (new File(destdirname)).mkdirs();
536                    } else if (!destDir.isDirectory()) {
537                        reporter.printError(getText(
538                            "doclet.destination_directory_not_directory_0",
539                            destDir.getPath()));
540                        return false;
541                    } else if (!destDir.canWrite()) {
542                        reporter.printError(getText(
543                            "doclet.destination_directory_not_writable_0",
544                            destDir.getPath()));
545                        return false;
546                    }
547                } else if (opt.equals("-docencoding")) {
548                    docencodingfound = true;
549                    if (!checkOutputFileEncoding(os[1], reporter)) {
550                        return false;
551                    }
552                } else if (opt.equals("-encoding")) {
553                    encoding = os[1];
554                }
555            }
556            if (!docencodingfound && encoding.length() > 0) {
557                if (!checkOutputFileEncoding(encoding, reporter)) {
558                    return false;
559                }
560            }
561            return true;
562        }
563    
564        /**
565         * Check the validity of the given Source or Output File encoding on this
566         * platform.
567         *
568         * @param docencoding output file encoding.
569         * @param reporter    used to report errors.
570         */
571        private boolean checkOutputFileEncoding(String docencoding,
572                DocErrorReporter reporter) {
573            OutputStream ost= new ByteArrayOutputStream();
574            OutputStreamWriter osw = null;
575            try {
576                osw = new OutputStreamWriter(ost, docencoding);
577            } catch (UnsupportedEncodingException exc) {
578                reporter.printError(getText("doclet.Encoding_not_supported",
579                    docencoding));
580                return false;
581            } finally {
582                try {
583                    if (osw != null) {
584                        osw.close();
585                    }
586                } catch (IOException exc) {
587                }
588            }
589            return true;
590        }
591    
592        /**
593         * Return true if the given doc-file subdirectory should be excluded and
594         * false otherwise.
595         * @param docfilesubdir the doc-files subdirectory to check.
596         */
597        public boolean shouldExcludeDocFileDir(String docfilesubdir){
598            if (excludedDocFileDirs.contains(docfilesubdir)) {
599                return true;
600            } else {
601                return false;
602            }
603        }
604    
605        /**
606         * Return true if the given qualifier should be excluded and false otherwise.
607         * @param qualifier the qualifier to check.
608         */
609        public boolean shouldExcludeQualifier(String qualifier){
610            if (excludedQualifiers.contains("all") ||
611                excludedQualifiers.contains(qualifier) ||
612                excludedQualifiers.contains(qualifier + ".*")) {
613                return true;
614            } else {
615                int index = -1;
616                while ((index = qualifier.indexOf(".", index + 1)) != -1) {
617                    if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) {
618                        return true;
619                    }
620                }
621                return false;
622            }
623        }
624    
625        /**
626         * Return the qualified name of the <code>ClassDoc</code> if it's qualifier is not excluded.  Otherwise,
627         * return the unqualified <code>ClassDoc</code> name.
628         * @param cd the <code>ClassDoc</code> to check.
629         */
630        public String getClassName(ClassDoc cd) {
631            PackageDoc pd = cd.containingPackage();
632            if (pd != null && shouldExcludeQualifier(cd.containingPackage().name())) {
633                return cd.name();
634            } else {
635                return cd.qualifiedName();
636            }
637        }
638    
639        public String getText(String key) {
640            try {
641                //Check the doclet specific properties file.
642                return getDocletSpecificMsg().getText(key);
643            } catch (Exception e) {
644                //Check the shared properties file.
645                return message.getText(key);
646            }
647        }
648    
649        public String getText(String key, String a1) {
650            try {
651                //Check the doclet specific properties file.
652                return getDocletSpecificMsg().getText(key, a1);
653            } catch (Exception e) {
654                //Check the shared properties file.
655                return message.getText(key, a1);
656            }
657        }
658    
659        public String getText(String key, String a1, String a2) {
660            try {
661                //Check the doclet specific properties file.
662                return getDocletSpecificMsg().getText(key, a1, a2);
663            } catch (Exception e) {
664                //Check the shared properties file.
665                return message.getText(key, a1, a2);
666            }
667        }
668    
669        public String getText(String key, String a1, String a2, String a3) {
670            try {
671                //Check the doclet specific properties file.
672                return getDocletSpecificMsg().getText(key, a1, a2, a3);
673            } catch (Exception e) {
674                //Check the shared properties file.
675                return message.getText(key, a1, a2, a3);
676            }
677        }
678    
679        /**
680         * Return true if the doc element is getting documented, depending upon
681         * -nodeprecated option and @deprecated tag used. Return true if
682         * -nodeprecated is not used or @deprecated tag is not used.
683         */
684        public boolean isGeneratedDoc(Doc doc) {
685            if (!nodeprecated) {
686                return true;
687            }
688            return (doc.tags("deprecated")).length == 0;
689        }
690    
691        /**
692         * Return the doclet specific instance of a writer factory.
693         * @return the {@link WriterFactory} for the doclet.
694         */
695        public abstract WriterFactory getWriterFactory();
696    
697        /**
698         * Return the input stream to the builder XML.
699         *
700         * @return the input steam to the builder XML.
701         * @throws FileNotFoundException when the given XML file cannot be found.
702         */
703        public InputStream getBuilderXML() throws FileNotFoundException {
704            return builderXMLPath == null ?
705                Configuration.class.getResourceAsStream(DEFAULT_BUILDER_XML) :
706                new FileInputStream(new File(builderXMLPath));
707        }
708    
709        /**
710         * Return the Locale for this document.
711         */
712        public abstract Locale getLocale();
713    
714        /**
715         * Return the comparator that will be used to sort member documentation.
716         * To no do any sorting, return null.
717         *
718         * @return the {@link java.util.Comparator} used to sort members.
719         */
720        public abstract Comparator<ProgramElementDoc> getMemberComparator();
721    }