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