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 }