1 /*
2 * Copyright (c) 1999, 2025, 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.javac.main;
27
28 import java.io.IOException;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.EnumSet;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.LinkedHashMap;
38 import java.util.LinkedHashSet;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.function.Predicate;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import java.util.stream.Stream;
45
46 import javax.lang.model.SourceVersion;
47 import javax.tools.JavaFileManager;
48 import javax.tools.JavaFileManager.Location;
49 import javax.tools.JavaFileObject;
50 import javax.tools.JavaFileObject.Kind;
51 import javax.tools.StandardJavaFileManager;
52 import javax.tools.StandardLocation;
53
54 import com.sun.tools.doclint.DocLint;
55 import com.sun.tools.javac.code.Source;
56 import com.sun.tools.javac.file.BaseFileManager;
57 import com.sun.tools.javac.file.JavacFileManager;
58 import com.sun.tools.javac.jvm.Profile;
59 import com.sun.tools.javac.jvm.Target;
60 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
61 import com.sun.tools.javac.platform.PlatformDescription;
62 import com.sun.tools.javac.platform.PlatformUtils;
63 import com.sun.tools.javac.resources.CompilerProperties.Errors;
64 import com.sun.tools.javac.resources.CompilerProperties.Fragments;
65 import com.sun.tools.javac.resources.CompilerProperties.LintWarnings;
66 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
67 import com.sun.tools.javac.util.Context;
68 import com.sun.tools.javac.util.JCDiagnostic;
69 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticInfo;
70 import com.sun.tools.javac.util.JCDiagnostic.Fragment;
71 import com.sun.tools.javac.util.List;
72 import com.sun.tools.javac.util.ListBuffer;
73 import com.sun.tools.javac.util.Log;
74 import com.sun.tools.javac.util.Log.PrefixKind;
75 import com.sun.tools.javac.util.Log.WriterKind;
76 import com.sun.tools.javac.util.Options;
77 import com.sun.tools.javac.util.PropagatedException;
78
79 /**
80 * Shared option and argument handling for command line and API usage of javac.
81 */
82 public class Arguments {
83
84 /**
85 * The context key for the arguments.
86 */
87 public static final Context.Key<Arguments> argsKey = new Context.Key<>();
88
89 private String ownName;
90 private Set<String> classNames;
91 private Set<Path> files;
92 private Map<Option, String> deferredFileManagerOptions;
93 private Set<JavaFileObject> fileObjects;
94 private boolean emptyAllowed;
95 private final Options options;
96
97 private JavaFileManager fileManager;
98 private final Log log;
99 private final Context context;
100
101 private enum ErrorMode { ILLEGAL_ARGUMENT, ILLEGAL_STATE, LOG };
102 private ErrorMode errorMode;
103 private boolean errors;
104
105 /**
106 * Gets the Arguments instance for this context.
107 *
108 * @param context the content
109 * @return the Arguments instance for this context.
110 */
111 public static Arguments instance(Context context) {
112 Arguments instance = context.get(argsKey);
113 if (instance == null) {
114 instance = new Arguments(context);
115 }
116 return instance;
117 }
118
119 @SuppressWarnings("this-escape")
120 protected Arguments(Context context) {
121 context.put(argsKey, this);
122 options = Options.instance(context);
123 log = Log.instance(context);
124 this.context = context;
125
126 // Ideally, we could init this here and update/configure it as
127 // needed, but right now, initializing a file manager triggers
128 // initialization of other items in the context, such as Lint
129 // and FSInfo, which should not be initialized until after
130 // processArgs
131 // fileManager = context.get(JavaFileManager.class);
132 }
133
134 private final OptionHelper cmdLineHelper = new OptionHelper() {
135 @Override
136 public String get(Option option) {
137 return options.get(option);
138 }
139
140 @Override
141 public void put(String name, String value) {
142 options.put(name, value);
143 }
144
145 @Override
146 public void remove(String name) {
147 options.remove(name);
148 }
149
150 @Override
151 public boolean handleFileManagerOption(Option option, String value) {
152 options.put(option, value);
153 deferredFileManagerOptions.put(option, value);
154 return true;
155 }
156
157 @Override
158 public Log getLog() {
159 return log;
160 }
161
162 @Override
163 public String getOwnName() {
164 return ownName;
165 }
166
167 @Override
168 public void initialize() {
169 options.initialize();
170 }
171
172 @Override
173 public void addFile(Path p) {
174 files.add(p);
175 }
176
177 @Override
178 public void addClassName(String s) {
179 classNames.add(s);
180 }
181
182 };
183
184 /**
185 * Initializes this Args instance with a set of command line args.
186 * The args will be processed in conjunction with the full set of
187 * command line options, including -help, -version etc.
188 * The args may also contain class names and filenames.
189 * Any errors during this call, and later during validate, will be reported
190 * to the log.
191 * @param ownName the name of this tool; used to prefix messages
192 * @param args the args to be processed
193 */
194 public void init(String ownName, Iterable<String> args) {
195 this.ownName = ownName;
196 errorMode = ErrorMode.LOG;
197 files = new LinkedHashSet<>();
198 deferredFileManagerOptions = new LinkedHashMap<>();
199 fileObjects = null;
200 classNames = new LinkedHashSet<>();
201 processArgs(args, Option.getJavaCompilerOptions(), cmdLineHelper, true, false);
202 if (errors) {
203 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
204 }
205 }
206
207 private final OptionHelper apiHelper = new GrumpyHelper(null) {
208 @Override
209 public String get(Option option) {
210 return options.get(option);
211 }
212
213 @Override
214 public void put(String name, String value) {
215 options.put(name, value);
216 }
217
218 @Override
219 public void remove(String name) {
220 options.remove(name);
221 }
222
223 @Override
224 public Log getLog() {
225 return Arguments.this.log;
226 }
227
228 @Override
229 public void initialize() {
230 options.initialize();
231 }
232 };
233
234 /**
235 * Initializes this Args instance with the parameters for a JavacTask.
236 * The options will be processed in conjunction with the restricted set
237 * of tool options, which does not include -help, -version, etc,
238 * nor does it include classes and filenames, which should be specified
239 * separately.
240 * File manager options are handled directly by the file manager.
241 * Any errors found while processing individual args will be reported
242 * via IllegalArgumentException.
243 * Any subsequent errors during validate will be reported via IllegalStateException.
244 * @param ownName the name of this tool; used to prefix messages
245 * @param options the options to be processed
246 * @param classNames the classes to be subject to annotation processing
247 * @param files the files to be compiled
248 */
249 public void init(String ownName,
250 Iterable<String> options,
251 Iterable<String> classNames,
252 Iterable<? extends JavaFileObject> files) {
253 this.ownName = ownName;
254 this.classNames = toSet(classNames);
255 this.fileObjects = toSet(files);
256 this.files = null;
257 errorMode = ErrorMode.ILLEGAL_ARGUMENT;
258 if (options != null) {
259 processArgs(toList(options), Option.getJavacToolOptions(), apiHelper, false, true);
260 }
261 errorMode = ErrorMode.ILLEGAL_STATE;
262 }
263
264 /**
265 * Minimal initialization for tools, like javadoc,
266 * to be able to process javac options for themselves,
267 * and then call validate.
268 * @param ownName the name of this tool; used to prefix messages
269 */
270 public void init(String ownName) {
271 this.ownName = ownName;
272 errorMode = ErrorMode.LOG;
273 }
274
275 /**
276 * Gets the files to be compiled.
277 * @return the files to be compiled
278 */
279 public Set<JavaFileObject> getFileObjects() {
280 if (fileObjects == null) {
281 fileObjects = new LinkedHashSet<>();
282 }
283 if (files != null) {
284 JavacFileManager jfm = (JavacFileManager) getFileManager();
285 for (JavaFileObject fo: jfm.getJavaFileObjectsFromPaths(files))
286 fileObjects.add(fo);
287 }
288 return fileObjects;
289 }
290
291 /**
292 * Gets the classes to be subject to annotation processing.
293 * @return the classes to be subject to annotation processing
294 */
295 public Set<String> getClassNames() {
296 return classNames;
297 }
298
299 /**
300 * Handles the {@code --release} option.
301 *
302 * @param additionalOptions a predicate to handle additional options implied by the
303 * {@code --release} option. The predicate should return true if all the additional
304 * options were processed successfully.
305 * @return true if successful, false otherwise
306 */
307 public boolean handleReleaseOptions(Predicate<Iterable<String>> additionalOptions) {
308 String platformString = options.get(Option.RELEASE);
309
310 checkOptionAllowed(platformString == null,
311 option -> reportDiag(Errors.ReleaseBootclasspathConflict(option)),
312 Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
313 Option.XBOOTCLASSPATH_PREPEND,
314 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
315 Option.EXTDIRS, Option.DJAVA_EXT_DIRS,
316 Option.SOURCE, Option.TARGET,
317 Option.SYSTEM, Option.UPGRADE_MODULE_PATH);
318
319 if (platformString != null) {
320 PlatformDescription platformDescription =
321 PlatformUtils.lookupPlatformDescription(platformString);
322
323 if (platformDescription == null) {
324 reportDiag(Errors.UnsupportedReleaseVersion(platformString));
325 return false;
326 }
327
328 options.put(Option.SOURCE, platformDescription.getSourceVersion());
329 options.put(Option.TARGET, platformDescription.getTargetVersion());
330
331 context.put(PlatformDescription.class, platformDescription);
332
333 if (!additionalOptions.test(platformDescription.getAdditionalOptions()))
334 return false;
335
336 JavaFileManager platformFM = platformDescription.getFileManager();
337 DelegatingJavaFileManager.installReleaseFileManager(context,
338 platformFM,
339 getFileManager());
340 }
341
342 return true;
343 }
344
345 /**
346 * Processes strings containing options and operands.
347 * @param args the strings to be processed
348 * @param allowableOpts the set of option declarations that are applicable
349 * @param helper a help for use by Option.process
350 * @param allowOperands whether or not to check for files and classes
351 * @param checkFileManager whether or not to check if the file manager can handle
352 * options which are not recognized by any of allowableOpts
353 * @return true if all the strings were successfully processed; false otherwise
354 * @throws IllegalArgumentException if a problem occurs and errorMode is set to
355 * ILLEGAL_ARGUMENT
356 */
357 private boolean processArgs(Iterable<String> args,
358 Set<Option> allowableOpts, OptionHelper helper,
359 boolean allowOperands, boolean checkFileManager) {
360 if (!doProcessArgs(args, allowableOpts, helper, allowOperands, checkFileManager))
361 return false;
362
363 if (!handleReleaseOptions(extra -> doProcessArgs(extra, allowableOpts, helper, allowOperands, checkFileManager)))
364 return false;
365
366 options.notifyListeners();
367
368 return true;
369 }
370
371 private boolean doProcessArgs(Iterable<String> args,
372 Set<Option> allowableOpts, OptionHelper helper,
373 boolean allowOperands, boolean checkFileManager) {
374 JavaFileManager fm = checkFileManager ? getFileManager() : null;
375 Iterator<String> argIter = args.iterator();
376 while (argIter.hasNext()) {
377 String arg = argIter.next();
378 if (arg.isEmpty()) {
379 reportDiag(Errors.InvalidFlag(arg));
380 return false;
381 }
382
383 Option option = null;
384
385 // first, check the provided set of javac options
386 if (arg.startsWith("-")) {
387 option = Option.lookup(arg, allowableOpts);
388 } else if (allowOperands && Option.SOURCEFILE.matches(arg)) {
389 option = Option.SOURCEFILE;
390 }
391
392 if (option != null) {
393 try {
394 option.handleOption(helper, arg, argIter);
395 } catch (Option.InvalidValueException e) {
396 error(e);
397 return false;
398 }
399 continue;
400 }
401
402 // check file manager option
403 if (fm != null && fm.handleOption(arg, argIter)) {
404 continue;
405 }
406
407 // none of the above
408 reportDiag(Errors.InvalidFlag(arg));
409 return false;
410 }
411
412 return true;
413 }
414
415 /**
416 * Validates the overall consistency of the options and operands
417 * processed by processOptions.
418 * @return true if all args are successfully validated; false otherwise.
419 * @throws IllegalStateException if a problem is found and errorMode is set to
420 * ILLEGAL_STATE
421 */
422 public boolean validate() {
423 JavaFileManager fm = getFileManager();
424 if (options.isSet(Option.MODULE)) {
425 if (!fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
426 log.error(Errors.OutputDirMustBeSpecifiedWithDashMOption);
427 } else if (!fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
428 log.error(Errors.ModulesourcepathMustBeSpecifiedWithDashMOption);
429 } else {
430 java.util.List<String> modules = Arrays.asList(options.get(Option.MODULE).split(","));
431 try {
432 for (String module : modules) {
433 Location sourceLoc = fm.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, module);
434 if (sourceLoc == null) {
435 log.error(Errors.ModuleNotFoundInModuleSourcePath(module));
436 } else {
437 Location classLoc = fm.getLocationForModule(StandardLocation.CLASS_OUTPUT, module);
438
439 for (JavaFileObject file : fm.list(sourceLoc, "", EnumSet.of(JavaFileObject.Kind.SOURCE), true)) {
440 String className = fm.inferBinaryName(sourceLoc, file);
441 JavaFileObject classFile = fm.getJavaFileForInput(classLoc, className, Kind.CLASS);
442
443 if (classFile == null || classFile.getLastModified() < file.getLastModified()) {
444 if (fileObjects == null)
445 fileObjects = new HashSet<>();
446 fileObjects.add(file);
447 }
448 }
449 }
450 }
451 } catch (IOException ex) {
452 log.printLines(PrefixKind.JAVAC, "msg.io");
453 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
454 return false;
455 }
456 }
457 }
458
459 if (isEmpty()) {
460 // It is allowed to compile nothing if just asking for help or version info.
461 // But also note that none of these options are supported in API mode.
462 if (options.isSet(Option.HELP)
463 || options.isSet(Option.X)
464 || options.isSet(Option.HELP_LINT)
465 || options.isSet(Option.VERSION)
466 || options.isSet(Option.FULLVERSION)
467 || options.isSet(Option.MODULE)) {
468 return true;
469 }
470
471 if (!emptyAllowed) {
472 if (!errors) {
473 if (JavaCompiler.explicitAnnotationProcessingRequested(options, fileManager)) {
474 reportDiag(Errors.NoSourceFilesClasses);
475 } else {
476 reportDiag(Errors.NoSourceFiles);
477 }
478 }
479 return false;
480 }
481 }
482
483 if (!checkDirectory(Option.D)) {
484 return false;
485 }
486 if (!checkDirectory(Option.S)) {
487 return false;
488 }
489 if (!checkDirectory(Option.H)) {
490 return false;
491 }
492
493 // The following checks are to help avoid accidental confusion between
494 // directories of modules and exploded module directories.
495 if (fm instanceof StandardJavaFileManager standardJavaFileManager) {
496 if (standardJavaFileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
497 Path outDir = standardJavaFileManager.getLocationAsPaths(StandardLocation.CLASS_OUTPUT).iterator().next();
498 if (standardJavaFileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
499 // multi-module mode
500 if (Files.exists(outDir.resolve("module-info.class"))) {
501 log.error(Errors.MultiModuleOutdirCannotBeExplodedModule(outDir));
502 }
503 } else {
504 // single-module or legacy mode
505 Path outDirParent = outDir.getParent();
506 if (outDirParent != null && Files.exists(outDirParent.resolve("module-info.class"))) {
507 log.warning(LintWarnings.OutdirIsInExplodedModule(outDir));
508 }
509 }
510 }
511 }
512
513
514 String sourceString = options.get(Option.SOURCE);
515 Source source = (sourceString != null)
516 ? Source.lookup(sourceString)
517 : Source.DEFAULT;
518 String targetString = options.get(Option.TARGET);
519 Target target = (targetString != null)
520 ? Target.lookup(targetString)
521 : Target.DEFAULT;
522
523 // We don't check source/target consistency for CLDC, as J2ME
524 // profiles are not aligned with J2SE targets; moreover, a
525 // single CLDC target may have many profiles. In addition,
526 // this is needed for the continued functioning of the JSR14
527 // prototype.
528 if (Character.isDigit(target.name.charAt(0))) {
529 if (target.compareTo(source.requiredTarget()) < 0) {
530 if (targetString != null) {
531 if (sourceString == null) {
532 reportDiag(Errors.TargetDefaultSourceConflict(source.name, targetString));
533 } else {
534 reportDiag(Errors.SourceTargetConflict(sourceString, targetString));
535 }
536 return false;
537 } else {
538 target = source.requiredTarget();
539 options.put("-target", target.name);
540 }
541 }
542 }
543
544 if (options.isSet(Option.PREVIEW)) {
545 if (sourceString == null) {
546 //enable-preview must be used with explicit -source or --release
547 report(Errors.PreviewWithoutSourceOrRelease);
548 return false;
549 } else if (source != Source.DEFAULT) {
550 //enable-preview must be used with latest source version
551 report(Errors.PreviewNotLatest(sourceString, Source.DEFAULT));
552 return false;
553 }
554 }
555
556 String profileString = options.get(Option.PROFILE);
557 if (profileString != null) {
558 Profile profile = Profile.lookup(profileString);
559 if (target.compareTo(Target.JDK1_8) <= 0 && !profile.isValid(target)) {
560 // note: -profile not permitted for target >= 9, so error (below) not warning (here)
561 reportDiag(Warnings.ProfileTargetConflict(profile, target));
562 }
563
564 // This check is only effective in command line mode,
565 // where the file manager options are added to options
566 if (options.get(Option.BOOT_CLASS_PATH) != null) {
567 reportDiag(Errors.ProfileBootclasspathConflict);
568 }
569 }
570
571 if (options.isSet(Option.SOURCE_PATH) && options.isSet(Option.MODULE_SOURCE_PATH)) {
572 reportDiag(Errors.SourcepathModulesourcepathConflict);
573 }
574
575 if (source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) {
576 if (fm instanceof BaseFileManager baseFileManager) {
577 if (source.compareTo(Source.JDK8) <= 0) {
578 if (baseFileManager.isDefaultBootClassPath()) {
579 log.warning(LintWarnings.SourceNoBootclasspath(source.name, releaseNote(source, targetString)));
580 }
581 } else if (baseFileManager.isDefaultSystemModulesPath()) {
582 log.warning(LintWarnings.SourceNoSystemModulesPath(source.name, releaseNote(source, targetString)));
583 }
584 }
585 }
586
587 boolean obsoleteOptionFound = false;
588
589 if (source.compareTo(Source.MIN) < 0) {
590 log.error(Errors.OptionRemovedSource(source.name, Source.MIN.name));
591 } else if (source == Source.MIN) {
592 log.warning(LintWarnings.OptionObsoleteSource(source.name));
593 obsoleteOptionFound = true;
594 }
595
596 if (target.compareTo(Target.MIN) < 0) {
597 log.error(Errors.OptionRemovedTarget(target, Target.MIN));
598 } else if (target == Target.MIN) {
599 log.warning(LintWarnings.OptionObsoleteTarget(target));
600 obsoleteOptionFound = true;
601 }
602
603 final Target t = target;
604 checkOptionAllowed(t.compareTo(Target.JDK1_8) <= 0,
605 option -> reportDiag(Errors.OptionNotAllowedWithTarget(option, t)),
606 Option.BOOT_CLASS_PATH,
607 Option.XBOOTCLASSPATH_PREPEND, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
608 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
609 Option.EXTDIRS, Option.DJAVA_EXT_DIRS,
610 Option.PROFILE);
611
612 checkOptionAllowed(t.compareTo(Target.JDK1_9) >= 0,
613 option -> reportDiag(Errors.OptionNotAllowedWithTarget(option, t)),
614 Option.MODULE_SOURCE_PATH, Option.UPGRADE_MODULE_PATH,
615 Option.SYSTEM, Option.MODULE_PATH, Option.ADD_MODULES,
616 Option.ADD_EXPORTS, Option.ADD_OPENS, Option.ADD_READS,
617 Option.LIMIT_MODULES,
618 Option.PATCH_MODULE);
619
620 if (fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
621 if (!options.isSet(Option.PROC, "only")
622 && !fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
623 log.error(Errors.NoOutputDir);
624 }
625 }
626
627 if (fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH) &&
628 fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH)) {
629 log.error(Errors.ProcessorpathNoProcessormodulepath);
630 }
631
632 if (obsoleteOptionFound) {
633 log.warning(LintWarnings.OptionObsoleteSuppression);
634 }
635
636 SourceVersion sv = Source.toSourceVersion(source);
637 validateAddExports(sv);
638 validateAddModules(sv);
639 validateAddReads(sv);
640 validateLimitModules(sv);
641 validateDefaultModuleForCreatedFiles(sv);
642
643 if (options.isSet(Option.ADD_OPENS)) {
644 log.warning(LintWarnings.AddopensIgnored);
645 }
646
647 return !errors && (log.nerrors == 0);
648 }
649
650 private Fragment releaseNote(Source source, String targetString) {
651 if (source.compareTo(Source.JDK8) <= 0) {
652 if (targetString != null) {
653 return Fragments.SourceNoBootclasspathWithTarget(source.name, targetString);
654 } else {
655 return Fragments.SourceNoBootclasspath(source.name);
656 }
657 } else {
658 if (targetString != null) {
659 return Fragments.SourceNoSystemModulesPathWithTarget(source.name, targetString);
660 } else {
661 return Fragments.SourceNoSystemModulesPath(source.name);
662 }
663 }
664 }
665
666 private void validateAddExports(SourceVersion sv) {
667 String addExports = options.get(Option.ADD_EXPORTS);
668 if (addExports != null) {
669 // Each entry must be of the form sourceModule/sourcePackage=target-list where
670 // target-list is a comma separated list of module or ALL-UNNAMED.
671 // Empty items in the target-list are ignored.
672 // There must be at least one item in the list; this is handled in Option.ADD_EXPORTS.
673 Pattern p = Option.ADD_EXPORTS.getPattern();
674 for (String e : addExports.split("\0")) {
675 Matcher m = p.matcher(e);
676 if (m.matches()) {
677 String sourceModuleName = m.group(1);
678 if (!SourceVersion.isName(sourceModuleName, sv)) {
679 // syntactically invalid source name: e.g. --add-exports m!/p1=m2
680 log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, sourceModuleName));
681 }
682 String sourcePackageName = m.group(2);
683 if (!SourceVersion.isName(sourcePackageName, sv)) {
684 // syntactically invalid source name: e.g. --add-exports m1/p!=m2
685 log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, sourcePackageName));
686 }
687
688 String targetNames = m.group(3);
689 for (String targetName : targetNames.split(",")) {
690 switch (targetName) {
691 case "":
692 case "ALL-UNNAMED":
693 break;
694
695 default:
696 if (!SourceVersion.isName(targetName, sv)) {
697 // syntactically invalid target name: e.g. --add-exports m1/p1=m!
698 log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, targetName));
699 }
700 break;
701 }
702 }
703 }
704 }
705 }
706 }
707
708 private void validateAddReads(SourceVersion sv) {
709 String addReads = options.get(Option.ADD_READS);
710 if (addReads != null) {
711 // Each entry must be of the form source=target-list where target-list is a
712 // comma-separated list of module or ALL-UNNAMED.
713 // Empty items in the target list are ignored.
714 // There must be at least one item in the list; this is handled in Option.ADD_READS.
715 Pattern p = Option.ADD_READS.getPattern();
716 for (String e : addReads.split("\0")) {
717 Matcher m = p.matcher(e);
718 if (m.matches()) {
719 String sourceName = m.group(1);
720 if (!SourceVersion.isName(sourceName, sv)) {
721 // syntactically invalid source name: e.g. --add-reads m!=m2
722 log.warning(Warnings.BadNameForOption(Option.ADD_READS, sourceName));
723 }
724
725 String targetNames = m.group(2);
726 for (String targetName : targetNames.split(",", -1)) {
727 switch (targetName) {
728 case "":
729 case "ALL-UNNAMED":
730 break;
731
732 default:
733 if (!SourceVersion.isName(targetName, sv)) {
734 // syntactically invalid target name: e.g. --add-reads m1=m!
735 log.warning(Warnings.BadNameForOption(Option.ADD_READS, targetName));
736 }
737 break;
738 }
739 }
740 }
741 }
742 }
743 }
744
745 private void validateAddModules(SourceVersion sv) {
746 String addModules = options.get(Option.ADD_MODULES);
747 if (addModules != null) {
748 // Each entry must be of the form target-list where target-list is a
749 // comma separated list of module names, or ALL-DEFAULT, ALL-SYSTEM,
750 // or ALL-MODULE_PATH.
751 // Empty items in the target list are ignored.
752 // There must be at least one item in the list; this is handled in Option.ADD_MODULES.
753 for (String moduleName : addModules.split(",")) {
754 switch (moduleName) {
755 case "":
756 case "ALL-SYSTEM":
757 case "ALL-MODULE-PATH":
758 break;
759
760 default:
761 if (!SourceVersion.isName(moduleName, sv)) {
762 // syntactically invalid module name: e.g. --add-modules m1,m!
763 log.error(Errors.BadNameForOption(Option.ADD_MODULES, moduleName));
764 }
765 break;
766 }
767 }
768 }
769 }
770
771 private void validateLimitModules(SourceVersion sv) {
772 String limitModules = options.get(Option.LIMIT_MODULES);
773 if (limitModules != null) {
774 // Each entry must be of the form target-list where target-list is a
775 // comma separated list of module names, or ALL-DEFAULT, ALL-SYSTEM,
776 // or ALL-MODULE_PATH.
777 // Empty items in the target list are ignored.
778 // There must be at least one item in the list; this is handled in Option.LIMIT_EXPORTS.
779 for (String moduleName : limitModules.split(",")) {
780 switch (moduleName) {
781 case "":
782 break;
783
784 default:
785 if (!SourceVersion.isName(moduleName, sv)) {
786 // syntactically invalid module name: e.g. --limit-modules m1,m!
787 log.error(Errors.BadNameForOption(Option.LIMIT_MODULES, moduleName));
788 }
789 break;
790 }
791 }
792 }
793 }
794
795 private void validateDefaultModuleForCreatedFiles(SourceVersion sv) {
796 String moduleName = options.get(Option.DEFAULT_MODULE_FOR_CREATED_FILES);
797 if (moduleName != null) {
798 if (!SourceVersion.isName(moduleName, sv)) {
799 // syntactically invalid module name: e.g. --default-module-for-created-files m!
800 log.error(Errors.BadNameForOption(Option.DEFAULT_MODULE_FOR_CREATED_FILES,
801 moduleName));
802 }
803 }
804 }
805
806 /**
807 * Returns true if there are no files or classes specified for use.
808 * @return true if there are no files or classes specified for use
809 */
810 public boolean isEmpty() {
811 return ((files == null) || files.isEmpty())
812 && ((fileObjects == null) || fileObjects.isEmpty())
813 && (classNames == null || classNames.isEmpty());
814 }
815
816 public void allowEmpty() {
817 this.emptyAllowed = true;
818 }
819
820 /**
821 * Gets the file manager options which may have been deferred
822 * during processArgs.
823 * @return the deferred file manager options
824 */
825 public Map<Option, String> getDeferredFileManagerOptions() {
826 return deferredFileManagerOptions;
827 }
828
829 /**
830 * Gets any options specifying plugins to be run.
831 * @return options for plugins
832 */
833 public Set<List<String>> getPluginOpts() {
834 String plugins = options.get(Option.PLUGIN);
835 if (plugins == null)
836 return Collections.emptySet();
837
838 Set<List<String>> pluginOpts = new LinkedHashSet<>();
839 for (String plugin: plugins.split("\\x00")) {
840 pluginOpts.add(List.from(plugin.split("\\s+")));
841 }
842 return Collections.unmodifiableSet(pluginOpts);
843 }
844
845 /**
846 * Gets any options specifying how doclint should be run.
847 * An empty list is returned if no doclint options are specified
848 * or if the only doclint option is -Xdoclint:none.
849 * @return options for doclint
850 */
851 public List<String> getDocLintOpts() {
852 String xdoclint = options.get(Option.XDOCLINT);
853 String xdoclintCustom = options.get(Option.XDOCLINT_CUSTOM);
854 if (xdoclint == null && xdoclintCustom == null)
855 return List.nil();
856
857 Set<String> doclintOpts = new LinkedHashSet<>();
858 if (xdoclint != null)
859 doclintOpts.add(DocLint.XMSGS_OPTION);
860 if (xdoclintCustom != null) {
861 for (String s: xdoclintCustom.split("\\s+")) {
862 if (s.isEmpty())
863 continue;
864 doclintOpts.add(DocLint.XMSGS_CUSTOM_PREFIX + s);
865 }
866 }
867
868 if (doclintOpts.equals(Collections.singleton(DocLint.XMSGS_CUSTOM_PREFIX + "none")))
869 return List.nil();
870
871 String checkPackages = options.get(Option.XDOCLINT_PACKAGE);
872 if (checkPackages != null) {
873 doclintOpts.add(DocLint.XCHECK_PACKAGE + checkPackages);
874 }
875
876 return List.from(doclintOpts.toArray(new String[doclintOpts.size()]));
877 }
878
879 private boolean checkDirectory(Option option) {
880 String value = options.get(option);
881 if (value == null) {
882 return true;
883 }
884 Path file = Paths.get(value);
885 if (Files.exists(file) && !Files.isDirectory(file)) {
886 reportDiag(Errors.FileNotDirectory(value));
887 return false;
888 }
889 return true;
890 }
891
892 private interface ErrorReporter {
893 void report(Option o);
894 }
895
896 void checkOptionAllowed(boolean allowed, ErrorReporter r, Option... opts) {
897 if (!allowed) {
898 Stream.of(opts)
899 .filter(options :: isSet)
900 .forEach(r :: report);
901 }
902 }
903
904 void reportDiag(DiagnosticInfo diag) {
905 errors = true;
906 switch (errorMode) {
907 case ILLEGAL_ARGUMENT: {
908 String msg = log.localize(diag);
909 throw new PropagatedException(new IllegalArgumentException(msg));
910 }
911 case ILLEGAL_STATE: {
912 String msg = log.localize(diag);
913 throw new PropagatedException(new IllegalStateException(msg));
914 }
915 case LOG:
916 report(diag);
917 }
918 }
919
920 void error(Option.InvalidValueException f) {
921 String msg = f.getMessage();
922 errors = true;
923 switch (errorMode) {
924 case ILLEGAL_ARGUMENT: {
925 throw new PropagatedException(new IllegalArgumentException(msg, f.getCause()));
926 }
927 case ILLEGAL_STATE: {
928 throw new PropagatedException(new IllegalStateException(msg, f.getCause()));
929 }
930 case LOG:
931 log.printRawLines(msg);
932 }
933 }
934
935 private void report(DiagnosticInfo diag) {
936 // Would be good to have support for -XDrawDiagnostics here
937 if (diag instanceof JCDiagnostic.Error errorDiag) {
938 log.error(errorDiag);
939 } else if (diag instanceof JCDiagnostic.Warning warningDiag){
940 log.warning(warningDiag);
941 }
942 }
943
944 private JavaFileManager getFileManager() {
945 if (fileManager == null)
946 fileManager = context.get(JavaFileManager.class);
947 return fileManager;
948 }
949
950 <T> ListBuffer<T> toList(Iterable<? extends T> items) {
951 ListBuffer<T> list = new ListBuffer<>();
952 if (items != null) {
953 for (T item : items) {
954 list.add(item);
955 }
956 }
957 return list;
958 }
959
960 <T> Set<T> toSet(Iterable<? extends T> items) {
961 Set<T> set = new LinkedHashSet<>();
962 if (items != null) {
963 for (T item : items) {
964 set.add(item);
965 }
966 }
967 return set;
968 }
969 }