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 }