001: /*
002: * Copyright 1997-2004 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 sole-instance.
117: */
118: public final MetaKeywords metakeywords = MetaKeywords
119: .getInstance(this );
120:
121: /**
122: * The list of doc-file subdirectories to exclude
123: */
124: protected Set excludedDocFileDirs;
125:
126: /**
127: * The list of qualifiers to exclude
128: */
129: protected Set excludedQualifiers;
130:
131: /**
132: * The Root of the generated Program Structure from the Doclet API.
133: */
134: public RootDoc root;
135:
136: /**
137: * Destination directory name, in which doclet will generate the entire
138: * documentation. Default is current directory.
139: */
140: public String destDirName = "";
141:
142: /**
143: * Destination directory name, in which doclet will copy the doc-files to.
144: */
145: public String docFileDestDirName = "";
146:
147: /**
148: * Encoding for this document. Default is default encoding for this
149: * platform.
150: */
151: public String docencoding = null;
152:
153: /**
154: * True if user wants to suppress descriptions and tags.
155: */
156: public boolean nocomment = false;
157:
158: /**
159: * Encoding for this document. Default is default encoding for this
160: * platform.
161: */
162: public String encoding = null;
163:
164: /**
165: * Generate author specific information for all the classes if @author
166: * tag is used in the doc comment and if -author option is used.
167: * <code>showauthor</code> is set to true if -author option is used.
168: * Default is don't show author information.
169: */
170: public boolean showauthor = false;
171:
172: /**
173: * Generate version specific information for the all the classes
174: * if @version tag is used in the doc comment and if -version option is
175: * used. <code>showversion</code> is set to true if -version option is
176: * used.Default is don't show version information.
177: */
178: public boolean showversion = false;
179:
180: /**
181: * Sourcepath from where to read the source files. Default is classpath.
182: *
183: */
184: public String sourcepath = "";
185:
186: /**
187: * Don't generate deprecated API information at all, if -nodeprecated
188: * option is used. <code>nodepracted</code> is set to true if
189: * -nodeprecated option is used. Default is generate deprected API
190: * information.
191: */
192: public boolean nodeprecated = false;
193:
194: /**
195: * The catalog of classes specified on the command-line
196: */
197: public ClassDocCatalog classDocCatalog;
198:
199: /**
200: * Message Retriever for the doclet, to retrieve message from the resource
201: * file for this Configuration, which is common for 1.1 and standard
202: * doclets.
203: *
204: * TODO: Make this private!!!
205: */
206: public MessageRetriever message = null;
207:
208: /**
209: * True if user wants to suppress time stamp in output.
210: * Default is false.
211: */
212: public boolean notimestamp = false;
213:
214: /**
215: * The package grouping sole-instance.
216: */
217: public final Group group = Group.getInstance(this );
218:
219: /**
220: * The tracker of external package links (sole-instance).
221: */
222: public final Extern extern = new Extern(this );
223:
224: /**
225: * Return the build date for the doclet.
226: */
227: public abstract String getDocletSpecificBuildDate();
228:
229: /**
230: * This method should be defined in all those doclets(configurations),
231: * which want to derive themselves from this Configuration. This method
232: * can be used to set its own command line options.
233: *
234: * @param options The array of option names and values.
235: * @throws DocletAbortException
236: */
237: public abstract void setSpecificDocletOptions(String[][] options);
238:
239: /**
240: * Return the doclet specific {@link MessageRetriever}
241: * @return the doclet specific MessageRetriever.
242: */
243: public abstract MessageRetriever getDocletSpecificMsg();
244:
245: /**
246: * An array of the packages specified on the command-line merged
247: * with the array of packages that contain the classes specified on the
248: * command-line. The array is sorted.
249: */
250: public PackageDoc[] packages;
251:
252: /**
253: * Constructor. Constructs the message retriever with resource file.
254: */
255: public Configuration() {
256: message = new MessageRetriever(this ,
257: "com.sun.tools.doclets.internal.toolkit.resources.doclets");
258: excludedDocFileDirs = new HashSet();
259: excludedQualifiers = new HashSet();
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") || option.equals("-xnodate")
298: || option.equals("-version")) {
299: return 1;
300: } else if (option.equals("-d") || option.equals("-docencoding")
301: || option.equals("-encoding")
302: || option.equals("-excludedocfilessubdir")
303: || option.equals("-link")
304: || option.equals("-sourcetab")
305: || option.equals("-noqualifier")
306: || option.equals("-output")
307: || option.equals("-sourcepath")
308: || option.equals("-tag") || option.equals("-taglet")
309: || option.equals("-tagletpath")) {
310: return 2;
311: } else if (option.equals("-group")
312: || option.equals("-linkoffline")) {
313: return 3;
314: } else {
315: return -1; // indicate we don't know about it
316: }
317: }
318:
319: /**
320: * Perform error checking on the given options.
321: *
322: * @param options the given options to check.
323: * @param reporter the reporter used to report errors.
324: */
325: public abstract boolean validOptions(String options[][],
326: DocErrorReporter reporter);
327:
328: private void initPackageArray() {
329: Set set = new HashSet(Arrays.asList(root.specifiedPackages()));
330: ClassDoc[] classes = root.specifiedClasses();
331: for (int i = 0; i < classes.length; i++) {
332: set.add(classes[i].containingPackage());
333: }
334: ArrayList results = new ArrayList(set);
335: Collections.sort(results);
336: packages = (PackageDoc[]) results.toArray(new PackageDoc[] {});
337: }
338:
339: /**
340: * Set the command line options supported by this configuration.
341: *
342: * @param options the two dimensional array of options.
343: */
344: public void setOptions(String[][] options) {
345: LinkedHashSet customTagStrs = new LinkedHashSet();
346: for (int oi = 0; oi < options.length; ++oi) {
347: String[] os = options[oi];
348: String opt = os[0].toLowerCase();
349: if (opt.equals("-d")) {
350: destDirName = addTrailingFileSep(os[1]);
351: docFileDestDirName = destDirName;
352: } else if (opt.equals("-docfilessubdirs")) {
353: copydocfilesubdirs = true;
354: } else if (opt.equals("-docencoding")) {
355: docencoding = os[1];
356: } else if (opt.equals("-encoding")) {
357: encoding = os[1];
358: } else if (opt.equals("-author")) {
359: showauthor = true;
360: } else if (opt.equals("-version")) {
361: showversion = true;
362: } else if (opt.equals("-nodeprecated")) {
363: nodeprecated = true;
364: } else if (opt.equals("-sourcepath")) {
365: sourcepath = os[1];
366: } else if (opt.equals("-classpath")
367: && sourcepath.length() == 0) {
368: sourcepath = os[1];
369: } else if (opt.equals("-excludedocfilessubdir")) {
370: addToSet(excludedDocFileDirs, os[1]);
371: } else if (opt.equals("-noqualifier")) {
372: addToSet(excludedQualifiers, os[1]);
373: } else if (opt.equals("-linksource")) {
374: linksource = true;
375: } else if (opt.equals("-sourcetab")) {
376: linksource = true;
377: try {
378: sourcetab = Integer.parseInt(os[1]);
379: } catch (NumberFormatException e) {
380: //Set to -1 so that warning will be printed
381: //to indicate what is valid argument.
382: sourcetab = -1;
383: }
384: if (sourcetab <= 0) {
385: message.warning("doclet.sourcetab_warning");
386: sourcetab = DocletConstants.DEFAULT_TAB_STOP_LENGTH;
387: }
388: } else if (opt.equals("-notimestamp")) {
389: notimestamp = true;
390: } else if (opt.equals("-nocomment")) {
391: nocomment = true;
392: } else if (opt.equals("-tag") || opt.equals("-taglet")) {
393: customTagStrs.add(os);
394: } else if (opt.equals("-tagletpath")) {
395: tagletpath = os[1];
396: } else if (opt.equals("-keywords")) {
397: keywords = true;
398: } else if (opt.equals("-serialwarn")) {
399: serialwarn = true;
400: } else if (opt.equals("-group")) {
401: group.checkPackageGroups(os[1], os[2]);
402: } else if (opt.equals("-link")) {
403: String url = os[1];
404: extern.url(url, url, root, false);
405: } else if (opt.equals("-linkoffline")) {
406: String url = os[1];
407: String pkglisturl = os[2];
408: extern.url(url, pkglisturl, root, true);
409: }
410: }
411: if (sourcepath.length() == 0) {
412: sourcepath = System.getProperty("env.class.path") == null ? ""
413: : System.getProperty("env.class.path");
414: }
415: if (docencoding == null) {
416: docencoding = encoding;
417: }
418:
419: classDocCatalog = new ClassDocCatalog(root.specifiedClasses());
420: initTagletManager(customTagStrs);
421: }
422:
423: /**
424: * Set the command line options supported by this configuration.
425: *
426: * @throws DocletAbortException
427: */
428: public void setOptions() {
429: initPackageArray();
430: setOptions(root.options());
431: setSpecificDocletOptions(root.options());
432: }
433:
434: /**
435: * Initialize the taglet manager. The strings to initialize the simple custom tags should
436: * be in the following format: "[tag name]:[location str]:[heading]".
437: * @param customTagStrs the set two dimentional arrays of strings. These arrays contain
438: * either -tag or -taglet arguments.
439: */
440: private void initTagletManager(Set customTagStrs) {
441: tagletManager = tagletManager == null ? new TagletManager(
442: nosince, showversion, showauthor, message)
443: : tagletManager;
444: String[] args;
445: for (Iterator it = customTagStrs.iterator(); it.hasNext();) {
446: args = (String[]) it.next();
447: if (args[0].equals("-taglet")) {
448: tagletManager.addCustomTag(args[1], tagletpath);
449: continue;
450: }
451: String[] tokens = Util.tokenize(args[1],
452: TagletManager.SIMPLE_TAGLET_OPT_SEPERATOR, 3);
453: if (tokens.length == 1) {
454: String tagName = args[1];
455: if (tagletManager.isKnownCustomTag(tagName)) {
456: //reorder a standard tag
457: tagletManager.addNewSimpleCustomTag(tagName, null,
458: "");
459: } else {
460: //Create a simple tag with the heading that has the same name as the tag.
461: StringBuffer heading = new StringBuffer(tagName
462: + ":");
463: heading.setCharAt(0, Character.toUpperCase(tagName
464: .charAt(0)));
465: tagletManager.addNewSimpleCustomTag(tagName,
466: 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],
471: tokens[1], "");
472: } else if (tokens.length >= 3) {
473: tagletManager.addNewSimpleCustomTag(tokens[0],
474: tokens[2], tokens[1]);
475: } else {
476: message.error(
477: "doclet.Error_invalid_custom_tag_argument",
478: args[1]);
479: }
480: }
481: }
482:
483: private void addToSet(Set s, String str) {
484: StringTokenizer st = new StringTokenizer(str, ":");
485: String current;
486: while (st.hasMoreTokens()) {
487: current = st.nextToken();
488: s.add(current);
489: }
490: }
491:
492: /**
493: * Add a traliling file separator, if not found or strip off extra trailing
494: * file separators if any.
495: *
496: * @param path Path under consideration.
497: * @return String Properly constructed path string.
498: */
499: String addTrailingFileSep(String path) {
500: String fs = System.getProperty("file.separator");
501: String dblfs = fs + fs;
502: int indexDblfs;
503: while ((indexDblfs = path.indexOf(dblfs)) >= 0) {
504: path = path.substring(0, indexDblfs)
505: + path.substring(indexDblfs + fs.length());
506: }
507: if (!path.endsWith(fs))
508: path += fs;
509: return path;
510: }
511:
512: /**
513: * This checks for the validity of the options used by the user.
514: * This works exactly like
515: * {@link com.sun.javadoc.Doclet#validOptions(String[][],
516: * DocErrorReporter)}. This will validate the options which are shared
517: * by our doclets. For example, this method will flag an error using
518: * the DocErrorReporter if user has used "-nohelp" and "-helpfile" option
519: * together.
520: *
521: * @param options options used on the command line.
522: * @param reporter used to report errors.
523: * @return true if all the options are valid.
524: */
525: public boolean generalValidOptions(String options[][],
526: DocErrorReporter reporter) {
527: boolean docencodingfound = false;
528: String encoding = "";
529: for (int oi = 0; oi < options.length; oi++) {
530: String[] os = options[oi];
531: String opt = os[0].toLowerCase();
532: if (opt.equals("-d")) {
533: String destdirname = addTrailingFileSep(os[1]);
534: File destDir = new File(destdirname);
535: if (!destDir.exists()) {
536: //Create the output directory (in case it doesn't exist yet)
537: reporter.printNotice(getText(
538: "doclet.dest_dir_create", destdirname));
539: (new File(destdirname)).mkdirs();
540: } else if (!destDir.isDirectory()) {
541: reporter
542: .printError(getText(
543: "doclet.destination_directory_not_directory_0",
544: destDir.getPath()));
545: return false;
546: } else if (!destDir.canWrite()) {
547: reporter
548: .printError(getText(
549: "doclet.destination_directory_not_writable_0",
550: destDir.getPath()));
551: return false;
552: }
553: } else if (opt.equals("-docencoding")) {
554: docencodingfound = true;
555: if (!checkOutputFileEncoding(os[1], reporter)) {
556: return false;
557: }
558: } else if (opt.equals("-encoding")) {
559: encoding = os[1];
560: }
561: }
562: if (!docencodingfound && encoding.length() > 0) {
563: if (!checkOutputFileEncoding(encoding, reporter)) {
564: return false;
565: }
566: }
567: return true;
568: }
569:
570: /**
571: * Check the validity of the given Source or Output File encoding on this
572: * platform.
573: *
574: * @param docencoding output file encoding.
575: * @param reporter used to report errors.
576: */
577: private boolean checkOutputFileEncoding(String docencoding,
578: DocErrorReporter reporter) {
579: OutputStream ost = new ByteArrayOutputStream();
580: OutputStreamWriter osw = null;
581: try {
582: osw = new OutputStreamWriter(ost, docencoding);
583: } catch (UnsupportedEncodingException exc) {
584: reporter.printError(getText(
585: "doclet.Encoding_not_supported", docencoding));
586: return false;
587: } finally {
588: try {
589: if (osw != null) {
590: osw.close();
591: }
592: } catch (IOException exc) {
593: }
594: }
595: return true;
596: }
597:
598: /**
599: * Return true if the given doc-file subdirectory should be excluded and
600: * false otherwise.
601: * @param docfilesubdir the doc-files subdirectory to check.
602: */
603: public boolean shouldExcludeDocFileDir(String docfilesubdir) {
604: if (excludedDocFileDirs.contains(docfilesubdir)) {
605: return true;
606: } else {
607: return false;
608: }
609: }
610:
611: /**
612: * Return true if the given qualifier should be excluded and false otherwise.
613: * @param qualifier the qualifier to check.
614: */
615: public boolean shouldExcludeQualifier(String qualifier) {
616: if (excludedQualifiers.contains("all")
617: || excludedQualifiers.contains(qualifier)
618: || excludedQualifiers.contains(qualifier + ".*")) {
619: return true;
620: } else {
621: int index = -1;
622: while ((index = qualifier.indexOf(".", index + 1)) != -1) {
623: if (excludedQualifiers.contains(qualifier.substring(0,
624: index + 1)
625: + "*")) {
626: return true;
627: }
628: }
629: return false;
630: }
631: }
632:
633: /**
634: * Return the qualified name of the <code>ClassDoc</code> if it's qualifier is not excluded. Otherwise,
635: * return the unqualified <code>ClassDoc</code> name.
636: * @param cd the <code>ClassDoc</code> to check.
637: */
638: public String getClassName(ClassDoc cd) {
639: PackageDoc pd = cd.containingPackage();
640: if (pd != null
641: && shouldExcludeQualifier(cd.containingPackage().name())) {
642: return cd.name();
643: } else {
644: return cd.qualifiedName();
645: }
646: }
647:
648: public String getText(String key) {
649: try {
650: //Check the doclet specific properties file.
651: return getDocletSpecificMsg().getText(key);
652: } catch (Exception e) {
653: //Check the shared properties file.
654: return message.getText(key);
655: }
656: }
657:
658: public String getText(String key, String a1) {
659: try {
660: //Check the doclet specific properties file.
661: return getDocletSpecificMsg().getText(key, a1);
662: } catch (Exception e) {
663: //Check the shared properties file.
664: return message.getText(key, a1);
665: }
666: }
667:
668: public String getText(String key, String a1, String a2) {
669: try {
670: //Check the doclet specific properties file.
671: return getDocletSpecificMsg().getText(key, a1, a2);
672: } catch (Exception e) {
673: //Check the shared properties file.
674: return message.getText(key, a1, a2);
675: }
676: }
677:
678: public String getText(String key, String a1, String a2, String a3) {
679: try {
680: //Check the doclet specific properties file.
681: return getDocletSpecificMsg().getText(key, a1, a2, a3);
682: } catch (Exception e) {
683: //Check the shared properties file.
684: return message.getText(key, a1, a2, a3);
685: }
686: }
687:
688: /**
689: * Return true if the doc element is getting documented, depending upon
690: * -nodeprecated option and @deprecated tag used. Return true if
691: * -nodeprecated is not used or @deprecated tag is not used.
692: */
693: public boolean isGeneratedDoc(Doc doc) {
694: if (!nodeprecated) {
695: return true;
696: }
697: return (doc.tags("deprecated")).length == 0;
698: }
699:
700: /**
701: * Return the doclet specific instance of a writer factory.
702: * @return the {@link WriterFactory} for the doclet.
703: */
704: public abstract WriterFactory getWriterFactory();
705:
706: /**
707: * Return the input stream to the builder XML.
708: *
709: * @return the input steam to the builder XML.
710: * @throws FileNotFoundException when the given XML file cannot be found.
711: */
712: public InputStream getBuilderXML() throws FileNotFoundException {
713: return builderXMLPath == null ? Configuration.class
714: .getResourceAsStream(DEFAULT_BUILDER_XML)
715: : new FileInputStream(new File(builderXMLPath));
716: }
717:
718: /**
719: * Return the comparator that will be used to sort member documentation.
720: * To no do any sorting, return null.
721: *
722: * @return the {@link java.util.Comparator} used to sort members.
723: */
724: public abstract Comparator getMemberComparator();
725: }
|