1 /*
   2  * Copyright (c) 1997, 2015, 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.nio.file.Path;
  33 import java.util.ArrayList;
  34 import java.util.Collection;
  35 import java.util.Collections;
  36 import java.util.Objects;
  37 
  38 import javax.tools.JavaFileManager;
  39 import javax.tools.JavaFileObject;
  40 import javax.tools.StandardJavaFileManager;
  41 import javax.tools.StandardLocation;
  42 
  43 import com.sun.javadoc.*;
  44 import com.sun.tools.javac.file.JavacFileManager;
  45 import com.sun.tools.javac.main.CommandLine;
  46 import com.sun.tools.javac.main.Option;
  47 import com.sun.tools.javac.file.BaseFileManager;
  48 import com.sun.tools.javac.platform.PlatformDescription;
  49 import com.sun.tools.javac.platform.PlatformUtils;
  50 import com.sun.tools.javac.util.ClientCodeException;
  51 import com.sun.tools.javac.util.Context;
  52 import com.sun.tools.javac.util.List;
  53 import com.sun.tools.javac.util.ListBuffer;
  54 import com.sun.tools.javac.util.Log;
  55 import com.sun.tools.javac.util.Options;
  56 
  57 import static com.sun.tools.javac.code.Flags.*;
  58 
  59 /**
  60  * Main program of Javadoc.
  61  * Previously named "Main".
  62  *
  63  *  <p><b>This is NOT part of any supported API.
  64  *  If you write code that depends on this, you do so at your own risk.
  65  *  This code and its internal interfaces are subject to change or
  66  *  deletion without notice.</b>
  67  *
  68  * @since 1.2
  69  * @author Robert Field
  70  * @author Neal Gafter (rewrite)
  71  */
  72 public class Start extends ToolOption.Helper {
  73     /** Context for this invocation. */
  74     private final Context context;
  75 
  76     private final String defaultDocletClassName;
  77     private final ClassLoader docletParentClassLoader;
  78 
  79     private static final String javadocName = "javadoc";
  80 
  81     private static final String standardDocletClassName =
  82         "com.sun.tools.doclets.standard.Standard";
  83 
  84     private final long defaultFilter = PUBLIC | PROTECTED;
  85 
  86     private final Messager messager;
  87 
  88     private DocletInvoker docletInvoker;
  89 
  90     /**
  91      * In API mode, exceptions thrown while calling the doclet are
  92      * propagated using ClientCodeException.
  93      */
  94     private boolean apiMode;
  95 
  96     private JavaFileManager fileManager;
  97 
  98     Start(String programName,
  99           PrintWriter errWriter,
 100           PrintWriter warnWriter,
 101           PrintWriter noticeWriter,
 102           String defaultDocletClassName) {
 103         this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null);
 104     }
 105 
 106     public Start(PrintWriter pw) {
 107         this(javadocName, pw, pw, pw, standardDocletClassName);
 108     }
 109 
 110     public Start(String programName,
 111           PrintWriter errWriter,
 112           PrintWriter warnWriter,
 113           PrintWriter noticeWriter,
 114           String defaultDocletClassName,
 115           ClassLoader docletParentClassLoader) {
 116         context = new Context();
 117         messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter);
 118         this.defaultDocletClassName = defaultDocletClassName;
 119         this.docletParentClassLoader = docletParentClassLoader;
 120     }
 121 
 122     Start(String programName, String defaultDocletClassName) {
 123         this(programName, defaultDocletClassName, null);
 124     }
 125 
 126     Start(String programName, String defaultDocletClassName,
 127           ClassLoader docletParentClassLoader) {
 128         context = new Context();
 129         messager = new Messager(context, programName);
 130         this.defaultDocletClassName = defaultDocletClassName;
 131         this.docletParentClassLoader = docletParentClassLoader;
 132     }
 133 
 134     Start(String programName, ClassLoader docletParentClassLoader) {
 135         this(programName, standardDocletClassName, docletParentClassLoader);
 136     }
 137 
 138     Start(String programName) {
 139         this(programName, standardDocletClassName);
 140     }
 141 
 142     Start(ClassLoader docletParentClassLoader) {
 143         this(javadocName, docletParentClassLoader);
 144     }
 145 
 146     public Start() {
 147         this(javadocName);
 148     }
 149 
 150     public Start(Context context) {
 151         this.context = Objects.requireNonNull(context);
 152         apiMode = true;
 153         defaultDocletClassName = standardDocletClassName;
 154         docletParentClassLoader = null;
 155 
 156         Log log = context.get(Log.logKey);
 157         if (log instanceof Messager)
 158             messager = (Messager) log;
 159         else {
 160             PrintWriter out = context.get(Log.outKey);
 161             messager = (out == null) ? new Messager(context, javadocName)
 162                     : new Messager(context, javadocName, out, out, out);
 163         }
 164     }
 165 
 166     /**
 167      * Usage
 168      */
 169     @Override
 170     void usage() {
 171         usage(true);
 172     }
 173 
 174     void usage(boolean exit) {
 175         usage("main.usage", "-help", null, exit);
 176     }
 177 
 178     @Override
 179     void Xusage() {
 180         Xusage(true);
 181     }
 182 
 183     void Xusage(boolean exit) {
 184         usage("main.Xusage", "-X", "main.Xusage.foot", exit);
 185     }
 186 
 187     private void usage(String main, String doclet, String foot, boolean exit) {
 188         // RFE: it would be better to replace the following with code to
 189         // write a header, then help for each option, then a footer.
 190         messager.notice(main);
 191 
 192         // let doclet print usage information (does nothing on error)
 193         if (docletInvoker != null) {
 194             // RFE: this is a pretty bad way to get the doclet to show
 195             // help info. Moreover, the output appears on stdout,
 196             // and <i>not</i> on any of the standard streams passed
 197             // to javadoc, and in particular, not to the noticeWriter
 198             // But, to fix this, we need to fix the Doclet API.
 199             docletInvoker.optionLength(doclet);
 200         }
 201 
 202         if (foot != null)
 203             messager.notice(foot);
 204 
 205         if (exit) exit();
 206     }
 207 
 208     /**
 209      * Exit
 210      */
 211     private void exit() {
 212         messager.exit();
 213     }
 214 
 215 
 216     /**
 217      * Main program - external wrapper
 218      */
 219     public int begin(String... argv) {
 220         boolean ok = begin(null, argv, Collections.<JavaFileObject> emptySet());
 221         return ok ? 0 : 1;
 222     }
 223 
 224     public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) {
 225         Collection<String> opts = new ArrayList<>();
 226         for (String opt: options) opts.add(opt);
 227         return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects);
 228     }
 229 
 230     private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) {
 231         boolean failed = false;
 232 
 233         try {
 234             failed = !parseAndExecute(docletClass, options, fileObjects);
 235         } catch (Messager.ExitJavadoc exc) {
 236             // ignore, we just exit this way
 237         } catch (OutOfMemoryError ee) {
 238             messager.error(Messager.NOPOS, "main.out.of.memory");
 239             failed = true;
 240         } catch (ClientCodeException e) {
 241             // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
 242             throw e;
 243         } catch (Error ee) {
 244             ee.printStackTrace(System.err);
 245             messager.error(Messager.NOPOS, "main.fatal.error");
 246             failed = true;
 247         } catch (Exception ee) {
 248             ee.printStackTrace(System.err);
 249             messager.error(Messager.NOPOS, "main.fatal.exception");
 250             failed = true;
 251         } finally {
 252             if (fileManager != null
 253                     && fileManager instanceof BaseFileManager
 254                     && ((BaseFileManager) fileManager).autoClose) {
 255                 try {
 256                     fileManager.close();
 257                 } catch (IOException ignore) {
 258                 }
 259             }
 260             messager.exitNotice();
 261             messager.flush();
 262         }
 263         failed |= messager.nerrors() > 0;
 264         failed |= rejectWarnings && messager.nwarnings() > 0;
 265         return !failed;
 266     }
 267 
 268     /**
 269      * Main program - internal
 270      */
 271     private boolean parseAndExecute(
 272             Class<?> docletClass,
 273             String[] argv,
 274             Iterable<? extends JavaFileObject> fileObjects) throws IOException {
 275         long tm = System.currentTimeMillis();
 276 
 277         ListBuffer<String> javaNames = new ListBuffer<>();
 278 
 279         // Preprocess @file arguments
 280         try {
 281             argv = CommandLine.parse(argv);
 282         } catch (FileNotFoundException e) {
 283             messager.error(Messager.NOPOS, "main.cant.read", e.getMessage());
 284             exit();
 285         } catch (IOException e) {
 286             e.printStackTrace(System.err);
 287             exit();
 288         }
 289 
 290 
 291         fileManager = context.get(JavaFileManager.class);
 292 
 293         setDocletInvoker(docletClass, fileManager, argv);
 294 
 295         compOpts = Options.instance(context);
 296         // Make sure no obsolete source/target messages are reported
 297         compOpts.put("-Xlint:-options", "-Xlint:-options");
 298 
 299         // Parse arguments
 300         for (int i = 0 ; i < argv.length ; i++) {
 301             String arg = argv[i];
 302 
 303             ToolOption o = ToolOption.get(arg);
 304             if (o != null) {
 305                 // hack: this restriction should be removed
 306                 if (o == ToolOption.LOCALE && i > 0)
 307                     usageError("main.locale_first");
 308 
 309                 if (o.hasArg) {
 310                     oneArg(argv, i++);
 311                     o.process(this, argv[i]);
 312                 } else {
 313                     setOption(arg);
 314                     o.process(this);
 315                 }
 316 
 317             } else if (arg.startsWith("-XD")) {
 318                 // hidden javac options
 319                 String s = arg.substring("-XD".length());
 320                 int eq = s.indexOf('=');
 321                 String key = (eq < 0) ? s : s.substring(0, eq);
 322                 String value = (eq < 0) ? s : s.substring(eq+1);
 323                 compOpts.put(key, value);
 324             }
 325             // call doclet for its options
 326             // other arg starts with - is invalid
 327             else if (arg.startsWith("-")) {
 328                 int optionLength;
 329                 optionLength = docletInvoker.optionLength(arg);
 330                 if (optionLength < 0) {
 331                     // error already displayed
 332                     exit();
 333                 } else if (optionLength == 0) {
 334                     // option not found
 335                     usageError("main.invalid_flag", arg);
 336                 } else {
 337                     // doclet added option
 338                     if ((i + optionLength) > argv.length) {
 339                         usageError("main.requires_argument", arg);
 340                     }
 341                     ListBuffer<String> args = new ListBuffer<>();
 342                     for (int j = 0; j < optionLength-1; ++j) {
 343                         args.append(argv[++i]);
 344                     }
 345                     setOption(arg, args.toList());
 346                 }
 347             } else {
 348                 javaNames.append(arg);
 349             }
 350         }
 351 
 352         if (fileManager == null) {
 353             JavacFileManager.preRegister(context);
 354             fileManager = context.get(JavaFileManager.class);
 355             if (fileManager instanceof BaseFileManager) {
 356                 ((BaseFileManager) fileManager).autoClose = true;
 357             }
 358         }
 359         if (fileManager instanceof BaseFileManager) {
 360             ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
 361         }
 362 
 363         String platformString = compOpts.get("-release");
 364 
 365         if (platformString != null) {
 366             if (compOpts.isSet("-source")) {
 367                 usageError("main.release.bootclasspath.conflict", "-source");
 368             }
 369             if (fileManagerOpts.containsKey(Option.BOOTCLASSPATH)) {
 370                 usageError("main.release.bootclasspath.conflict", Option.BOOTCLASSPATH.getText());
 371             }
 372 
 373             PlatformDescription platformDescription =
 374                     PlatformUtils.lookupPlatformDescription(platformString);
 375 
 376             if (platformDescription == null) {
 377                 usageError("main.unsupported.release.version", platformString);
 378             }
 379 
 380             compOpts.put(Option.SOURCE, platformDescription.getSourceVersion());
 381 
 382             context.put(PlatformDescription.class, platformDescription);
 383 
 384             Collection<Path> platformCP = platformDescription.getPlatformPath();
 385 
 386             if (platformCP != null) {
 387                 if (fileManager instanceof StandardJavaFileManager) {
 388                     StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
 389 
 390                     sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
 391                 } else {
 392                     usageError("main.release.not.standard.file.manager", platformString);
 393                 }
 394             }
 395         }
 396 
 397         compOpts.notifyListeners();
 398 
 399         if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) {
 400             usageError("main.No_packages_or_classes_specified");
 401         }
 402 
 403         if (!docletInvoker.validOptions(options.toList())) {
 404             // error message already displayed
 405             exit();
 406         }
 407 
 408         JavadocTool comp = JavadocTool.make0(context);
 409         if (comp == null) return false;
 410 
 411         if (showAccess == null) {
 412             setFilter(defaultFilter);
 413         }
 414 
 415         LanguageVersion languageVersion = docletInvoker.languageVersion();
 416         RootDocImpl root = comp.getRootDocImpl(
 417                 docLocale,
 418                 encoding,
 419                 showAccess,
 420                 javaNames.toList(),
 421                 options.toList(),
 422                 fileObjects,
 423                 breakiterator,
 424                 subPackages.toList(),
 425                 excludedPackages.toList(),
 426                 docClasses,
 427                 // legacy?
 428                 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1,
 429                 quiet);
 430 
 431         // release resources
 432         comp = null;
 433 
 434         // pass off control to the doclet
 435         boolean ok = root != null;
 436         if (ok) ok = docletInvoker.start(root);
 437 
 438         // We're done.
 439         if (compOpts.get("-verbose") != null) {
 440             tm = System.currentTimeMillis() - tm;
 441             messager.notice("main.done_in", Long.toString(tm));
 442         }
 443 
 444         return ok;
 445     }
 446 
 447     private <T> boolean isEmpty(Iterable<T> iter) {
 448         return !iter.iterator().hasNext();
 449     }
 450 
 451     /**
 452      * Init the doclet invoker.
 453      * The doclet class may be given explicitly, or via the -doclet option in
 454      * argv.
 455      * If the doclet class is not given explicitly, it will be loaded from
 456      * the file manager's DOCLET_PATH location, if available, or via the
 457      * -doclet path option in argv.
 458      * @param docletClass The doclet class. May be null.
 459      * @param fileManager The file manager used to get the class loader to load
 460      * the doclet class if required. May be null.
 461      * @param argv Args containing -doclet and -docletpath, in case they are required.
 462      */
 463     private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) {
 464         if (docletClass != null) {
 465             docletInvoker = new DocletInvoker(messager, docletClass, apiMode);
 466             // TODO, check no -doclet, -docletpath
 467             return;
 468         }
 469 
 470         String docletClassName = null;
 471         String docletPath = null;
 472 
 473         // Parse doclet specifying arguments
 474         for (int i = 0 ; i < argv.length ; i++) {
 475             String arg = argv[i];
 476             if (arg.equals(ToolOption.DOCLET.opt)) {
 477                 oneArg(argv, i++);
 478                 if (docletClassName != null) {
 479                     usageError("main.more_than_one_doclet_specified_0_and_1",
 480                                docletClassName, argv[i]);
 481                 }
 482                 docletClassName = argv[i];
 483             } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
 484                 oneArg(argv, i++);
 485                 if (docletPath == null) {
 486                     docletPath = argv[i];
 487                 } else {
 488                     docletPath += File.pathSeparator + argv[i];
 489                 }
 490             }
 491         }
 492 
 493         if (docletClassName == null) {
 494             docletClassName = defaultDocletClassName;
 495         }
 496 
 497         // attempt to find doclet
 498         docletInvoker = new DocletInvoker(messager, fileManager,
 499                 docletClassName, docletPath,
 500                 docletParentClassLoader,
 501                 apiMode);
 502     }
 503 
 504     /**
 505      * Set one arg option.
 506      * Error and exit if one argument is not provided.
 507      */
 508     private void oneArg(String[] args, int index) {
 509         if ((index + 1) < args.length) {
 510             setOption(args[index], args[index+1]);
 511         } else {
 512             usageError("main.requires_argument", args[index]);
 513         }
 514     }
 515 
 516     @Override
 517     void usageError(String key, Object... args) {
 518         messager.error(Messager.NOPOS, key, args);
 519         usage(true);
 520     }
 521 
 522     /**
 523      * indicate an option with no arguments was given.
 524      */
 525     private void setOption(String opt) {
 526         String[] option = { opt };
 527         options.append(option);
 528     }
 529 
 530     /**
 531      * indicate an option with one argument was given.
 532      */
 533     private void setOption(String opt, String argument) {
 534         String[] option = { opt, argument };
 535         options.append(option);
 536     }
 537 
 538     /**
 539      * indicate an option with the specified list of arguments was given.
 540      */
 541     private void setOption(String opt, List<String> arguments) {
 542         String[] args = new String[arguments.length() + 1];
 543         int k = 0;
 544         args[k++] = opt;
 545         for (List<String> i = arguments; i.nonEmpty(); i=i.tail) {
 546             args[k++] = i.head;
 547         }
 548         options.append(args);
 549     }
 550 }