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