From 8bfea76a05a958be39a32b48d47e7b3824ead956 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 11 May 2026 09:21:07 +0200 Subject: [PATCH 1/5] Adding support for --limit-modules, --module-path and similar options. --- .../modules/jackpot30/cmdline/Main.java | 91 ++++++++- .../modules/jackpot30/cmdline/MainTest.java | 185 +++++++++++++++++- 2 files changed, 267 insertions(+), 9 deletions(-) diff --git a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java index 511ab15..b918c84 100644 --- a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java +++ b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java @@ -68,6 +68,7 @@ import joptsimple.OptionSet; import org.netbeans.api.actions.Savable; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.JavaClassPathConstants; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.ModificationResult; import org.netbeans.modules.editor.tools.storage.api.ToolPreferences; @@ -108,6 +109,7 @@ import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.netbeans.spi.java.hints.Hint.Kind; import org.netbeans.spi.java.hints.HintContext; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; @@ -408,20 +410,43 @@ private static GroupOptions setupGroupParser(OptionParser parser) { return new GroupOptions(parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), - parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT)); + parser.accepts("add-exports", "javac's addd-exports option").withRequiredArg().ofType(String.class), + parser.accepts("add-modules", "javac's add-modules option").withRequiredArg().ofType(String.class), + parser.accepts("limit-modules", "javac's limit-modules option").withRequiredArg().ofType(String.class), + parser.accepts("module-path", "module path").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), + parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT), + parser.accepts("system", "system modules").withRequiredArg().ofType(File.class)); } private static final class GroupOptions { private final ArgumentAcceptingOptionSpec classpath; private final ArgumentAcceptingOptionSpec bootclasspath; private final ArgumentAcceptingOptionSpec sourcepath; + private final ArgumentAcceptingOptionSpec addExports; + private final ArgumentAcceptingOptionSpec addModules; + private final ArgumentAcceptingOptionSpec limitModules; + private final ArgumentAcceptingOptionSpec modulePath; private final ArgumentAcceptingOptionSpec source; - - public GroupOptions(ArgumentAcceptingOptionSpec classpath, ArgumentAcceptingOptionSpec bootclasspath, ArgumentAcceptingOptionSpec sourcepath, ArgumentAcceptingOptionSpec source) { + private final ArgumentAcceptingOptionSpec system; + + public GroupOptions(ArgumentAcceptingOptionSpec classpath, + ArgumentAcceptingOptionSpec bootclasspath, + ArgumentAcceptingOptionSpec sourcepath, + ArgumentAcceptingOptionSpec addExports, + ArgumentAcceptingOptionSpec addModules, + ArgumentAcceptingOptionSpec limitModules, + ArgumentAcceptingOptionSpec modulePath, + ArgumentAcceptingOptionSpec source, + ArgumentAcceptingOptionSpec system) { this.classpath = classpath; this.bootclasspath = bootclasspath; this.sourcepath = sourcepath; + this.limitModules = limitModules; + this.modulePath = modulePath; this.source = source; + this.addExports = addExports; + this.addModules = addModules; + this.system = system; } } @@ -1008,9 +1033,14 @@ private static final class WarningsAndErrors { private static final class RootConfiguration { private final List rootFolders; private final ClassPath bootCP; + private final ClassPath systemCP; private final ClassPath compileCP; + private final ClassPath modulePathCP; private final ClassPath sourceCP; private final ClassPath binaryCP; + private final String addExports; + private final String addModules; + private final String limitModules; private final String sourceLevel; public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException { @@ -1029,9 +1059,14 @@ public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOE } this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, Utils.createDefaultBootClassPath()); + this.systemCP = createClassPath(parsed.has(groupOptions.system) ? parsed.valuesOf(groupOptions.bootclasspath) : null, null); //XXX: this needs to be a JRTFS! this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY); + this.modulePathCP = createClassPath(parsed.has(groupOptions.modulePath) ? parsed.valuesOf(groupOptions.modulePath) : null, ClassPath.EMPTY); //XXX: expand directories(?) this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]))); - this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP); + this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP, systemCP, modulePathCP); + this.addExports = parsed.has(groupOptions.addExports) ? parsed.valueOf(groupOptions.addExports) : null; + this.addModules = parsed.has(groupOptions.addModules) ? parsed.valueOf(groupOptions.addModules) : null;; + this.limitModules = parsed.has(groupOptions.limitModules) ? parsed.valueOf(groupOptions.limitModules) : null;; this.sourceLevel = parsed.valueOf(groupOptions.source); } @@ -1088,8 +1123,12 @@ public ClassPath findClassPath(FileObject file, String type) { if (rootConfiguration.sourceCP.findOwnerRoot(file) != null) { if (ClassPath.BOOT.equals(type)) { return rootConfiguration.bootCP; + } else if (JavaClassPathConstants.MODULE_BOOT_PATH.equals(type)) { + return rootConfiguration.systemCP; } else if (ClassPath.COMPILE.equals(type)) { return rootConfiguration.compileCP; + } else if (JavaClassPathConstants.MODULE_COMPILE_PATH.equals(type)) { + return rootConfiguration.modulePathCP; } else if (ClassPath.SOURCE.equals(type)) { return rootConfiguration.sourceCP; } @@ -1099,6 +1138,50 @@ public ClassPath findClassPath(FileObject file, String type) { } } + @ServiceProvider(service=CompilerOptionsQueryImplementation.class, position=100) + public static final class CompilerOptionsQueryImpl implements CompilerOptionsQueryImplementation { + private final Result result = new Result() { + @Override + public List getArguments() { + RootConfiguration rootConfiguration = currentRootConfiguration.get(); + + if (rootConfiguration == null) { + return List.of(); + } + + List result = new ArrayList<>(); + + if (rootConfiguration.addModules != null) { + result.add("--add-modules"); + result.add(rootConfiguration.addModules); + } + + if (rootConfiguration.limitModules != null) { + result.add("--limit-modules"); + result.add(rootConfiguration.limitModules); + } + + if (rootConfiguration.addExports != null) { + result.add("--add-exports"); + result.add(rootConfiguration.addExports); + } + + return result; + } + @Override + public void addChangeListener(ChangeListener cl) { + } + @Override + public void removeChangeListener(ChangeListener cl) { + } + }; + + @Override + public Result getOptions(FileObject fo) { + return result; + } + } + @ServiceProvider(service=SourceLevelQueryImplementation2.class, position=100) public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 { diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index a709ae6..782d7c5 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -30,7 +30,11 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -38,6 +42,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.junit.runner.Result; import org.netbeans.junit.NbTestCase; @@ -55,6 +60,7 @@ public class MainTest extends NbTestCase { private static final String TEST_HINT = "Usage of [Collection|Map].size() == 0"; + private static final String BASE_64_PREFIX = "BASE64: "; public MainTest(String name) { super(name); @@ -1064,6 +1070,147 @@ public void testSource17() throws Exception { "--source", "17"); } + public void testModulePath() throws Exception { + clearWorkDir(); + + List maFiles = List.of( + writeToPath("src/ma/module-info.java", + """ + module ma { + exports api; + } + """), + writeToPath("src/ma/api/Api.java", + """ + package api; + import java.util.Collection; + public class Api { + public static Collection get() { + return null; + } + } + """), + writeToPath("src/ma/impl/Impl.java", + """ + package impl; + import java.util.Collection; + public class Impl { + public static Collection get() { + return null; + } + } + """)); + File classesOutput = new File(getWorkDir(), "classes/ma"); + + classesOutput.getParentFile().mkdirs(); + + try (StandardJavaFileManager fm = + ToolProvider.getSystemJavaCompiler() + .getStandardFileManager(null, null, null)) { + Boolean compilationResult = + ToolProvider.getSystemJavaCompiler() + .getTask(null, null, null, List.of("-d", classesOutput.getAbsolutePath()), null, + fm.getJavaFileObjectsFromFiles(maFiles)) + .call(); + if (compilationResult == null || !compilationResult) { + throw new AssertionError("Cannot compile the module!"); + } + } + + String golden = + """ + package test; + import api.Api; + import impl.Impl; + public class Test { + private void test() { + boolean b1 = Api.get().isEmpty(); + boolean b2 = Impl.get().isEmpty(); + } + } + """; + + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import api.Api; + import impl.Impl; + public class Test { + private void test() { + boolean b1 = Api.get().size() == 0; + boolean b2 = Impl.get().size() == 0; + } + } + """); + filesAndOptions.addAll(listFiles(classesOutput)); + filesAndOptions.add(null); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--module-path"); filesAndOptions.add(classesOutput.getAbsolutePath()); + filesAndOptions.add("--add-modules"); filesAndOptions.add("ma"); + filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl=ALL-UNNAMED"); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } + + public void testLimitModules() throws Exception { + clearWorkDir(); + + String golden = + """ + package test; + import com.sun.tools.javac.*; + public class Test { + private boolean test() { + return Main2.test().isEmpty(); + } + } + """; + + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import com.sun.tools.javac.*; + public class Test { + private boolean test() { + return Main2.test().size() == 0; + } + } + """); + filesAndOptions.add("src/com/sun/tools/javac/Main2.java"); + filesAndOptions.add(""" + package com.sun.tools.javac; + import java.util.List; + public class Main2 { + public static List test() { + return List.of(); + } + } + """); + filesAndOptions.add(null); + filesAndOptions.add(DONT_APPEND_PATH); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--limit-modules"); filesAndOptions.add("java.base"); + filesAndOptions.add("${workdir}/src"); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } + private static final String DONT_APPEND_PATH = new String("DONT_APPEND_PATH"); private static final String IGNORE = new String("IGNORE"); @@ -1094,11 +1241,7 @@ private void doRunCompiler(Validator fileContentValidator, Validator stdOutValid clearWorkDir(); for (int cntr = 0; cntr < fileAndContent.size(); cntr += 2) { - File target = new File(getWorkDir(), fileAndContent.get(cntr)); - - target.getParentFile().mkdirs(); - - TestUtils.copyStringToFile(target, fileAndContent.get(cntr + 1)); + writeToPath(fileAndContent.get(cntr), fileAndContent.get(cntr + 1)); } File wd = getWorkDir(); @@ -1266,6 +1409,38 @@ private static Validator equivalentValidator(final String expected) { }; } + private File writeToPath(String relativePath, String content) throws Exception { + File target = new File(getWorkDir(), relativePath); + + target.getParentFile().mkdirs(); + + if (content.startsWith(BASE_64_PREFIX)) { + Files.write(target.toPath(), Base64.getDecoder().decode(content.substring(BASE_64_PREFIX.length()))); + } else { + TestUtils.copyStringToFile(target, content); + } + + return target; + } + + private List listFiles(File directory) throws IOException { + Path base = getWorkDir().toPath(); + List result = new ArrayList<>(); + + Files.walk(directory.toPath()) + .filter(Files::isRegularFile) + .forEach(p -> { + try { + result.add(base.relativize(p).toString()); + result.add(BASE_64_PREFIX + Base64.getEncoder().encodeToString(Files.readAllBytes(p))); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + + return result; + } + private static interface Validator { public void validate(String content); } From c4f857afa92e00116f4d4c44575dccd73769bc0f Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 19 May 2026 08:28:31 +0200 Subject: [PATCH 2/5] Fixing tests. --- .../modules/jackpot30/cmdline/CreateTool.java | 5 ++++- .../jackpot30/cmdline/CreateToolTest.java | 7 ++++-- .../modules/jackpot30/cmdline/MainTest.java | 22 ++++++++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java index e64f6d4..f1b9fbb 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import javax.annotation.processing.Processor; import org.netbeans.modules.jackpot30.cmdline.Main.BCPFallBack; +import org.netbeans.modules.jackpot30.cmdline.Main.CompilerOptionsQueryImpl; import org.netbeans.modules.jackpot30.cmdline.Main.SourceLevelQueryImpl; import org.netbeans.modules.jackpot30.cmdline.lib.CreateStandaloneJar; import org.netbeans.modules.jackpot30.cmdline.lib.CreateStandaloneJar.Info; @@ -31,6 +32,7 @@ import org.netbeans.modules.java.platform.DefaultJavaPlatformProvider; import org.netbeans.modules.project.ui.OpenProjectsTrampolineImpl; import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; /** @@ -45,13 +47,14 @@ public CreateTool(String name) { @Override protected Info computeInfo() { - return new Info().addAdditionalRoots(Main.class.getName(), DeclarativeHintsTestBase.class.getName(), OpenProjectsTrampolineImpl.class.getName(), J2SEProject.class.getName(), DefaultJavaPlatformProvider.class.getName(), PatternConvertorImpl.class.getName(), BCPFallBack.class.getName(), "org.slf4j.impl.StaticLoggerBinder") + return new Info().addAdditionalRoots(Main.class.getName(), DeclarativeHintsTestBase.class.getName(), OpenProjectsTrampolineImpl.class.getName(), J2SEProject.class.getName(), DefaultJavaPlatformProvider.class.getName(), PatternConvertorImpl.class.getName(), BCPFallBack.class.getName(), "org.slf4j.impl.StaticLoggerBinder", CompilerOptionsQueryImpl.class.getName()) .addAdditionalResources("org/netbeans/modules/java/hints/resources/Bundle.properties", "org/netbeans/modules/java/hints/declarative/resources/Bundle.properties") .addAdditionalLayers("org/netbeans/modules/java/hints/resources/layer.xml", "org/netbeans/modules/java/hints/declarative/resources/layer.xml") .addMetaInfRegistrations(new MetaInfRegistration(org.netbeans.modules.project.uiapi.OpenProjectsTrampoline.class, OpenProjectsTrampolineImpl.class)) .addMetaInfRegistrations(new MetaInfRegistration(ClassPathProvider.class.getName(), BCPFallBack.class.getName(), 9999)) .addMetaInfRegistrations(new MetaInfRegistration(ClassPathProvider.class.getName(), Main.ClassPathProviderImpl.class.getName(), 100)) .addMetaInfRegistrations(new MetaInfRegistration(SourceLevelQueryImplementation2.class.getName(), SourceLevelQueryImpl.class.getName(), 100)) + .addMetaInfRegistrations(new MetaInfRegistration(CompilerOptionsQueryImplementation.class.getName(), CompilerOptionsQueryImpl.class.getName(), 100)) .addMetaInfRegistrationToCopy(PatternConvertor.class.getName()) .addExcludePattern(Pattern.compile("junit\\.framework\\..*")) .setEscapeJavaxLang(); diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java index 3119a08..d3c6915 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java @@ -71,11 +71,14 @@ protected void reallyRunCompiler(File workingDir, int exitcode, String[] output, int actualExitCode = p.waitFor(); - assertEquals(exitcode, actualExitCode); - outCopy.doJoin(); errCopy.doJoin(); + + assertEquals(exitcode, actualExitCode); } catch (Throwable t) { + System.err.println(output[0]); + System.err.println(output[1]); + throw new IOException(t); } } diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index 782d7c5..4db71a5 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -1273,14 +1273,20 @@ private void doRunCompiler(Validator fileContentValidator, Validator stdOutValid reallyRunCompiler(wd, exitcode, output, options.toArray(new String[0])); - if (fileContentValidator != null) { - fileContentValidator.validate(TestUtils.copyFileToString(source)); - } - if (stdOutValidator != null) { - stdOutValidator.validate(output[0].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); - } - if (stdErrValidator != null) { - stdErrValidator.validate(output[1].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + try { + if (fileContentValidator != null) { + fileContentValidator.validate(TestUtils.copyFileToString(source)); + } + if (stdOutValidator != null) { + stdOutValidator.validate(output[0].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + } + if (stdErrValidator != null) { + stdErrValidator.validate(output[1].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + } + } catch (Throwable t) { + System.err.println(output[0]); + System.err.println(output[1]); + throw t; } } From 8b03a84c9ce543ebe034b1bfa724fc42ee2a18d8 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 25 May 2026 19:23:33 +0200 Subject: [PATCH 3/5] Stabilization. --- .../modules/jackpot30/cmdline/lib/Utils.java | 65 +++----- cmdline/tool/nbproject/project.xml | 9 ++ .../modules/jackpot30/cmdline/Main.java | 57 ++++++- .../modules/jackpot30/cmdline/CreateTool.java | 1 - .../modules/jackpot30/cmdline/MainTest.java | 148 +++++++++++++----- 5 files changed, 187 insertions(+), 93 deletions(-) diff --git a/cmdline/lib/src/org/netbeans/modules/jackpot30/cmdline/lib/Utils.java b/cmdline/lib/src/org/netbeans/modules/jackpot30/cmdline/lib/Utils.java index ceb220c..7f388cc 100644 --- a/cmdline/lib/src/org/netbeans/modules/jackpot30/cmdline/lib/Utils.java +++ b/cmdline/lib/src/org/netbeans/modules/jackpot30/cmdline/lib/Utils.java @@ -98,51 +98,30 @@ public static String categoryName(String id, Map id2DisplayName) //copied from BootClassPathUtil: public static ClassPath createDefaultBootClassPath() { - String cp = System.getProperty("sun.boot.class.path"); - if (cp != null) { - List urls = new ArrayList<>(); - String[] paths = cp.split(Pattern.quote(System.getProperty("path.separator"))); - for (String path : paths) { - File f = new File(path); - - if (!f.canRead()) - continue; - - FileObject fo = FileUtil.toFileObject(f); - if (FileUtil.isArchiveFile(fo)) { - fo = FileUtil.getArchiveRoot(fo); - } - if (fo != null) { - urls.add(fo.toURL()); - } - } - return ClassPathSupport.createClassPath((URL[])urls.toArray(new URL[0])); - } else { - try { - Class.forName("org.netbeans.ProxyURLStreamHandlerFactory").getMethod("register") - .invoke(null); - } catch (ClassNotFoundException | NoSuchMethodException | - SecurityException | IllegalAccessException | - IllegalArgumentException | InvocationTargetException ex) { - throw new IllegalStateException(ex); - } - final List modules = new ArrayList<>(); - final File installDir = new File(System.getProperty("java.home")); - final URI imageURI = getImageURI(installDir); - try { - final FileObject jrtRoot = URLMapper.findFileObject(imageURI.toURL()); - final FileObject root = getModulesRoot(jrtRoot); - for (FileObject module : root.getChildren()) { - modules.add(ClassPathSupport.createResource(module.toURL())); - } - } catch (MalformedURLException e) { - throw new IllegalStateException(e); - } - if (modules.isEmpty()) { - throw new IllegalStateException("No modules!"); + try { + Class.forName("org.netbeans.ProxyURLStreamHandlerFactory").getMethod("register") + .invoke(null); + } catch (ClassNotFoundException | NoSuchMethodException | + SecurityException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException ex) { + throw new IllegalStateException(ex); + } + final List modules = new ArrayList<>(); + final File installDir = new File(System.getProperty("java.home")); + final URI imageURI = getImageURI(installDir); + try { + final FileObject jrtRoot = URLMapper.findFileObject(imageURI.toURL()); + final FileObject root = getModulesRoot(jrtRoot); + for (FileObject module : root.getChildren()) { + modules.add(ClassPathSupport.createResource(module.toURL())); } - return ClassPathSupport.createClassPath(modules); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + if (modules.isEmpty()) { + throw new IllegalStateException("No modules!"); } + return ClassPathSupport.createClassPath(modules); } private static final String PROTOCOL = "nbjrt"; //NOI18N diff --git a/cmdline/tool/nbproject/project.xml b/cmdline/tool/nbproject/project.xml index d6e96a6..c0f4a9a 100644 --- a/cmdline/tool/nbproject/project.xml +++ b/cmdline/tool/nbproject/project.xml @@ -138,6 +138,15 @@ + + org.netbeans.modules.java.j2seplatform + + + + 1 + + + org.netbeans.modules.java.project diff --git a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java index b918c84..add1338 100644 --- a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java +++ b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java @@ -37,6 +37,7 @@ import java.lang.Runtime.Version; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -98,6 +99,8 @@ import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper.ProgressHandleAbstraction; import org.netbeans.modules.java.hints.spiimpl.batch.Scopes; import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings; +import org.netbeans.modules.java.j2seplatform.platformdefinition.Util; +import org.netbeans.modules.java.source.parsing.JavacParser; import org.netbeans.modules.parsing.impl.indexing.CacheFolder; import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater; import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation; @@ -1038,7 +1041,7 @@ private static final class RootConfiguration { private final ClassPath modulePathCP; private final ClassPath sourceCP; private final ClassPath binaryCP; - private final String addExports; + private final List addExports; private final String addModules; private final String limitModules; private final String sourceLevel; @@ -1059,17 +1062,50 @@ public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOE } this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, Utils.createDefaultBootClassPath()); - this.systemCP = createClassPath(parsed.has(groupOptions.system) ? parsed.valuesOf(groupOptions.bootclasspath) : null, null); //XXX: this needs to be a JRTFS! + this.systemCP = parsed.has(groupOptions.system) ? systemPath(parsed.valueOf(groupOptions.system)) : Utils.createDefaultBootClassPath(); this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY); - this.modulePathCP = createClassPath(parsed.has(groupOptions.modulePath) ? parsed.valuesOf(groupOptions.modulePath) : null, ClassPath.EMPTY); //XXX: expand directories(?) + if (parsed.has(groupOptions.modulePath)) { + this.modulePathCP = createClassPath(expandModulePathEntries(parsed.valuesOf(groupOptions.modulePath)), ClassPath.EMPTY); + } else { + this.modulePathCP = ClassPath.EMPTY; + } this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]))); this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP, systemCP, modulePathCP); - this.addExports = parsed.has(groupOptions.addExports) ? parsed.valueOf(groupOptions.addExports) : null; + this.addExports = parsed.has(groupOptions.addExports) ? parsed.valuesOf(groupOptions.addExports) : null; //TOOD: test check multiple(!!) this.addModules = parsed.has(groupOptions.addModules) ? parsed.valueOf(groupOptions.addModules) : null;; this.limitModules = parsed.has(groupOptions.limitModules) ? parsed.valueOf(groupOptions.limitModules) : null;; this.sourceLevel = parsed.valueOf(groupOptions.source); } + private ClassPath systemPath(File system) throws IOException { + try { + Method createModulePath = Util.class.getDeclaredMethod("createModulePath", Collection.class); + createModulePath.setAccessible(true); + return (ClassPath) createModulePath.invoke(null, List.of(FileUtil.toFileObject(system))); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } + + private List expandModulePathEntries(Iterable modulePathEntries) { + List expandedModulePath = new ArrayList<>(); + + for (File entry : modulePathEntries) { + if (entry.isFile()) { + expandedModulePath.add(entry); + } else if (new File(entry, "module-info.class").canRead()) { + expandedModulePath.add(entry); + } else if (entry.isDirectory()) { + File[] children = entry.listFiles(); + + if (children != null) { + expandedModulePath.addAll(List.of(children)); + } + } + } + + return expandedModulePath; + } } private static final class GlobalConfiguration { @@ -1162,8 +1198,10 @@ public List getArguments() { } if (rootConfiguration.addExports != null) { - result.add("--add-exports"); - result.add(rootConfiguration.addExports); + rootConfiguration.addExports.forEach(exp -> { + result.add("--add-exports"); + result.add(exp); + }); } return result; @@ -1309,7 +1347,7 @@ public ClassPath findClassPath(FileObject file, String type) { } } - if (ClassPath.BOOT.equals(type)) { + if (ClassPath.BOOT.equals(type) || JavaClassPathConstants.MODULE_BOOT_PATH.equals(type)) { return Utils.createDefaultBootClassPath(); } return null; @@ -1384,4 +1422,9 @@ private static void copyPreferences(Preferences from, Preferences to) throws Bac copyPreferences(from.node(child), to.node(child)); } } + + static { + //disable the source level downgrade, to avoid falling back to --release (which breaks --add-exports on system classes): + JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true; + } } diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java index f1b9fbb..2fcd4a3 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java @@ -19,7 +19,6 @@ package org.netbeans.modules.jackpot30.cmdline; import java.util.regex.Pattern; -import javax.annotation.processing.Processor; import org.netbeans.modules.jackpot30.cmdline.Main.BCPFallBack; import org.netbeans.modules.jackpot30.cmdline.Main.CompilerOptionsQueryImpl; import org.netbeans.modules.jackpot30.cmdline.Main.SourceLevelQueryImpl; diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index 4db71a5..eb11e40 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -39,8 +40,10 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.jar.JarOutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; @@ -639,10 +642,6 @@ public void testNoHintsFoundWithGroups() throws Exception { } public void testAutomaticTestRun() throws Exception { - if (System.getProperty("sun.boot.class.path") != null) { - //TODO XXX: this test does not pass on JDK 8 - return ; - } class Config { private final String commandLineOption; private final int result; @@ -1117,6 +1116,29 @@ public static Collection get() { } } + File moduleJar = new File(getWorkDir(), "ma.jar"); + + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(moduleJar))) { + Path classesOutputPath = classesOutput.toPath(); + + Files.walk(classesOutputPath) + .forEach(p -> { + try { + String path = classesOutputPath.relativize(p).toString(); + + if (path.isEmpty()) { + } else if (Files.isDirectory(p)) { + jos.putNextEntry(new ZipEntry(path + "/")); + } else { + jos.putNextEntry(new ZipEntry(path)); + jos.write(Files.readAllBytes(p)); + } + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + } + String golden = """ package test; @@ -1130,34 +1152,36 @@ private void test() { } """; - List filesAndOptions = new ArrayList<>(); - - filesAndOptions.add("src/test/Test.java"); - filesAndOptions.add(""" - package test; - import api.Api; - import impl.Impl; - public class Test { - private void test() { - boolean b1 = Api.get().size() == 0; - boolean b2 = Impl.get().size() == 0; + for (File modulePathVariants : new File[] {moduleJar, classesOutput, classesOutput.getParentFile()}) { + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import api.Api; + import impl.Impl; + public class Test { + private void test() { + boolean b1 = Api.get().size() == 0; + boolean b2 = Impl.get().size() == 0; + } } - } - """); - filesAndOptions.addAll(listFiles(classesOutput)); - filesAndOptions.add(null); - filesAndOptions.add("--apply"); - filesAndOptions.add("--hint"); - filesAndOptions.add(TEST_HINT); - filesAndOptions.add("--source"); filesAndOptions.add("17"); - filesAndOptions.add("--module-path"); filesAndOptions.add(classesOutput.getAbsolutePath()); - filesAndOptions.add("--add-modules"); filesAndOptions.add("ma"); - filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl=ALL-UNNAMED"); - - doRunCompiler(golden, - null, - null, - filesAndOptions.toArray(String[]::new)); + """); + filesAndOptions.addAll(listFiles(classesOutput, moduleJar)); + filesAndOptions.add(null); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--module-path"); filesAndOptions.add(modulePathVariants.getAbsolutePath()); + filesAndOptions.add("--add-modules"); filesAndOptions.add("ma"); + filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl=ALL-UNNAMED"); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } } public void testLimitModules() throws Exception { @@ -1211,6 +1235,44 @@ public static List test() { filesAndOptions.toArray(String[]::new)); } + public void testSystemOption() throws Exception { + clearWorkDir(); + + String golden = + """ + package test; + import java.util.List; + public class Test { + private void test(List l) { + boolean b = l.isEmpty(); + } + } + """; + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import java.util.List; + public class Test { + private void test(List l) { + boolean b = l.size() == 0; + } + } + """); + filesAndOptions.add(null); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--system"); filesAndOptions.add(System.getProperty("java.home")); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } + private static final String DONT_APPEND_PATH = new String("DONT_APPEND_PATH"); private static final String IGNORE = new String("IGNORE"); @@ -1429,20 +1491,22 @@ private File writeToPath(String relativePath, String content) throws Exception { return target; } - private List listFiles(File directory) throws IOException { + private List listFiles(File... files) throws IOException { Path base = getWorkDir().toPath(); List result = new ArrayList<>(); - Files.walk(directory.toPath()) - .filter(Files::isRegularFile) - .forEach(p -> { - try { - result.add(base.relativize(p).toString()); - result.add(BASE_64_PREFIX + Base64.getEncoder().encodeToString(Files.readAllBytes(p))); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - }); + for (File file : files) { + Files.walk(file.toPath()) + .filter(Files::isRegularFile) + .forEach(p -> { + try { + result.add(base.relativize(p).toString()); + result.add(BASE_64_PREFIX + Base64.getEncoder().encodeToString(Files.readAllBytes(p))); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + } return result; } From 695080c81e6cf0763774a0133655867d55fde2e7 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 26 May 2026 08:56:07 +0200 Subject: [PATCH 4/5] Resolving TODO. --- .../netbeans/modules/jackpot30/cmdline/Main.java | 6 +++--- .../modules/jackpot30/cmdline/MainTest.java | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java index add1338..3ac2c7b 100644 --- a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java +++ b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java @@ -1071,9 +1071,9 @@ public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOE } this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]))); this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP, systemCP, modulePathCP); - this.addExports = parsed.has(groupOptions.addExports) ? parsed.valuesOf(groupOptions.addExports) : null; //TOOD: test check multiple(!!) - this.addModules = parsed.has(groupOptions.addModules) ? parsed.valueOf(groupOptions.addModules) : null;; - this.limitModules = parsed.has(groupOptions.limitModules) ? parsed.valueOf(groupOptions.limitModules) : null;; + this.addExports = parsed.has(groupOptions.addExports) ? parsed.valuesOf(groupOptions.addExports) : null; + this.addModules = parsed.has(groupOptions.addModules) ? parsed.valueOf(groupOptions.addModules) : null; + this.limitModules = parsed.has(groupOptions.limitModules) ? parsed.valueOf(groupOptions.limitModules) : null; this.sourceLevel = parsed.valueOf(groupOptions.source); } diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index eb11e40..191a6b1 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -1099,6 +1099,16 @@ public static Collection get() { } } """)); + writeToPath("src/ma/impl2/Impl2.java", + """ + package impl2; + import java.util.Collection; + public class Impl2 { + public static Collection get() { + return null; + } + } + """)); File classesOutput = new File(getWorkDir(), "classes/ma"); classesOutput.getParentFile().mkdirs(); @@ -1144,10 +1154,12 @@ public static Collection get() { package test; import api.Api; import impl.Impl; + import impl2.Impl2; public class Test { private void test() { boolean b1 = Api.get().isEmpty(); boolean b2 = Impl.get().isEmpty(); + boolean b3 = Impl2.get().isEmpty(); } } """; @@ -1160,10 +1172,12 @@ private void test() { package test; import api.Api; import impl.Impl; + import impl2.Impl2; public class Test { private void test() { boolean b1 = Api.get().size() == 0; boolean b2 = Impl.get().size() == 0; + boolean b3 = Impl2.get().size() == 0; } } """); @@ -1176,6 +1190,7 @@ private void test() { filesAndOptions.add("--module-path"); filesAndOptions.add(modulePathVariants.getAbsolutePath()); filesAndOptions.add("--add-modules"); filesAndOptions.add("ma"); filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl=ALL-UNNAMED"); + filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl2=ALL-UNNAMED"); doRunCompiler(golden, null, From c2ec615c8612e9744490ac978bb10e0feeffc57a Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 26 May 2026 11:14:44 +0200 Subject: [PATCH 5/5] Fixing build. --- .../src/org/netbeans/modules/jackpot30/cmdline/MainTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index 191a6b1..4393508 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -1098,7 +1098,7 @@ public static Collection get() { return null; } } - """)); + """), writeToPath("src/ma/impl2/Impl2.java", """ package impl2;