diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/imports/ComputeImports.java b/java/java.editor/src/org/netbeans/modules/java/editor/imports/ComputeImports.java index 28f0cdceb135..297824f59621 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/imports/ComputeImports.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/imports/ComputeImports.java @@ -104,7 +104,6 @@ public ComputeImports(final CompilationInfo info) { } private final CompilationInfo info; - private CompilationInfo allInfo; private final PreferenceChangeListener pcl = new PreferenceChangeListener() { @Override @@ -165,42 +164,7 @@ private ComputeImports computeCandidatesEx(Set forcedUnresolved) { if (cache != null) { return cache; } - boolean modules = false; - - if (info.getSourceVersion().compareTo(SourceVersion.RELEASE_9) <= 0) { - if (info.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.SOURCE).findResource("module-info.java") != null) { - modules = true; - } - } - - if (modules) { - ClasspathInfo cpInfo = info.getClasspathInfo(); - ClasspathInfo extraInfo = ClasspathInfo.create( - ClassPathSupport.createProxyClassPath( - cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT), - cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_BOOT)), - ClassPathSupport.createProxyClassPath( - cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE), - cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_COMPILE), - cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_CLASS)), - cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE)); - JavaSource src = JavaSource.create(extraInfo, info.getSnapshot().getSource().getFileObject()); - try { - src.runUserActionTask(new Task() { - @Override - public void run(CompilationController parameter) throws Exception { - allInfo = parameter; - parameter.toPhase(JavaSource.Phase.RESOLVED); - doComputeCandidates(forcedUnresolved); - } - }, true); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } else { - allInfo = info; - doComputeCandidates(forcedUnresolved); - } + doComputeCandidates(forcedUnresolved); info.putCachedValue(IMPORT_CANDIDATES_KEY, this, CacheClearPolicy.ON_CHANGE); return this; } @@ -225,7 +189,7 @@ private synchronized void setVisitor(TreeVisitorImpl visitor) { private void doComputeCandidates(Set forcedUnresolved) { final CompilationUnitTree cut = info.getCompilationUnit(); - ClasspathInfo cpInfo = allInfo.getClasspathInfo(); + ClasspathInfo cpInfo = info.getClasspathInfo(); final TreeVisitorImpl v = new TreeVisitorImpl(info); setVisitor(v); try { @@ -257,7 +221,7 @@ private void doComputeCandidates(Set forcedUnresolved) { for (ElementHandle typeName : typeNames) { if (isCancelled()) return; - TypeElement te = typeName.resolve(allInfo); + TypeElement te = typeName.resolve(info); if (te == null) { Logger.getLogger(ComputeImports.class.getName()).log(Level.INFO, "Cannot resolve type element \"" + typeName + "\"."); @@ -283,7 +247,7 @@ private void doComputeCandidates(Set forcedUnresolved) { if (isCancelled()) return; - final TypeElement te = p.getEnclosingType().resolve(allInfo); + final TypeElement te = p.getEnclosingType().resolve(info); final Set idents = p.getSymbols(); if (te != null) { for (Element ne : te.getEnclosedElements()) { @@ -312,7 +276,7 @@ private void doComputeCandidates(Set forcedUnresolved) { possibleMethodFQNs.clear(); for (Hint hint: v.hints) { - wasChanged |= hint.filter(allInfo, this); + wasChanged |= hint.filter(info, this); } } diff --git a/java/java.source.base/src/org/netbeans/api/java/source/ClasspathInfo.java b/java/java.source.base/src/org/netbeans/api/java/source/ClasspathInfo.java index c80f9cb83d86..ad9b72c4d252 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/ClasspathInfo.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/ClasspathInfo.java @@ -61,6 +61,7 @@ import org.netbeans.modules.parsing.impl.indexing.PathRegistry; import org.netbeans.spi.java.classpath.ClassPathFactory; import org.netbeans.spi.java.classpath.ClassPathImplementation; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.BaseUtilities; @@ -525,8 +526,8 @@ ClassPath getCachedClassPath (PathKind pathKind) { public synchronized @NonNull ClassIndex getClassIndex () { if ( usagesQuery == null ) { usagesQuery = new ClassIndex ( - this.bootClassPath, - this.compileClassPath, + ClassPathSupport.createProxyClassPath(this.bootClassPath, this.moduleBootPath), + ClassPathSupport.createProxyClassPath(this.compileClassPath, this.moduleClassPath, this.moduleCompilePath), this.cachedSrcClassPath); } return usagesQuery; diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ClasspathInfoTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ClasspathInfoTest.java new file mode 100644 index 000000000000..f44d60d45ec9 --- /dev/null +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ClasspathInfoTest.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.api.java.source; + +import java.io.File; +import java.io.OutputStream; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.TypeElement; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.source.SourceUtilsTestUtil.FileDescription; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.source.BootClassPathUtil; +import org.netbeans.modules.java.source.indexing.JavaBinaryIndexer; +import org.netbeans.spi.editor.mimelookup.MimeDataProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +public class ClasspathInfoTest extends NbTestCase { + + public ClasspathInfoTest(String name) { + super(name); + } + + @Override + public void setUp() throws Exception { + SourceUtilsTestUtil.prepareTest(new String[0], new Object[] { + new JavaBinaryIndexerProvider() + }); + } + + public void testGetClassIndex() throws Exception { + clearWorkDir(); + + File work = getWorkDir(); + FileObject workFO = FileUtil.toFileObject(work); + + assertNotNull(workFO); + + FileObject module1 = workFO.createFolder("module1"); + FileObject module1Src = module1.createFolder("src"); + FileObject module1Classes = module1.createFolder("classes"); + + SourceUtilsTestUtil.writeFiles(module1Src, + new FileDescription("module-info.java", + """ + module module1 { + exports api; + } + """), + new FileDescription("api/Api.java", + """ + package api; + public class Api { + } + """)); + SourceUtilsTestUtil.compile(module1Src, module1Classes, "21"); + + FileObject module2 = workFO.createFolder("module2"); + FileObject module2Src = module2.createFolder("src"); + FileObject module2Classes = module2.createFolder("classes"); + + SourceUtilsTestUtil.writeFiles(module2Src, + new FileDescription("module-info.java", + """ + module module2 { + requires transitive module1; + exports api2; + } + """), + new FileDescription("api2/Api2.java", + """ + package api2; + public class Api2 { + } + """)); + SourceUtilsTestUtil.compile(module2Src, module2Classes, "21", "--module-path", FileUtil.toFile(module1Classes).getAbsolutePath()); + + FileObject module3 = workFO.createFolder("module3"); + FileObject module3Src = module3.createFolder("src"); + FileObject module3Classes = module3.createFolder("classes"); + + SourceUtilsTestUtil.writeFiles(module3Src, + new FileDescription("module-info.java", + """ + module module3 { + exports api3; + } + """), + new FileDescription("api3/Api3.java", + """ + package api3; + public class Api3 { + } + """)); + SourceUtilsTestUtil.compile(module3Src, module3Classes, "21"); + + FileObject patch = workFO.createFolder("patch"); + FileObject patchSrc = patch.createFolder("src"); + FileObject patchClasses = patch.createFolder("classes"); + + SourceUtilsTestUtil.writeFiles(patchSrc, + new FileDescription("api/AddedPatch.java", + """ + package api; + public class AddedPatch { + } + """)); + SourceUtilsTestUtil.compile(patchSrc, patchClasses, "21"); + + FileObject src = workFO.createFolder("src"); + FileObject classes = workFO.createFolder("classes"); + + SourceUtilsTestUtil.writeFiles(src, + new FileDescription("module-info.java", + """ + module test { + requires module2; + } + """), + new FileDescription("test/Test.java", + """ + package test; + public class Test { + } + """)); + + SourceUtilsTestUtil.prepareTest(src, classes, workFO.createFolder("cache"), new FileObject[] { + module1Classes, module2Classes, module3Classes, patchClasses //to make indexing work ( :-( ) - TODO: can be made better? + }, new FileObject[] { + module1Classes, module2Classes, module3Classes + }); + SourceUtilsTestUtil.setSourceLevel(src, "21"); + SourceUtilsTestUtil.compileRecursively(src); + + ClassPath srcPath = ClassPathSupport.createClassPath(src); + FileObject testFile = src.getFileObject("test/Test.java"); + + ClasspathInfo cpInfo; + + cpInfo = new ClasspathInfo.Builder(BootClassPathUtil.getBootClassPath()) //bootclasspath: prevent source level downgrade + .setModuleBootPath(BootClassPathUtil.getModuleBootPath()) + .setModuleCompilePath(ClassPathSupport.createClassPath(module1Classes.toURL(), module2Classes.toURL(), module3Classes.toURL())) + .setSourcePath(srcPath).build(); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[]{ + srcPath}); + Set found; + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api.Api", "api2.Api2", /*the index returns all element from the cpInfo, only only those visible from the source: */"api3.Api3"), found); + found = getDeclaredTypes(cpInfo, testFile, "String"); + assertTrue(found.contains("java.lang.String")); + assertTrue(found.contains("java.lang.StringBuilder")); + + //module classpath: + cpInfo = new ClasspathInfo.Builder(ClassPath.EMPTY) //attempt to cause source level downgrade + .setModuleBootPath(BootClassPathUtil.getModuleBootPath()) + .setModuleCompilePath(ClassPathSupport.createClassPath(module1Classes.toURL(), module2Classes.toURL(), module3Classes.toURL())) + .setModuleClassPath(ClassPathSupport.createClassPath(module3Classes.toURL())) + .setSourcePath(srcPath).build(); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api.Api", "api2.Api2", /*the index returns all element from the cpInfo, only only those visible from the source: */"api3.Api3"), found); + found = getDeclaredTypes(cpInfo, testFile, "String"); + assertTrue(found.contains("java.lang.String")); + assertTrue(found.contains("java.lang.StringBuilder")); + found = getDeclaredTypes(cpInfo, testFile, "List"); + assertTrue(found.contains("java.util.List")); + assertTrue(found.contains("java.awt.List")); //the index returns all element from the cpInfo, only only those visible from the source + + //module classpath: + cpInfo = new ClasspathInfo.Builder(BootClassPathUtil.getBootClassPath()) //bootclasspath: prevent source level downgrade + .setModuleBootPath(BootClassPathUtil.getModuleBootPath()) + .setModuleCompilePath(ClassPathSupport.createClassPath(module1Classes.toURL(), module2Classes.toURL(), module3Classes.toURL())) + .setModuleClassPath(ClassPathSupport.createClassPath(module3Classes.toURL())) + .setSourcePath(srcPath).build(); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api.Api", "api2.Api2", /*the index returns all element from the cpInfo, only only those visible from the source: */"api3.Api3"), found); + SourceUtilsTestUtil.setCompilerOptions(src, List.of("--add-reads=test=ALL-UNNAMED")); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api.Api", "api2.Api2", "api3.Api3"), found); + SourceUtilsTestUtil.setCompilerOptions(src, null); + + //unnamed module: + src.getFileObject("module-info.java").delete(); + SourceUtilsTestUtil.compileRecursively(src); + SourceUtilsTestUtil.setCompilerOptions(src, List.of("--limit-modules=module2", "--add-modules=module2")); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api.Api", "api2.Api2", /*the index returns all element from the cpInfo, only only those visible from the source: */"api3.Api3"), found); + SourceUtilsTestUtil.setCompilerOptions(src, List.of("--limit-modules=module3", "--add-modules=module3")); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api3.Api3", /*the index returns all element from the cpInfo, only only those visible from the source: */"api.Api", "api2.Api2"), found); + SourceUtilsTestUtil.setCompilerOptions(src, null); + + //patch module is sent using CompilerOptionsQuery, and is not available for ClasspathInfo: +// //patch-module: +// SourceUtilsTestUtil.setCompilerOptions(src, List.of("--add-modules=module1", "--patch-module=module1=" + FileUtil.toFile(patchClasses).getAbsolutePath())); +// found = getDeclaredTypes(cpInfo, testFile, "AddedPat"); +// assertEquals(Set.of("api.AddedPatch"), found); + + //source level == 8: + SourceUtilsTestUtil.setSourceLevel(src, "8"); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of(/*the index returns all element from the cpInfo, only only those visible from the source: */"api.Api", "api2.Api2", "api3.Api3"), found); + cpInfo = new ClasspathInfo.Builder(BootClassPathUtil.getBootClassPath()) + .setClassPath(ClassPathSupport.createClassPath(module3Classes.toURL())) + .setSourcePath(srcPath).build(); + found = getDeclaredTypes(cpInfo, testFile, "Api"); + assertEquals(Set.of("api3.Api3"), found); + } + + private Set getDeclaredTypes(ClasspathInfo cpInfo, FileObject file, String prefix) throws Exception { + //force reparse: + byte[] data = file.asBytes(); + try (OutputStream out = file.getOutputStream()) { + out.write(data); + } + + Set found = new HashSet<>(); + + JavaSource.create(cpInfo, file) + .runWhenScanFinished(cc -> {}, true).get(); //ensure indexing finished + + ClassIndex ci = cpInfo.getClassIndex(); + Set> types = ci.getDeclaredTypes(prefix, ClassIndex.NameKind.PREFIX, EnumSet.of(ClassIndex.SearchScope.DEPENDENCIES)); + types.stream() + .map(eh -> eh.getBinaryName()) + .forEach(bn -> { + assertFalse(found.contains(bn)); //ensure no duplicates + found.add(bn); + }); + + found.remove("com.sun.tools.javac.util.DefinedBy$Api"); //TODO: workaround + return found; + } + + public static final class JavaBinaryIndexerProvider implements MimeDataProvider { + + private final Lookup lookup = Lookups.fixed(new JavaBinaryIndexer.Factory()); + + public Lookup getLookup(MimePath mimePath) { + if (mimePath.getPath().isEmpty()) { + return lookup; + } + return Lookup.EMPTY; + } + + } + + static { + System.setProperty("SourcePath.no.source.filter", "true"); + } +} diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementUtilitiesTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementUtilitiesTest.java index 0d369a34024e..f5e33b7ffd44 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementUtilitiesTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementUtilitiesTest.java @@ -57,6 +57,7 @@ import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNotNull; +import org.netbeans.api.java.source.SourceUtilsTestUtil.FileDescription; /** * @@ -94,39 +95,12 @@ private void prepareTest(FileDescription... fileNameAndContent) throws Exception SourceUtilsTestUtil.prepareTest(sourceRoot, buildRoot, cache, EMPTY_PATH, modulePathElements); if (fileNameAndContent.length > 0) { - testFO = writeFiles(sourceRoot, fileNameAndContent); + testFO = SourceUtilsTestUtil.writeFiles(sourceRoot, fileNameAndContent); } else { testFO = sourceRoot.createData("Test.java"); } } - private FileObject writeFiles(FileObject src, - FileDescription... fileNameAndContent) throws Exception { - FileObject firstFile = null; - - for (FileDescription fileDescription : fileNameAndContent) { - FileObject f = writeFile(src, - fileDescription.path(), - fileDescription.content()); - - if (firstFile == null) { - firstFile = f; - } - } - - return firstFile; - } - - private FileObject writeFile(FileObject root, - String path, - String content) throws Exception { - FileObject file = FileUtil.createData(root, path); - - TestUtilities.copyStringToFile(FileUtil.toFile(file), content); - - return file; - } - public void testGetImplementationOfAndOverriden() throws Exception { prepareTest(); SourceUtilsTestUtil.setSourceLevel(testFO, "8"); @@ -857,7 +831,7 @@ public void testTransitivelyExportedPackages() throws Exception { FileObject module1Src = module1.createFolder("src"); FileObject module1Classes = module1.createFolder("classes"); - writeFiles(module1Src, + SourceUtilsTestUtil.writeFiles(module1Src, new FileDescription("module-info.java", """ module module1 { @@ -890,13 +864,13 @@ public class Api1c { public class Impl1 { } """)); - compile(module1Src, module1Classes, "24"); + SourceUtilsTestUtil.compile(module1Src, module1Classes, "24"); FileObject module2 = workFO.createFolder("module2"); FileObject module2Src = module2.createFolder("src"); FileObject module2Classes = module2.createFolder("classes"); - writeFiles(module2Src, + SourceUtilsTestUtil.writeFiles(module2Src, new FileDescription("module-info.java", """ module module2 { @@ -930,7 +904,7 @@ public class Api2c { public class Impl2 { } """)); - compile(module2Src, module2Classes, "24", "--module-path", FileUtil.toFile(module1Classes).getAbsolutePath()); + SourceUtilsTestUtil.compile(module2Src, module2Classes, "24", "--module-path", FileUtil.toFile(module1Classes).getAbsolutePath()); modulePathElements = new FileObject[] { module1Classes, @@ -963,30 +937,4 @@ public void run(CompilationController controller) throws IOException { } }, true); } - - private void compile(FileObject src, FileObject classes, String sourceLevel, String... extraOpts) throws IOException { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { - List sources = new ArrayList<>(); - - for (Enumeration en = src.getChildren(true); en.hasMoreElements(); ) { - FileObject c = en.nextElement(); - - if (c.isData() && "text/x-java".equals(c.getMIMEType())) { - sources.add(FileUtil.toFile(c)); - } - } - - Iterable sourceFileObjects = fm.getJavaFileObjectsFromFiles(sources); - List options = new ArrayList<>(); - - options.addAll(List.of("--release", sourceLevel, "-d")); - options.addAll(List.of(FileUtil.toFile(classes).getAbsolutePath())); - options.addAll(List.of(extraOpts)); - - assertTrue(compiler.getTask(null, fm, null, options, null, sourceFileObjects).call()); - } - } - - private record FileDescription(String path, String content) {} } diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTestUtil.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTestUtil.java index 9a1c9220283c..233ed5b4e211 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTestUtil.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTestUtil.java @@ -30,6 +30,10 @@ import java.util.stream.Collectors; import javax.swing.event.ChangeListener; import javax.swing.text.Document; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; import org.junit.Assert; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.java.classpath.ClassPath; @@ -72,6 +76,9 @@ import org.openide.util.lookup.ServiceProvider; import org.xml.sax.SAXException; +import static junit.framework.TestCase.assertTrue; +import org.netbeans.modules.java.source.indexing.JavaBinaryIndexer; + /** * * @author Jan Lahoda @@ -447,6 +454,60 @@ public void run( CompilationController info ) { } + public static void compile(FileObject src, FileObject classes, String sourceLevel, String... extraOpts) throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { + List sources = new ArrayList<>(); + + for (Enumeration en = src.getChildren(true); en.hasMoreElements(); ) { + FileObject c = en.nextElement(); + + if (c.isData() && "text/x-java".equals(c.getMIMEType())) { + sources.add(FileUtil.toFile(c)); + } + } + + Iterable sourceFileObjects = fm.getJavaFileObjectsFromFiles(sources); + List options = new ArrayList<>(); + + options.addAll(List.of("--release", sourceLevel, "-d")); + options.addAll(List.of(FileUtil.toFile(classes).getAbsolutePath())); + options.addAll(List.of(extraOpts)); + + assertTrue(compiler.getTask(null, fm, null, options, null, sourceFileObjects).call()); + } + } + + public static FileObject writeFiles(FileObject src, + FileDescription... fileNameAndContent) throws Exception { + FileObject firstFile = null; + + for (FileDescription fileDescription : fileNameAndContent) { + FileObject f = writeFile(src, + fileDescription.path(), + fileDescription.content()); + + if (firstFile == null) { + firstFile = f; + } + } + + return firstFile; + } + + public static FileObject writeFile(FileObject root, + String path, + String content) throws Exception { + FileObject file = FileUtil.createData(root, path); + + TestUtilities.copyStringToFile(FileUtil.toFile(file), content); + + return file; + } + + + public record FileDescription(String path, String content) {} + @ServiceProvider(service=MimeDataProvider.class) public static final class JavacParserProvider implements MimeDataProvider {