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