Arguments.java revision 3151:9006c168c651
1/* 2 * Copyright (c) 1999, 2015, 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 if (!checkDirectory(Option.H)) { 421 return false; 422 } 423 424 String sourceString = options.get(Option.SOURCE); 425 Source source = (sourceString != null) 426 ? Source.lookup(sourceString) 427 : Source.DEFAULT; 428 String targetString = options.get(Option.TARGET); 429 Target target = (targetString != null) 430 ? Target.lookup(targetString) 431 : Target.DEFAULT; 432 433 // We don't check source/target consistency for CLDC, as J2ME 434 // profiles are not aligned with J2SE targets; moreover, a 435 // single CLDC target may have many profiles. In addition, 436 // this is needed for the continued functioning of the JSR14 437 // prototype. 438 if (Character.isDigit(target.name.charAt(0))) { 439 if (target.compareTo(source.requiredTarget()) < 0) { 440 if (targetString != null) { 441 if (sourceString == null) { 442 error("warn.target.default.source.conflict", 443 targetString, 444 source.requiredTarget().name); 445 } else { 446 error("warn.source.target.conflict", 447 sourceString, 448 source.requiredTarget().name); 449 } 450 return false; 451 } else { 452 target = source.requiredTarget(); 453 options.put("-target", target.name); 454 } 455 } 456 } 457 458 String profileString = options.get(Option.PROFILE); 459 if (profileString != null) { 460 Profile profile = Profile.lookup(profileString); 461 if (!profile.isValid(target)) { 462 error("warn.profile.target.conflict", profileString, target.name); 463 } 464 465 // This check is only effective in command line mode, 466 // where the file manager options are added to options 467 if (options.get(Option.BOOTCLASSPATH) != null) { 468 error("err.profile.bootclasspath.conflict"); 469 } 470 } 471 472 boolean lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option); 473 474 if (lintOptions && source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) { 475 JavaFileManager fm = getFileManager(); 476 if (fm instanceof BaseFileManager) { 477 if (((BaseFileManager) fm).isDefaultBootClassPath()) 478 log.warning(LintCategory.OPTIONS, "source.no.bootclasspath", source.name); 479 } 480 } 481 482 boolean obsoleteOptionFound = false; 483 484 if (source.compareTo(Source.MIN) < 0) { 485 log.error("option.removed.source", source.name, Source.MIN.name); 486 } else if (source == Source.MIN && lintOptions) { 487 log.warning(LintCategory.OPTIONS, "option.obsolete.source", source.name); 488 obsoleteOptionFound = true; 489 } 490 491 if (target.compareTo(Target.MIN) < 0) { 492 log.error("option.removed.target", target.name, Target.MIN.name); 493 } else if (target == Target.MIN && lintOptions) { 494 log.warning(LintCategory.OPTIONS, "option.obsolete.target", target.name); 495 obsoleteOptionFound = true; 496 } 497 498 if (obsoleteOptionFound) 499 log.warning(LintCategory.OPTIONS, "option.obsolete.suppression"); 500 501 return !errors; 502 } 503 504 /** 505 * Returns true if there are no files or classes specified for use. 506 * @return true if there are no files or classes specified for use 507 */ 508 public boolean isEmpty() { 509 return ((files == null) || files.isEmpty()) 510 && ((fileObjects == null) || fileObjects.isEmpty()) 511 && classNames.isEmpty(); 512 } 513 514 /** 515 * Gets the file manager options which may have been deferred 516 * during processArgs. 517 * @return the deferred file manager options 518 */ 519 public Map<Option, String> getDeferredFileManagerOptions() { 520 return deferredFileManagerOptions; 521 } 522 523 /** 524 * Gets any options specifying plugins to be run. 525 * @return options for plugins 526 */ 527 public Set<List<String>> getPluginOpts() { 528 String plugins = options.get(Option.PLUGIN); 529 if (plugins == null) 530 return Collections.emptySet(); 531 532 Set<List<String>> pluginOpts = new LinkedHashSet<>(); 533 for (String plugin: plugins.split("\\x00")) { 534 pluginOpts.add(List.from(plugin.split("\\s+"))); 535 } 536 return Collections.unmodifiableSet(pluginOpts); 537 } 538 539 /** 540 * Gets any options specifying how doclint should be run. 541 * An empty list is returned if no doclint options are specified 542 * or if the only doclint option is -Xdoclint:none. 543 * @return options for doclint 544 */ 545 public List<String> getDocLintOpts() { 546 String xdoclint = options.get(Option.XDOCLINT); 547 String xdoclintCustom = options.get(Option.XDOCLINT_CUSTOM); 548 if (xdoclint == null && xdoclintCustom == null) 549 return List.nil(); 550 551 Set<String> doclintOpts = new LinkedHashSet<>(); 552 if (xdoclint != null) 553 doclintOpts.add(DocLint.XMSGS_OPTION); 554 if (xdoclintCustom != null) { 555 for (String s: xdoclintCustom.split("\\s+")) { 556 if (s.isEmpty()) 557 continue; 558 doclintOpts.add(s.replace(Option.XDOCLINT_CUSTOM.text, DocLint.XMSGS_CUSTOM_PREFIX)); 559 } 560 } 561 562 if (doclintOpts.equals(Collections.singleton(DocLint.XMSGS_CUSTOM_PREFIX + "none"))) 563 return List.nil(); 564 565 String checkPackages = options.get(Option.XDOCLINT_PACKAGE); 566 567 if (checkPackages != null) { 568 for (String s : checkPackages.split("\\s+")) { 569 doclintOpts.add(s.replace(Option.XDOCLINT_PACKAGE.text, DocLint.XCHECK_PACKAGE)); 570 } 571 } 572 573 // standard doclet normally generates H1, H2, 574 // so for now, allow user comments to assume that 575 doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2"); 576 577 return List.from(doclintOpts.toArray(new String[doclintOpts.size()])); 578 } 579 580 private boolean checkDirectory(Option option) { 581 String value = options.get(option); 582 if (value == null) { 583 return true; 584 } 585 File file = new File(value); 586 if (file.exists() && !file.isDirectory()) { 587 error("err.file.not.directory", value); 588 return false; 589 } 590 return true; 591 } 592 593 private interface ErrorReporter { 594 void report(Option o); 595 } 596 597 void checkOptionAllowed(boolean allowed, ErrorReporter r, Option... opts) { 598 if (!allowed) { 599 Stream.of(opts) 600 .filter(options :: isSet) 601 .forEach(r :: report); 602 } 603 } 604 605 void error(String key, Object... args) { 606 errors = true; 607 switch (errorMode) { 608 case ILLEGAL_ARGUMENT: { 609 String msg = log.localize(PrefixKind.JAVAC, key, args); 610 throw new PropagatedException(new IllegalArgumentException(msg)); 611 } 612 case ILLEGAL_STATE: { 613 String msg = log.localize(PrefixKind.JAVAC, key, args); 614 throw new PropagatedException(new IllegalStateException(msg)); 615 } 616 case LOG: 617 report(key, args); 618 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName); 619 } 620 } 621 622 void warning(String key, Object... args) { 623 report(key, args); 624 } 625 626 private void report(String key, Object... args) { 627 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args)); 628 } 629 630 private JavaFileManager getFileManager() { 631 if (fileManager == null) 632 fileManager = context.get(JavaFileManager.class); 633 return fileManager; 634 } 635 636 <T> ListBuffer<T> toList(Iterable<? extends T> items) { 637 ListBuffer<T> list = new ListBuffer<>(); 638 if (items != null) { 639 for (T item : items) { 640 list.add(item); 641 } 642 } 643 return list; 644 } 645 646 <T> Set<T> toSet(Iterable<? extends T> items) { 647 Set<T> set = new LinkedHashSet<>(); 648 if (items != null) { 649 for (T item : items) { 650 set.add(item); 651 } 652 } 653 return set; 654 } 655} 656