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