1 /* gnu.classpath.tools.gjdoc.Main
2 Copyright (C) 2001 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package gnu.classpath.tools.gjdoc;
40 import com.sun.javadoc.*;
43 import java.lang.reflect.*;
44 import java.text.Collator;
46 import gnu.classpath.tools.FileSystemClassLoader;
49 * Class that will launch the gjdoc tool.
51 public final class Main
55 * Do we load classes that are referenced as base class?
57 static final boolean DESCEND_SUPERCLASS = true;
60 * Do we load classes that are referenced as interface?
62 static final boolean DESCEND_INTERFACES = false;
65 * Do we load classes that are imported in a source file?
67 static final boolean DESCEND_IMPORTED = true;
70 * Document only public members.
72 static final int COVERAGE_PUBLIC = 0;
75 * Document only public and protected members.
77 static final int COVERAGE_PROTECTED = 1;
80 * Document public, protected and package private members.
82 static final int COVERAGE_PACKAGE = 2;
85 * Document all members.
87 static final int COVERAGE_PRIVATE = 3;
90 * FIXME: This should come from a ResourceBundle
92 private static final String STRING_TRY_GJDOC_HELP =
93 "Try `gjdoc --help' for more information.";
96 * Grid for looking up whether a particular access level is included in the
99 static final boolean[][] coverageTemplates = new boolean[][]
101 { true, false, false, false }, // public
103 { true, true, false, false }, // protected
105 { true, true, true, false }, // package
107 { true, true, true, true }, // private
111 * Holds the Singleton instance of this class.
113 private static Main instance = new Main();
116 * Avoid re-instantiation of this class.
122 private static RootDocImpl rootDoc;
124 private ErrorReporter reporter;
127 * Cache for version string from resource /version.properties
129 private String gjdocVersion;
132 * <code>false</code> during Phase I: preparation of the documentation data.
133 * <code>true</code> during Phase II: documentation output by doclet.
135 boolean docletRunning = false;
137 //---- Command line options
140 * Option "-doclet": name of the Doclet class to use.
142 private String option_doclet = "gnu.classpath.tools.doclets.htmldoclet.HtmlDoclet";
145 * Option "-overview": path to the special overview file.
147 private String option_overview;
150 * Option "-coverage": which members to include in generated documentation.
152 private int option_coverage = COVERAGE_PROTECTED;
155 * Option "-help": display command line usage.
157 private boolean option_help;
160 * Option "-docletpath": path to doclet classes.
162 private String option_docletpath;
165 * Option "-classpath": path to additional classes.
167 private String option_classpath;
170 * Option "-sourcepath": path to the Java source files to be documented.
171 * FIXME: this should be a list of paths
173 private List option_sourcepath = new ArrayList();
176 * Option "-extdirs": path to Java extension files.
178 private String option_extdirs;
181 * Option "-verbose": Be verbose when generating documentation.
183 private boolean option_verbose;
186 * Option "-nowarn": Do not print warnings.
188 private boolean option_nowarn;
191 * Option "-locale:" Specify the locale charset of Java source files.
193 private Locale option_locale = new Locale("en", "us");
196 * Option "-encoding": Specify character encoding of Java source files.
198 private String option_encoding;
201 * Option "-J": Specify flags to be passed to Java runtime.
203 private List option_java_flags = new LinkedList(); //ArrayList();
206 * Option "-source:" should be 1.4 to handle assertions, 1.1 is no
209 private String option_source = "1.2";
212 * Option "-subpackages": list of subpackages to be recursively
215 private List option_subpackages = new ArrayList();
218 * Option "-exclude": list of subpackages to exclude.
220 private List option_exclude = new ArrayList();
223 * Option "-breakiterator" - whether to use BreakIterator for
224 * detecting the end of the first sentence.
226 private boolean option_breakiterator;
229 * Option "-licensetext" - whether to copy license text.
231 private boolean option_licensetext;
234 * The locale-dependent collator used for sorting.
236 private Collator collator;
239 * true when --version has been specified on the command line.
241 private boolean option_showVersion;
244 * true when -bootclasspath has been specified on the command line.
246 private boolean option_bootclasspath_specified;
249 * true when -all has been specified on the command line.
251 private boolean option_all;
254 * true when -reflection has been specified on the command line.
256 private boolean option_reflection;
258 // TODO: add the rest of the options as instance variables
261 * Parse all source files/packages and subsequentially start the Doclet given
262 * on the command line.
264 * @param allOptions List of all command line tokens
266 private boolean startDoclet(List allOptions)
272 //--- Fetch the Class object for the Doclet.
274 Debug.log(1, "loading doclet class...");
278 if (null != option_docletpath) {
280 FileSystemClassLoader docletPathClassLoader
281 = new FileSystemClassLoader(option_docletpath);
282 System.err.println("trying to load class " + option_doclet + " from path " + option_docletpath);
283 docletClass = docletPathClassLoader.findClass(option_doclet);
285 catch (Exception e) {
286 docletClass = Class.forName(option_doclet);
290 docletClass = Class.forName(option_doclet);
292 //Object docletInstance = docletClass.newInstance();
294 Debug.log(1, "doclet class loaded...");
296 Method startTempMethod = null;
297 Method startMethod = null;
298 Method optionLenMethod = null;
299 Method validOptionsMethod = null;
301 //--- Try to find the optionLength method in the Doclet class.
305 optionLenMethod = docletClass.getMethod("optionLength", new Class[]
308 catch (NoSuchMethodException e)
310 // Ignore if not found; it's OK it the Doclet class doesn't define
314 //--- Try to find the validOptions method in the Doclet class.
318 validOptionsMethod = docletClass.getMethod("validOptions", new Class[]
319 { String[][].class, DocErrorReporter.class });
321 catch (NoSuchMethodException e)
323 // Ignore if not found; it's OK it the Doclet class doesn't define
327 //--- Find the start method in the Doclet class; complain if not found
331 startTempMethod = docletClass.getMethod("start", new Class[]
332 { TemporaryStore.class });
338 startMethod = docletClass.getMethod("start", new Class[]
341 //--- Feed the custom command line tokens to the Doclet
343 // stores all recognized options
344 List options = new LinkedList();
346 // stores packages and classes defined on the command line
347 List packageAndClasses = new LinkedList();
349 for (Iterator it = allOptions.iterator(); it.hasNext();)
351 String option = (String) it.next();
353 Debug.log(9, "parsing option '" + option + "'");
355 if (option.startsWith("-"))
360 int optlen = optionLength(option);
362 //--- Try to get option length from Doclet class
364 if (optlen <= 0 && optionLenMethod != null)
367 optionLenMethod.invoke(null, new Object[]
370 Debug.log(3, "invoking optionLen method");
372 optlen = ((Integer) optionLenMethod.invoke(null, new Object[]
373 { option })).intValue();
375 Debug.log(3, "done");
380 if (option.startsWith("-JD")) {
381 // Simulate VM option -D
382 String propertyValue = option.substring(3);
383 int ndx = propertyValue.indexOf('=');
385 reporter.printError("Illegal format in option " + option + ": use -JDproperty=value");
389 String property = propertyValue.substring(0, ndx);
390 String value = propertyValue.substring(ndx + 1);
391 System.setProperty(property, value);
394 else if (option.startsWith("-J")) {
395 //--- Warn if VM option is encountered
396 reporter.printWarning("Ignored option " + option + ". Pass this option to the VM if required.");
399 //--- Complain if not found
401 reporter.printError("Unknown option " + option);
402 reporter.printNotice(STRING_TRY_GJDOC_HELP);
409 //--- Read option values
411 String[] optionAndValues = new String[optlen];
412 optionAndValues[0] = option;
413 for (int i = 1; i < optlen; ++i)
417 reporter.printError("Missing value for option " + option);
422 optionAndValues[i] = (String) it.next();
426 //--- Store option for processing later
428 options.add(optionAndValues);
431 else if (option.length() > 0)
434 //--- Add to list of packages/classes if not option or option
437 packageAndClasses.add(option);
441 Debug.log(9, "options parsed...");
443 //--- For each package specified with the -subpackages option on
444 // the command line, recursively find all valid java files
447 //--- For each class or package specified on the command line,
448 // check that it exists and find out whether it is a class
451 for (Iterator it = option_subpackages.iterator(); it.hasNext();)
453 String subpackage = (String) it.next();
454 Set foundPackages = new LinkedHashSet();
456 for (Iterator pit = option_sourcepath.iterator(); pit.hasNext(); ) {
457 File sourceDir = (File)pit.next();
458 File packageDir = new File(sourceDir, subpackage.replace('.', File.separatorChar));
459 findPackages(subpackage, packageDir, foundPackages);
462 addFoundPackages(subpackage, foundPackages);
466 Set foundPackages = new LinkedHashSet();
467 for (Iterator pit = option_sourcepath.iterator(); pit.hasNext(); ) {
468 File sourceDir = (File)pit.next();
469 findPackages("", sourceDir, foundPackages);
471 addFoundPackages(null, foundPackages);
472 for (Iterator packageIt = foundPackages.iterator(); packageIt.hasNext(); ) {
473 String packageName = (String)packageIt.next();
474 if (null == packageName) {
477 rootDoc.addSpecifiedPackageName(packageName);
481 for (Iterator it = packageAndClasses.iterator(); it.hasNext();)
484 String classOrPackage = (String) it.next();
486 boolean foundSourceFile = false;
488 if (classOrPackage.endsWith(".java")) {
489 for (Iterator pit = option_sourcepath.iterator(); pit.hasNext() && !foundSourceFile; ) {
490 File sourceDir = (File)pit.next();
491 File sourceFile = new File(sourceDir, classOrPackage);
492 if (sourceFile.exists() && !sourceFile.isDirectory()) {
493 rootDoc.addSpecifiedSourceFile(sourceFile);
494 foundSourceFile = true;
498 if (!foundSourceFile) {
499 File sourceFile = new File(classOrPackage);
500 if (sourceFile.exists() && !sourceFile.isDirectory()) {
501 rootDoc.addSpecifiedSourceFile(sourceFile);
502 foundSourceFile = true;
507 if (!foundSourceFile) {
508 //--- Check for illegal name
510 if (classOrPackage.startsWith(".")
511 || classOrPackage.endsWith(".")
512 || classOrPackage.indexOf("..") > 0
513 || !checkCharSet(classOrPackage,
514 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_."))
516 throw new ParseException("Illegal class or package name '"
517 + classOrPackage + "'");
520 //--- Assemble absolute path to package
522 String classOrPackageRelPath = classOrPackage.replace('.',
525 //--- Create one file object each for a possible package directory
526 // and a possible class file, and find out if they exist.
528 List packageDirs = rootDoc.findSourceFiles(classOrPackageRelPath);
529 List sourceFiles = rootDoc.findSourceFiles(classOrPackageRelPath + ".java");
531 boolean packageDirExists = !packageDirs.isEmpty();
532 boolean sourceFileExists = !sourceFiles.isEmpty();
534 //--- Complain if neither exists: not found
536 if (!packageDirExists && !sourceFileExists)
538 reporter.printError("Class or package " + classOrPackage
543 //--- Complain if both exist: ambigious
546 if (packageDirExists && sourceFileExists)
548 reporter.printError("Ambigious class/package name "
549 + classOrPackage + ".");
553 //--- Otherwise, if the package directory exists, it is a package
556 if (packageDirExists) {
557 Iterator packageDirIt = packageDirs.iterator();
558 boolean packageDirFound = false;
559 while (packageDirIt.hasNext()) {
560 File packageDir = (File)packageDirIt.next();
561 if (packageDir.isDirectory()) {
562 rootDoc.addSpecifiedPackageName(classOrPackage);
563 packageDirFound = true;
567 if (!packageDirFound) {
568 reporter.printError("No suitable file or directory found for" + classOrPackage);
573 //--- Otherwise, emit error message
576 reporter.printError("No sources files found for package " + classOrPackage);
581 //--- Complain if no packages or classes specified
588 //--- Validate custom options passed on command line
589 // by asking the Doclet if they are OK.
591 String[][] customOptionArr = (String[][]) options
592 .toArray(new String[0][0]);
593 if (validOptionsMethod != null
594 && !((Boolean) validOptionsMethod.invoke(null, new Object[]
595 { customOptionArr, reporter })).booleanValue())
597 // Not ok: shutdown system.
598 reporter.printNotice(STRING_TRY_GJDOC_HELP);
602 if (!rootDoc.hasSpecifiedPackagesOrClasses()) {
603 reporter.printError("No packages or classes specified.");
604 reporter.printNotice(STRING_TRY_GJDOC_HELP);
608 rootDoc.setOptions(customOptionArr);
612 //--- Bail out if no classes found
614 if (0 == rootDoc.classes().length
615 && 0 == rootDoc.specifiedPackages().length
616 && 0 == rootDoc.specifiedClasses().length)
618 reporter.printError("No packages or classes found(!).");
622 //--- Our work is done, tidy up memory
627 //--- Set flag indicating Phase II of documentation generation
629 docletRunning = true;
631 //--- Invoke the start method on the Doclet: produce output
633 reporter.printNotice("Running doclet...");
635 TemporaryStore tstore = new TemporaryStore(Main.rootDoc);
637 Thread.currentThread().setContextClassLoader(docletClass.getClassLoader());
639 if (null != startTempMethod)
641 startTempMethod.invoke(null, new Object[]
646 startMethod.invoke(null, new Object[]
647 { tstore.getAndClear() });
650 //--- Let the user know how many warnings/errors occured
652 if (reporter.getWarningCount() > 0)
654 reporter.printNotice(reporter.getWarningCount() + " warnings");
657 if (reporter.getErrorCount() > 0)
659 reporter.printNotice(reporter.getErrorCount() + " errors");
674 private void addFoundPackages(String subpackage, Set foundPackages)
676 if (foundPackages.isEmpty()) {
677 reporter.printWarning("No classes found under subpackage " + subpackage);
680 boolean onePackageAdded = false;
681 for (Iterator rit = foundPackages.iterator(); rit.hasNext();) {
682 String foundPackage = (String)rit.next();
683 boolean excludeThisPackage = false;
685 for (Iterator eit = option_exclude.iterator(); eit.hasNext();) {
686 String excludePackage = (String)eit.next();
687 if (foundPackage.equals(excludePackage) ||
688 foundPackage.startsWith(excludePackage + ":")) {
689 excludeThisPackage = true;
694 if (!excludeThisPackage) {
695 rootDoc.addSpecifiedPackageName(foundPackage);
696 onePackageAdded = true;
699 if (!onePackageAdded) {
700 if (null != subpackage) {
701 reporter.printWarning("No non-excluded classes found under subpackage " + subpackage);
704 reporter.printWarning("No non-excluded classes found.");
711 * Verify that the given file is a valid Java source file and that
712 * it specifies the given package.
714 private boolean isValidJavaFile(File file,
715 String expectedPackage)
718 InputStream in = new BufferedInputStream(new FileInputStream(file));
720 int ch, prevChar = 0;
722 final int STATE_DEFAULT = 0;
723 final int STATE_COMMENT = 1;
724 final int STATE_LINE_COMMENT = 2;
726 int state = STATE_DEFAULT;
728 StringBuffer word = new StringBuffer();
731 while ((ch = in.read()) >= 0) {
732 String completeWord = null;
736 if (prevChar == '*' && ch == '/') {
737 state = STATE_DEFAULT;
741 case STATE_LINE_COMMENT:
743 state = STATE_DEFAULT;
748 if (prevChar == '/' && ch == '*') {
749 word.deleteCharAt(word.length() - 1);
750 if (word.length() > 0) {
751 completeWord = word.toString();
754 state = STATE_COMMENT;
756 else if (prevChar == '/' && ch == '/') {
757 word.deleteCharAt(word.length() - 1);
758 if (word.length() > 0) {
759 completeWord = word.toString();
762 state = STATE_LINE_COMMENT;
764 else if (" \t\r\n".indexOf(ch) >= 0) {
765 if (word.length() > 0) {
766 completeWord = word.toString();
770 else if (1 == wordIndex && ';' == ch) {
771 if (word.length() > 0) {
772 completeWord = word.toString();
776 // empty package name in source file: "package ;" -> invalid source file
782 word.append((char)ch);
787 if (null != completeWord) {
788 if (0 == wordIndex && !"package".equals(completeWord)) {
790 return "".equals(expectedPackage);
792 else if (1 == wordIndex) {
794 return expectedPackage.equals(completeWord);
802 // no package or class found before end-of-file -> invalid source file
807 catch (IOException e) {
808 reporter.printWarning("Could not examine file " + file + ": " + e);
814 * Recursively try to locate valid Java packages under the given
815 * package specified by its name and its directory. Add the names
816 * of all valid packages to the result list.
818 private void findPackages(String subpackage,
822 File[] files = packageDir.listFiles();
824 for (int i=0; i<files.length; ++i) {
825 File file = files[i];
826 if (!file.isDirectory() && file.getName().endsWith(".java")) {
827 if (isValidJavaFile(file, subpackage)) {
828 if ("".equals(subpackage)) {
832 result.add(subpackage);
838 for (int i=0; i<files.length; ++i) {
839 File file = files[i];
840 if (file.isDirectory()) {
841 String newSubpackage;
842 if (null != subpackage && subpackage.length() > 0) {
843 newSubpackage = subpackage + "." + file.getName();
846 newSubpackage = file.getName();
848 findPackages(newSubpackage, file, result);
857 private static boolean validOptions(String options[][],
858 DocErrorReporter reporter)
861 boolean foundDocletOption = false;
862 for (int i = 0; i < options.length; i++)
864 String[] opt = options[i];
865 if (opt[0].equalsIgnoreCase("-doclet"))
867 if (foundDocletOption)
869 reporter.printError("Only one -doclet option allowed.");
874 foundDocletOption = true;
883 * Main entry point. This is the method called when gjdoc is invoked from the
887 * command line arguments
889 public static void main(String[] args)
894 //--- Remember current time for profiling purposes
896 Timer.setStartTime();
898 //--- Handle control to the Singleton instance of this class
900 int result = instance.start(args);
906 else if (result > 0) {
907 // errors encountered
917 //--- unexpected error
924 * Parses command line arguments and subsequentially handles control to the
925 * startDoclet() method
927 * @param args The command line parameters.
929 public static int execute(String[] args)
933 int result = instance.start(args);
938 else if (result > 0) {
939 // errors encountered
955 * @param programName Name of the program (for error messages). *disregarded*
956 * @param args The command line parameters.
957 * @returns The return code.
959 public static int execute(String programName,
962 return execute(args);
966 * @param programName Name of the program (for error messages).
967 * @param defaultDocletClassName Fully qualified class name.
968 * @param args The command line parameters.
969 * @returns The return code.
971 public static int execute(String programName,
972 String defaultDocletClassName,
975 // not yet implemented
979 * @param programName Name of the program (for error messages).
980 * @param defaultDocletClassName Fully qualified class name.
981 * @param args The command line parameters.
982 * @returns The return code.
984 public static int execute(String programName,
985 String defaultDocletClassName,
988 // not yet implemented
992 * @param programName Name of the program (for error messages).
993 * @param errWriter PrintWriter to receive error messages.
994 * @param warnWriter PrintWriter to receive error messages.
995 * @param noticeWriter PrintWriter to receive error messages.
996 * @param defaultDocletClassName Fully qualified class name.
997 * @param args The command line parameters.
998 * @returns The return code.
1000 public static int execute(String programName,
1001 PrintWriter errWriter,
1002 PrintWriter warnWriter,
1003 PrintWriter noticeWriter,
1004 String defaultDocletClassName,
1007 // not yet implemented
1011 * Parses command line arguments and subsequentially handles control to the
1012 * startDoclet() method
1015 * Command line arguments, as passed to the main() method
1016 * @return {@code -1} in case of a fatal error (invalid arguments),
1017 * or the number of errors encountered.
1018 * @exception ParseException
1020 * @exception IOException
1021 * if an IO problem occur
1023 public int start(String[] args) throws ParseException, IOException
1026 //--- Collect unparsed arguments in array and resolve references
1027 // to external argument files.
1029 List arguments = new ArrayList(args.length);
1031 for (int i = 0; i < args.length; ++i)
1033 if (!args[i].startsWith("@"))
1035 arguments.add(args[i]);
1039 FileReader reader = new FileReader(args[i].substring(1));
1040 StreamTokenizer st = new StreamTokenizer(reader);
1042 st.wordChars('\u0000', '\uffff');
1045 st.whitespaceChars(' ', ' ');
1046 st.whitespaceChars('\t', '\t');
1047 st.whitespaceChars('\r', '\r');
1048 st.whitespaceChars('\n', '\n');
1049 while (st.nextToken() != StreamTokenizer.TT_EOF)
1051 arguments.add(st.sval);
1056 //--- Initialize Map for option parsing
1060 //--- This will hold all options recognized by gjdoc itself
1061 // and their associated arguments.
1062 // Contains objects of type String[], where each entry
1063 // specifies an option along with its aguments.
1065 List options = new LinkedList();
1067 //--- This will hold all command line tokens not recognized
1068 // to be part of a standard option.
1069 // These options are intended to be processed by the doclet
1070 // Contains objects of type String, where each entry is
1071 // one unrecognized token.
1073 List customOptions = new LinkedList();
1075 rootDoc = new RootDocImpl();
1076 reporter = rootDoc.getReporter();
1078 //--- Iterate over all options given on the command line
1080 for (Iterator it = arguments.iterator(); it.hasNext();)
1083 String arg = (String) it.next();
1085 //--- Check if gjdoc recognizes this option as a standard option
1086 // and remember the options' argument count
1088 int optlen = optionLength(arg);
1090 //--- Argument count == 0 indicates that the option is not recognized.
1091 // Add it to the list of custom option tokens
1093 //--- Otherwise the option is recognized as a standard option.
1094 // if all required arguments are supplied. Create a new String
1095 // array for the option and its arguments, and store it
1096 // in the options array.
1100 String[] option = new String[optlen];
1102 boolean optargs_ok = true;
1103 for (int j = 1; j < optlen && optargs_ok; ++j)
1107 option[j] = (String) it.next();
1108 if (option[j].startsWith("-"))
1119 options.add(option);
1122 // If the option requires more arguments than given on the
1123 // command line, issue a fatal error
1125 reporter.printFatal("Missing value for option " + arg + ".");
1130 //--- Create an array of String arrays from the dynamic array built above
1132 String[][] optionArr = (String[][]) options.toArray(new String[options
1135 //--- Validate all options and issue warnings/errors
1137 if (validOptions(optionArr, rootDoc))
1140 //--- We got valid options; parse them and store the parsed values
1141 // in 'option_*' fields.
1143 readOptions(optionArr);
1145 //--- Show version and exit if requested by user
1147 if (option_showVersion) {
1148 System.out.println("gjdoc " + getGjdocVersion());
1152 if (option_bootclasspath_specified) {
1153 reporter.printWarning("-bootclasspath ignored: not supported by"
1154 + " gjdoc wrapper script, or no wrapper script in use.");
1157 // If we have an empty source path list, add the current directory ('.')
1159 if (option_sourcepath.size() == 0)
1160 option_sourcepath.add(new File("."));
1162 //--- We have all information we need to start the doclet at this time
1164 if (null != option_encoding) {
1165 rootDoc.setSourceEncoding(option_encoding);
1168 // be quiet about this for now:
1169 // reporter.printNotice("No encoding specified, using platform default: " + System.getProperty("file.encoding"));
1170 rootDoc.setSourceEncoding(System.getProperty("file.encoding"));
1172 rootDoc.setSourcePath(option_sourcepath);
1174 //addJavaLangClasses();
1176 if (!startDoclet(arguments)) {
1181 return reporter.getErrorCount();
1184 private void addJavaLangClasses()
1187 String resourceName = "/java.lang-classes-" + option_source + ".txt";
1188 InputStream in = getClass().getResourceAsStream(resourceName);
1189 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
1191 while ((line = reader.readLine()) != null) {
1193 String className = line.trim();
1194 if (className.length() > 0) {
1195 ClassDocImpl classDoc =
1196 new ClassDocImpl(null, new PackageDocImpl("java.lang"),
1197 ProgramElementDocImpl.ACCESS_PUBLIC,
1198 false, false, null);
1199 classDoc.setClass(className);
1200 rootDoc.addClassDoc(classDoc);
1206 * Helper class for parsing command line arguments. An instance of this class
1207 * represents a particular option accepted by gjdoc (e.g. '-sourcepath') along
1208 * with the number of expected arguments and behavior to parse the arguments.
1210 private abstract class OptionProcessor
1214 * Number of arguments expected by this option.
1216 private int argCount;
1219 * Initializes this instance.
1222 * number of arguments
1224 public OptionProcessor(int argCount)
1226 this.argCount = argCount;
1230 * Overridden by derived classes with behavior to parse the arguments
1231 * specified with this option.
1234 * command line arguments
1236 abstract void process(String[] args);
1240 * Maps option tags (e.g. '-sourcepath') to OptionProcessor objects.
1241 * Initialized only once by method initOptions(). FIXME: Rename to
1242 * 'optionProcessors'.
1244 private static Map options = null;
1247 * Initialize all OptionProcessor objects needed to scan/parse command line
1248 * options. This cannot be done in a static initializer block because
1249 * OptionProcessors need access to the Singleton instance of the Main class.
1251 private void initOptions()
1254 options = new HashMap();
1256 //--- Put one OptionProcessor object into the map
1257 // for each option recognized.
1259 options.put("-overview", new OptionProcessor(2)
1262 void process(String[] args)
1264 option_overview = args[0];
1267 options.put("-public", new OptionProcessor(1)
1270 void process(String[] args)
1272 option_coverage = COVERAGE_PUBLIC;
1275 options.put("-protected", new OptionProcessor(1)
1278 void process(String[] args)
1280 option_coverage = COVERAGE_PROTECTED;
1283 options.put("-package", new OptionProcessor(1)
1286 void process(String[] args)
1288 option_coverage = COVERAGE_PACKAGE;
1291 options.put("-private", new OptionProcessor(1)
1294 void process(String[] args)
1296 option_coverage = COVERAGE_PRIVATE;
1299 OptionProcessor helpProcessor = new OptionProcessor(1)
1302 void process(String[] args)
1308 options.put("-help", helpProcessor);
1309 options.put("--help", helpProcessor);
1310 options.put("-doclet", new OptionProcessor(2)
1313 void process(String[] args)
1315 option_doclet = args[0];
1318 options.put("-docletpath", new OptionProcessor(2)
1321 void process(String[] args)
1323 option_docletpath = args[0];
1326 options.put("-nowarn", new OptionProcessor(1)
1329 void process(String[] args)
1331 option_nowarn = true;
1334 options.put("-source", new OptionProcessor(2)
1337 void process(String[] args)
1339 option_source = args[0];
1340 if (!"1.2".equals(option_source)
1341 && !"1.3".equals(option_source)
1342 && !"1.4".equals(option_source)) {
1344 throw new RuntimeException("Only he following values are currently"
1345 + " supported for option -source: 1.2, 1.3, 1.4.");
1349 OptionProcessor sourcePathProcessor = new OptionProcessor(2) {
1350 void process(String[] args)
1352 Debug.log(1, "-sourcepath is '" + args[0] + "'");
1353 for (StringTokenizer st = new StringTokenizer(args[0],
1354 File.pathSeparator); st.hasMoreTokens();)
1356 String path = st.nextToken();
1357 File file = new File(path);
1358 if (!(file.exists()))
1360 throw new RuntimeException("The source path " + path
1361 + " does not exist.");
1363 option_sourcepath.add(file);
1367 options.put("-s", sourcePathProcessor);
1368 options.put("-sourcepath", sourcePathProcessor);
1369 options.put("-subpackages", new OptionProcessor(2)
1371 void process(String[] args)
1373 StringTokenizer st = new StringTokenizer(args[0], ":");
1374 while (st.hasMoreTokens()) {
1375 String packageName = st.nextToken();
1377 if (packageName.startsWith(".")
1378 || packageName.endsWith(".")
1379 || packageName.indexOf("..") > 0
1380 || !checkCharSet(packageName,
1381 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_.")) {
1382 throw new RuntimeException("Illegal package name '"
1383 + packageName + "'");
1385 option_subpackages.add(packageName);
1389 options.put("-exclude", new OptionProcessor(2)
1391 void process(String[] args)
1393 StringTokenizer st = new StringTokenizer(args[0], ":");
1394 while (st.hasMoreTokens()) {
1395 String packageName = st.nextToken();
1397 if (packageName.startsWith(".")
1398 || packageName.endsWith(".")
1399 || packageName.indexOf("..") > 0
1400 || !checkCharSet(packageName,
1401 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_.")) {
1402 throw new RuntimeException("Illegal package name '"
1403 + packageName + "'");
1405 option_exclude.add(packageName);
1409 // TODO include other options here
1410 options.put("-verbose", new OptionProcessor(1)
1413 void process(String[] args)
1415 option_verbose = true;
1416 System.err.println("WARNING: Unsupported option -verbose ignored");
1419 options.put("-quiet", new OptionProcessor(1)
1422 void process(String[] args)
1424 reporter.setQuiet(true);
1427 options.put("-locale", new OptionProcessor(2)
1430 void process(String[] args)
1432 String localeName = args[0];
1433 String language = null;
1434 String country = null;
1435 String variant = null;
1436 StringTokenizer st = new StringTokenizer(localeName, "_");
1437 if (st.hasMoreTokens()) {
1438 language = st.nextToken();
1440 if (st.hasMoreTokens()) {
1441 country = st.nextToken();
1443 if (st.hasMoreTokens()) {
1444 variant = st.nextToken();
1446 if (variant != null) {
1447 option_locale = new Locale(language, country, variant);
1449 else if (country != null) {
1450 option_locale = new Locale(language, country);
1452 else if (language != null) {
1453 option_locale = new Locale(language);
1456 throw new RuntimeException("Illegal locale specification '"
1457 + localeName + "'");
1461 options.put("-encoding", new OptionProcessor(2)
1464 void process(String[] args)
1466 option_encoding = args[0];
1469 options.put("-breakiterator", new OptionProcessor(1)
1471 void process(String[] args)
1473 option_breakiterator = true;
1476 options.put("-licensetext", new OptionProcessor(1)
1478 void process(String[] args)
1480 option_licensetext = true;
1483 options.put("-overview", new OptionProcessor(2)
1485 void process(String[] args)
1488 getRootDoc().setRawCommentText(RootDocImpl.readHtmlBody(new File(args[0])));
1490 catch (IOException e) {
1491 throw new RuntimeException("Cannot read file specified in option -overview: " + e.getMessage());
1495 options.put("-classpath", new OptionProcessor(2)
1497 void process(String[] args)
1499 reporter.printWarning("-classpath option could not be passed to the VM. Faking it with ");
1500 reporter.printWarning(" System.setProperty(\"java.class.path\", \"" + args[0] + "\");");
1501 System.setProperty("java.class.path", args[0]);
1504 options.put("--version", new OptionProcessor(1)
1506 void process(String[] args)
1508 option_showVersion = true;
1511 options.put("-bootclasspath", new OptionProcessor(1)
1513 void process(String[] args)
1515 option_bootclasspath_specified = true;
1518 options.put("-all", new OptionProcessor(1)
1520 void process(String[] args)
1525 options.put("-reflection", new OptionProcessor(1)
1527 void process(String[] args)
1529 option_reflection = true;
1535 * Determine how many arguments the given option requires.
1538 * The name of the option without leading dash.
1540 private static int optionLength(String option)
1543 OptionProcessor op = (OptionProcessor) options.get(option.toLowerCase());
1551 * Process all given options. Assumes that the options have been validated
1555 * Each element is a series of Strings where [0] is the name of the
1556 * option and [1..n] are the arguments to the option.
1558 private void readOptions(String[][] optionArr)
1561 //--- For each option, find the appropriate OptionProcessor
1562 // and call its process() method
1564 for (int i = 0; i < optionArr.length; ++i)
1566 String[] opt = optionArr[i];
1567 String[] args = new String[opt.length - 1];
1568 System.arraycopy(opt, 1, args, 0, opt.length - 1);
1569 OptionProcessor op = (OptionProcessor) options.get(opt[0].toLowerCase());
1575 * Print command line usage.
1577 private static void usage()
1581 + "USAGE: gjdoc [options] [packagenames] "
1582 + "[sourcefiles] [@files]\n\n"
1583 + " --version Show version information and exit\n"
1584 + " -all Process all source files found in the source path\n"
1585 + " -overview <file> Read overview documentation from HTML file\n"
1586 + " -public Include only public classes and members\n"
1587 + " -protected Include protected and public classes and members\n"
1588 + " This is the default\n"
1589 + " -package Include package/protected/public classes and members\n"
1590 + " -private Include all classes and members\n"
1591 + " -help, --help Show this information\n"
1592 + " -doclet <class> Doclet class to use for generating output\n"
1593 + " -docletpath <classpath> Specifies the search path for the doclet and\n"
1595 + " -source <release> Provide source compatibility with specified\n"
1596 + " release (1.4 to handle assertion)\n"
1597 + " -sourcepath <pathlist> Where to look for source files\n"
1598 + " -s <pathlist> Alias for -sourcepath\n"
1599 + " -subpackages <spkglist> List of subpackages to recursively load\n"
1600 + " -exclude <pkglist> List of packages to exclude\n"
1601 + " -verbose Output messages about what Gjdoc is doing [ignored]\n"
1602 + " -quiet Do not print non-error and non-warning messages\n"
1603 + " -locale <name> Locale to be used, e.g. en_US or en_US_WIN\n"
1604 + " -encoding <name> Source file encoding name\n"
1605 + " -breakiterator Compute first sentence with BreakIterator\n"
1606 + " -classpath <pathlist> Set the path used for loading auxilliary classes\n"
1608 + "Standard doclet options:\n"
1609 + " -d Set target directory\n"
1610 + " -use Includes the 'Use' page for each documented class\n"
1612 + " -version Includes the '@version' tag\n"
1613 + " -author Includes the '@author' tag\n"
1614 + " -splitindex Splits the index file into multiple files\n"
1615 + " -windowtitle <text> Browser window title\n"
1616 + " -doctitle <text> Title near the top of the overview summary file\n"
1617 + " (HTML allowed)\n"
1618 + " -title <text> Title for this set of API documentation\n"
1619 + " (deprecated, -doctitle should be used instead)\n"
1620 + " -header <text> Text to include in the top navigation bar\n"
1621 + " (HTML allowed)\n"
1622 + " -footer <text> Text to include in the bottom navigation bar\n"
1623 + " (HTML allowed)\n"
1624 + " -bottom <text> Text to include at the bottom of each output file\n"
1625 + " (HTML allowed)\n"
1626 + " -link <extdoc URL> Link to external generated documentation at URL\n"
1627 + " -linkoffline <extdoc URL> <packagelistLoc>\n"
1628 + " Link to external generated documentation for\n"
1629 + " the specified package-list\n"
1630 + " -linksource Creates an HTML version of each source file\n"
1631 + " -group <groupheading> <packagepattern:packagepattern:...>\n"
1632 + " Separates packages on the overview page into groups\n"
1633 + " -nodeprecated Prevents the generation of any deprecated API\n"
1634 + " -nodeprecatedlist Prevents the generation of the file containing\n"
1635 + " the list of deprecated APIs and the link to the\n"
1636 + " navigation bar to that page\n"
1637 + " -nosince Omit the '@since' tag\n"
1638 + " -notree Do not generate the class/interface hierarchy page\n"
1639 + " -noindex Do not generate the index file\n"
1640 + " -nohelp Do not generate the help link\n"
1641 + " -nonavbar Do not generate the navbar, header and footer\n"
1642 + " -helpfile <filen> Path to an alternate help file\n"
1643 + " -stylesheetfile <file> Path to an alternate CSS stylesheet\n"
1644 + " -addstylesheet <file> Path to an additional CSS stylesheet\n"
1645 + " -serialwarn Complain about missing '@serial' tags [ignored]\n"
1646 + " -charset <IANACharset> Specifies the HTML charset\n"
1647 + " -docencoding <IANACharset>\n"
1648 + " Specifies the encoding of the generated HTML files\n"
1649 + " -tag <tagname>:Xaoptcmf:\"<taghead>\"\n"
1650 + " Enables gjdoc to interpret a custom tag\n"
1651 + " -taglet Adds a Taglet class to the map of taglets\n"
1652 + " -tagletpath Sets the CLASSPATH to load subsequent Taglets from\n"
1653 + " -docfilessubdirs Enables deep copy of 'doc-files' directories\n"
1654 + " -excludedocfilessubdir <name1:name2:...>\n"
1655 + " Excludes 'doc-files' subdirectories with a give name\n"
1656 + " -noqualifier all|<packagename1:packagename2:...>\n"
1657 + " Do never fully qualify given package names\n"
1658 + " -nocomment Suppress the entire comment body including the main\n"
1659 + " description and all tags, only generate declarations\n"
1661 + "Gjdoc extension options:\n"
1662 + " -reflection Use reflection for resolving unqualified class names\n"
1663 + " -licensetext Include license text from source files\n"
1664 + " -validhtml Use valid HTML/XML names (breaks compatibility)\n"
1665 + " -baseurl <url> Hardwire the given base URL into generated pages\n"
1667 + " -genhtml Generate HTML code instead of XML code. This is the\n"
1669 + " -geninfo Generate Info code instead of XML code.\n"
1670 + " -xslsheet <file> If specified, XML files will be written to a\n"
1671 + " temporary directory and transformed using the\n"
1672 + " given XSL sheet. The result of the transformation\n"
1673 + " is written to the output directory. Not required if\n"
1674 + " -genhtml or -geninfo has been specified.\n"
1675 + " -xmlonly Generate XML code only, do not generate HTML code.\n"
1676 + " -bottomnote HTML code to include at the bottom of each page.\n"
1677 + " -nofixhtml If not specified, heurestics will be applied to\n"
1678 + " fix broken HTML code in comments.\n"
1679 + " -nohtmlwarn Do not emit warnings when encountering broken HTML\n"
1681 + " -noemailwarn Do not emit warnings when encountering strings like\n"
1682 + " <abc@foo.com>.\n"
1683 + " -indentstep <n> How many spaces to indent each tag level in\n"
1684 + " generated XML code.\n"
1685 + " -xsltdriver <class> Specifies the XSLT driver to use for transformation.\n"
1686 + " By default, xsltproc is used.\n"
1687 + " -postprocess <class> XmlDoclet postprocessor class to apply after XSL\n"
1688 + " transformation.\n"
1689 + " -compress Generated info pages will be Zip-compressed.\n"
1690 + " -workpath Specify a temporary directory to use.\n"
1691 + " -authormail <type> Specify handling of mail addresses in @author tags.\n"
1692 + " no-replace do not replace mail addresses (default).\n"
1693 + " mailto-name replace by <a>Real Name</a>.\n"
1694 + " name-mailto-address replace by Real Name (<a>abc@foo.com</a>).\n"
1695 + " name-mangled-address replace by Real Name (<a>abc AT foo DOT com</a>).\n"
1701 * The root of the gjdoc tool.
1703 * @return all the options of the gjdoc application.
1705 public static RootDocImpl getRootDoc()
1711 * Get the gjdoc singleton.
1713 * @return the gjdoc instance.
1715 public static Main getInstance()
1721 * Is this access level covered?
1723 * @param accessLevel
1724 * the access level we want to know if it is covered.
1725 * @return true if the access level is covered.
1727 public boolean includeAccessLevel(int accessLevel)
1729 return coverageTemplates[option_coverage][accessLevel];
1733 * Is the doclet running?
1735 * @return true if it's running
1737 public boolean isDocletRunning()
1739 return docletRunning;
1743 * Check the charset. Check that all the characters of the string 'toCheck'
1744 * and query if they exist in the 'charSet'. The order does not matter. The
1745 * number of times a character is in the variable does not matter.
1748 * the charset to check.
1750 * the reference charset
1751 * @return true if they match.
1753 public static boolean checkCharSet(String toCheck, String charSet)
1755 for (int i = 0; i < toCheck.length(); ++i)
1757 if (charSet.indexOf(toCheck.charAt(i)) < 0)
1764 * Makes the RootDoc eligible for the GC.
1766 public static void releaseRootDoc()
1772 * Return whether the -breakiterator option has been specified.
1774 public boolean isUseBreakIterator()
1776 return this.option_breakiterator
1777 || !getLocale().getLanguage().equals(Locale.ENGLISH.getLanguage());
1781 * Return whether boilerplate license text should be copied.
1783 public boolean isCopyLicenseText()
1785 return this.option_licensetext;
1789 * Return the locale specified using the -locale option or the
1792 public Locale getLocale()
1794 return this.option_locale;
1798 * Return the collator to use based on the specified -locale
1799 * option. If no collator can be found for the given locale, a
1800 * warning is emitted and the default collator is used instead.
1802 public Collator getCollator()
1804 if (null == this.collator) {
1805 Locale locale = getLocale();
1806 this.collator = Collator.getInstance(locale);
1807 Locale defaultLocale = Locale.getDefault();
1808 if (null == this.collator
1809 && !defaultLocale.equals(locale)) {
1810 this.collator = Collator.getInstance(defaultLocale);
1811 if (null != this.collator) {
1812 reporter.printWarning("No collator found for locale "
1813 + locale.getDisplayName()
1814 + "; using collator for default locale "
1815 + defaultLocale.getDisplayName()
1819 this.collator = Collator.getInstance();
1820 reporter.printWarning("No collator found for specified locale "
1821 + locale.getDisplayName()
1822 + " or default locale "
1823 + defaultLocale.getDisplayName()
1824 + ": using default collator.");
1827 if (null == this.collator) {
1828 this.collator = Collator.getInstance();
1829 reporter.printWarning("No collator found for locale "
1830 + locale.getDisplayName()
1831 + ": using default collator.");
1834 return this.collator;
1837 public boolean isCacheRawComments()
1842 public String getGjdocVersion()
1844 if (null == gjdocVersion) {
1845 gjdocVersion = gnu.classpath.Configuration.CLASSPATH_VERSION;
1847 return gjdocVersion;
1850 public boolean isReflectionEnabled()
1852 return this.option_reflection;