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 }