1 /* 2 * Copyright (c) 1997, 2014, 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 Start(String programName, 107 PrintWriter errWriter, 108 PrintWriter warnWriter, 109 PrintWriter noticeWriter, 110 String defaultDocletClassName, 111 ClassLoader docletParentClassLoader) { 112 context = new Context(); 113 messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter); 114 this.defaultDocletClassName = defaultDocletClassName; 115 this.docletParentClassLoader = docletParentClassLoader; 116 } 117 118 Start(String programName, String defaultDocletClassName) { 119 this(programName, defaultDocletClassName, null); 120 } 121 122 Start(String programName, String defaultDocletClassName, 123 ClassLoader docletParentClassLoader) { 124 context = new Context(); 125 messager = new Messager(context, programName); 126 this.defaultDocletClassName = defaultDocletClassName; 127 this.docletParentClassLoader = docletParentClassLoader; 128 } 129 130 Start(String programName, ClassLoader docletParentClassLoader) { 131 this(programName, standardDocletClassName, docletParentClassLoader); 132 } 133 134 Start(String programName) { 135 this(programName, standardDocletClassName); 136 } 137 138 Start(ClassLoader docletParentClassLoader) { 139 this(javadocName, docletParentClassLoader); 140 } 141 142 Start() { 143 this(javadocName); 144 } 145 146 public Start(Context context) { 147 this.context = Objects.requireNonNull(context); 148 apiMode = true; 149 defaultDocletClassName = standardDocletClassName; 150 docletParentClassLoader = null; 151 152 Log log = context.get(Log.logKey); 153 if (log instanceof Messager) 154 messager = (Messager) log; 155 else { 156 PrintWriter out = context.get(Log.outKey); 157 messager = (out == null) ? new Messager(context, javadocName) 158 : new Messager(context, javadocName, out, out, out); 159 } 160 } 161 162 /** 163 * Usage 164 */ 165 @Override 166 void usage() { 167 usage(true); 168 } 169 170 void usage(boolean exit) { 171 usage("main.usage", "-help", null, exit); 172 } 173 174 @Override 175 void Xusage() { 176 Xusage(true); 177 } 178 179 void Xusage(boolean exit) { 180 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 181 } 182 183 private void usage(String main, String doclet, String foot, boolean exit) { 184 // RFE: it would be better to replace the following with code to 185 // write a header, then help for each option, then a footer. 186 messager.notice(main); 187 188 // let doclet print usage information (does nothing on error) 189 if (docletInvoker != null) { 190 // RFE: this is a pretty bad way to get the doclet to show 191 // help info. Moreover, the output appears on stdout, 192 // and <i>not</i> on any of the standard streams passed 193 // to javadoc, and in particular, not to the noticeWriter 194 // But, to fix this, we need to fix the Doclet API. 195 docletInvoker.optionLength(doclet); 196 } 197 198 if (foot != null) 199 messager.notice(foot); 200 201 if (exit) exit(); 202 } 203 204 /** 205 * Exit 206 */ 207 private void exit() { 208 messager.exit(); 209 } 210 211 212 /** 213 * Main program - external wrapper 214 */ 215 int begin(String... argv) { 216 boolean ok = begin(null, argv, Collections.<JavaFileObject> emptySet()); 217 return ok ? 0 : 1; 218 } 219 220 public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) { 221 Collection<String> opts = new ArrayList<>(); 222 for (String opt: options) opts.add(opt); 223 return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects); 224 } 225 226 private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) { 227 boolean failed = false; 228 229 try { 230 failed = !parseAndExecute(docletClass, options, fileObjects); 231 } catch (Messager.ExitJavadoc exc) { 232 // ignore, we just exit this way 233 } catch (OutOfMemoryError ee) { 234 messager.error(Messager.NOPOS, "main.out.of.memory"); 235 failed = true; 236 } catch (ClientCodeException e) { 237 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 238 throw e; 239 } catch (Error ee) { 240 ee.printStackTrace(System.err); 241 messager.error(Messager.NOPOS, "main.fatal.error"); 242 failed = true; 243 } catch (Exception ee) { 244 ee.printStackTrace(System.err); 245 messager.error(Messager.NOPOS, "main.fatal.exception"); 246 failed = true; 247 } finally { 248 if (fileManager != null 249 && fileManager instanceof BaseFileManager 250 && ((BaseFileManager) fileManager).autoClose) { 251 try { 252 fileManager.close(); 253 } catch (IOException ignore) { 254 } 255 } 256 messager.exitNotice(); 257 messager.flush(); 258 } 259 failed |= messager.nerrors() > 0; 260 failed |= rejectWarnings && messager.nwarnings() > 0; 261 return !failed; 262 } 263 264 /** 265 * Main program - internal 266 */ 267 private boolean parseAndExecute( 268 Class<?> docletClass, 269 String[] argv, 270 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 271 long tm = System.currentTimeMillis(); 272 273 ListBuffer<String> javaNames = new ListBuffer<>(); 274 275 // Preprocess @file arguments 276 try { 277 argv = CommandLine.parse(argv); 278 } catch (FileNotFoundException e) { 279 messager.error(Messager.NOPOS, "main.cant.read", e.getMessage()); 280 exit(); 281 } catch (IOException e) { 282 e.printStackTrace(System.err); 283 exit(); 284 } 285 286 287 fileManager = context.get(JavaFileManager.class); 288 289 setDocletInvoker(docletClass, fileManager, argv); 290 291 compOpts = Options.instance(context); 292 // Make sure no obsolete source/target messages are reported 293 compOpts.put("-Xlint:-options", "-Xlint:-options"); 294 295 // Parse arguments 296 for (int i = 0 ; i < argv.length ; i++) { 297 String arg = argv[i]; 298 299 ToolOption o = ToolOption.get(arg); 300 if (o != null) { 301 // hack: this restriction should be removed 302 if (o == ToolOption.LOCALE && i > 0) 303 usageError("main.locale_first"); 304 305 if (o.hasArg) { 306 oneArg(argv, i++); 307 o.process(this, argv[i]); 308 } else { 309 setOption(arg); 310 o.process(this); 311 } 312 313 } else if (arg.startsWith("-XD")) { 314 // hidden javac options 315 String s = arg.substring("-XD".length()); 316 int eq = s.indexOf('='); 317 String key = (eq < 0) ? s : s.substring(0, eq); 318 String value = (eq < 0) ? s : s.substring(eq+1); 319 compOpts.put(key, value); 320 } 321 // call doclet for its options 322 // other arg starts with - is invalid 323 else if (arg.startsWith("-")) { 324 int optionLength; 325 optionLength = docletInvoker.optionLength(arg); 326 if (optionLength < 0) { 327 // error already displayed 328 exit(); 329 } else if (optionLength == 0) { 330 // option not found 331 usageError("main.invalid_flag", arg); 332 } else { 333 // doclet added option 334 if ((i + optionLength) > argv.length) { 335 usageError("main.requires_argument", arg); 336 } 337 ListBuffer<String> args = new ListBuffer<>(); 338 for (int j = 0; j < optionLength-1; ++j) { 339 args.append(argv[++i]); 340 } 341 setOption(arg, args.toList()); 342 } 343 } else { 344 javaNames.append(arg); 345 } 346 } 347 348 if (fileManager == null) { 349 JavacFileManager.preRegister(context); 350 fileManager = context.get(JavaFileManager.class); 351 if (fileManager instanceof BaseFileManager) { 352 ((BaseFileManager) fileManager).autoClose = true; 353 } 354 } 355 if (fileManager instanceof BaseFileManager) { 356 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 357 } 358 359 String platformString = compOpts.get("-release"); 360 361 if (platformString != null) { 362 if (compOpts.isSet("-source")) { 363 usageError("main.release.bootclasspath.conflict", "-source"); 364 } 365 if (fileManagerOpts.containsKey(Option.BOOTCLASSPATH)) { 366 usageError("main.release.bootclasspath.conflict", Option.BOOTCLASSPATH.getText()); 367 } 368 369 PlatformDescription platformDescription = 370 PlatformUtils.lookupPlatformDescription(platformString); 371 372 if (platformDescription == null) { 373 usageError("main.unsupported.release.version", platformString); 374 } 375 376 compOpts.put(Option.SOURCE, platformDescription.getSourceVersion()); 377 378 context.put(PlatformDescription.class, platformDescription); 379 380 Collection<Path> platformCP = platformDescription.getPlatformPath(); 381 382 if (platformCP != null) { 383 if (fileManager instanceof StandardJavaFileManager) { 384 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 385 386 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 387 } else { 388 usageError("main.release.not.standard.file.manager", platformString); 389 } 390 } 391 } 392 393 compOpts.notifyListeners(); 394 395 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 396 usageError("main.No_packages_or_classes_specified"); 397 } 398 399 if (!docletInvoker.validOptions(options.toList())) { 400 // error message already displayed 401 exit(); 402 } 403 404 JavadocTool comp = JavadocTool.make0(context); 405 if (comp == null) return false; 406 407 if (showAccess == null) { 408 setFilter(defaultFilter); 409 } 410 411 LanguageVersion languageVersion = docletInvoker.languageVersion(); 412 RootDocImpl root = comp.getRootDocImpl( 413 docLocale, 414 encoding, 415 showAccess, 416 javaNames.toList(), 417 options.toList(), 418 fileObjects, 419 breakiterator, 420 subPackages.toList(), 421 excludedPackages.toList(), 422 docClasses, 423 // legacy? 424 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, 425 quiet); 426 427 // release resources 428 comp = null; 429 430 // pass off control to the doclet 431 boolean ok = root != null; 432 if (ok) ok = docletInvoker.start(root); 433 434 // We're done. 435 if (compOpts.get("-verbose") != null) { 436 tm = System.currentTimeMillis() - tm; 437 messager.notice("main.done_in", Long.toString(tm)); 438 } 439 440 return ok; 441 } 442 443 private <T> boolean isEmpty(Iterable<T> iter) { 444 return !iter.iterator().hasNext(); 445 } 446 447 /** 448 * Init the doclet invoker. 449 * The doclet class may be given explicitly, or via the -doclet option in 450 * argv. 451 * If the doclet class is not given explicitly, it will be loaded from 452 * the file manager's DOCLET_PATH location, if available, or via the 453 * -doclet path option in argv. 454 * @param docletClass The doclet class. May be null. 455 * @param fileManager The file manager used to get the class loader to load 456 * the doclet class if required. May be null. 457 * @param argv Args containing -doclet and -docletpath, in case they are required. 458 */ 459 private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) { 460 if (docletClass != null) { 461 docletInvoker = new DocletInvoker(messager, docletClass, apiMode); 462 // TODO, check no -doclet, -docletpath 463 return; 464 } 465 466 String docletClassName = null; 467 String docletPath = null; 468 469 // Parse doclet specifying arguments 470 for (int i = 0 ; i < argv.length ; i++) { 471 String arg = argv[i]; 472 if (arg.equals(ToolOption.DOCLET.opt)) { 473 oneArg(argv, i++); 474 if (docletClassName != null) { 475 usageError("main.more_than_one_doclet_specified_0_and_1", 476 docletClassName, argv[i]); 477 } 478 docletClassName = argv[i]; 479 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 480 oneArg(argv, i++); 481 if (docletPath == null) { 482 docletPath = argv[i]; 483 } else { 484 docletPath += File.pathSeparator + argv[i]; 485 } 486 } 487 } 488 489 if (docletClassName == null) { 490 docletClassName = defaultDocletClassName; 491 } 492 493 // attempt to find doclet 494 docletInvoker = new DocletInvoker(messager, fileManager, 495 docletClassName, docletPath, 496 docletParentClassLoader, 497 apiMode); 498 } 499 500 /** 501 * Set one arg option. 502 * Error and exit if one argument is not provided. 503 */ 504 private void oneArg(String[] args, int index) { 505 if ((index + 1) < args.length) { 506 setOption(args[index], args[index+1]); 507 } else { 508 usageError("main.requires_argument", args[index]); 509 } 510 } 511 512 @Override 513 void usageError(String key, Object... args) { 514 messager.error(Messager.NOPOS, key, args); 515 usage(true); 516 } 517 518 /** 519 * indicate an option with no arguments was given. 520 */ 521 private void setOption(String opt) { 522 String[] option = { opt }; 523 options.append(option); 524 } 525 526 /** 527 * indicate an option with one argument was given. 528 */ 529 private void setOption(String opt, String argument) { 530 String[] option = { opt, argument }; 531 options.append(option); 532 } 533 534 /** 535 * indicate an option with the specified list of arguments was given. 536 */ 537 private void setOption(String opt, List<String> arguments) { 538 String[] args = new String[arguments.length() + 1]; 539 int k = 0; 540 args[k++] = opt; 541 for (List<String> i = arguments; i.nonEmpty(); i=i.tail) { 542 args[k++] = i.head; 543 } 544 options.append(args); 545 } 546 } | 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 jdk.javadoc.internal.tool; 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.text.BreakIterator; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.Collections; 38 import java.util.List; 39 import java.util.Locale; 40 import java.util.Objects; 41 import java.util.Set; 42 import static javax.tools.DocumentationTool.Location.*; 43 import javax.tools.JavaFileManager; 44 import javax.tools.JavaFileObject; 45 import javax.tools.StandardJavaFileManager; 46 import javax.tools.StandardLocation; 47 48 import com.sun.tools.javac.api.JavacTrees; 49 import com.sun.tools.javac.file.BaseFileManager; 50 import com.sun.tools.javac.file.JavacFileManager; 51 import com.sun.tools.javac.main.CommandLine; 52 import com.sun.tools.javac.platform.PlatformDescription; 53 import com.sun.tools.javac.platform.PlatformUtils; 54 import com.sun.tools.javac.util.ClientCodeException; 55 import com.sun.tools.javac.util.Context; 56 import com.sun.tools.javac.util.Log; 57 import com.sun.tools.javac.util.Options; 58 59 import jdk.javadoc.doclet.Doclet; 60 import jdk.javadoc.doclet.Doclet.Option; 61 import jdk.javadoc.doclet.DocletEnvironment; 62 63 import static com.sun.tools.javac.main.Option.*; 64 /** 65 * Main program of Javadoc. 66 * Previously named "Main". 67 * 68 * <p><b>This is NOT part of any supported API. 69 * If you write code that depends on this, you do so at your own risk. 70 * This code and its internal interfaces are subject to change or 71 * deletion without notice.</b> 72 * 73 * @since 1.2 74 * @author Robert Field 75 * @author Neal Gafter (rewrite) 76 */ 77 public class Start extends ToolOption.Helper { 78 /** Context for this invocation. */ 79 private final Context context; 80 81 private static final String ProgramName = "javadoc"; 82 83 // meaning we allow all visibility of PROTECTED and PUBLIC 84 private static final String defaultModifier = "protected"; 85 86 private Messager messager; 87 88 private final String docletName; 89 90 private final ClassLoader classLoader; 91 92 private Class<?> docletClass; 93 94 private Doclet doclet; 95 96 // used to determine the locale for the messager 97 private Locale locale; 98 99 100 /** 101 * In API mode, exceptions thrown while calling the doclet are 102 * propagated using ClientCodeException. 103 */ 104 private boolean apiMode; 105 106 private JavaFileManager fileManager; 107 108 Start() { 109 this(null, null, null, null, null); 110 } 111 112 Start(PrintWriter writer) { 113 this(null, null, writer, null, null); 114 } 115 116 Start(Context context, String programName, PrintWriter writer, 117 String docletName, ClassLoader classLoader) { 118 this.context = context == null ? new Context() : context; 119 String pname = programName == null ? ProgramName : programName; 120 this.messager = writer == null 121 ? new Messager(this.context, pname) 122 : new Messager(this.context, pname, writer, writer); 123 this.docletName = docletName; 124 this.classLoader = classLoader; 125 this.docletClass = null; 126 this.locale = Locale.getDefault(); 127 } 128 129 public Start(Context context) { 130 this.docletClass = null; 131 this.context = Objects.requireNonNull(context); 132 this.apiMode = true; 133 this.docletName = null; 134 this.classLoader = null; 135 this.locale = Locale.getDefault(); 136 } 137 138 void initMessager() { 139 if (!apiMode) 140 return; 141 if (messager == null) { 142 Log log = context.get(Log.logKey); 143 if (log instanceof Messager) { 144 messager = (Messager) log; 145 } else { 146 PrintWriter out = context.get(Log.outKey); 147 messager = (out == null) 148 ? new Messager(context, ProgramName) 149 : new Messager(context, ProgramName, out, out); 150 } 151 } 152 } 153 154 /** 155 * Usage 156 */ 157 @Override 158 void usage() { 159 usage(true); 160 } 161 162 void usage(boolean exit) { 163 usage("main.usage", "-help", null, exit); 164 } 165 166 @Override 167 void Xusage() { 168 Xusage(true); 169 } 170 171 void Xusage(boolean exit) { 172 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 173 } 174 175 private void usage(String main, String option, String foot, boolean exit) { 176 messager.notice(main); 177 // let doclet print usage information (does nothing on error) 178 if (docletClass != null) { 179 String name = doclet.getName(); 180 Set<Option> supportedOptions = doclet.getSupportedOptions(); 181 messager.notice("main.doclet.usage.header", name); 182 Option.Kind myKind = option.equals("-X") 183 ? Option.Kind.EXTENDED 184 : Option.Kind.STANDARD; 185 supportedOptions.stream() 186 .filter(opt -> opt.getKind() == myKind) 187 .forEach(opt -> messager.printNotice(opt.toString())); 188 } 189 if (foot != null) 190 messager.notice(foot); 191 192 if (exit) exit(); 193 } 194 195 /** 196 * Exit 197 */ 198 private void exit() { 199 messager.exit(); 200 } 201 202 /** 203 * Main program - external wrapper 204 */ 205 int begin(String... argv) { 206 // Preprocess @file arguments 207 try { 208 argv = CommandLine.parse(argv); 209 } catch (FileNotFoundException e) { 210 messager.error("main.cant.read", e.getMessage()); 211 exit(); 212 } catch (IOException e) { 213 e.printStackTrace(System.err); 214 exit(); 215 } 216 217 List<String> argList = Arrays.asList(argv); 218 boolean ok = begin(argList, Collections.<JavaFileObject> emptySet()); 219 return ok ? 0 : 1; 220 } 221 222 // Called by 199 API. 223 public boolean begin(Class<?> docletClass, 224 Iterable<String> options, 225 Iterable<? extends JavaFileObject> fileObjects) { 226 this.docletClass = docletClass; 227 List<String> opts = new ArrayList<>(); 228 for (String opt: options) 229 opts.add(opt); 230 return begin(opts, fileObjects); 231 } 232 233 private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { 234 235 fileManager = context.get(JavaFileManager.class); 236 if (fileManager == null) { 237 JavacFileManager.preRegister(context); 238 fileManager = context.get(JavaFileManager.class); 239 if (fileManager instanceof BaseFileManager) { 240 ((BaseFileManager) fileManager).autoClose = true; 241 } 242 } 243 // locale and doclet needs to be determined first 244 docletClass = preProcess(fileManager, options); 245 246 if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { 247 // no need to dispatch to old, safe to init now 248 initMessager(); 249 messager.setLocale(locale); 250 try { 251 doclet = (Doclet) docletClass.newInstance(); 252 } catch (InstantiationException | IllegalAccessException exc) { 253 if (!apiMode) { 254 error("main.could_not_instantiate_class", docletClass); 255 messager.exit(); 256 } 257 throw new ClientCodeException(exc); 258 } 259 } else { 260 if (this.apiMode) { 261 com.sun.tools.javadoc.Start ostart 262 = new com.sun.tools.javadoc.Start(context); 263 return ostart.begin(docletClass, options, fileObjects); 264 } 265 String[] array = options.toArray(new String[options.size()]); 266 return com.sun.tools.javadoc.Main.execute(array) == 0; 267 } 268 269 boolean failed = false; 270 try { 271 failed = !parseAndExecute(options, fileObjects); 272 } catch (Messager.ExitJavadoc exc) { 273 // ignore, we just exit this way 274 } catch (OutOfMemoryError ee) { 275 messager.error("main.out.of.memory"); 276 failed = true; 277 } catch (ClientCodeException e) { 278 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 279 throw e; 280 } catch (Error ee) { 281 ee.printStackTrace(System.err); 282 messager.error("main.fatal.error"); 283 failed = true; 284 } catch (Exception ee) { 285 ee.printStackTrace(System.err); 286 messager.error("main.fatal.exception"); 287 failed = true; 288 } finally { 289 if (fileManager != null 290 && fileManager instanceof BaseFileManager 291 && ((BaseFileManager) fileManager).autoClose) { 292 try { 293 fileManager.close(); 294 } catch (IOException ignore) {} 295 } 296 boolean haveErrorWarnings = messager.nerrors() > 0 || 297 (rejectWarnings && messager.nwarnings() > 0); 298 if (failed && !haveErrorWarnings) { 299 // the doclet failed, but nothing reported, flag it!. 300 messager.error("main.unknown.error"); 301 } 302 failed |= haveErrorWarnings; 303 messager.exitNotice(); 304 messager.flush(); 305 } 306 return !failed; 307 } 308 309 /** 310 * Main program - internal 311 */ 312 private boolean parseAndExecute(List<String> argList, 313 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 314 long tm = System.currentTimeMillis(); 315 316 List<String> javaNames = new ArrayList<>(); 317 318 compOpts = Options.instance(context); 319 320 // Make sure no obsolete source/target messages are reported 321 compOpts.put("-Xlint:-options", "-Xlint:-options"); 322 323 doclet.init(locale, messager); 324 parseArgs(argList, javaNames); 325 326 if (fileManager instanceof BaseFileManager) { 327 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 328 } 329 330 String platformString = compOpts.get("-release"); 331 332 if (platformString != null) { 333 if (compOpts.isSet("-source")) { 334 usageError("main.release.bootclasspath.conflict", "-source"); 335 } 336 if (fileManagerOpts.containsKey(BOOTCLASSPATH)) { 337 usageError("main.release.bootclasspath.conflict", BOOTCLASSPATH.getText()); 338 } 339 340 PlatformDescription platformDescription = 341 PlatformUtils.lookupPlatformDescription(platformString); 342 343 if (platformDescription == null) { 344 usageError("main.unsupported.release.version", platformString); 345 } 346 347 compOpts.put(SOURCE, platformDescription.getSourceVersion()); 348 349 context.put(PlatformDescription.class, platformDescription); 350 351 Collection<Path> platformCP = platformDescription.getPlatformPath(); 352 353 if (platformCP != null) { 354 if (fileManager instanceof StandardJavaFileManager) { 355 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 356 357 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 358 } else { 359 usageError("main.release.not.standard.file.manager", platformString); 360 } 361 } 362 } 363 364 compOpts.notifyListeners(); 365 366 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 367 usageError("main.No_packages_or_classes_specified"); 368 } 369 370 JavadocTool comp = JavadocTool.make0(context); 371 if (comp == null) return false; 372 373 if (showAccess == null) { 374 setFilter(defaultModifier); 375 } 376 377 DocletEnvironment root = comp.getEnvironment( 378 encoding, 379 showAccess, 380 overviewpath, 381 javaNames, 382 fileObjects, 383 subPackages, 384 excludedPackages, 385 docClasses, 386 quiet); 387 388 // release resources 389 comp = null; 390 391 if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 392 JavacTrees trees = JavacTrees.instance(context); 393 trees.setBreakIterator(BreakIterator.getSentenceInstance(locale)); 394 } 395 // pass off control to the doclet 396 boolean ok = root != null; 397 if (ok) ok = doclet.run(root); 398 399 // We're done. 400 if (compOpts.get("-verbose") != null) { 401 tm = System.currentTimeMillis() - tm; 402 messager.notice("main.done_in", Long.toString(tm)); 403 } 404 405 return ok; 406 } 407 408 Set<Doclet.Option> docletOptions = null; 409 int handleDocletOptions(int idx, List<String> args, boolean isToolOption) { 410 if (docletOptions == null) { 411 docletOptions = doclet.getSupportedOptions(); 412 } 413 String arg = args.get(idx); 414 415 for (Doclet.Option opt : docletOptions) { 416 if (opt.matches(arg)) { 417 if (args.size() - idx < opt.getArgumentCount()) { 418 usageError("main.requires_argument", arg); 419 } 420 opt.process(arg, args.listIterator(idx + 1)); 421 idx += opt.getArgumentCount(); 422 return idx; 423 } 424 } 425 // check if arg is accepted by the tool before emitting error 426 if (!isToolOption) 427 usageError("main.invalid_flag", arg); 428 return idx; 429 } 430 431 private Class<?> preProcess(JavaFileManager jfm, List<String> argv) { 432 // doclet specifying arguments 433 String userDocletPath = null; 434 String userDocletName = null; 435 436 // Step 1: loop through the args, set locale early on, if found. 437 for (int i = 0 ; i < argv.size() ; i++) { 438 String arg = argv.get(i); 439 if (arg.equals(ToolOption.LOCALE.opt)) { 440 oneArg(argv, i++); 441 String lname = argv.get(i); 442 locale = getLocale(lname); 443 } else if (arg.equals(ToolOption.DOCLET.opt)) { 444 oneArg(argv, i++); 445 if (userDocletName != null) { 446 usageError("main.more_than_one_doclet_specified_0_and_1", 447 userDocletName, argv.get(i)); 448 } 449 if (docletName != null) { 450 usageError("main.more_than_one_doclet_specified_0_and_1", 451 docletName, argv.get(i)); 452 } 453 userDocletName = argv.get(i); 454 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 455 oneArg(argv, i++); 456 if (userDocletPath == null) { 457 userDocletPath = argv.get(i); 458 } else { 459 userDocletPath += File.pathSeparator + argv.get(i); 460 } 461 } 462 } 463 // Step 2: a doclet has already been provided, 464 // nothing more to do. 465 if (docletClass != null) { 466 return docletClass; 467 } 468 // Step 3: doclet name specified ? if so find a ClassLoader, 469 // and load it. 470 if (userDocletName != null) { 471 ClassLoader cl = classLoader; 472 if (cl == null) { 473 if (!fileManager.hasLocation(DOCLET_PATH)) { 474 List<File> paths = new ArrayList<>(); 475 if (userDocletPath != null) { 476 for (String pathname : userDocletPath.split(File.pathSeparator)) { 477 paths.add(new File(pathname)); 478 } 479 } 480 try { 481 ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); 482 } catch (IOException ioe) { 483 panic("main.doclet_no_classloader_found", ioe); 484 return null; // keep compiler happy 485 } 486 } 487 cl = fileManager.getClassLoader(DOCLET_PATH); 488 if (cl == null) { 489 // despite doclet specified on cmdline no classloader found! 490 panic("main.doclet_no_classloader_found", userDocletName); 491 return null; // keep compiler happy 492 } 493 try { 494 return cl.loadClass(userDocletName); 495 } catch (ClassNotFoundException cnfe) { 496 panic("main.doclet_class_not_found", userDocletName); 497 return null; // keep compiler happy 498 } 499 } 500 } 501 // Step 4: we have a doclet, try loading it, otherwise 502 // return back the standard doclet 503 if (docletName != null) { 504 try { 505 return Class.forName(docletName, true, getClass().getClassLoader()); 506 } catch (ClassNotFoundException cnfe) { 507 panic("main.doclet_class_not_found", userDocletName); 508 return null; // happy compiler, should not happen 509 } 510 } else { 511 return jdk.javadoc.internal.doclets.standard.Standard.class; 512 } 513 } 514 515 private void parseArgs(List<String> args, List<String> javaNames) { 516 for (int i = 0 ; i < args.size() ; i++) { 517 String arg = args.get(i); 518 ToolOption o = ToolOption.get(arg); 519 if (o != null) { 520 // handle a doclet argument that may be needed however 521 // don't increment the index, and allow the tool to consume args 522 handleDocletOptions(i, args, true); 523 524 if (o.hasArg) { 525 oneArg(args, i++); 526 o.process(this, args.get(i)); 527 } else { 528 setOption(arg); 529 o.process(this); 530 } 531 } else if (arg.startsWith("-XD")) { 532 // hidden javac options 533 String s = arg.substring("-XD".length()); 534 int eq = s.indexOf('='); 535 String key = (eq < 0) ? s : s.substring(0, eq); 536 String value = (eq < 0) ? s : s.substring(eq+1); 537 compOpts.put(key, value); 538 } else if (arg.startsWith("-")) { 539 i = handleDocletOptions(i, args, false); 540 } else { 541 javaNames.add(arg); 542 } 543 } 544 } 545 546 private <T> boolean isEmpty(Iterable<T> iter) { 547 return !iter.iterator().hasNext(); 548 } 549 550 /** 551 * Set one arg option. 552 * Error and exit if one argument is not provided. 553 */ 554 private void oneArg(List<String> args, int index) { 555 if ((index + 1) < args.size()) { 556 setOption(args.get(index), args.get(index+1)); 557 } else { 558 usageError("main.requires_argument", args.get(index)); 559 } 560 } 561 562 @Override 563 void usageError(String key, Object... args) { 564 error(key, args); 565 usage(true); 566 } 567 568 // a terminal call, will not return 569 void panic(String key, Object... args) { 570 error(key, args); 571 messager.exit(); 572 } 573 574 void error(String key, Object... args) { 575 messager.error(key, args); 576 } 577 578 /** 579 * indicate an option with no arguments was given. 580 */ 581 private void setOption(String opt) { 582 String[] option = { opt }; 583 options.add(Arrays.asList(option)); 584 } 585 586 /** 587 * indicate an option with one argument was given. 588 */ 589 private void setOption(String opt, String argument) { 590 String[] option = { opt, argument }; 591 options.add(Arrays.asList(option)); 592 } 593 594 /** 595 * indicate an option with the specified list of arguments was given. 596 */ 597 private void setOption(String opt, List<String> arguments) { 598 List<String> args = new ArrayList<>(arguments.size() + 1); 599 args.add(opt); 600 args.addAll(arguments); 601 options.add(args); 602 } 603 604 /** 605 * Get the locale if specified on the command line 606 * else return null and if locale option is not used 607 * then return default locale. 608 */ 609 private Locale getLocale(String localeName) { 610 Locale userlocale = null; 611 if (localeName == null || localeName.isEmpty()) { 612 return Locale.getDefault(); 613 } 614 int firstuscore = localeName.indexOf('_'); 615 int seconduscore = -1; 616 String language = null; 617 String country = null; 618 String variant = null; 619 if (firstuscore == 2) { 620 language = localeName.substring(0, firstuscore); 621 seconduscore = localeName.indexOf('_', firstuscore + 1); 622 if (seconduscore > 0) { 623 if (seconduscore != firstuscore + 3 624 || localeName.length() <= seconduscore + 1) { 625 usageError("main.malformed_locale_name", localeName); 626 return null; 627 } 628 country = localeName.substring(firstuscore + 1, 629 seconduscore); 630 variant = localeName.substring(seconduscore + 1); 631 } else if (localeName.length() == firstuscore + 3) { 632 country = localeName.substring(firstuscore + 1); 633 } else { 634 usageError("main.malformed_locale_name", localeName); 635 return null; 636 } 637 } else if (firstuscore == -1 && localeName.length() == 2) { 638 language = localeName; 639 } else { 640 usageError("main.malformed_locale_name", localeName); 641 return null; 642 } 643 userlocale = searchLocale(language, country, variant); 644 if (userlocale == null) { 645 usageError("main.illegal_locale_name", localeName); 646 return null; 647 } else { 648 return userlocale; 649 } 650 } 651 652 /** 653 * Search the locale for specified language, specified country and 654 * specified variant. 655 */ 656 private Locale searchLocale(String language, String country, 657 String variant) { 658 for (Locale loc : Locale.getAvailableLocales()) { 659 if (loc.getLanguage().equals(language) && 660 (country == null || loc.getCountry().equals(country)) && 661 (variant == null || loc.getVariant().equals(variant))) { 662 return loc; 663 } 664 } 665 return null; 666 } 667 } |