1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javadoc;
  27 
  28 import java.io.File;
  29 import java.io.FileNotFoundException;
  30 import java.io.IOException;
  31 import java.io.PrintWriter;
  32 import java.util.ArrayList;
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 
  36 import javax.tools.JavaFileManager;
  37 import javax.tools.JavaFileObject;
  38 
  39 import com.sun.javadoc.*;
  40 import com.sun.tools.javac.main.CommandLine;
  41 import com.sun.tools.javac.util.ClientCodeException;
  42 import com.sun.tools.javac.util.Context;
  43 import com.sun.tools.javac.util.List;
  44 import com.sun.tools.javac.util.ListBuffer;
  45 import com.sun.tools.javac.util.Log;
  46 import com.sun.tools.javac.util.Options;
  47 import static com.sun.tools.javac.code.Flags.*;
  48 
  49 /**
  50  * Main program of Javadoc.
  51  * Previously named "Main".
  52  *
  53  *  <p><b>This is NOT part of any supported API.
  54  *  If you write code that depends on this, you do so at your own risk.
  55  *  This code and its internal interfaces are subject to change or
  56  *  deletion without notice.</b>
  57  *
  58  * @since 1.2
  59  * @author Robert Field
  60  * @author Neal Gafter (rewrite)
  61  */
  62 public class Start extends ToolOption.Helper {
  63     /** Context for this invocation. */
  64     private final Context context;
  65 
  66     private final String defaultDocletClassName;
  67     private final ClassLoader docletParentClassLoader;
  68 
  69     private static final String javadocName = "javadoc";
  70 
  71     private static final String standardDocletClassName =
  72         "com.sun.tools.doclets.standard.Standard";
  73 
  74     private long defaultFilter = PUBLIC | PROTECTED;
  75 
  76     private final Messager messager;
  77 
  78     private DocletInvoker docletInvoker;
  79 
  80     /**
  81      * In API mode, exceptions thrown while calling the doclet are
  82      * propagated using ClientCodeException.
  83      */
  84     private boolean apiMode;
  85 
  86     Start(String programName,
  87           PrintWriter errWriter,
  88           PrintWriter warnWriter,
  89           PrintWriter noticeWriter,
  90           String defaultDocletClassName) {
  91         this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null);
  92     }
  93 
  94     Start(String programName,
  95           PrintWriter errWriter,
  96           PrintWriter warnWriter,
  97           PrintWriter noticeWriter,
  98           String defaultDocletClassName,
  99           ClassLoader docletParentClassLoader) {
 100         context = new Context();
 101         messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter);
 102         this.defaultDocletClassName = defaultDocletClassName;
 103         this.docletParentClassLoader = docletParentClassLoader;
 104     }
 105 
 106     Start(String programName, String defaultDocletClassName) {
 107         this(programName, defaultDocletClassName, null);
 108     }
 109 
 110     Start(String programName, String defaultDocletClassName,
 111           ClassLoader docletParentClassLoader) {
 112         context = new Context();
 113         messager = new Messager(context, programName);
 114         this.defaultDocletClassName = defaultDocletClassName;
 115         this.docletParentClassLoader = docletParentClassLoader;
 116     }
 117 
 118     Start(String programName, ClassLoader docletParentClassLoader) {
 119         this(programName, standardDocletClassName, docletParentClassLoader);
 120     }
 121 
 122     Start(String programName) {
 123         this(programName, standardDocletClassName);
 124     }
 125 
 126     Start(ClassLoader docletParentClassLoader) {
 127         this(javadocName, docletParentClassLoader);
 128     }
 129 
 130     Start() {
 131         this(javadocName);
 132     }
 133 
 134     public Start(Context context) {
 135         context.getClass(); // null check
 136         this.context = context;
 137         apiMode = true;
 138         defaultDocletClassName = standardDocletClassName;
 139         docletParentClassLoader = null;
 140 
 141         Log log = context.get(Log.logKey);
 142         if (log instanceof Messager)
 143             messager = (Messager) log;
 144         else {
 145             PrintWriter out = context.get(Log.outKey);
 146             messager = (out == null) ? new Messager(context, javadocName)
 147                     : new Messager(context, javadocName, out, out, out);
 148         }
 149     }
 150 
 151     /**
 152      * Usage
 153      */
 154     @Override
 155     void usage() {
 156         usage(true);
 157     }
 158 
 159     void usage(boolean exit) {
 160         usage("main.usage", "-help", null, exit);
 161     }
 162 
 163     @Override
 164     void Xusage() {
 165         Xusage(true);
 166     }
 167 
 168     void Xusage(boolean exit) {
 169         usage("main.Xusage", "-X", "main.Xusage.foot", exit);
 170     }
 171 
 172     private void usage(String main, String doclet, String foot, boolean exit) {
 173         // RFE: it would be better to replace the following with code to
 174         // write a header, then help for each option, then a footer.
 175         messager.notice(main);
 176 
 177         // let doclet print usage information (does nothing on error)
 178         if (docletInvoker != null) {
 179             // RFE: this is a pretty bad way to get the doclet to show
 180             // help info. Moreover, the output appears on stdout,
 181             // and <i>not</i> on any of the standard streams passed
 182             // to javadoc, and in particular, not to the noticeWriter
 183             // But, to fix this, we need to fix the Doclet API.
 184             docletInvoker.optionLength(doclet);
 185         }
 186 
 187         if (foot != null)
 188             messager.notice(foot);
 189 
 190         if (exit) exit();
 191     }
 192 
 193     /**
 194      * Exit
 195      */
 196     private void exit() {
 197         messager.exit();
 198     }
 199 
 200 
 201     /**
 202      * Main program - external wrapper
 203      */
 204     int begin(String... argv) {
 205         boolean ok = begin(null, argv, Collections.<JavaFileObject> emptySet());
 206         return ok ? 0 : 1;
 207     }
 208 
 209     public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) {
 210         Collection<String> opts = new ArrayList<String>();
 211         for (String opt: options) opts.add(opt);
 212         return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects);
 213     }
 214 
 215     private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) {
 216         boolean failed = false;
 217 
 218         try {
 219             failed = !parseAndExecute(docletClass, options, fileObjects);
 220         } catch (Messager.ExitJavadoc exc) {
 221             // ignore, we just exit this way
 222         } catch (OutOfMemoryError ee) {
 223             messager.error(Messager.NOPOS, "main.out.of.memory");
 224             failed = true;
 225         } catch (ClientCodeException e) {
 226             // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
 227             throw e;
 228         } catch (Error ee) {
 229             ee.printStackTrace(System.err);
 230             messager.error(Messager.NOPOS, "main.fatal.error");
 231             failed = true;
 232         } catch (Exception ee) {
 233             ee.printStackTrace(System.err);
 234             messager.error(Messager.NOPOS, "main.fatal.exception");
 235             failed = true;
 236         } finally {
 237             messager.exitNotice();
 238             messager.flush();
 239         }
 240         failed |= messager.nerrors() > 0;
 241         failed |= rejectWarnings && messager.nwarnings() > 0;
 242         return !failed;
 243     }
 244 
 245     /**
 246      * Main program - internal
 247      */
 248     private boolean parseAndExecute(
 249             Class<?> docletClass,
 250             String[] argv,
 251             Iterable<? extends JavaFileObject> fileObjects) throws IOException {
 252         long tm = System.currentTimeMillis();
 253 
 254         ListBuffer<String> javaNames = new ListBuffer<String>();
 255 
 256         // Preprocess @file arguments
 257         try {
 258             argv = CommandLine.parse(argv);
 259         } catch (FileNotFoundException e) {
 260             messager.error(Messager.NOPOS, "main.cant.read", e.getMessage());
 261             exit();
 262         } catch (IOException e) {
 263             e.printStackTrace(System.err);
 264             exit();
 265         }
 266 
 267 
 268         JavaFileManager fileManager = context.get(JavaFileManager.class);
 269         setDocletInvoker(docletClass, fileManager, argv);
 270 
 271         compOpts = Options.instance(context);
 272         // Make sure no obsolete source/target messages are reported
 273         compOpts.put("-Xlint:", "-options");
 274 
 275         // Parse arguments
 276         for (int i = 0 ; i < argv.length ; i++) {
 277             String arg = argv[i];
 278 
 279             ToolOption o = ToolOption.get(arg);
 280             if (o != null) {
 281                 // hack: this restriction should be removed
 282                 if (o == ToolOption.LOCALE && i > 0)
 283                     usageError("main.locale_first");
 284 
 285                 if (o.hasArg) {
 286                     oneArg(argv, i++);
 287                     o.process(this, argv[i]);
 288                 } else {
 289                     setOption(arg);
 290                     o.process(this);
 291                 }
 292 
 293             } else if (arg.startsWith("-XD")) {
 294                 // hidden javac options
 295                 String s = arg.substring("-XD".length());
 296                 int eq = s.indexOf('=');
 297                 String key = (eq < 0) ? s : s.substring(0, eq);
 298                 String value = (eq < 0) ? s : s.substring(eq+1);
 299                 compOpts.put(key, value);
 300             }
 301             // call doclet for its options
 302             // other arg starts with - is invalid
 303             else if (arg.startsWith("-")) {
 304                 int optionLength;
 305                 optionLength = docletInvoker.optionLength(arg);
 306                 if (optionLength < 0) {
 307                     // error already displayed
 308                     exit();
 309                 } else if (optionLength == 0) {
 310                     // option not found
 311                     usageError("main.invalid_flag", arg);
 312                 } else {
 313                     // doclet added option
 314                     if ((i + optionLength) > argv.length) {
 315                         usageError("main.requires_argument", arg);
 316                     }
 317                     ListBuffer<String> args = new ListBuffer<String>();
 318                     for (int j = 0; j < optionLength-1; ++j) {
 319                         args.append(argv[++i]);
 320                     }
 321                     setOption(arg, args.toList());
 322                 }
 323             } else {
 324                 javaNames.append(arg);
 325             }
 326         }
 327         compOpts.notifyListeners();
 328 
 329         if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
 330             usageError("main.No_packages_or_classes_specified");
 331         }
 332 
 333         if (!docletInvoker.validOptions(options.toList())) {
 334             // error message already displayed
 335             exit();
 336         }
 337 
 338         JavadocTool comp = JavadocTool.make0(context);
 339         if (comp == null) return false;
 340 
 341         if (showAccess == null) {
 342             setFilter(defaultFilter);
 343         }
 344 
 345         LanguageVersion languageVersion = docletInvoker.languageVersion();
 346         RootDocImpl root = comp.getRootDocImpl(
 347                 docLocale,
 348                 encoding,
 349                 showAccess,
 350                 javaNames.toList(),
 351                 options.toList(),
 352                 fileObjects,
 353                 breakiterator,
 354                 subPackages.toList(),
 355                 excludedPackages.toList(),
 356                 docClasses,
 357                 // legacy?
 358                 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1,
 359                 quiet);
 360 
 361         // release resources
 362         comp = null;
 363 
 364         // pass off control to the doclet
 365         boolean ok = root != null;
 366         if (ok) ok = docletInvoker.start(root);
 367 
 368         // We're done.
 369         if (compOpts.get("-verbose") != null) {
 370             tm = System.currentTimeMillis() - tm;
 371             messager.notice("main.done_in", Long.toString(tm));
 372         }
 373 
 374         return ok;
 375     }
 376 
 377     private <T> boolean isEmpty(Iterable<T> iter) {
 378         return !iter.iterator().hasNext();
 379     }
 380 
 381     /**
 382      * Init the doclet invoker.
 383      * The doclet class may be given explicitly, or via the -doclet option in
 384      * argv.
 385      * If the doclet class is not given explicitly, it will be loaded from
 386      * the file manager's DOCLET_PATH location, if available, or via the
 387      * -doclet path option in argv.
 388      * @param docletClass The doclet class. May be null.
 389      * @param fileManager The file manager used to get the class loader to load
 390      * the doclet class if required. May be null.
 391      * @param argv Args containing -doclet and -docletpath, in case they are required.
 392      */
 393     private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) {
 394         if (docletClass != null) {
 395             docletInvoker = new DocletInvoker(messager, docletClass, apiMode);
 396             // TODO, check no -doclet, -docletpath
 397             return;
 398         }
 399 
 400         String docletClassName = null;
 401         String docletPath = null;
 402 
 403         // Parse doclet specifying arguments
 404         for (int i = 0 ; i < argv.length ; i++) {
 405             String arg = argv[i];
 406             if (arg.equals(ToolOption.DOCLET.opt)) {
 407                 oneArg(argv, i++);
 408                 if (docletClassName != null) {
 409                     usageError("main.more_than_one_doclet_specified_0_and_1",
 410                                docletClassName, argv[i]);
 411                 }
 412                 docletClassName = argv[i];
 413             } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
 414                 oneArg(argv, i++);
 415                 if (docletPath == null) {
 416                     docletPath = argv[i];
 417                 } else {
 418                     docletPath += File.pathSeparator + argv[i];
 419                 }
 420             }
 421         }
 422 
 423         if (docletClassName == null) {
 424             docletClassName = defaultDocletClassName;
 425         }
 426 
 427         // attempt to find doclet
 428         docletInvoker = new DocletInvoker(messager, fileManager,
 429                 docletClassName, docletPath,
 430                 docletParentClassLoader,
 431                 apiMode);
 432     }
 433 
 434     /**
 435      * Set one arg option.
 436      * Error and exit if one argument is not provided.
 437      */
 438     private void oneArg(String[] args, int index) {
 439         if ((index + 1) < args.length) {
 440             setOption(args[index], args[index+1]);
 441         } else {
 442             usageError("main.requires_argument", args[index]);
 443         }
 444     }
 445 
 446     @Override
 447     void usageError(String key, Object... args) {
 448         messager.error(Messager.NOPOS, key, args);
 449         usage(true);
 450     }
 451 
 452     /**
 453      * indicate an option with no arguments was given.
 454      */
 455     private void setOption(String opt) {
 456         String[] option = { opt };
 457         options.append(option);
 458     }
 459 
 460     /**
 461      * indicate an option with one argument was given.
 462      */
 463     private void setOption(String opt, String argument) {
 464         String[] option = { opt, argument };
 465         options.append(option);
 466     }
 467 
 468     /**
 469      * indicate an option with the specified list of arguments was given.
 470      */
 471     private void setOption(String opt, List<String> arguments) {
 472         String[] args = new String[arguments.length() + 1];
 473         int k = 0;
 474         args[k++] = opt;
 475         for (List<String> i = arguments; i.nonEmpty(); i=i.tail) {
 476             args[k++] = i.head;
 477         }
 478         options.append(args);
 479     }
 480 }