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.javadoc;
027    
028    import com.sun.javadoc.*;
029    
030    import com.sun.tools.javac.main.CommandLine;
031    import com.sun.tools.javac.util.Context;
032    import com.sun.tools.javac.util.List;
033    import com.sun.tools.javac.util.ListBuffer;
034    import com.sun.tools.javac.util.Options;
035    
036    import java.io.IOException;
037    import java.io.File;
038    import java.io.FileNotFoundException;
039    import java.io.PrintWriter;
040    
041    import java.util.StringTokenizer;
042    
043    import static com.sun.tools.javac.code.Flags.*;
044    
045    /**
046     * Main program of Javadoc.
047     * Previously named "Main".
048     *
049     * @since 1.2
050     * @author Robert Field
051     * @author Neal Gafter (rewrite)
052     */
053    class Start {
054        /** Context for this invocation. */
055        private final Context context;
056    
057        private final String defaultDocletClassName;
058        private final ClassLoader docletParentClassLoader;
059    
060        private static final String javadocName = "javadoc";
061    
062        private static final String standardDocletClassName =
063            "com.sun.tools.doclets.standard.Standard";
064    
065        private ListBuffer<String[]> options = new ListBuffer<String[]>();
066    
067        private ModifierFilter showAccess = null;
068    
069        private long defaultFilter = PUBLIC | PROTECTED;
070    
071        private Messager messager;
072    
073        String docLocale = "";
074    
075        boolean breakiterator = false;
076        boolean quiet = false;
077        String encoding = null;
078    
079        private DocletInvoker docletInvoker;
080    
081        private static final int F_VERBOSE = 1 << 0;
082        private static final int F_WARNINGS = 1 << 2;
083    
084        /* Treat warnings as errors. */
085        private boolean rejectWarnings = false;
086    
087        Start(String programName,
088              PrintWriter errWriter,
089              PrintWriter warnWriter,
090              PrintWriter noticeWriter,
091              String defaultDocletClassName) {
092            this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null);
093        }
094    
095        Start(String programName,
096              PrintWriter errWriter,
097              PrintWriter warnWriter,
098              PrintWriter noticeWriter,
099              String defaultDocletClassName,
100              ClassLoader docletParentClassLoader) {
101            context = new Context();
102            messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter);
103            this.defaultDocletClassName = defaultDocletClassName;
104            this.docletParentClassLoader = docletParentClassLoader;
105        }
106    
107        Start(String programName, String defaultDocletClassName) {
108            this(programName, defaultDocletClassName, null);
109        }
110    
111        Start(String programName, String defaultDocletClassName,
112              ClassLoader docletParentClassLoader) {
113            context = new Context();
114            messager = new Messager(context, programName);
115            this.defaultDocletClassName = defaultDocletClassName;
116            this.docletParentClassLoader = docletParentClassLoader;
117        }
118    
119        Start(String programName, ClassLoader docletParentClassLoader) {
120            this(programName, standardDocletClassName, docletParentClassLoader);
121        }
122    
123        Start(String programName) {
124            this(programName, standardDocletClassName);
125        }
126    
127        Start(ClassLoader docletParentClassLoader) {
128            this(javadocName, docletParentClassLoader);
129        }
130    
131        Start() {
132            this(javadocName);
133        }
134    
135        /**
136         * Usage
137         */
138        private void usage() {
139            messager.notice("main.usage");
140    
141            // let doclet print usage information (does nothing on error)
142            if (docletInvoker != null) {
143                docletInvoker.optionLength("-help");
144            }
145        }
146    
147        /**
148         * Exit
149         */
150        private void exit() {
151            messager.exit();
152        }
153    
154    
155        /**
156         * Main program - external wrapper
157         */
158        int begin(String... argv) {
159            boolean failed = false;
160    
161            try {
162                failed = !parseAndExecute(argv);
163            } catch(Messager.ExitJavadoc exc) {
164                // ignore, we just exit this way
165            } catch (OutOfMemoryError ee) {
166                messager.error(null, "main.out.of.memory");
167                failed = true;
168            } catch (Error ee) {
169                ee.printStackTrace();
170                messager.error(null, "main.fatal.error");
171                failed = true;
172            } catch (Exception ee) {
173                ee.printStackTrace();
174                messager.error(null, "main.fatal.exception");
175                failed = true;
176            } finally {
177                messager.exitNotice();
178                messager.flush();
179            }
180            failed |= messager.nerrors() > 0;
181            failed |= rejectWarnings && messager.nwarnings() > 0;
182            return failed ? 1 : 0;
183        }
184    
185        private void addToList(ListBuffer<String> list, String str){
186            StringTokenizer st = new StringTokenizer(str, ":");
187            String current;
188            while(st.hasMoreTokens()){
189                current = st.nextToken();
190                list.append(current);
191            }
192        }
193    
194        /**
195         * Main program - internal
196         */
197        private boolean parseAndExecute(String... argv) throws IOException {
198            long tm = System.currentTimeMillis();
199    
200            ListBuffer<String> javaNames = new ListBuffer<String>();
201    
202            // Preprocess @file arguments
203            try {
204                argv = CommandLine.parse(argv);
205            } catch (FileNotFoundException e) {
206                messager.error(null, "main.cant.read", e.getMessage());
207                exit();
208            } catch (IOException e) {
209                e.printStackTrace();
210                exit();
211            }
212    
213            setDocletInvoker(argv);
214            ListBuffer<String> subPackages = new ListBuffer<String>();
215            ListBuffer<String> excludedPackages = new ListBuffer<String>();
216            Options compOpts = Options.instance(context);
217            boolean docClasses = false;
218    
219            // Parse arguments
220            for (int i = 0 ; i < argv.length ; i++) {
221                String arg = argv[i];
222                if (arg.equals("-subpackages")) {
223                    oneArg(argv, i++);
224                    addToList(subPackages, argv[i]);
225                } else if (arg.equals("-exclude")){
226                    oneArg(argv, i++);
227                    addToList(excludedPackages, argv[i]);
228                } else if (arg.equals("-verbose")) {
229                    setOption(arg);
230                    compOpts.put("-verbose", "");
231                } else if (arg.equals("-encoding")) {
232                    oneArg(argv, i++);
233                    encoding = argv[i];
234                    compOpts.put("-encoding", argv[i]);
235                } else if (arg.equals("-breakiterator")) {
236                    breakiterator = true;
237                    setOption("-breakiterator");
238                } else if (arg.equals("-quiet")) {
239                    quiet = true;
240                    setOption("-quiet");
241                } else if (arg.equals("-help")) {
242                    usage();
243                    exit();
244                } else if (arg.equals("-Xclasses")) {
245                    setOption(arg);
246                    docClasses = true;
247                } else if (arg.equals("-Xwerror")) {
248                    setOption(arg);
249                    rejectWarnings = true;
250                } else if (arg.equals("-private")) {
251                    setOption(arg);
252                    setFilter(ModifierFilter.ALL_ACCESS);
253                } else if (arg.equals("-package")) {
254                    setOption(arg);
255                    setFilter(PUBLIC | PROTECTED |
256                              ModifierFilter.PACKAGE );
257                } else if (arg.equals("-protected")) {
258                    setOption(arg);
259                    setFilter(PUBLIC | PROTECTED );
260                } else if (arg.equals("-public")) {
261                    setOption(arg);
262                    setFilter(PUBLIC);
263                } else if (arg.equals("-source")) {
264                    oneArg(argv, i++);
265                    if (compOpts.get("-source") != null) {
266                        usageError("main.option.already.seen", arg);
267                    }
268                    compOpts.put("-source", argv[i]);
269                } else if (arg.equals("-prompt")) {
270                    compOpts.put("-prompt", "-prompt");
271                    messager.promptOnError = true;
272                } else if (arg.equals("-sourcepath")) {
273                    oneArg(argv, i++);
274                    if (compOpts.get("-sourcepath") != null) {
275                        usageError("main.option.already.seen", arg);
276                    }
277                    compOpts.put("-sourcepath", argv[i]);
278                } else if (arg.equals("-classpath")) {
279                    oneArg(argv, i++);
280                    if (compOpts.get("-classpath") != null) {
281                        usageError("main.option.already.seen", arg);
282                    }
283                    compOpts.put("-classpath", argv[i]);
284                } else if (arg.equals("-sysclasspath")) {
285                    oneArg(argv, i++);
286                    if (compOpts.get("-bootclasspath") != null) {
287                        usageError("main.option.already.seen", arg);
288                    }
289                    compOpts.put("-bootclasspath", argv[i]);
290                } else if (arg.equals("-bootclasspath")) {
291                    oneArg(argv, i++);
292                    if (compOpts.get("-bootclasspath") != null) {
293                        usageError("main.option.already.seen", arg);
294                    }
295                    compOpts.put("-bootclasspath", argv[i]);
296                } else if (arg.equals("-extdirs")) {
297                    oneArg(argv, i++);
298                    if (compOpts.get("-extdirs") != null) {
299                        usageError("main.option.already.seen", arg);
300                    }
301                    compOpts.put("-extdirs", argv[i]);
302                } else if (arg.equals("-overview")) {
303                    oneArg(argv, i++);
304                } else if (arg.equals("-doclet")) {
305                    i++;  // handled in setDocletInvoker
306                } else if (arg.equals("-docletpath")) {
307                    i++;  // handled in setDocletInvoker
308                } else if (arg.equals("-locale")) {
309                    if (i != 0)
310                        usageError("main.locale_first");
311                    oneArg(argv, i++);
312                    docLocale = argv[i];
313                } else if (arg.startsWith("-XD")) {
314                    String s = arg.substring("-XD".length());
315                    int eq = s.indexOf('=');
316                    String key = (eq < 0) ? s : s.substring(0, eq);
317                    String value = (eq < 0) ? s : s.substring(eq+1);
318                    compOpts.put(key, value);
319                }
320                // call doclet for its options
321                // other arg starts with - is invalid
322                else if ( arg.startsWith("-") ) {
323                    int optionLength;
324                    optionLength = docletInvoker.optionLength(arg);
325                    if (optionLength < 0) {
326                        // error already displayed
327                        exit();
328                    } else if (optionLength == 0) {
329                        // option not found
330                        usageError("main.invalid_flag", arg);
331                    } else {
332                        // doclet added option
333                        if ((i + optionLength) > argv.length) {
334                            usageError("main.requires_argument", arg);
335                        }
336                        ListBuffer<String> args = new ListBuffer<String>();
337                        for (int j = 0; j < optionLength-1; ++j) {
338                            args.append(argv[++i]);
339                        }
340                        setOption(arg, args.toList());
341                    }
342                } else {
343                    javaNames.append(arg);
344                }
345            }
346    
347            if (javaNames.isEmpty() && subPackages.isEmpty()) {
348                usageError("main.No_packages_or_classes_specified");
349            }
350    
351            if (!docletInvoker.validOptions(options.toList())) {
352                // error message already displayed
353                exit();
354            }
355    
356            JavadocTool comp = JavadocTool.make0(context);
357            if (comp == null) return false;
358    
359            if (showAccess == null) {
360                setFilter(defaultFilter);
361            }
362    
363            LanguageVersion languageVersion = docletInvoker.languageVersion();
364            RootDocImpl root = comp.getRootDocImpl(
365                    docLocale, encoding, showAccess,
366                    javaNames.toList(), options.toList(), breakiterator,
367                    subPackages.toList(), excludedPackages.toList(),
368                    docClasses,
369                    // legacy?
370                    languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, quiet);
371    
372            // pass off control to the doclet
373            boolean ok = root != null;
374            if (ok) ok = docletInvoker.start(root);
375    
376            // We're done.
377            if (compOpts.get("-verbose") != null) {
378                tm = System.currentTimeMillis() - tm;
379                messager.notice("main.done_in", Long.toString(tm));
380            }
381    
382            return ok;
383        }
384    
385        private void setDocletInvoker(String[] argv) {
386            String docletClassName = null;
387            String docletPath = null;
388    
389            // Parse doclet specifying arguments
390            for (int i = 0 ; i < argv.length ; i++) {
391                String arg = argv[i];
392                if (arg.equals("-doclet")) {
393                    oneArg(argv, i++);
394                    if (docletClassName != null) {
395                        usageError("main.more_than_one_doclet_specified_0_and_1",
396                                   docletClassName, argv[i]);
397                    }
398                    docletClassName = argv[i];
399                } else if (arg.equals("-docletpath")) {
400                    oneArg(argv, i++);
401                    if (docletPath == null) {
402                        docletPath = argv[i];
403                    } else {
404                        docletPath += File.pathSeparator + argv[i];
405                    }
406                }
407            }
408    
409            if (docletClassName == null) {
410                docletClassName = defaultDocletClassName;
411            }
412    
413            // attempt to find doclet
414            docletInvoker = new DocletInvoker(messager,
415                                              docletClassName, docletPath,
416                                              docletParentClassLoader);
417        }
418    
419        private void setFilter(long filterBits) {
420            if (showAccess != null) {
421                messager.error(null, "main.incompatible.access.flags");
422                usage();
423                exit();
424            }
425            showAccess = new ModifierFilter(filterBits);
426        }
427    
428        /**
429         * Set one arg option.
430         * Error and exit if one argument is not provided.
431         */
432        private void oneArg(String[] args, int index) {
433            if ((index + 1) < args.length) {
434                setOption(args[index], args[index+1]);
435            } else {
436                usageError("main.requires_argument", args[index]);
437            }
438        }
439    
440        private void usageError(String key) {
441            messager.error(null, key);
442            usage();
443            exit();
444        }
445    
446        private void usageError(String key, String a1) {
447            messager.error(null, key, a1);
448            usage();
449            exit();
450        }
451    
452        private void usageError(String key, String a1, String a2) {
453            messager.error(null, key, a1, a2);
454            usage();
455            exit();
456        }
457    
458        /**
459         * indicate an option with no arguments was given.
460         */
461        private void setOption(String opt) {
462            String[] option = { opt };
463            options.append(option);
464        }
465    
466        /**
467         * indicate an option with one argument was given.
468         */
469        private void setOption(String opt, String argument) {
470            String[] option = { opt, argument };
471            options.append(option);
472        }
473    
474        /**
475         * indicate an option with the specified list of arguments was given.
476         */
477        private void setOption(String opt, List<String> arguments) {
478            String[] args = new String[arguments.length() + 1];
479            int k = 0;
480            args[k++] = opt;
481            for (List<String> i = arguments; i.nonEmpty(); i=i.tail) {
482                args[k++] = i.head;
483            }
484            options = options.append(args);
485        }
486    
487    }