Start.java revision 3595:81692f730015
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 26package jdk.javadoc.internal.tool; 27 28import java.io.File; 29import java.io.FileNotFoundException; 30import java.io.IOException; 31import java.io.PrintWriter; 32import java.nio.file.Path; 33import java.text.BreakIterator; 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.Collection; 37import java.util.Collections; 38import java.util.List; 39import java.util.Locale; 40import java.util.Objects; 41import java.util.Set; 42 43import javax.tools.JavaFileManager; 44import javax.tools.JavaFileObject; 45import javax.tools.StandardJavaFileManager; 46import javax.tools.StandardLocation; 47 48import com.sun.tools.javac.api.JavacTrees; 49import com.sun.tools.javac.file.BaseFileManager; 50import com.sun.tools.javac.file.JavacFileManager; 51import com.sun.tools.javac.main.CommandLine; 52import com.sun.tools.javac.main.OptionHelper; 53import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 54import com.sun.tools.javac.platform.PlatformDescription; 55import com.sun.tools.javac.platform.PlatformUtils; 56import com.sun.tools.javac.util.ClientCodeException; 57import com.sun.tools.javac.util.Context; 58import com.sun.tools.javac.util.Log; 59import com.sun.tools.javac.util.Options; 60 61import jdk.javadoc.doclet.Doclet; 62import jdk.javadoc.doclet.Doclet.Option; 63import jdk.javadoc.doclet.DocletEnvironment; 64 65import static javax.tools.DocumentationTool.Location.*; 66 67import static com.sun.tools.javac.main.Option.*; 68 69/** 70 * Main program of Javadoc. 71 * Previously named "Main". 72 * 73 * <p><b>This is NOT part of any supported API. 74 * If you write code that depends on this, you do so at your own risk. 75 * This code and its internal interfaces are subject to change or 76 * deletion without notice.</b> 77 * 78 * @author Robert Field 79 * @author Neal Gafter (rewrite) 80 */ 81public class Start extends ToolOption.Helper { 82 83 @SuppressWarnings("deprecation") 84 private static final Class<?> OldStdDoclet = 85 com.sun.tools.doclets.standard.Standard.class; 86 87 private static final Class<?> StdDoclet = 88 jdk.javadoc.doclets.StandardDoclet.class; 89 /** Context for this invocation. */ 90 private final Context context; 91 92 private static final String ProgramName = "javadoc"; 93 94 private Messager messager; 95 96 private final String docletName; 97 98 private final ClassLoader classLoader; 99 100 private Class<?> docletClass; 101 102 private Doclet doclet; 103 104 // used to determine the locale for the messager 105 private Locale locale; 106 107 108 /** 109 * In API mode, exceptions thrown while calling the doclet are 110 * propagated using ClientCodeException. 111 */ 112 private boolean apiMode; 113 114 private JavaFileManager fileManager; 115 116 Start() { 117 this(null, null, null, null, null); 118 } 119 120 Start(PrintWriter writer) { 121 this(null, null, writer, null, null); 122 } 123 124 Start(Context context, String programName, PrintWriter writer, 125 String docletName, ClassLoader classLoader) { 126 this.context = context == null ? new Context() : context; 127 String pname = programName == null ? ProgramName : programName; 128 this.messager = writer == null 129 ? new Messager(this.context, pname) 130 : new Messager(this.context, pname, writer, writer); 131 this.docletName = docletName; 132 this.classLoader = classLoader; 133 this.docletClass = null; 134 this.locale = Locale.getDefault(); 135 } 136 137 public Start(Context context) { 138 this.docletClass = null; 139 this.context = Objects.requireNonNull(context); 140 this.apiMode = true; 141 this.docletName = null; 142 this.classLoader = null; 143 this.locale = Locale.getDefault(); 144 } 145 146 void initMessager() { 147 if (!apiMode) 148 return; 149 if (messager == null) { 150 Log log = context.get(Log.logKey); 151 if (log instanceof Messager) { 152 messager = (Messager) log; 153 } else { 154 PrintWriter out = context.get(Log.errKey); 155 messager = (out == null) 156 ? new Messager(context, ProgramName) 157 : new Messager(context, ProgramName, out, out); 158 } 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", "main.usage.foot", 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 option, String foot, boolean exit) { 184 messager.notice(main); 185 // let doclet print usage information (does nothing on error) 186 if (docletClass != null) { 187 String name = doclet.getName(); 188 Set<Option> supportedOptions = doclet.getSupportedOptions(); 189 messager.notice("main.doclet.usage.header", name); 190 Option.Kind myKind = option.equals("-X") 191 ? Option.Kind.EXTENDED 192 : Option.Kind.STANDARD; 193 supportedOptions.stream() 194 .filter(opt -> opt.getKind() == myKind) 195 .forEach(opt -> messager.printNotice(opt.toString())); 196 } 197 if (foot != null) 198 messager.notice(foot); 199 200 if (exit) 201 throw new Messager.ExitJavadoc(); 202 } 203 204 205 /** 206 * Main program - external wrapper. In order to maintain backward 207 * CLI compatibility, we dispatch to the old tool or the old doclet's 208 * Start mechanism, based on the options present on the command line 209 * with the following precedence: 210 * 1. presence of -Xold, dispatch to old tool 211 * 2. doclet variant, if old, dispatch to old Start 212 * 3. taglet variant, if old, dispatch to old Start 213 * 214 * Thus the presence of -Xold switches the tool, soon after command files 215 * if any, are expanded, this is performed here, noting that the messager 216 * is available at this point in time. 217 * The doclet/taglet tests are performed in the begin method, further on, 218 * this is to minimize argument processing and most importantly the impact 219 * of class loader creation, needed to detect the doclet/taglet class variants. 220 */ 221 @SuppressWarnings("deprecation") 222 int begin(String... argv) { 223 // Preprocess @file arguments 224 try { 225 argv = CommandLine.parse(argv); 226 } catch (FileNotFoundException e) { 227 messager.error("main.cant.read", e.getMessage()); 228 throw new Messager.ExitJavadoc(); 229 } catch (IOException e) { 230 e.printStackTrace(System.err); 231 throw new Messager.ExitJavadoc(); 232 } 233 234 if (argv.length > 0 && "-Xold".equals(argv[0])) { 235 messager.warning("main.legacy_api"); 236 String[] nargv = Arrays.copyOfRange(argv, 1, argv.length); 237 return com.sun.tools.javadoc.Main.execute(nargv); 238 } 239 boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet()); 240 return ok ? 0 : 1; 241 } 242 243 // Called by 199 API. 244 public boolean begin(Class<?> docletClass, 245 Iterable<String> options, 246 Iterable<? extends JavaFileObject> fileObjects) { 247 this.docletClass = docletClass; 248 List<String> opts = new ArrayList<>(); 249 for (String opt: options) 250 opts.add(opt); 251 252 return begin(opts, fileObjects); 253 } 254 255 @SuppressWarnings("deprecation") 256 private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { 257 fileManager = context.get(JavaFileManager.class); 258 if (fileManager == null) { 259 JavacFileManager.preRegister(context); 260 fileManager = context.get(JavaFileManager.class); 261 if (fileManager instanceof BaseFileManager) { 262 ((BaseFileManager) fileManager).autoClose = true; 263 } 264 } 265 // locale, doclet and maybe taglet, needs to be determined first 266 docletClass = preProcess(fileManager, options); 267 if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { 268 // no need to dispatch to old, safe to init now 269 initMessager(); 270 messager.setLocale(locale); 271 try { 272 Object o = docletClass.getConstructor().newInstance(); 273 doclet = (Doclet) o; 274 } catch (ReflectiveOperationException exc) { 275 exc.printStackTrace(); 276 if (!apiMode) { 277 error("main.could_not_instantiate_class", docletClass); 278 throw new Messager.ExitJavadoc(); 279 } 280 throw new ClientCodeException(exc); 281 } 282 } else { 283 if (this.apiMode) { 284 com.sun.tools.javadoc.main.Start ostart 285 = new com.sun.tools.javadoc.main.Start(context); 286 return ostart.begin(docletClass, options, fileObjects); 287 } 288 warn("main.legacy_api"); 289 String[] array = options.toArray(new String[options.size()]); 290 return com.sun.tools.javadoc.Main.execute(array) == 0; 291 } 292 293 boolean failed = false; 294 try { 295 failed = !parseAndExecute(options, fileObjects); 296 } catch (Messager.ExitJavadoc exc) { 297 // ignore, we just exit this way 298 } catch (OutOfMemoryError ee) { 299 messager.error("main.out.of.memory"); 300 failed = true; 301 } catch (ClientCodeException e) { 302 // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl 303 throw e; 304 } catch (Error ee) { 305 ee.printStackTrace(System.err); 306 messager.error("main.fatal.error"); 307 failed = true; 308 } catch (Exception ee) { 309 ee.printStackTrace(System.err); 310 messager.error("main.fatal.exception"); 311 failed = true; 312 } finally { 313 if (fileManager != null 314 && fileManager instanceof BaseFileManager 315 && ((BaseFileManager) fileManager).autoClose) { 316 try { 317 fileManager.close(); 318 } catch (IOException ignore) {} 319 } 320 boolean haveErrorWarnings = messager.nerrors() > 0 || 321 (rejectWarnings && messager.nwarnings() > 0); 322 if (failed && !haveErrorWarnings) { 323 // the doclet failed, but nothing reported, flag it!. 324 messager.error("main.unknown.error"); 325 } 326 failed |= haveErrorWarnings; 327 messager.exitNotice(); 328 messager.flush(); 329 } 330 return !failed; 331 } 332 333 /** 334 * Main program - internal 335 */ 336 @SuppressWarnings("unchecked") 337 private boolean parseAndExecute(List<String> argList, 338 Iterable<? extends JavaFileObject> fileObjects) throws IOException { 339 long tm = System.currentTimeMillis(); 340 341 List<String> javaNames = new ArrayList<>(); 342 343 compOpts = Options.instance(context); 344 345 // Make sure no obsolete source/target messages are reported 346 com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options"); 347 348 doclet.init(locale, messager); 349 parseArgs(argList, javaNames); 350 351 if (fileManager instanceof BaseFileManager) { 352 ((BaseFileManager) fileManager).handleOptions(fileManagerOpts); 353 } 354 355 String platformString = compOpts.get("--release"); 356 357 if (platformString != null) { 358 if (compOpts.isSet("-source")) { 359 usageError("main.release.bootclasspath.conflict", "-source"); 360 } 361 if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) { 362 usageError("main.release.bootclasspath.conflict", BOOT_CLASS_PATH.getPrimaryName()); 363 } 364 365 PlatformDescription platformDescription = 366 PlatformUtils.lookupPlatformDescription(platformString); 367 368 if (platformDescription == null) { 369 usageError("main.unsupported.release.version", platformString); 370 } 371 372 compOpts.put(SOURCE, platformDescription.getSourceVersion()); 373 374 context.put(PlatformDescription.class, platformDescription); 375 376 Collection<Path> platformCP = platformDescription.getPlatformPath(); 377 378 if (platformCP != null) { 379 if (fileManager instanceof StandardJavaFileManager) { 380 StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; 381 382 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 383 } else { 384 usageError("main.release.not.standard.file.manager", platformString); 385 } 386 } 387 } 388 389 compOpts.notifyListeners(); 390 List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE, 391 s -> Collections.EMPTY_LIST); 392 393 if (modules.isEmpty()) { 394 List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES, 395 s -> Collections.EMPTY_LIST); 396 if (subpkgs.isEmpty()) { 397 if (javaNames.isEmpty() && isEmpty(fileObjects)) { 398 usageError("main.No_modules_packages_or_classes_specified"); 399 } 400 } 401 } 402 403 JavadocTool comp = JavadocTool.make0(context); 404 if (comp == null) return false; 405 406 DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts, 407 javaNames, 408 fileObjects); 409 410 // release resources 411 comp = null; 412 413 if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { 414 JavacTrees trees = JavacTrees.instance(context); 415 trees.setBreakIterator(BreakIterator.getSentenceInstance(locale)); 416 } 417 // pass off control to the doclet 418 boolean ok = docEnv != null; 419 if (ok) ok = doclet.run(docEnv); 420 421 // We're done. 422 if (compOpts.get("-verbose") != null) { 423 tm = System.currentTimeMillis() - tm; 424 messager.notice("main.done_in", Long.toString(tm)); 425 } 426 427 return ok; 428 } 429 430 Set<Doclet.Option> docletOptions = null; 431 int handleDocletOptions(int idx, List<String> args, boolean isToolOption) { 432 if (docletOptions == null) { 433 docletOptions = doclet.getSupportedOptions(); 434 } 435 String arg = args.get(idx); 436 437 for (Doclet.Option opt : docletOptions) { 438 if (opt.matches(arg)) { 439 if (args.size() - idx < opt.getArgumentCount()) { 440 usageError("main.requires_argument", arg); 441 } 442 opt.process(arg, args.listIterator(idx + 1)); 443 idx += opt.getArgumentCount(); 444 return idx; 445 } 446 } 447 // check if arg is accepted by the tool before emitting error 448 if (!isToolOption) 449 usageError("main.invalid_flag", arg); 450 return idx; 451 } 452 453 private Class<?> preProcess(JavaFileManager jfm, List<String> argv) { 454 // doclet specifying arguments 455 String userDocletPath = null; 456 String userDocletName = null; 457 458 // taglet specifying arguments, since tagletpath is a doclet 459 // functionality, assume they are repeated and inspect all. 460 List<File> userTagletPath = new ArrayList<>(); 461 List<String> userTagletNames = new ArrayList<>(); 462 463 // Step 1: loop through the args, set locale early on, if found. 464 for (int i = 0 ; i < argv.size() ; i++) { 465 String arg = argv.get(i); 466 if (arg.equals(ToolOption.LOCALE.opt)) { 467 checkOneArg(argv, i++); 468 String lname = argv.get(i); 469 locale = getLocale(lname); 470 } else if (arg.equals(ToolOption.DOCLET.opt)) { 471 checkOneArg(argv, i++); 472 if (userDocletName != null) { 473 usageError("main.more_than_one_doclet_specified_0_and_1", 474 userDocletName, argv.get(i)); 475 } 476 if (docletName != null) { 477 usageError("main.more_than_one_doclet_specified_0_and_1", 478 docletName, argv.get(i)); 479 } 480 userDocletName = argv.get(i); 481 } else if (arg.equals(ToolOption.DOCLETPATH.opt)) { 482 checkOneArg(argv, i++); 483 if (userDocletPath == null) { 484 userDocletPath = argv.get(i); 485 } else { 486 userDocletPath += File.pathSeparator + argv.get(i); 487 } 488 } else if ("-taglet".equals(arg)) { 489 userTagletNames.add(argv.get(i + 1)); 490 } else if ("-tagletpath".equals(arg)) { 491 for (String pathname : argv.get(i + 1).split(File.pathSeparator)) { 492 userTagletPath.add(new File(pathname)); 493 } 494 } 495 } 496 497 // Step 2: a doclet is provided, nothing more to do. 498 if (docletClass != null) { 499 return docletClass; 500 } 501 502 // Step 3: doclet name specified ? if so find a ClassLoader, 503 // and load it. 504 if (userDocletName != null) { 505 ClassLoader cl = classLoader; 506 if (cl == null) { 507 if (!fileManager.hasLocation(DOCLET_PATH)) { 508 List<File> paths = new ArrayList<>(); 509 if (userDocletPath != null) { 510 for (String pathname : userDocletPath.split(File.pathSeparator)) { 511 paths.add(new File(pathname)); 512 } 513 } 514 try { 515 ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); 516 } catch (IOException ioe) { 517 error("main.doclet_could_not_set_location", paths); 518 throw new Messager.ExitJavadoc(); 519 } 520 } 521 cl = fileManager.getClassLoader(DOCLET_PATH); 522 if (cl == null) { 523 // despite doclet specified on cmdline no classloader found! 524 error("main.doclet_no_classloader_found", userDocletName); 525 throw new Messager.ExitJavadoc(); 526 } 527 } 528 try { 529 Class<?> klass = cl.loadClass(userDocletName); 530 return klass; 531 } catch (ClassNotFoundException cnfe) { 532 error("main.doclet_class_not_found", userDocletName); 533 throw new Messager.ExitJavadoc(); 534 } 535 } 536 537 // Step 4: we have a doclet, try loading it 538 if (docletName != null) { 539 try { 540 return Class.forName(docletName, true, getClass().getClassLoader()); 541 } catch (ClassNotFoundException cnfe) { 542 error("main.doclet_class_not_found", userDocletName); 543 throw new Messager.ExitJavadoc(); 544 } 545 } 546 547 // Step 5: we don't have a doclet specified, do we have taglets ? 548 if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) { 549 // found a bogey, return the old doclet 550 return OldStdDoclet; 551 } 552 553 // finally 554 return StdDoclet; 555 } 556 557 /* 558 * This method returns true iff it finds a legacy taglet, but for 559 * all other conditions including errors it returns false, allowing 560 * nature to take its own course. 561 */ 562 @SuppressWarnings("deprecation") 563 private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) { 564 if (!fileManager.hasLocation(TAGLET_PATH)) { 565 try { 566 ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths); 567 } catch (IOException ioe) { 568 error("main.doclet_could_not_set_location", tagletPaths); 569 throw new Messager.ExitJavadoc(); 570 } 571 } 572 ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH); 573 if (cl == null) { 574 // no classloader found! 575 error("main.doclet_no_classloader_found", tagletNames.get(0)); 576 throw new Messager.ExitJavadoc(); 577 } 578 for (String tagletName : tagletNames) { 579 try { 580 Class<?> klass = cl.loadClass(tagletName); 581 if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) { 582 return true; 583 } 584 } catch (ClassNotFoundException cnfe) { 585 error("main.doclet_class_not_found", tagletName); 586 throw new Messager.ExitJavadoc(); 587 } 588 } 589 return false; 590 } 591 592 private void parseArgs(List<String> args, List<String> javaNames) { 593 for (int i = 0 ; i < args.size() ; i++) { 594 String arg = args.get(i); 595 ToolOption o = ToolOption.get(arg); 596 if (o != null) { 597 // handle a doclet argument that may be needed however 598 // don't increment the index, and allow the tool to consume args 599 handleDocletOptions(i, args, true); 600 601 if (o.hasArg) { 602 checkOneArg(args, i++); 603 o.process(this, args.get(i)); 604 } else if (o.hasSuffix) { 605 o.process(this, arg); 606 } else { 607 o.process(this); 608 } 609 } else if (arg.startsWith("-XD")) { 610 // hidden javac options 611 String s = arg.substring("-XD".length()); 612 int eq = s.indexOf('='); 613 String key = (eq < 0) ? s : s.substring(0, eq); 614 String value = (eq < 0) ? s : s.substring(eq+1); 615 compOpts.put(key, value); 616 } else if (arg.startsWith("-")) { 617 i = handleDocletOptions(i, args, false); 618 } else { 619 javaNames.add(arg); 620 } 621 } 622 } 623 624 private <T> boolean isEmpty(Iterable<T> iter) { 625 return !iter.iterator().hasNext(); 626 } 627 628 /** 629 * Check the one arg option. 630 * Error and exit if one argument is not provided. 631 */ 632 private void checkOneArg(List<String> args, int index) { 633 if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) { 634 usageError("main.requires_argument", args.get(index)); 635 } 636 } 637 638 @Override 639 void usageError(String key, Object... args) { 640 error(key, args); 641 usage(true); 642 } 643 644 void error(String key, Object... args) { 645 messager.error(key, args); 646 } 647 648 void warn(String key, Object... args) { 649 messager.warning(key, args); 650 } 651 652 /** 653 * Get the locale if specified on the command line 654 * else return null and if locale option is not used 655 * then return default locale. 656 */ 657 private Locale getLocale(String localeName) { 658 Locale userlocale = null; 659 if (localeName == null || localeName.isEmpty()) { 660 return Locale.getDefault(); 661 } 662 int firstuscore = localeName.indexOf('_'); 663 int seconduscore = -1; 664 String language = null; 665 String country = null; 666 String variant = null; 667 if (firstuscore == 2) { 668 language = localeName.substring(0, firstuscore); 669 seconduscore = localeName.indexOf('_', firstuscore + 1); 670 if (seconduscore > 0) { 671 if (seconduscore != firstuscore + 3 672 || localeName.length() <= seconduscore + 1) { 673 usageError("main.malformed_locale_name", localeName); 674 return null; 675 } 676 country = localeName.substring(firstuscore + 1, 677 seconduscore); 678 variant = localeName.substring(seconduscore + 1); 679 } else if (localeName.length() == firstuscore + 3) { 680 country = localeName.substring(firstuscore + 1); 681 } else { 682 usageError("main.malformed_locale_name", localeName); 683 return null; 684 } 685 } else if (firstuscore == -1 && localeName.length() == 2) { 686 language = localeName; 687 } else { 688 usageError("main.malformed_locale_name", localeName); 689 return null; 690 } 691 userlocale = searchLocale(language, country, variant); 692 if (userlocale == null) { 693 usageError("main.illegal_locale_name", localeName); 694 return null; 695 } else { 696 return userlocale; 697 } 698 } 699 700 /** 701 * Search the locale for specified language, specified country and 702 * specified variant. 703 */ 704 private Locale searchLocale(String language, String country, 705 String variant) { 706 for (Locale loc : Locale.getAvailableLocales()) { 707 if (loc.getLanguage().equals(language) && 708 (country == null || loc.getCountry().equals(country)) && 709 (variant == null || loc.getVariant().equals(variant))) { 710 return loc; 711 } 712 } 713 return null; 714 } 715 716 @Override 717 OptionHelper getOptionHelper() { 718 return new GrumpyHelper(null) { 719 @Override 720 public String get(com.sun.tools.javac.main.Option option) { 721 return compOpts.get(option); 722 } 723 724 @Override 725 public void put(String name, String value) { 726 compOpts.put(name, value); 727 } 728 }; 729 } 730} 731