diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameRefactoringPlugin.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameRefactoringPlugin.java index 8378dc3b3198..f0d96c8656da 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameRefactoringPlugin.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameRefactoringPlugin.java @@ -367,23 +367,8 @@ protected JavaSource getJavaSource(Phase p) { if (treePathHandle == null && docTreePathHandle == null) { return null; } - switch (p) { - case PRECHECK: - case FASTCHECKPARAMETERS: - return JavaSource.forFileObject(docTreePathHandle != null? docTreePathHandle.getTreePathHandle().getFileObject() - : treePathHandle.getFileObject()); - case PREPARE: - case CHECKPARAMETERS: - if(treePathHandle != null && treePathHandle.getKind() == Tree.Kind.LABELED_STATEMENT) { - return JavaSource.forFileObject(treePathHandle.getFileObject()); - } - ClasspathInfo cpInfo = getClasspathInfo(refactoring); - JavaSource source = JavaSource.create(cpInfo, docTreePathHandle != null? docTreePathHandle.getTreePathHandle().getFileObject() - : treePathHandle.getFileObject()); - return source; - - } - throw new IllegalStateException(); + return JavaSource.forFileObject(docTreePathHandle != null? docTreePathHandle.getTreePathHandle().getFileObject() + : treePathHandle.getFileObject()); } @Override @@ -408,16 +393,10 @@ private Set getRelevantFiles() { JavaSource source = getJavaSource(Phase.PREPARE); try { - source.runUserActionTask(new CancellableTask() { - - @Override - public void cancel() { - throw new UnsupportedOperationException("Not supported yet."); // NOI18N - } - + source.runUserActionTask(new Task() { @Override public void run(CompilationController info) throws Exception { - final ClassIndex idx = info.getClasspathInfo().getClassIndex(); + final ClassIndex idx = getClasspathInfo(refactoring).getClassIndex(); info.toPhase(JavaSource.Phase.RESOLVED); Element el = docTreePathHandle != null? ((DocTrees)info.getTrees()).getElement(docTreePathHandle.resolve(info)) : treePathHandle.resolveElement(info); @@ -631,7 +610,7 @@ public Problem prepare(RefactoringElementsBag elements) { Set a = getRelevantFiles(); fireProgressListenerStart(AbstractRefactoring.PREPARE, a.size()); TransformTask transform = new TransformTask(new RenameTransformer(treePathHandle, docTreePathHandle, refactoring, allMethods, recordLinkedDeclarations, refactoring.isSearchInComments()), treePathHandle != null && treePathHandle.getKind() == Tree.Kind.LABELED_STATEMENT ? null : treePathHandle); - Problem problem = createAndAddElements(a, transform, elements, refactoring,getClasspathInfo(refactoring)); + Problem problem = createAndAddElements(a, transform, elements, null); fireProgressListenerStop(); return problem; } diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java index 99555656184d..bcaab9d1ecb5 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/JavaRefactoringPlugin.java @@ -268,13 +268,15 @@ private Collection processFiles(Set sourceFiles, return results; } - private void processFiles(Map> work, ClasspathInfo info, boolean modification, Collection results, CancellableTask task) throws IOException, IllegalArgumentException { + //the meaning of overrideInfo (used to be info) is very unclear, and is suspicious. Possibly, it should be eliminated: + private void processFiles(Map> work, ClasspathInfo overrideInfo, boolean modification, Collection results, CancellableTask task) throws IOException, IllegalArgumentException { for (Map.Entry> entry : work.entrySet()) { if (cancelRequested.get()) { results.clear(); return; } final FileObject root = entry.getKey(); + ClasspathInfo info = overrideInfo; if (info == null) { ClassPath bootPath = ClassPath.getClassPath(root, ClassPath.BOOT); if (bootPath == null) { diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/plugins/Bundle_test.properties b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/plugins/Bundle_test.properties index d362c653fbaa..067ec0affd78 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/plugins/Bundle_test.properties +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/plugins/Bundle_test.properties @@ -108,4 +108,5 @@ ERR_InvertMethodInInterface=ERR_InvertMethodInInterface #SafeDelete ERR_VarNotInBlockOrMethod=ERR_VarNotInBlockOrMethod WRN_Implements=WRN_Implements -ERR_ReferencesFound=ERR_ReferencesFound \ No newline at end of file +ERR_ReferencesFound=ERR_ReferencesFound +ERR_Overrides=ERR_Overrides diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/ComplexProjectHierarchyTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/ComplexProjectHierarchyTest.java new file mode 100644 index 000000000000..23f107a94623 --- /dev/null +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/ComplexProjectHierarchyTest.java @@ -0,0 +1,180 @@ +/* + * 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.modules.refactoring.java.test; + +import com.sun.source.util.TreePath; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import org.netbeans.api.java.source.CompilationController; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.TreePathHandle; +import org.netbeans.modules.refactoring.api.Problem; +import org.netbeans.modules.refactoring.api.RefactoringSession; +import org.netbeans.modules.refactoring.api.RenameRefactoring; +import org.netbeans.modules.refactoring.java.ui.JavaRenameProperties; +import org.openide.filesystems.FileObject; +import org.openide.util.lookup.Lookups; + +public class ComplexProjectHierarchyTest extends RefactoringTestBase { + + private final ProjectDesc side = new ProjectDesc("side"); + private final ProjectDesc base = new ProjectDesc("base"); + private final ProjectDesc main = new ProjectDesc("main", side, base); + + public ComplexProjectHierarchyTest(String name) { + super(name, "17"); + } + + public void testComplexRename1() throws Exception { + writeFilesAndWaitForScan(getSource(side), + new File("side/Side.java", + """ + package side; + public class Side {} + """)); + + String baseCode = """ + package base; + public class Base { + public void te|st(T t) {} + } + """; + int pos = baseCode.indexOf('|'); + + writeFilesAndWaitForScan(getSource(base), + new File("base/Base.java", + baseCode.substring(0, pos) + baseCode.substring(pos + 1))); + + writeFilesAndWaitForScan(getSource(main), + new File("main/Main.java", + """ + package main; + import base.Base; + import side.Side; + public class Main extends Base { + public void test(Side t) {} + public void test(String t) {} + public void run() { + test(new Side()); + } + } + """)); + performRename(getSource(base).getFileObject("base/Base.java"), pos, "test2", null, false); + verifyContent(getSource(main), + new File("main/Main.java", + """ + package main; + import base.Base; + import side.Side; + public class Main extends Base { + public void test2(Side t) {} + public void test(String t) {} + public void run() { + test2(new Side()); + } + } + """)); + } + + public void testComplexRename2() throws Exception { + writeFilesAndWaitForScan(getSource(side), + new File("side/Side.java", + """ + package side; + public class Side {} + """)); + writeFilesAndWaitForScan(getSource(base), + new File("base/Base.java", + """ + package base; + public class Base { + public void test(T t) {} + } + """)); + + String mainCode = """ + package main; + import base.Base; + import side.Side; + public class Main extends Base { + public void test(Side t) {} + public void test(String t) {} + public void run() { + te|st(new Side()); + } + } + """; + + int pos = mainCode.indexOf('|'); + + writeFilesAndWaitForScan(getSource(main), + new File("main/Main.java", + mainCode.substring(0, pos) + mainCode.substring(pos + 1))); + performRename(getSource(main).getFileObject("main/Main.java"), pos, "test2", null, false, new Problem(false, "ERR_Overrides")); + verifyContent(getSource(main), + new File("main/Main.java", + """ + package main; + import base.Base; + import side.Side; + public class Main extends Base { + public void test2(Side t) {} + public void test(String t) {} + public void run() { + test2(new Side()); + } + } + """)); + } + + @Override + protected List projects() { + return List.of(main, base, side); + } + + private void performRename(FileObject source, final int absPos, final String newname, final JavaRenameProperties props, final boolean searchInComments, Problem... expectedProblems) throws Exception { + final RenameRefactoring[] r = new RenameRefactoring[1]; + JavaSource.forFileObject(source) + .runUserActionTask(javac -> { + javac.toPhase(JavaSource.Phase.RESOLVED); + TreePath tp = javac.getTreeUtilities().pathFor(absPos); + + r[0] = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(tp, javac))); + r[0].setNewName(newname); + r[0].setSearchInComments(searchInComments); + if(props != null) { + r[0].getContext().add(props); + } + }, true); + + RefactoringSession rs = RefactoringSession.create("Rename"); + List problems = new LinkedList<>(); + + addAllProblems(problems, r[0].preCheck()); + if (!problemIsFatal(problems)) { + addAllProblems(problems, r[0].prepare(rs)); + } + if (!problemIsFatal(problems)) { + addAllProblems(problems, rs.doRefactoring(true)); + } + + assertProblems(Arrays.asList(expectedProblems), problems); + } +} \ No newline at end of file diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java index ebc261b08126..ad685bbbb9ef 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java @@ -21,6 +21,7 @@ import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; +import java.net.URL; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; @@ -30,14 +31,18 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.swing.event.ChangeListener; 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.classpath.JavaClassPathConstants; +import org.netbeans.api.java.queries.SourceForBinaryQuery; import org.netbeans.api.java.source.SourceUtilsTestUtil; import org.netbeans.api.java.source.TestUtilities; import org.netbeans.api.project.Project; @@ -61,12 +66,14 @@ import org.netbeans.spi.gototest.TestLocator.LocationResult; import org.netbeans.spi.java.classpath.ClassPathProvider; import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation; import org.netbeans.spi.project.ProjectFactory; import org.netbeans.spi.project.ProjectState; import org.netbeans.spi.project.support.GenericSources; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; @@ -194,7 +201,9 @@ public File(String filename, String content) { protected FileObject src; protected FileObject test; protected Project prj; - private ClassPath sourcePath; + private Map projectDesc2Impl = new HashMap<>(); + private Map projectImpl2Desc = new HashMap<>(); + private ClassPath[] sourcePath; private final String sourcelevel; @Override @@ -209,18 +218,20 @@ protected void setUp() throws Exception { new ClassPathProvider() { @Override public ClassPath findClassPath(FileObject file, String type) { - if (sourcePath != null && sourcePath.contains(file)){ - if (ClassPath.BOOT.equals(type)) { - return TestUtil.getBootClassPath(); - } - if (JavaClassPathConstants.MODULE_BOOT_PATH.equals(type)) { - return BootClassPathUtil.getModuleBootPath(); - } - if (ClassPath.COMPILE.equals(type)) { - return ClassPathSupport.createClassPath(new FileObject[0]); - } - if (ClassPath.SOURCE.equals(type)) { - return sourcePath; + for (ProjectImpl impl : projectImpl2Desc.keySet()) { + if (impl.sourcePath().contains(file)){ + if (ClassPath.BOOT.equals(type)) { + return TestUtil.getBootClassPath(); + } + if (JavaClassPathConstants.MODULE_BOOT_PATH.equals(type)) { + return BootClassPathUtil.getModuleBootPath(); + } + if (ClassPath.COMPILE.equals(type)) { + return impl.compilePath(); + } + if (ClassPath.SOURCE.equals(type)) { + return impl.sourcePath(); + } } } @@ -228,15 +239,24 @@ public ClassPath findClassPath(FileObject file, String type) { } }, new ProjectFactory() { + private ProjectImpl projectImplFor(FileObject projectDirectory) { + for (ProjectImpl impl : projectImpl2Desc.keySet()) { + if (impl.prjDir() == projectDirectory) { + return impl; + } + } + return null; + } @Override public boolean isProject(FileObject projectDirectory) { - return src != null && src.getParent() == projectDirectory; + return projectImplFor(projectDirectory) != null; } @Override public Project loadProject(final FileObject projectDirectory, ProjectState state) throws IOException { - if (!isProject(projectDirectory)) { - return null; - } + ProjectImpl impl = projectImplFor(projectDirectory); + if (impl == null) { + return null; + } return new Project() { @Override public FileObject getProjectDirectory() { @@ -249,8 +269,9 @@ public Lookup getLookup() { @Override public SourceGroup[] getSourceGroups(String type) { - return new SourceGroup[] {GenericSources.group(p, src.getParent(), "source", "Java Sources", null, null), - GenericSources.group(p, test, "testsources", "Test Sources", null, null)}; + //XXX: the source groups are weird - why .getParent() for sources? + return new SourceGroup[] {GenericSources.group(p, impl.src().getParent(), "source", "Java Sources", null, null), + GenericSources.group(p, impl.test(), "testsources", "Test Sources", null, null)}; } @Override @@ -265,7 +286,7 @@ public void removeChangeListener(ChangeListener listener) { }; } @Override - public void saveProject(Project project) throws IOException, ClassCastException {} + public void saveProject(Project project) throws IOException, ClassCastException {} }, new TestLocator() { @@ -287,11 +308,12 @@ public LocationResult findOpposite(FileObject fo, int caretOffset) { return new LocationResult("File not found"); //NOI18N } + ProjectImpl impl = projectImpl2Desc.keySet().stream().filter(i -> i.sourcePath == srcCp).findAny().orElseThrow(); String baseResName = srcCp.getResourceName(fo, '/', false); String testResName = getTestResName(baseResName, fo.getExt()); assert testResName != null; - FileObject fileObject = test.getFileObject(testResName); - if(fileObject != null) { + FileObject fileObject = impl.test.getFileObject(testResName); + if(fileObject != null) { return new LocationResult(fileObject, -1); } @@ -305,17 +327,19 @@ public void findOpposite(FileObject fo, int caretOffset, LocationListener callba @Override public FileType getFileType(FileObject fo) { - if(FileUtil.isParentOf(test, fo)) { - return FileType.TEST; - } else if(FileUtil.isParentOf(src, fo)) { - return FileType.TESTED; + for (ProjectImpl impl : projectImpl2Desc.keySet()) { + if(FileUtil.isParentOf(impl.test(), fo)) { + return FileType.TEST; + } else if(FileUtil.isParentOf(impl.src(), fo)) { + return FileType.TESTED; + } } return FileType.NEITHER; } private String getTestResName(String baseResName, String ext) { - StringBuilder buf - = new StringBuilder(baseResName.length() + ext.length() + 10); + StringBuilder buf + = new StringBuilder(baseResName.length() + ext.length() + 10); buf.append(baseResName).append("Test"); //NOI18N if (ext.length() != 0) { buf.append('.').append(ext); @@ -329,17 +353,78 @@ private String getTestResName(String baseResName, String ext) { public String getSourceLevel(FileObject javaFile) { return sourcelevel; } + }, + new SourceForBinaryQueryImplementation() { + @Override + public SourceForBinaryQuery.Result findSourceRoots(URL binaryRoot) { + FileObject binary = URLMapper.findFileObject(binaryRoot); + for (ProjectImpl impl : projectImpl2Desc.keySet()) { + if (impl.output().equals(binary)) { + return new SourceForBinaryQuery.Result() { + @Override + public FileObject[] getRoots() { + return new FileObject[] {impl.src, impl.test}; + } + @Override + public void addChangeListener(ChangeListener l) {} + @Override + public void removeChangeListener(ChangeListener l) {} + }; + } + } + return null; + } }}); Main.initializeURLFactory(); org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects(); // org.netbeans.modules.java.source.TreeLoader.DISABLE_CONFINEMENT_TEST = true; - prepareTest(); - org.netbeans.api.project.ui.OpenProjects.getDefault().open(new Project[] {prj = ProjectManager.getDefault().findProject(src.getParent())}, false); + FileObject workdir = SourceUtilsTestUtil.makeScratchDir(this); + Map tempProjectDesc2Impl = new HashMap<>(); + Map tempProjectImpl2Desc = new HashMap<>(); + List projects = projects(); + + for (ProjectDesc desc : projects) { + FileObject projectFolder = FileUtil.createFolder(workdir, desc.name()); + FileObject src = FileUtil.createFolder(projectFolder, "src"); + FileObject test = FileUtil.createFolder(projectFolder, "test"); + FileObject output = FileUtil.createFolder(projectFolder, "output"); + ProjectImpl impl = new ProjectImpl(projectFolder, src, test, output, ClassPathSupport.createClassPath(src, test), ClassPath.EMPTY); + tempProjectDesc2Impl.put(desc, impl); + tempProjectImpl2Desc.put(impl, desc); + } + + //correct the compile classpath: + for (Entry e : tempProjectImpl2Desc.entrySet()) { + ClassPath compileCP = ClassPathSupport.createClassPath(Arrays.stream(e.getValue().dependencies).map(desc -> tempProjectDesc2Impl.get(desc).output()).toArray(FileObject[]::new)); + ProjectImpl newProjectImpl = new ProjectImpl(e.getKey().prjDir(), e.getKey().src(), e.getKey().test(), e.getKey().output(), e.getKey().sourcePath(), compileCP); + projectImpl2Desc.put(newProjectImpl, e.getValue()); + projectDesc2Impl.put(e.getValue(), newProjectImpl); + } + + //load projects: + Function loadProject = dir -> { + try { + return ProjectManager.getDefault().findProject(dir); + } catch (IOException | IllegalArgumentException ex) { + throw new AssertionError(ex); + } + }; + Map tempDesc2Project = tempProjectDesc2Impl.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> loadProject.apply(e.getValue().prjDir()))); + + FileObject cache = FileUtil.createFolder(workdir, "cache"); + + CacheFolder.setCacheFolder(cache); + + org.netbeans.api.project.ui.OpenProjects.getDefault().open(tempDesc2Project.values().toArray(Project[]::new), false); MimeTypes.setAllMimeTypes(Collections.singleton("text/x-java")); - sourcePath = ClassPathSupport.createClassPath(src, test); - GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {sourcePath}); + ProjectImpl primaryImpl = tempProjectDesc2Impl.get(projects.get(0)); + src = primaryImpl.src(); + test = primaryImpl.test(); + prj = tempDesc2Project.get(projects.get(0)); + sourcePath = projectImpl2Desc.keySet().stream().map(impl -> impl.sourcePath).toArray(ClassPath[]::new); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, sourcePath); RepositoryUpdater.getDefault().start(true); super.setUp(); FileUtil.createData(FileUtil.getConfigRoot(), "Templates/Classes/Empty.java"); @@ -349,7 +434,7 @@ public String getSourceLevel(FileObject javaFile) { @Override protected void tearDown() throws Exception { super.tearDown(); - GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {sourcePath}); + GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, sourcePath); org.netbeans.api.project.ui.OpenProjects.getDefault().close(new Project[] {prj}); CountDownLatch cdl = new CountDownLatch(1); RepositoryUpdater.getDefault().stop(() -> { @@ -357,19 +442,12 @@ protected void tearDown() throws Exception { }); cdl.await(); prj = null; + projectImpl2Desc.clear(); } - private void prepareTest() throws Exception { - FileObject workdir = SourceUtilsTestUtil.makeScratchDir(this); - - FileObject projectFolder = FileUtil.createFolder(workdir, "testProject"); - src = FileUtil.createFolder(projectFolder, "src"); - test = FileUtil.createFolder(projectFolder, "test"); - - FileObject cache = FileUtil.createFolder(workdir, "cache"); - - CacheFolder.setCacheFolder(cache); - } + public FileObject getSource(ProjectDesc desc) { + return projectDesc2Impl.get(desc).src(); + } @ServiceProvider(service=MimeDataProvider.class) public static final class MimeDataProviderImpl implements MimeDataProvider { @@ -417,4 +495,10 @@ protected void runTest() throws Throwable { throw exc; } + protected List projects() { + return List.of(new ProjectDesc("testProject")); + } + + protected record ProjectDesc(String name, ProjectDesc... dependencies) {} + private record ProjectImpl(FileObject prjDir, FileObject src, FileObject test, FileObject output, ClassPath sourcePath, ClassPath compilePath) {} } \ No newline at end of file