Arguments.java revision 3019:176472b94f2e
1/* 2 * Copyright (c) 1999, 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 */ 25package com.sun.tools.javac.main; 26 27import java.io.File; 28import java.io.IOException; 29import java.nio.file.Path; 30import java.util.Collection; 31import java.util.Collections; 32import java.util.Iterator; 33import java.util.LinkedHashMap; 34import java.util.LinkedHashSet; 35import java.util.Map; 36import java.util.Set; 37import java.util.stream.Stream; 38 39import javax.tools.JavaFileManager; 40import javax.tools.JavaFileObject; 41import javax.tools.StandardJavaFileManager; 42import javax.tools.StandardLocation; 43 44import com.sun.tools.doclint.DocLint; 45import com.sun.tools.javac.code.Lint.LintCategory; 46import com.sun.tools.javac.code.Source; 47import com.sun.tools.javac.file.BaseFileManager; 48import com.sun.tools.javac.file.JavacFileManager; 49import com.sun.tools.javac.jvm.Profile; 50import com.sun.tools.javac.jvm.Target; 51import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 52import com.sun.tools.javac.platform.PlatformDescription; 53import com.sun.tools.javac.platform.PlatformUtils; 54import com.sun.tools.javac.util.Context; 55import com.sun.tools.javac.util.List; 56import com.sun.tools.javac.util.ListBuffer; 57import com.sun.tools.javac.util.Log; 58import com.sun.tools.javac.util.Log.PrefixKind; 59import com.sun.tools.javac.util.Log.WriterKind; 60import com.sun.tools.javac.util.Options; 61import com.sun.tools.javac.util.PropagatedException; 62 63/** 64 * Shared option and argument handling for command line and API usage of javac. 65 */ 66public class Arguments { 67 68 /** 69 * The context key for the arguments. 70 */ 71 public static final Context.Key<Arguments> argsKey = new Context.Key<>(); 72 73 private String ownName; 74 private Set<String> classNames; 75 private Set<File> files; 76 private Map<Option, String> deferredFileManagerOptions; 77 private Set<JavaFileObject> fileObjects; 78 private final Options options; 79 80 private JavaFileManager fileManager; 81 private final Log log; 82 private final Context context; 83 84 private enum ErrorMode { ILLEGAL_ARGUMENT, ILLEGAL_STATE, LOG }; 85 private ErrorMode errorMode; 86 private boolean errors; 87 88 /** 89 * Gets the Arguments instance for this context. 90 * 91 * @param context the content 92 * @return the Arguments instance for this context. 93 */ 94 public static Arguments instance(Context context) { 95 Arguments instance = context.get(argsKey); 96 if (instance == null) { 97 instance = new Arguments(context); 98 } 99 return instance; 100 } 101 102 protected Arguments(Context context) { 103 context.put(argsKey, this); 104 options = Options.instance(context); 105 log = Log.instance(context); 106 this.context = context; 107 108 // Ideally, we could init this here and update/configure it as 109 // needed, but right now, initializing a file manager triggers 110 // initialization of other items in the context, such as Lint 111 // and FSInfo, which should not be initialized until after 112 // processArgs 113 // fileManager = context.get(JavaFileManager.class); 114 } 115 116 private final OptionHelper cmdLineHelper = new OptionHelper() { 117 @Override 118 public String get(Option option) { 119 return options.get(option); 120 } 121 122 @Override 123 public void put(String name, String value) { 124 options.put(name, value); 125 } 126 127 @Override 128 public void remove(String name) { 129 options.remove(name); 130 } 131 132 @Override 133 public boolean handleFileManagerOption(Option option, String value) { 134 options.put(option.getText(), value); 135 deferredFileManagerOptions.put(option, value); 136 return true; 137 } 138 139 @Override 140 public Log getLog() { 141 return log; 142 } 143 144 @Override 145 public String getOwnName() { 146 return ownName; 147 } 148 149 @Override 150 public void error(String key, Object... args) { 151 Arguments.this.error(key, args); 152 } 153 154 @Override 155 public void addFile(File f) { 156 files.add(f); 157 } 158 159 @Override 160 public void addClassName(String s) { 161 classNames.add(s); 162 } 163 164 }; 165 166 /** 167 * Initializes this Args instance with a set of command line args. 168 * The args will be processed in conjunction with the full set of 169 * command line options, including -help, -version etc. 170 * The args may also contain class names and filenames. 171 * Any errors during this call, and later during validate, will be reported 172 * to the log. 173 * @param ownName the name of this tool; used to prefix messages 174 * @param args the args to be processed 175 */ 176 public void init(String ownName, String... args) { 177 this.ownName = ownName; 178 errorMode = ErrorMode.LOG; 179 files = new LinkedHashSet<>(); 180 deferredFileManagerOptions = new LinkedHashMap<>(); 181 fileObjects = null; 182 classNames = new LinkedHashSet<>(); 183 processArgs(List.from(args), Option.getJavaCompilerOptions(), cmdLineHelper, true, false); 184 } 185 186 private final OptionHelper apiHelper = new GrumpyHelper(null) { 187 @Override 188 public String get(Option option) { 189 return options.get(option.getText()); 190 } 191 192 @Override 193 public void put(String name, String value) { 194 options.put(name, value); 195 } 196 197 @Override 198 public void remove(String name) { 199 options.remove(name); 200 } 201 202 @Override 203 public void error(String key, Object... args) { 204 Arguments.this.error(key, args); 205 } 206 207 @Override 208 public Log getLog() { 209 return Arguments.this.log; 210 } 211 }; 212 213 /** 214 * Initializes this Args instance with the parameters for a JavacTask. 215 * The options will be processed in conjunction with the restricted set 216 * of tool options, which does not include -help, -version, etc, 217 * nor does it include classes and filenames, which should be specified 218 * separately. 219 * File manager options are handled directly by the file manager. 220 * Any errors found while processing individual args will be reported 221 * via IllegalArgumentException. 222 * Any subsequent errors during validate will be reported via IllegalStateException. 223 * @param ownName the name of this tool; used to prefix messages 224 * @param options the options to be processed 225 * @param classNames the classes to be subject to annotation processing 226 * @param files the files to be compiled 227 */ 228 public void init(String ownName, 229 Iterable<String> options, 230 Iterable<String> classNames, 231 Iterable<? extends JavaFileObject> files) { 232 this.ownName = ownName; 233 this.classNames = toSet(classNames); 234 this.fileObjects = toSet(files); 235 this.files = null; 236 errorMode = ErrorMode.ILLEGAL_ARGUMENT; 237 if (options != null) { 238 processArgs(toList(options), Option.getJavacToolOptions(), apiHelper, false, true); 239 } 240 errorMode = ErrorMode.ILLEGAL_STATE; 241 } 242 243 /** 244 * Gets the files to be compiled. 245 * @return the files to be compiled 246 */ 247 public Set<JavaFileObject> getFileObjects() { 248 if (fileObjects == null) { 249 if (files == null) { 250 fileObjects = Collections.emptySet(); 251 } else { 252 fileObjects = new LinkedHashSet<>(); 253 JavacFileManager jfm = (JavacFileManager) getFileManager(); 254 for (JavaFileObject fo: jfm.getJavaFileObjectsFromFiles(files)) 255 fileObjects.add(fo); 256 } 257 } 258 return fileObjects; 259 } 260 261 /** 262 * Gets the classes to be subject to annotation processing. 263 * @return the classes to be subject to annotation processing 264 */ 265 public Set<String> getClassNames() { 266 return classNames; 267 } 268 269 /** 270 * Processes strings containing options and operands. 271 * @param args the strings to be processed 272 * @param allowableOpts the set of option declarations that are applicable 273 * @param helper a help for use by Option.process 274 * @param allowOperands whether or not to check for files and classes 275 * @param checkFileManager whether or not to check if the file manager can handle 276 * options which are not recognized by any of allowableOpts 277 * @return true if all the strings were successfully processed; false otherwise 278 * @throws IllegalArgumentException if a problem occurs and errorMode is set to 279 * ILLEGAL_ARGUMENT 280 */ 281 private boolean processArgs(Iterable<String> args, 282 Set<Option> allowableOpts, OptionHelper helper, 283 boolean allowOperands, boolean checkFileManager) { 284 if (!doProcessArgs(args, allowableOpts, helper, allowOperands, checkFileManager)) 285 return false; 286 287 String platformString = options.get(Option.RELEASE); 288 289 checkOptionAllowed(platformString == null, 290 option -> error("err.release.bootclasspath.conflict", option.getText()), 291 Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND, 292 Option.XBOOTCLASSPATH_PREPEND, Option.ENDORSEDDIRS, Option.EXTDIRS, Option.SOURCE, 293 Option.TARGET); 294 295 if (platformString != null) { 296 PlatformDescription platformDescription = PlatformUtils.lookupPlatformDescription(platformString); 297 298 if (platformDescription == null) { 299 error("err.unsupported.release.version", platformString); 300 return false; 301 } 302 303 options.put(Option.SOURCE, platformDescription.getSourceVersion()); 304 options.put(Option.TARGET, platformDescription.getTargetVersion()); 305 306 context.put(PlatformDescription.class, platformDescription); 307 308 if (!doProcessArgs(platformDescription.getAdditionalOptions(), allowableOpts, helper, allowOperands, checkFileManager)) 309 return false; 310 311 Collection<Path> platformCP = platformDescription.getPlatformPath(); 312 313 if (platformCP != null) { 314 JavaFileManager fm = getFileManager(); 315 316 if (!(fm instanceof StandardJavaFileManager)) { 317 error("err.release.not.standard.file.manager"); 318 return false; 319 } 320 321 try { 322 StandardJavaFileManager sfm = (StandardJavaFileManager) fm; 323 324 sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP); 325 } catch (IOException ex) { 326 log.printLines(PrefixKind.JAVAC, "msg.io"); 327 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 328 return false; 329 } 330 } 331 } 332 333 options.notifyListeners(); 334 335 return true; 336 } 337 338 private boolean doProcessArgs(Iterable<String> args, 339 Set<Option> allowableOpts, OptionHelper helper, 340 boolean allowOperands, boolean checkFileManager) { 341 JavaFileManager fm = checkFileManager ? getFileManager() : null; 342 Iterator<String> argIter = args.iterator(); 343 while (argIter.hasNext()) { 344 String arg = argIter.next(); 345 if (arg.isEmpty()) { 346 error("err.invalid.flag", arg); 347 return false; 348 } 349 350 Option option = null; 351 if (arg.startsWith("-")) { 352 for (Option o : allowableOpts) { 353 if (o.matches(arg)) { 354 option = o; 355 break; 356 } 357 } 358 } else if (allowOperands && Option.SOURCEFILE.matches(arg)) { 359 option = Option.SOURCEFILE; 360 } 361 362 if (option == null) { 363 if (fm != null && fm.handleOption(arg, argIter)) { 364 continue; 365 } 366 error("err.invalid.flag", arg); 367 return false; 368 } 369 370 if (option.hasArg()) { 371 if (!argIter.hasNext()) { 372 error("err.req.arg", arg); 373 return false; 374 } 375 String operand = argIter.next(); 376 if (option.process(helper, arg, operand)) { 377 return false; 378 } 379 } else { 380 if (option.process(helper, arg)) { 381 return false; 382 } 383 } 384 } 385 386 return true; 387 } 388 389 /** 390 * Validates the overall consistency of the options and operands 391 * processed by processOptions. 392 * @return true if all args are successfully validating; false otherwise. 393 * @throws IllegalStateException if a problem is found and errorMode is set to 394 * ILLEGAL_STATE 395 */ 396 public boolean validate() { 397 if (isEmpty()) { 398 // It is allowed to compile nothing if just asking for help or version info. 399 // But also note that none of these options are supported in API mode. 400 if (options.isSet(Option.HELP) 401 || options.isSet(Option.X) 402 || options.isSet(Option.VERSION) 403 || options.isSet(Option.FULLVERSION)) 404 return true; 405 406 if (JavaCompiler.explicitAnnotationProcessingRequested(options)) { 407 error("err.no.source.files.classes"); 408 } else { 409 error("err.no.source.files"); 410 } 411 return false; 412 } 413 414 if (!checkDirectory(Option.D)) { 415 return false; 416 } 417 if (!checkDirectory(Option.S)) { 418 return false; 419 } 420 421 String sourceString = options.get(Option.SOURCE); 422 Source source = (sourceString != null) 423 ? Source.lookup(sourceString) 424 : Source.DEFAULT; 425 String targetString = options.get(Option.TARGET); 426 Target target = (targetString != null) 427 ? Target.lookup(targetString) 428 : Target.DEFAULT; 429 430 // We don't check source/target consistency for CLDC, as J2ME 431 // profiles are not aligned with J2SE targets; moreover, a 432 // single CLDC target may have many profiles. In addition, 433 // this is needed for the continued functioning of the JSR14 434 // prototype. 435 if (Character.isDigit(target.name.charAt(0))) { 436 if (target.compareTo(source.requiredTarget()) < 0) { 437 if (targetString != null) { 438 if (sourceString == null) { 439 error("warn.target.default.source.conflict", 440 targetString, 441 source.requiredTarget().name); 442 } else { 443 error("warn.source.target.conflict", 444 sourceString, 445 source.requiredTarget().name); 446 } 447 return false; 448 } else { 449 target = source.requiredTarget(); 450 options.put("-target", target.name); 451 } 452 } 453 } 454 455 String profileString = options.get(Option.PROFILE); 456 if (profileString != null) { 457 Profile profile = Profile.lookup(profileString); 458 if (!profile.isValid(target)) { 459 error("warn.profile.target.conflict", profileString, target.name); 460 } 461 462 // This check is only effective in command line mode, 463 // where the file manager options are added to options 464 if (options.get(Option.BOOTCLASSPATH) != null) { 465 error("err.profile.bootclasspath.conflict"); 466 } 467 } 468 469 boolean lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option); 470 471 if (lintOptions && source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) { 472 JavaFileManager fm = getFileManager(); 473 if (fm instanceof BaseFileManager) { 474 if (((BaseFileManager) fm).isDefaultBootClassPath()) 475 log.warning(LintCategory.OPTIONS, "source.no.bootclasspath", source.name); 476 } 477 } 478 479 boolean obsoleteOptionFound = false; 480 481 if (source.compareTo(Source.MIN) < 0) { 482 log.error("option.removed.source", source.name, Source.MIN.name); 483 } else if (source == Source.MIN && lintOptions) { 484 log.warning(LintCategory.OPTIONS, "option.obsolete.source", source.name); 485 obsoleteOptionFound = true; 486 } 487 488 if (target.compareTo(Target.MIN) < 0) { 489 log.error("option.removed.target", target.name, Target.MIN.name); 490 } else if (target == Target.MIN && lintOptions) { 491 log.warning(LintCategory.OPTIONS, "option.obsolete.target", target.name); 492 obsoleteOptionFound = true; 493 } 494 495 if (obsoleteOptionFound) 496 log.warning(LintCategory.OPTIONS, "option.obsolete.suppression"); 497 498 return !errors; 499 } 500 501 /** 502 * Returns true if there are no files or classes specified for use. 503 * @return true if there are no files or classes specified for use 504 */ 505 public boolean isEmpty() { 506 return ((files == null) || files.isEmpty()) 507 && ((fileObjects == null) || fileObjects.isEmpty()) 508 && classNames.isEmpty(); 509 } 510 511 /** 512 * Gets the file manager options which may have been deferred 513 * during processArgs. 514 * @return the deferred file manager options 515 */ 516 public Map<Option, String> getDeferredFileManagerOptions() { 517 return deferredFileManagerOptions; 518 } 519 520 /** 521 * Gets any options specifying plugins to be run. 522 * @return options for plugins 523 */ 524 public Set<List<String>> getPluginOpts() { 525 String plugins = options.get(Option.PLUGIN); 526 if (plugins == null) 527 return Collections.emptySet(); 528 529 Set<List<String>> pluginOpts = new LinkedHashSet<>(); 530 for (String plugin: plugins.split("\\x00")) { 531 pluginOpts.add(List.from(plugin.split("\\s+"))); 532 } 533 return Collections.unmodifiableSet(pluginOpts); 534 } 535 536 /** 537 * Gets any options specifying how doclint should be run. 538 * An empty list is returned if no doclint options are specified 539 * or if the only doclint option is -Xdoclint:none. 540 * @return options for doclint 541 */ 542 public List<String> getDocLintOpts() { 543 String xdoclint = options.get(Option.XDOCLINT); 544 String xdoclintCustom = options.get(Option.XDOCLINT_CUSTOM); 545 if (xdoclint == null && xdoclintCustom == null) 546 return List.nil(); 547 548 Set<String> doclintOpts = new LinkedHashSet<>(); 549 if (xdoclint != null) 550 doclintOpts.add(DocLint.XMSGS_OPTION); 551 if (xdoclintCustom != null) { 552 for (String s: xdoclintCustom.split("\\s+")) { 553 if (s.isEmpty()) 554 continue; 555 doclintOpts.add(s.replace(Option.XDOCLINT_CUSTOM.text, DocLint.XMSGS_CUSTOM_PREFIX)); 556 } 557 } 558 559 if (doclintOpts.equals(Collections.singleton(DocLint.XMSGS_CUSTOM_PREFIX + "none"))) 560 return List.nil(); 561 562 String checkPackages = options.get(Option.XDOCLINT_PACKAGE); 563 564 if (checkPackages != null) { 565 for (String s : checkPackages.split("\\s+")) { 566 doclintOpts.add(s.replace(Option.XDOCLINT_PACKAGE.text, DocLint.XCHECK_PACKAGE)); 567 } 568 } 569 570 // standard doclet normally generates H1, H2, 571 // so for now, allow user comments to assume that 572 doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2"); 573 574 return List.from(doclintOpts.toArray(new String[doclintOpts.size()])); 575 } 576 577 private boolean checkDirectory(Option option) { 578 String value = options.get(option); 579 if (value == null) { 580 return true; 581 } 582 File file = new File(value); 583 if (!file.exists()) { 584 error("err.dir.not.found", value); 585 return false; 586 } 587 if (!file.isDirectory()) { 588 error("err.file.not.directory", value); 589 return false; 590 } 591 return true; 592 } 593 594 private interface ErrorReporter { 595 void report(Option o); 596 } 597 598 void checkOptionAllowed(boolean allowed, ErrorReporter r, Option... opts) { 599 if (!allowed) { 600 Stream.of(opts) 601 .filter(options :: isSet) 602 .forEach(r :: report); 603 } 604 } 605 606 void error(String key, Object... args) { 607 errors = true; 608 switch (errorMode) { 609 case ILLEGAL_ARGUMENT: { 610 String msg = log.localize(PrefixKind.JAVAC, key, args); 611 throw new PropagatedException(new IllegalArgumentException(msg)); 612 } 613 case ILLEGAL_STATE: { 614 String msg = log.localize(PrefixKind.JAVAC, key, args); 615 throw new PropagatedException(new IllegalStateException(msg)); 616 } 617 case LOG: 618 report(key, args); 619 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName); 620 } 621 } 622 623 void warning(String key, Object... args) { 624 report(key, args); 625 } 626 627 private void report(String key, Object... args) { 628 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args)); 629 } 630 631 private JavaFileManager getFileManager() { 632 if (fileManager == null) 633 fileManager = context.get(JavaFileManager.class); 634 return fileManager; 635 } 636 637 <T> ListBuffer<T> toList(Iterable<? extends T> items) { 638 ListBuffer<T> list = new ListBuffer<>(); 639 if (items != null) { 640 for (T item : items) { 641 list.add(item); 642 } 643 } 644 return list; 645 } 646 647 <T> Set<T> toSet(Iterable<? extends T> items) { 648 Set<T> set = new LinkedHashSet<>(); 649 if (items != null) { 650 for (T item : items) { 651 set.add(item); 652 } 653 } 654 return set; 655 } 656} 657