1 /* 2 * Copyright (c) 1997, 2013, 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.util.ArrayList; 33 import java.util.Collection; 34 import java.util.Collections; 35 36 import javax.tools.JavaFileManager; 37 import javax.tools.JavaFileObject; 38 39 import com.sun.javadoc.*; 40 import com.sun.tools.javac.main.CommandLine; 41 import com.sun.tools.javac.util.ClientCodeException; 42 import com.sun.tools.javac.util.Context; 43 import com.sun.tools.javac.util.List; 44 import com.sun.tools.javac.util.ListBuffer; 45 import com.sun.tools.javac.util.Log; 46 import com.sun.tools.javac.util.Options; 47 import static com.sun.tools.javac.code.Flags.*; 48 49 /** 50 * Main program of Javadoc. 51 * Previously named "Main". 52 * 53 * <p><b>This is NOT part of any supported API. 54 * If you write code that depends on this, you do so at your own risk. 55 * This code and its internal interfaces are subject to change or 56 * deletion without notice.</b> 57 * 58 * @since 1.2 59 * @author Robert Field 60 * @author Neal Gafter (rewrite) 61 */ 62 public class Start extends ToolOption.Helper { 63 /** Context for this invocation. */ 64 private final Context context; 65 66 private final String defaultDocletClassName; 67 private final ClassLoader docletParentClassLoader; 68 69 private static final String javadocName = "javadoc"; 70 71 private static final String standardDocletClassName = 72 "com.sun.tools.doclets.standard.Standard"; 73 74 private long defaultFilter = PUBLIC | PROTECTED; 75 76 private final Messager messager; 77 78 private DocletInvoker docletInvoker; 79 80 /** 81 * In API mode, exceptions thrown while calling the doclet are 82 * propagated using ClientCodeException. 83 */ 84 private boolean apiMode; 85 Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName)86 Start(String programName, 87 PrintWriter errWriter, 88 PrintWriter warnWriter, 89 PrintWriter noticeWriter, 90 String defaultDocletClassName) { 91 this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null); 92 } 93 Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, String defaultDocletClassName, ClassLoader docletParentClassLoader)94 Start(String programName, 95 PrintWriter errWriter, 96 PrintWriter warnWriter, 97 PrintWriter noticeWriter, 98 String defaultDocletClassName, 99 ClassLoader docletParentClassLoader) { 100 context = new Context(); 101 messager = new Messager(context, programName, errWriter, warnWriter, noticeWriter); 102 this.defaultDocletClassName = defaultDocletClassName; 103 this.docletParentClassLoader = docletParentClassLoader; 104 } 105 Start(String programName, String defaultDocletClassName)106 Start(String programName, String defaultDocletClassName) { 107 this(programName, defaultDocletClassName, null); 108 } 109 Start(String programName, String defaultDocletClassName, ClassLoader docletParentClassLoader)110 Start(String programName, String defaultDocletClassName, 111 ClassLoader docletParentClassLoader) { 112 context = new Context(); 113 messager = new Messager(context, programName); 114 this.defaultDocletClassName = defaultDocletClassName; 115 this.docletParentClassLoader = docletParentClassLoader; 116 } 117 Start(String programName, ClassLoader docletParentClassLoader)118 Start(String programName, ClassLoader docletParentClassLoader) { 119 this(programName, standardDocletClassName, docletParentClassLoader); 120 } 121 Start(String programName)122 Start(String programName) { 123 this(programName, standardDocletClassName); 124 } 125 Start(ClassLoader docletParentClassLoader)126 Start(ClassLoader docletParentClassLoader) { 127 this(javadocName, docletParentClassLoader); 128 } 129 Start()130 Start() { 131 this(javadocName); 132 } 133 Start(Context context)134 public Start(Context context) { 135 context.getClass(); // null check 136 this.context = context; 137 apiMode = true; 138 defaultDocletClassName = standardDocletClassName; 139 docletParentClassLoader = null; 140 141 Log log = context.get(Log.logKey); 142 if (log instanceof Messager) 143 messager = (Messager) log; 144 else { 145 PrintWriter out = context.get(Log.outKey); 146 messager = (out == null) ? new Messager(context, javadocName) 147 : new Messager(context, javadocName, out, out, out); 148 } 149 } 150 151 /** 152 * Usage 153 */ 154 @Override usage()155 void usage() { 156 usage(true); 157 } 158 usage(boolean exit)159 void usage(boolean exit) { 160 usage("main.usage", "-help", null, exit); 161 } 162 163 @Override Xusage()164 void Xusage() { 165 Xusage(true); 166 } 167 Xusage(boolean exit)168 void Xusage(boolean exit) { 169 usage("main.Xusage", "-X", "main.Xusage.foot", exit); 170 } 171 usage(String main, String doclet, String foot, boolean exit)172 private void usage(String main, String doclet, String foot, boolean exit) { 173 // RFE: it would be better to replace the following with code to 174 // write a header, then help for each option, then a footer. 175 messager.notice(main); 176 177 // let doclet print usage information (does nothing on error) 178 if (docletInvoker != null) { 179 // RFE: this is a pretty bad way to get the doclet to show 180 // help info. Moreover, the output appears on stdout, 181 // and <i>not</i> on any of the standard streams passed 182 // to javadoc, and in particular, not to the noticeWriter 183 // But, to fix this, we need to fix the Doclet API. 184 docletInvoker.optionLength(doclet); 185 } 186 187 if (foot != null) 188 messager.notice(foot); 189 190 if (exit) exit(); 191 } 192 193 /** 194 * Exit 195 */ exit()196 private void exit() { 197 messager.exit(); 198 } 199 200 201 /** 202 * Main program - external wrapper 203 */ begin(String... argv)204 int begin(String... argv) { 205 boolean ok = begin(null, argv, Collections.<JavaFileObject> emptySet()); 206 return ok ? 0 : 1; 207 } 208 begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects)209 public boolean begin(Class<?> docletClass, Iterable<String> options, Iterable<? extends JavaFileObject> fileObjects) { 210 Collection<String> opts = new ArrayList<String>(); 211 for (String opt: options) opts.add(opt); 212 return begin(docletClass, opts.toArray(new String[opts.size()]), fileObjects); 213 } 214 begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects)215 private boolean begin(Class<?> docletClass, String[] options, Iterable<? extends JavaFileObject> fileObjects) { 216 boolean failed = false; 217 218 try { 219 failed = !parseAndExecute(docletClass, options, fileObjects); 220 } catch (Messager.ExitJavadoc exc) { 221 // ignore, we just exit this way 222 } catch (OutOfMemoryError ee) { 223 messager.error(Messager.NOPOS, "main.out.of.memory"); 224 failed = true; 225 } catch (ClientCodeException e) { 226 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 227 throw e; 228 } catch (Error ee) { 229 ee.printStackTrace(System.err); 230 messager.error(Messager.NOPOS, "main.fatal.error"); 231 failed = true; 232 } catch (Exception ee) { 233 ee.printStackTrace(System.err); 234 messager.error(Messager.NOPOS, "main.fatal.exception"); 235 failed = true; 236 } finally { 237 messager.exitNotice(); 238 messager.flush(); 239 } 240 failed |= messager.nerrors() > 0; 241 failed |= rejectWarnings && messager.nwarnings() > 0; 242 return !failed; 243 } 244 245 /** 246 * Main program - internal 247 */ parseAndExecute( Class<?> docletClass, String[] argv, Iterable<? extends JavaFileObject> fileObjects)248 private boolean parseAndExecute( 249 Class<?> docletClass, 250 String[] argv, 251 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 252 long tm = System.currentTimeMillis(); 253 254 ListBuffer<String> javaNames = new ListBuffer<String>(); 255 256 // Preprocess @file arguments 257 try { 258 argv = CommandLine.parse(argv); 259 } catch (FileNotFoundException e) { 260 messager.error(Messager.NOPOS, "main.cant.read", e.getMessage()); 261 exit(); 262 } catch (IOException e) { 263 e.printStackTrace(System.err); 264 exit(); 265 } 266 267 268 JavaFileManager fileManager = context.get(JavaFileManager.class); 269 setDocletInvoker(docletClass, fileManager, argv); 270 271 compOpts = Options.instance(context); 272 // Make sure no obsolete source/target messages are reported 273 compOpts.put("-Xlint:-options", "-Xlint:-options"); 274 275 // Parse arguments 276 for (int i = 0 ; i < argv.length ; i++) { 277 String arg = argv[i]; 278 279 ToolOption o = ToolOption.get(arg); 280 if (o != null) { 281 // hack: this restriction should be removed 282 if (o == ToolOption.LOCALE && i > 0) 283 usageError("main.locale_first"); 284 285 if (o.hasArg) { 286 oneArg(argv, i++); 287 o.process(this, argv[i]); 288 } else { 289 setOption(arg); 290 o.process(this); 291 } 292 293 } else if (arg.startsWith("-XD")) { 294 // hidden javac options 295 String s = arg.substring("-XD".length()); 296 int eq = s.indexOf('='); 297 String key = (eq < 0) ? s : s.substring(0, eq); 298 String value = (eq < 0) ? s : s.substring(eq+1); 299 compOpts.put(key, value); 300 } 301 // call doclet for its options 302 // other arg starts with - is invalid 303 else if (arg.startsWith("-")) { 304 int optionLength; 305 optionLength = docletInvoker.optionLength(arg); 306 if (optionLength < 0) { 307 // error already displayed 308 exit(); 309 } else if (optionLength == 0) { 310 // option not found 311 usageError("main.invalid_flag", arg); 312 } else { 313 // doclet added option 314 if ((i + optionLength) > argv.length) { 315 usageError("main.requires_argument", arg); 316 } 317 ListBuffer<String> args = new ListBuffer<String>(); 318 for (int j = 0; j < optionLength-1; ++j) { 319 args.append(argv[++i]); 320 } 321 setOption(arg, args.toList()); 322 } 323 } else { 324 javaNames.append(arg); 325 } 326 } 327 compOpts.notifyListeners(); 328 329 if (javaNames.isEmpty() && subPackages.isEmpty() && isEmpty(fileObjects)) { 330 usageError("main.No_packages_or_classes_specified"); 331 } 332 333 if (!docletInvoker.validOptions(options.toList())) { 334 // error message already displayed 335 exit(); 336 } 337 338 JavadocTool comp = JavadocTool.make0(context); 339 if (comp == null) return false; 340 341 if (showAccess == null) { 342 setFilter(defaultFilter); 343 } 344 345 LanguageVersion languageVersion = docletInvoker.languageVersion(); 346 RootDocImpl root = comp.getRootDocImpl( 347 docLocale, 348 encoding, 349 showAccess, 350 javaNames.toList(), 351 options.toList(), 352 fileObjects, 353 breakiterator, 354 subPackages.toList(), 355 excludedPackages.toList(), 356 docClasses, 357 // legacy? 358 languageVersion == null || languageVersion == LanguageVersion.JAVA_1_1, 359 quiet); 360 361 // release resources 362 comp = null; 363 364 // pass off control to the doclet 365 boolean ok = root != null; 366 if (ok) ok = docletInvoker.start(root); 367 368 // We're done. 369 if (compOpts.get("-verbose") != null) { 370 tm = System.currentTimeMillis() - tm; 371 messager.notice("main.done_in", Long.toString(tm)); 372 } 373 374 return ok; 375 } 376 isEmpty(Iterable<T> iter)377 private <T> boolean isEmpty(Iterable<T> iter) { 378 return !iter.iterator().hasNext(); 379 } 380 381 /** 382 * Init the doclet invoker. 383 * The doclet class may be given explicitly, or via the -doclet option in 384 * argv. 385 * If the doclet class is not given explicitly, it will be loaded from 386 * the file manager's DOCLET_PATH location, if available, or via the 387 * -doclet path option in argv. 388 * @param docletClass The doclet class. May be null. 389 * @param fileManager The file manager used to get the class loader to load 390 * the doclet class if required. May be null. 391 * @param argv Args containing -doclet and -docletpath, in case they are required. 392 */ setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv)393 private void setDocletInvoker(Class<?> docletClass, JavaFileManager fileManager, String[] argv) { 394 if (docletClass != null) { 395 docletInvoker = new DocletInvoker(messager, docletClass, apiMode); 396 // TODO, check no -doclet, -docletpath 397 return; 398 } 399 400 String docletClassName = null; 401 String docletPath = null; 402 403 // Parse doclet specifying arguments 404 for (int i = 0 ; i < argv.length ; i++) { 405 String arg = argv[i]; 406 if (arg.equals(ToolOption.DOCLET.opt)) { 407 oneArg(argv, i++); 408 if (docletClassName != null) { 409 usageError("main.more_than_one_doclet_specified_0_and_1", 410 docletClassName, argv[i]); 411 } 412 docletClassName = argv[i]; 413 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 414 oneArg(argv, i++); 415 if (docletPath == null) { 416 docletPath = argv[i]; 417 } else { 418 docletPath += File.pathSeparator + argv[i]; 419 } 420 } 421 } 422 423 if (docletClassName == null) { 424 docletClassName = defaultDocletClassName; 425 } 426 427 // attempt to find doclet 428 docletInvoker = new DocletInvoker(messager, fileManager, 429 docletClassName, docletPath, 430 docletParentClassLoader, 431 apiMode); 432 } 433 434 /** 435 * Set one arg option. 436 * Error and exit if one argument is not provided. 437 */ oneArg(String[] args, int index)438 private void oneArg(String[] args, int index) { 439 if ((index + 1) < args.length) { 440 setOption(args[index], args[index+1]); 441 } else { 442 usageError("main.requires_argument", args[index]); 443 } 444 } 445 446 @Override usageError(String key, Object... args)447 void usageError(String key, Object... args) { 448 messager.error(Messager.NOPOS, key, args); 449 usage(true); 450 } 451 452 /** 453 * indicate an option with no arguments was given. 454 */ setOption(String opt)455 private void setOption(String opt) { 456 String[] option = { opt }; 457 options.append(option); 458 } 459 460 /** 461 * indicate an option with one argument was given. 462 */ setOption(String opt, String argument)463 private void setOption(String opt, String argument) { 464 String[] option = { opt, argument }; 465 options.append(option); 466 } 467 468 /** 469 * indicate an option with the specified list of arguments was given. 470 */ setOption(String opt, List<String> arguments)471 private void setOption(String opt, List<String> arguments) { 472 String[] args = new String[arguments.length() + 1]; 473 int k = 0; 474 args[k++] = opt; 475 for (List<String> i = arguments; i.nonEmpty(); i=i.tail) { 476 args[k++] = i.head; 477 } 478 options.append(args); 479 } 480 } 481