diff --git a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/Utilities.java b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/Utilities.java index 0901d96e3fd8..60255382e069 100644 --- a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/Utilities.java +++ b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/Utilities.java @@ -268,16 +268,24 @@ private static Token findIdentifierSpanImpl(CompilationInfo info, T if (class2Kind.get(MethodTree.class).contains(leaf.getKind())) { MethodTree method = (MethodTree) leaf; + TreePath parentPath = decl.getParentPath(); List rightTrees = new ArrayList(); - rightTrees.addAll(method.getParameters()); + boolean ignoreParameters = parentPath.getLeaf().getKind() == Kind.RECORD && + !method.getParameters().isEmpty() && + info.getTreeUtilities().isSynthetic(new TreePath(decl, method.getParameters().get(0))); + + if (!ignoreParameters) { + rightTrees.addAll(method.getParameters()); + } + rightTrees.addAll(method.getThrows()); rightTrees.add(method.getBody()); Name name = method.getName(); if (method.getReturnType() == null) - name = ((ClassTree) decl.getParentPath().getLeaf()).getSimpleName(); + name = ((ClassTree) parentPath.getLeaf()).getSimpleName(); return findIdentifierSpanImpl(info, leaf, method.getReturnType(), rightTrees, name.toString(), info.getCompilationUnit(), info.getTrees().getSourcePositions()); } diff --git a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java index 29b188e84bab..205680824678 100644 --- a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java +++ b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/MarkOccDetTest.java @@ -349,6 +349,36 @@ public void testMatchBindings() throws Exception { "[MARK_OCCURRENCES], 3:30-3:33"); } + public void testRecordCompactConstructors1() throws Exception { + performTest("MatchBindings.java", + "public record MatchBindings(String compact) {\n" + + " public MatchBindings {\n" + + " }\n" + + " private static MatchBindings create() {\n" + + " return new MatchBindings(null);\n" + + " }\n" + + "}\n", + 1, + 20, + "[MARK_OCCURRENCES], 1:11-1:24", + "[MARK_OCCURRENCES], 4:19-4:32"); + } + + public void testRecordCompactConstructors2() throws Exception { + performTest("MatchBindings.java", + "public record MatchBindings(String compact) {\n" + + " public MatchBindings {\n" + + " }\n" + + " private static MatchBindings create() {\n" + + " return new MatchBindings(null);\n" + + " }\n" + + "}\n", + 4, + 20, + "[MARK_OCCURRENCES], 1:11-1:24", + "[MARK_OCCURRENCES], 4:19-4:32"); + } + //Support for exotic identifiers has been removed 6999438 public void REMOVEDtestExoticIdentifiers1() throws Exception { performTest("ExoticIdentifier", 3, 43); diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformer.java b/java/java.editor/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformer.java index 5e775e157aea..2622a86c9a33 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformer.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformer.java @@ -451,6 +451,9 @@ private static boolean allowInstantRename(CompilationInfo info, Element e, Eleme return false; } } + if (info.getElementUtilities().getLinkedRecordElements(e).size() > 1) { + return false; + } if (org.netbeans.modules.java.editor.base.semantic.Utilities.isPrivateElement(e)) { return true; } diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenameActionTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenameActionTest.java index fa1439d916c7..ebe1fcedad9e 100644 --- a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenameActionTest.java +++ b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenameActionTest.java @@ -297,6 +297,16 @@ public void testIsInaccessibleOutsideOuterClassForAnnTypeMethodOfPrivateNestedCl validateChangePoints(changePoints, 87, 93); } + public void testNoInstanceRenameForRecordComponents() throws Exception { + sourceLevel = "17"; + + boolean[] wasResolved = new boolean[1]; + Collection changePoints = performTest("package test; public class Test { private record Rec(String component) { } }", 120 - 55, wasResolved); + + assertNull(changePoints); + assertTrue(wasResolved[0]); + } + private void validateChangePoints(Collection changePoints, int... origs) { Set awaited = new HashSet(); @@ -345,6 +355,7 @@ public String toString() { } private FileObject source; + private String sourceLevel; private Collection performTest(String sourceCode, final int offset, boolean[] wasResolved) throws Exception { FileObject root = makeScratchDir(this); @@ -358,7 +369,11 @@ private Collection performTest(String sourceCode, final int offset, boole writeIntoFile(source, sourceCode); SourceUtilsTestUtil.prepareTest(sourceDir, buildDir, cacheDir, new FileObject[0]); - + + if (sourceLevel != null) { + SourceUtilsTestUtil.setSourceLevel(sourceDir, sourceLevel); + } + DataObject od = DataObject.find(source); EditorCookie ec = od.getCookie(EditorCookie.class); Document doc = ec.openDocument(); diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformerTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformerTest.java index 4930bfaa9194..cf7697002067 100644 --- a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformerTest.java +++ b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/rename/InstantRenamePerformerTest.java @@ -56,7 +56,9 @@ * @author lahvac */ public class InstantRenamePerformerTest extends NbTestCase { - + + private String sourceLevel; + public InstantRenamePerformerTest(String testName) { super(testName); } @@ -193,6 +195,13 @@ public void testPatternBinding() throws Exception { performTest("package test; public class Test { public void test(Object o) {boolean b = o instanceof String s|tr && str.isEmpty(); } }", 117 - 22, ke, "package test; public class Test { public void test(Object o) {boolean b = o instanceof String satr && satr.isEmpty(); } }", true); } + public void testRecordWithCompactConstructor1() throws Exception { + sourceLevel = "17"; + + KeyEvent ke = new KeyEvent(new JFrame(), KeyEvent.KEY_TYPED, 0, 0, KeyEvent.VK_UNDEFINED, 'e'); + performTest("package test; public class Test { private record R|c(String str) { public Rc {} } }", 72 - 22, ke, - 1, "package test; public class Test { private record Rec(String str) { public Rec {} } }", true); + } + private void performTest(String sourceCode, int offset, KeyEvent ke, String golden, boolean stillInRename) throws Exception { performTest(sourceCode, offset, ke, -1, golden, stillInRename); } @@ -217,6 +226,9 @@ private void performTest(String sourceCode, int offset, KeyEvent[] kes, int sele TestUtilities.copyStringToFile(source, sourceCode.replaceFirst(Pattern.quote("|"), "")); SourceUtilsTestUtil.prepareTest(sourceDir, buildDir, cacheDir, new FileObject[0]); + if (sourceLevel != null) { + SourceUtilsTestUtil.setSourceLevel(sourceDir, sourceLevel); + } SourceUtilsTestUtil.compileRecursively(sourceDir); DataObject od = DataObject.find(source); diff --git a/java/java.source.base/apichanges.xml b/java/java.source.base/apichanges.xml index 8b5551495cf2..820ac01a8ab9 100644 --- a/java/java.source.base/apichanges.xml +++ b/java/java.source.base/apichanges.xml @@ -25,6 +25,18 @@ Java Source API + + + Adding TreeMaker.RecordComponent + + + + + + Adding TreeMaker.RecordComponent and ElementUtilities.getLinkedRecordElements. + + + Adding TreeMaker.StringTemplate diff --git a/java/java.source.base/nbproject/project.properties b/java/java.source.base/nbproject/project.properties index 018f72d7e763..7d9fff6702d8 100644 --- a/java/java.source.base/nbproject/project.properties +++ b/java/java.source.base/nbproject/project.properties @@ -23,7 +23,7 @@ javadoc.name=Java Source Base javadoc.title=Java Source Base javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=2.69.0 +spec.version.base=2.70.0 test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/nb-javac-api.jar test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\ ${o.n.core.dir}/lib/boot.jar:\ diff --git a/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java b/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java index c17c623259a9..076e41c446ae 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java @@ -235,7 +235,7 @@ private T resolveImpl (final ModuleElement module, final JavacTaskImpl jt) { } case PARAMETER: { - assert signatures.length == 3; + assert signatures.length == 4; final Element type = getTypeElementByBinaryName (module, signatures[0], jt); if (type instanceof TypeElement) { final List members = type.getEnclosedElements(); @@ -526,7 +526,12 @@ public static ElementHandle createModuleElementHandle( ElementKind eek = ee.getKind(); if (eek == ElementKind.METHOD || eek == ElementKind.CONSTRUCTOR) { assert ee instanceof ExecutableElement; - String[] _sigs = ClassFileUtil.createExecutableDescriptor((ExecutableElement)ee); + ExecutableElement eel = (ExecutableElement)ee; + if (!eel.getParameters().contains(element)) { + //may be e.g. a lambda parameter: + throw new IllegalArgumentException("Not a parameter for a method or a constructor."); + } + String[] _sigs = ClassFileUtil.createExecutableDescriptor(eel); signatures = new String[_sigs.length + 1]; System.arraycopy(_sigs, 0, signatures, 0, _sigs.length); signatures[_sigs.length] = element.getSimpleName().toString(); diff --git a/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java b/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java index 11001a9037d6..2a178ca9507e 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java @@ -60,6 +60,7 @@ import java.util.ListIterator; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -928,7 +929,143 @@ public ExecutableElement getDescriptorElement(TypeElement origin) { } return null; } - + + /** + * Find all elements that are linked record elements for the given input. Will + * return the record component element, field, accessor method, and canonical constructor + * parameters. + * + * This method can be called on any {@code Element}, and will return a collection + * with a single entry if the provided element is not a record element. + * + * @param forElement for which the linked elements should be found + * @return a collection containing the provided element, plus any additional elements + * that are linked to it by the Java record specification + * @since 2.70 + */ + public Collection getLinkedRecordElements(Element forElement) { + Parameters.notNull("forElement", forElement); + + TypeElement record = null; + Name componentName = null; + + switch (forElement.getKind()) { + case FIELD -> { + Element enclosing = forElement.getEnclosingElement(); + if (enclosing.getKind() == ElementKind.RECORD) { + record = (TypeElement) enclosing; + componentName = forElement.getSimpleName(); + } + } + case PARAMETER -> { + Element enclosing = forElement.getEnclosingElement(); + if (enclosing.getKind() == ElementKind.CONSTRUCTOR) { + Element enclosingType = enclosing.getEnclosingElement(); + if (enclosingType.getKind() == ElementKind.RECORD) { + TypeElement recordType = (TypeElement) enclosingType; + ExecutableElement constructor = recordCanonicalConstructor(recordType); + if (constructor != null && constructor.equals(enclosing)) { + int idx = constructor.getParameters().indexOf(forElement); + if (idx >= 0 && idx < recordType.getRecordComponents().size()) { + RecordComponentElement component = recordType.getRecordComponents().get(idx); + if (component.getSimpleName().equals(forElement.getSimpleName())) { + record = recordType; + componentName = component.getSimpleName(); + } + } + } + } + } + } + case METHOD -> { + Element enclosing = forElement.getEnclosingElement(); + ExecutableElement method = (ExecutableElement) forElement; + if (method.getParameters().isEmpty() && enclosing.getKind() == ElementKind.RECORD) { + TypeElement recordType = (TypeElement) enclosing; + for (RecordComponentElement component : recordType.getRecordComponents()) { + if (forElement.equals(component.getAccessor())) { + record = recordType; + componentName = component.getSimpleName(); + } + } + } + } + case RECORD_COMPONENT -> { + record = (TypeElement) forElement.getEnclosingElement(); + componentName = forElement.getSimpleName(); + } + } + + if (record == null) { + return Collections.singleton(forElement); + } + + RecordComponentElement component = null; + int componentIdx = 0; + + for (RecordComponentElement c : record.getRecordComponents()) { + if (c.getSimpleName().equals(componentName)) { + component = c; + break; + } + componentIdx++; + } + + if (component == null) { + //erroneous state(?), ignore: + return Collections.singleton(forElement); + } + + Set result = new HashSet<>(); + + result.add(component); + result.add(component.getAccessor()); + + for (Element el : record.getEnclosedElements()) { + if (el.getKind() == ElementKind.FIELD && el.getSimpleName().equals(componentName)) { + result.add(el); + break; + } + } + + ExecutableElement canonicalConstructor = recordCanonicalConstructor(record); + if (canonicalConstructor != null && componentIdx < canonicalConstructor.getParameters().size()) { + result.add(canonicalConstructor.getParameters().get(componentIdx)); + } + + return result; + } + + private ExecutableElement recordCanonicalConstructor(TypeElement recordType) { + Supplier fallback = + () -> { + List recordComponents = recordType.getRecordComponents(); + for (ExecutableElement c : ElementFilter.constructorsIn(recordType.getEnclosedElements())) { + if (recordComponents.size() == c.getParameters().size()) { + Iterator componentIt = recordComponents.iterator(); + Iterator parameterIt = c.getParameters().iterator(); + boolean componentMatches = true; + + while (componentIt.hasNext() && parameterIt.hasNext() && componentMatches) { + TypeMirror componentType = componentIt.next().asType(); + TypeMirror parameterType = parameterIt.next().asType(); + + componentMatches &= info.getTypes().isSameType(componentType, parameterType); + } + if (componentMatches) { + return c; + } + } + } + return null; + }; + return ElementFilter.constructorsIn(recordType.getEnclosedElements()) + .stream() + .filter(info.getElements()::isCanonicalConstructor) + .findAny() + .orElseGet(fallback); + } + // private implementation -------------------------------------------------- private static final Set NOT_OVERRIDABLE = EnumSet.of(Modifier.STATIC, Modifier.FINAL, Modifier.PRIVATE); diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java index 6b8b05e16e79..4f7d8beb50c9 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java @@ -1277,6 +1277,21 @@ public VariableTree Variable(ModifiersTree modifiers, return delegate.Variable(modifiers, name, type, initializer); } + /** + * Creates a new VariableTree for a record component. + * + * @param modifiers the modifiers of this record component. + * @param name the name of the record component. + * @param type the type of this record component. + * @see com.sun.source.tree.VariableTree + * @since 2.70 + */ + public VariableTree RecordComponent(ModifiersTree modifiers, + CharSequence name, + Tree type) { + return delegate.RecordComponent(modifiers, name, type); + } + /** * Creates a new BindingPatternTree. * @deprecated diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java b/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java index 286427f834cd..14b097f0afb8 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java @@ -41,6 +41,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; @@ -322,6 +323,14 @@ private static boolean isSupported(Element el) { case RECORD: //TODO: record component return true; + case PARAMETER: + //only method and constructor parameters supported (not lambda): + if (el.getEnclosingElement().getKind() == ElementKind.METHOD || + el.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR) { + return ((ExecutableElement) el.getEnclosingElement()).getParameters().contains(el); + } else { + return false; + } default: return false; } diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java index a7453c44778e..737c3d6ad0d6 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java @@ -185,8 +185,8 @@ public boolean isExpressionStatement(ExpressionTree tree) { } /**Returns whether or not the given tree is synthetic - generated by the parser. - * Please note that this method does not check trees transitively - a child of a syntetic tree - * may be considered non-syntetic. + * Please note that this method does not check trees transitively - a child of a synthetic tree + * may be considered non-synthetic. * * @return true if the given tree is synthetic, false otherwise * @throws NullPointerException if the given tree is null @@ -210,6 +210,16 @@ public boolean isSynthetic(TreePath path) throws NullPointerException { return true; } } + if (path.getLeaf().getKind() == Kind.VARIABLE && + path.getParentPath() != null && + path.getParentPath().getLeaf().getKind() == Kind.METHOD && + path.getParentPath().getParentPath() != null && + path.getParentPath().getParentPath().getLeaf().getKind() == Kind.RECORD) { + JCMethodDecl m = (JCMethodDecl) path.getParentPath().getLeaf(); + if ((m.mods.flags & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0 && m.getParameters().contains(path.getLeaf())) { + return true; + } + } path = path.getParentPath(); } diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java index aed7625d6e78..d28e43c61956 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java @@ -925,6 +925,15 @@ public VariableTree Variable(ModifiersTree modifiers, (JCExpression)type, (JCExpression)initializer); } + public VariableTree RecordComponent(ModifiersTree modifiers, + CharSequence name, + Tree type) { + JCModifiers augmentedModifiers = (JCModifiers) Modifiers(modifiers.getFlags(), modifiers.getAnnotations()); + + augmentedModifiers.flags |= Flags.RECORD; + + return Variable(augmentedModifiers, name, type, null); + } public Tree BindingPattern(CharSequence name, Tree type) { try { diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java index 08527547a62a..6a561506eaa0 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java @@ -193,7 +193,6 @@ public class CasualDiff { private final Names names; private final TreeMaker make; private static final Logger LOG = Logger.getLogger(CasualDiff.class.getName()); - public static final int GENERATED_MEMBER = 1<<24; private Map diffInfo = new HashMap<>(); private final Map tree2Tag; @@ -1008,6 +1007,47 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { // it can be > (GT) or >> (SHIFT) insertHint = tokenSequence.offset() + tokenSequence.token().length(); } + //TODO: class to record and vice versa! + if (oldT.getKind() == Kind.RECORD && newT.getKind() == Kind.RECORD) { + ComponentsAndOtherMembers oldParts = splitOutRecordComponents(filteredOldTDefs); + ComponentsAndOtherMembers newParts = splitOutRecordComponents(filteredNewTDefs); + int posHint; + if (oldParts.components().isEmpty()) { + // compute the position. Find the parameters closing ')', its + // start position is important for us. This is used when + // there was not any parameter in original tree. + int startOffset = oldT.pos; + + moveFwdToToken(tokenSequence, startOffset, JavaTokenId.RPAREN); + posHint = tokenSequence.offset(); + } else { + // take the position of the first old parameter + posHint = oldParts.components.iterator().next().getStartPosition(); + } + if (!listsMatch(oldParts.components, newParts.components)) { + copyTo(localPointer, posHint); + int old = printer.setPrec(TreeInfo.noPrec); + parameterPrint = true; + JCClassDecl oldEnclClass = printer.enclClass; + printer.enclClass = null; + localPointer = diffParameterList(oldParts.components, newParts.components, null, posHint, Measure.MEMBER); + printer.enclClass = oldEnclClass; + parameterPrint = false; + printer.setPrec(old); + } + //make sure the ')' is printed: + moveFwdToToken(tokenSequence, oldParts.components.isEmpty() ? posHint : endPos(oldParts.components.get(oldParts.components.size() - 1)), JavaTokenId.RPAREN); + tokenSequence.moveNext(); + posHint = tokenSequence.offset(); + if (localPointer < posHint) + copyTo(localPointer, localPointer = posHint); + filteredOldTDefs = oldParts.defs; + filteredNewTDefs = newParts.defs; + tokenSequence.move(localPointer); + moveToSrcRelevant(tokenSequence, Direction.FORWARD); + // it can be > (GT) or >> (SHIFT) + insertHint = tokenSequence.offset() + tokenSequence.token().length(); + } switch (getChangeKind(oldT.extending, newT.extending)) { case NOCHANGE: insertHint = oldT.extending != null ? endPos(oldT.extending) : insertHint; @@ -1119,6 +1159,25 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { return bounds[1]; } + private ComponentsAndOtherMembers splitOutRecordComponents(List defs) { + ListBuffer components = new ListBuffer<>(); + ListBuffer filteredDefs = new ListBuffer<>(); + + for (JCTree t : defs) { + if (t.getKind() == Kind.VARIABLE && + (((JCVariableDecl) t).mods.flags & RECORD) != 0) { + components.add(t); + } else { + filteredDefs.add(t); + } + } + + return new ComponentsAndOtherMembers(components.toList(), + filteredDefs.toList()); + } + + record ComponentsAndOtherMembers(List components, List defs) {} + /** * When the enumeration contains just methods, it is necessary to preced them with single ;. If a constant is * inserted, it must be inserted first; and the semicolon should be removed. This method will attempt to remove entire @@ -4026,11 +4085,7 @@ else if (Kind.VARIABLE == tree.getKind()) { // collect enum constants, make a field group from them // and set the flag. enumConstants.add(var); - } // filter syntetic member variable, i.e. variable which are in - // the tree, but not available in the source. - else if ((var.mods.flags & GENERATED_MEMBER) != 0) - continue; - else { + } else { if (!fieldGroup.isEmpty()) { int oldPos = getOldPos(fieldGroup.get(0)); diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java index 92dcfdb519e4..f3c1cf42735c 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/ElementHandleTest.java @@ -20,6 +20,7 @@ package org.netbeans.api.java.source; import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import com.sun.tools.javac.model.JavacElements; @@ -39,6 +40,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -678,6 +680,80 @@ public void testHandleClassBasedCompilations() throws Exception { SourceLevelQueryImpl.getDefault().setSourceLevel(jlObject, null); } + public void testMethodParameter1() throws Exception { + try (PrintWriter out = new PrintWriter ( new OutputStreamWriter (data.getOutputStream()))) { + out.println(""" + public class Test { + public Test(int cParam) {} + public void test(int mParam) { + FI fi = lParam -> {}; + } + interface FI { + public void test(int x) {} + } + } + """); + } + final JavaSource js = JavaSource.create(ClasspathInfo.create(ClassPathProviderImpl.getDefault().findClassPath(data,ClassPath.BOOT), ClassPathProviderImpl.getDefault().findClassPath(data, ClassPath.COMPILE), null), data); + assertNotNull(js); + AtomicInteger testCount = new AtomicInteger(); + + js.runUserActionTask(new Task() { + public void run(CompilationController parameter) throws Exception { + parameter.toPhase(JavaSource.Phase.RESOLVED); + new TreePathScanner() { + @Override + public Void visitVariable(VariableTree node, Void p) { + if (node.getName().toString().endsWith("Param")) { + Element el = parameter.getTrees().getElement(getCurrentPath()); + if (el.getSimpleName().contentEquals("lParam")) { + try { + ElementHandle.create(el); + fail("Expected exception didn't happen!"); + } catch (IllegalArgumentException ex) { + //OK + } + } else { + assertEquals(el, ElementHandle.create(el).resolve(parameter)); + } + testCount.incrementAndGet(); + } + return super.visitVariable(node, p); + } + }.scan(parameter.getCompilationUnit(), null); + } + }, true); + + assertEquals(3, testCount.get()); + } + + public void testMethodParameter2() throws Exception { + ClassPath systemClasses = BootClassPathUtil.getModuleBootPath(); + ClassPath bcp = BootClassPathUtil.getBootClassPath(); + FileObject jlObject = bcp.findResource("java/lang/String.class"); + assertNotNull(jlObject); + ClasspathInfo cpInfo = new ClasspathInfo.Builder(bcp) + .setModuleBootPath(systemClasses) + .build(); + JavaSource js = JavaSource.create(cpInfo, jlObject); + assertNotNull(js); + SourceLevelQueryImpl.getDefault().setSourceLevel(jlObject, "11"); + js.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); + TypeElement tl = cc.getTopLevelElements().get(0); + for (Element el : tl.getEnclosedElements()) { + if (el.getKind() != ElementKind.METHOD && + el.getKind() != ElementKind.CONSTRUCTOR) { + continue; + } + for (VariableElement parameter : ((ExecutableElement) el).getParameters()) { + assertEquals(parameter, ElementHandle.create(parameter).resolve(cc)); + } + } + }, true); + SourceLevelQueryImpl.getDefault().setSourceLevel(jlObject, null); + } + private Element[] getStringElements (Element stringElement) { List members = ((TypeElement)stringElement).getEnclosedElements(); Element[] result = new Element[3]; 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 40205da17ee9..745d01003d17 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 @@ -31,12 +31,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import org.netbeans.api.java.source.JavaSourceTest.SourceLevelQueryImpl; import org.netbeans.junit.NbTestCase; import org.openide.filesystems.FileObject; @@ -599,4 +602,206 @@ public void testGetGlobalTypes() throws Exception { }, true); } + public void testGetLinkedRecordElements1() throws Exception { + prepareTest(); + SourceUtilsTestUtil.setSourceLevel(testFO, "17"); + TestUtilities.copyStringToFile(FileUtil.toFile(testFO), + """ + package test; + public record R(String component) {} + """ + ); + SourceLevelQueryImpl.sourceLevel = "17"; + + JavaSource javaSource = JavaSource.forFileObject(testFO); + javaSource.runUserActionTask((CompilationController controller) -> { + controller.toPhase(JavaSource.Phase.RESOLVED); + + TypeElement record = controller.getTopLevelElements().get(0); + ElementUtilities utils = controller.getElementUtilities(); + Collection linked = utils.getLinkedRecordElements(record.getRecordComponents().get(0)); + Set linkedEncoded = linked.stream() + .map(Element::getKind) + .map(ElementKind::name) + .collect(Collectors.toCollection(TreeSet::new)); + assertEquals(new TreeSet<>(Arrays.asList("FIELD", "METHOD", "PARAMETER", "RECORD_COMPONENT")), + linkedEncoded); + + for (Element linkedElement : linked) { + if (!linked.equals(utils.getLinkedRecordElements(linkedElement))) { + utils.getLinkedRecordElements(linkedElement); + } + assertEquals(linked, utils.getLinkedRecordElements(linkedElement)); + } + }, true); + } + + public void testGetLinkedRecordElements2() throws Exception { + prepareTest(); + SourceUtilsTestUtil.setSourceLevel(testFO, "17"); + TestUtilities.copyStringToFile(FileUtil.toFile(testFO), + """ + package test; + public record R(String component) { + public R { + this.component = component; + } + public String component() { + return component; + } + } + """ + ); + SourceLevelQueryImpl.sourceLevel = "17"; + + JavaSource javaSource = JavaSource.forFileObject(testFO); + javaSource.runUserActionTask((CompilationController controller) -> { + controller.toPhase(JavaSource.Phase.RESOLVED); + + TypeElement record = controller.getTopLevelElements().get(0); + ElementUtilities utils = controller.getElementUtilities(); + Collection linked = utils.getLinkedRecordElements(record.getRecordComponents().get(0)); + Set linkedEncoded = linked.stream() + .map(Element::getKind) + .map(ElementKind::name) + .collect(Collectors.toCollection(TreeSet::new)); + assertEquals(new TreeSet<>(Arrays.asList("FIELD", "METHOD", "PARAMETER", "RECORD_COMPONENT")), + linkedEncoded); + + for (Element linkedElement : linked) { + if (!linked.equals(utils.getLinkedRecordElements(linkedElement))) { + utils.getLinkedRecordElements(linkedElement); + } + assertEquals(linked, utils.getLinkedRecordElements(linkedElement)); + } + }, true); + } + + public void testGetLinkedRecordElements3() throws Exception { + prepareTest(); + SourceUtilsTestUtil.setSourceLevel(testFO, "17"); + TestUtilities.copyStringToFile(FileUtil.toFile(testFO), + """ + package test; + public record R(String component) { + public R(String component) { + this.component = component; + } + public String component() { + return component; + } + } + """ + ); + SourceLevelQueryImpl.sourceLevel = "17"; + + JavaSource javaSource = JavaSource.forFileObject(testFO); + javaSource.runUserActionTask((CompilationController controller) -> { + controller.toPhase(JavaSource.Phase.RESOLVED); + + TypeElement record = controller.getTopLevelElements().get(0); + ElementUtilities utils = controller.getElementUtilities(); + Collection linked = utils.getLinkedRecordElements(record.getRecordComponents().get(0)); + Set linkedEncoded = linked.stream() + .map(Element::getKind) + .map(ElementKind::name) + .collect(Collectors.toCollection(TreeSet::new)); + assertEquals(new TreeSet<>(Arrays.asList("FIELD", "METHOD", "PARAMETER", "RECORD_COMPONENT")), + linkedEncoded); + + for (Element linkedElement : linked) { + if (!linked.equals(utils.getLinkedRecordElements(linkedElement))) { + utils.getLinkedRecordElements(linkedElement); + } + assertEquals(linked, utils.getLinkedRecordElements(linkedElement)); + } + }, true); + } + + public void testGetLinkedRecordElements4() throws Exception { + prepareTest(); + SourceUtilsTestUtil.setSourceLevel(testFO, "17"); + TestUtilities.copyStringToFile(FileUtil.toFile(testFO), + """ + package test; + public record R(String component) { + public R(String anotherName) { //error + this.component = anotherName; + } + public String component() { + return component; + } + } + """ + ); + SourceLevelQueryImpl.sourceLevel = "17"; + + JavaSource javaSource = JavaSource.forFileObject(testFO); + javaSource.runUserActionTask((CompilationController controller) -> { + controller.toPhase(JavaSource.Phase.RESOLVED); + + TypeElement record = controller.getTopLevelElements().get(0); + Element brokenParameter = ElementFilter.constructorsIn(record.getEnclosedElements()).get(0).getParameters().get(0); + ElementUtilities utils = controller.getElementUtilities(); + Collection linked = utils.getLinkedRecordElements(brokenParameter); + Set linkedEncoded = linked.stream() + .map(Element::getKind) + .map(ElementKind::name) + .collect(Collectors.toCollection(TreeSet::new)); + assertEquals(new TreeSet<>(Arrays.asList("PARAMETER")), + linkedEncoded); + }, true); + } + + public void testGetLinkedRecordElements5() throws Exception { + prepareTest(); + SourceUtilsTestUtil.setSourceLevel(sourceRoot, "17"); + TestUtilities.copyStringToFile(FileUtil.toFile(testFO), + """ + package test; + public record R(String component) { + public R { + this.component = component; + } + public String component() { + return component; + } + } + """ + ); + FileObject useFO = FileUtil.createData(sourceRoot, "test/Use.java"); + TestUtilities.copyStringToFile(FileUtil.toFile(useFO), + """ + package test; + public class Use {} + """ + ); + SourceLevelQueryImpl.sourceLevel = "17"; + + SourceUtilsTestUtil.compileRecursively(sourceRoot); + + JavaSource javaSource = JavaSource.forFileObject(useFO); + javaSource.runUserActionTask((CompilationController controller) -> { + controller.toPhase(JavaSource.Phase.RESOLVED); + + TypeElement record = controller.getElements().getTypeElement("test.R"); + Element component = record.getRecordComponents().get(0); + ElementUtilities utils = controller.getElementUtilities(); + Collection linked = utils.getLinkedRecordElements(component); + Set linkedEncoded = linked.stream() + .map(Element::getKind) + .map(ElementKind::name) + .collect(Collectors.toCollection(TreeSet::new)); + assertEquals(new TreeSet<>(Arrays.asList("FIELD", "METHOD", "PARAMETER", "RECORD_COMPONENT")), + linkedEncoded); + + for (Element linkedElement : linked) { + if (!linked.equals(utils.getLinkedRecordElements(linkedElement))) { + utils.getLinkedRecordElements(linkedElement); + } + assertEquals(linked, utils.getLinkedRecordElements(linkedElement)); + } + }, true); + } + } diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TestUtilities.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TestUtilities.java index c85365e643c2..21b1039a262b 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TestUtilities.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TestUtilities.java @@ -304,4 +304,15 @@ public static Path getJRTFS() throws IOException { return null; } + public static TestInput splitCodeAndPos(String input) { + int pos = input.indexOf('|'); + + if (pos == (-1)) { + throw new IllegalArgumentException("Does not specify a caret position: " + input); + } + + return new TestInput(input.substring(0, pos) + input.substring(pos + 1), pos); + } + + public record TestInput(String code, int pos) {} } diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TreeUtilitiesTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TreeUtilitiesTest.java index 67a769ca0b8b..14b7657f89ab 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TreeUtilitiesTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/TreeUtilitiesTest.java @@ -18,6 +18,7 @@ */ package org.netbeans.api.java.source; +import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.BlockTree; import com.sun.source.tree.ClassTree; @@ -53,6 +54,7 @@ import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.source.Comment.Style; import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.TestUtilities.TestInput; import org.netbeans.junit.NbTestCase; import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.openide.filesystems.FileObject; @@ -146,6 +148,54 @@ public void testIsSyntheticNewClassExtends() throws Exception { assertFalse(info.getTreeUtilities().isSynthetic(new TreePath(tp, nct.getIdentifier()))); } + public void testIsSyntheticCompactConstructorParams() throws Exception { + TestInput input = TestUtilities.splitCodeAndPos(""" + package t; + public record R(String component) { + public R| { + } + } + """); + prepareTest("Test", input.code()); + + TreePath tp = info.getTreeUtilities().pathFor(input.pos()); + MethodTree mt = (MethodTree) tp.getLeaf(); + + assertTrue(info.getTreeUtilities().isSynthetic(new TreePath(tp, mt.getParameters().get(0)))); + } + + public void testIsNotSyntheticExplicitConstructorParams() throws Exception { + TestInput input = TestUtilities.splitCodeAndPos(""" + package t; + public record R(String component) { + public R|(String component) { + } + } + """); + prepareTest("Test", input.code()); + + TreePath tp = info.getTreeUtilities().pathFor(input.pos()); + MethodTree mt = (MethodTree) tp.getLeaf(); + + assertFalse(info.getTreeUtilities().isSynthetic(new TreePath(tp, mt.getParameters().get(0)))); + } + + public void testIsNotSyntheticImplicitValueAttributeAssignment() throws Exception { + TestInput input = TestUtilities.splitCodeAndPos(""" + package t; + @An|n(1) + public @interface Ann { + public int value(); + } + """); + prepareTest("Test", input.code()); + + TreePath tp = info.getTreeUtilities().pathFor(input.pos()); + AnnotationTree at = (AnnotationTree) tp.getParentPath().getLeaf(); + + assertFalse(info.getTreeUtilities().isSynthetic(new TreePath(tp, at.getArguments().get(0)))); + } + public void testIsSyntheticNewClassImplements() throws Exception { prepareTest("Test", "package test; import java.io.*; public class Test { void t() { new Serializable() { }; } }"); diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java new file mode 100644 index 000000000000..bc33aa888e5d --- /dev/null +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java @@ -0,0 +1,250 @@ +/* + * 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.gen; + +import java.io.*; +import com.sun.source.tree.*; +import com.sun.source.tree.Tree.Kind; +import java.util.EnumSet; +import javax.lang.model.element.Modifier; +import org.netbeans.api.java.source.*; +import static org.netbeans.api.java.source.JavaSource.*; +import org.netbeans.junit.NbTestSuite; + +public class RecordTest extends GeneratorTestMDRCompat { + + private String sourceLevel; + + public RecordTest(String testName) { + super(testName); + } + + public static NbTestSuite suite() { + NbTestSuite suite = new NbTestSuite(); + suite.addTestSuite(RecordTest.class); + return suite; + } + + public void testRenameComponent() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R(String component) {} + """); + String golden = + """ + package hierbas.del.litoral; + public record R(String newName) {} + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + for (Tree m : classTree.getMembers()) { + if (m.getKind() == Kind.VARIABLE) { + workingCopy.rewrite(m, make.setLabel(m, "newName")); + } + } + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + public void testAddFirstComponent() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R() {} + """); + String golden = + """ + package hierbas.del.litoral; + public record R(String component) {} + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + VariableTree newComponent = make.RecordComponent(make.Modifiers(EnumSet.noneOf(Modifier.class)), + "component", + make.Type("java.lang.String")); + ClassTree newClassTree = make.addClassMember(classTree, newComponent); + workingCopy.rewrite(classTree, newClassTree); + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + public void testAddSecondComponent() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R(String existing) {} + """); + String golden = + """ + package hierbas.del.litoral; + public record R(String existing, String component) {} + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + VariableTree newComponent = make.RecordComponent(make.Modifiers(EnumSet.noneOf(Modifier.class)), + "component", + make.Type("java.lang.String")); + ClassTree newClassTree = make.addClassMember(classTree, newComponent); + workingCopy.rewrite(classTree, newClassTree); + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + public void testRemoveLastComponent() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R(String component) {} + """); + String golden = + """ + package hierbas.del.litoral; + public record R() {} + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + for (Tree m : classTree.getMembers()) { + if (m.getKind() == Kind.VARIABLE) { + workingCopy.rewrite(classTree, make.removeClassMember(classTree, m)); + break; + } + } + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + public void testRemoveComponent() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R(String first, String component) {} + """); + String golden = + """ + package hierbas.del.litoral; + public record R(String first) {} + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + for (Tree m : classTree.getMembers()) { + if (m.getKind() == Kind.VARIABLE && + ((VariableTree) m).getName().contentEquals("component")) { + workingCopy.rewrite(classTree, make.removeClassMember(classTree, m)); + break; + } + } + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + String getGoldenPckg() { + return ""; + } + + String getSourcePckg() { + return ""; + } + + @Override + String getSourceLevel() { + return sourceLevel; + } + +} diff --git a/java/refactoring.java/nbproject/project.properties b/java/refactoring.java/nbproject/project.properties index 482f48c8d29e..5f57bb0404c3 100644 --- a/java/refactoring.java/nbproject/project.properties +++ b/java/refactoring.java/nbproject/project.properties @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -javac.source=1.8 +javac.release=17 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java index 8804024eb0b5..5377f15eb677 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java @@ -332,69 +332,11 @@ public static boolean hasSetter(CompilationInfo info, TypeElement typeElement, V return false; } - /** - * Works as TreeUtilities.isSynthetic, but treats implicit annotation parameter (value) as - * non-synthetic. See defect #270036 - */ public static boolean isSyntheticPath(CompilationInfo ci, TreePath path) { TreeUtilities tu = ci.getTreeUtilities(); - if (path == null) - throw new NullPointerException(); - - while (path != null) { - SYNT: if (isSynthetic(ci, path.getCompilationUnit(), path.getLeaf())) { - if (path.getLeaf().getKind() == Tree.Kind.ASSIGNMENT && - path.getParentPath() != null && path.getParentPath().getLeaf().getKind() == Tree.Kind.ANNOTATION) { - AssignmentTree aTree = (AssignmentTree)path.getLeaf(); - if (aTree.getVariable().getKind() == Tree.Kind.IDENTIFIER && - ((IdentifierTree)aTree.getVariable()).getName().contentEquals("value")) { // implicit value is not synthetic - break SYNT; - } - } - return true; - } - - path = path.getParentPath(); - } - - return false; + return tu.isSynthetic(path); } - // - static boolean isSynthetic(CompilationInfo info, CompilationUnitTree cut, Tree leaf) throws NullPointerException { - JCTree tree = (JCTree) leaf; - - if (tree.pos == (-1)) - return true; - - if (leaf.getKind() == Kind.METHOD) { - //check for synthetic constructor: - return (((JCTree.JCMethodDecl)leaf).mods.flags & Flags.GENERATEDCONSTR) != 0L; - } - - //check for synthetic superconstructor call: - if (leaf.getKind() == Kind.EXPRESSION_STATEMENT) { - ExpressionStatementTree est = (ExpressionStatementTree) leaf; - - if (est.getExpression().getKind() == Kind.METHOD_INVOCATION) { - MethodInvocationTree mit = (MethodInvocationTree) est.getExpression(); - - if (mit.getMethodSelect().getKind() == Kind.IDENTIFIER) { - IdentifierTree it = (IdentifierTree) mit.getMethodSelect(); - - if ("super".equals(it.getName().toString())) { - SourcePositions sp = info.getTrees().getSourcePositions(); - - return sp.getEndPosition(cut, leaf) == (-1); - } - } - } - } - - return false; - } - // - // public static final String DEFAULT_NAME = "par"; // NOI18N public static String makeNameUnique(CompilationInfo info, Scope s, String name) { 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 9c50d4175347..8378dc3b3198 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 @@ -54,6 +54,7 @@ public class RenameRefactoringPlugin extends JavaRefactoringPlugin { private Set> allMethods = new HashSet>(); + private Set recordLinkedDeclarations = new HashSet<>(); private boolean doCheckName = true; private Integer overriddenByMethodsCount = null; private Integer overridesMethodsCount = null; @@ -422,36 +423,42 @@ public void run(CompilationController info) throws Exception { : treePathHandle.resolveElement(info); ElementKind kind = el.getKind(); ElementHandle enclosingType; - if (el instanceof TypeElement) { + if (kind.isClass() || kind.isInterface()) { enclosingType = ElementHandle.create((TypeElement)el); } else { enclosingType = ElementHandle.create(info.getElementUtilities().enclosingTypeElement(el)); } set.add(SourceUtils.getFile(el, info.getClasspathInfo())); - if (el.getModifiers().contains(Modifier.PRIVATE)) { - if (kind == ElementKind.METHOD) { - //add all references of overriding methods - allMethods.add(ElementHandle.create((ExecutableElement)el)); + for (Element linked : info.getElementUtilities().getLinkedRecordElements(el)) { + ElementKind linkedKind = linked.getKind(); + if (!el.equals(linked)) { + recordLinkedDeclarations.add(TreePathHandle.create(linked, info)); } - } else { - if (kind.isField()) { - set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.FIELD_REFERENCES), EnumSet.of(ClassIndex.SearchScope.SOURCE))); - } else if (el instanceof TypeElement) { - set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.TYPE_REFERENCES, ClassIndex.SearchKind.IMPLEMENTORS),EnumSet.of(ClassIndex.SearchScope.SOURCE))); - } else if (kind == ElementKind.METHOD) { - //add all references of overriding methods - allMethods.add(ElementHandle.create((ExecutableElement)el)); - for (ExecutableElement e:JavaRefactoringUtils.getOverridingMethods((ExecutableElement)el, info, cancelRequested)) { - addMethods(e, set, info, idx); + if (linked.getModifiers().contains(Modifier.PRIVATE)) { + if (linkedKind == ElementKind.METHOD) { + //add all references of overriding methods + allMethods.add(ElementHandle.create((ExecutableElement)linked)); } - //add all references of overriden methods - for (ExecutableElement ov: JavaRefactoringUtils.getOverriddenMethods((ExecutableElement)el, info)) { - addMethods(ov, set, info, idx); - for (ExecutableElement e:JavaRefactoringUtils.getOverridingMethods( ov,info, cancelRequested)) { + } else { + if (linkedKind.isField()) { + set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.FIELD_REFERENCES), EnumSet.of(ClassIndex.SearchScope.SOURCE))); + } else if (linked instanceof TypeElement) { + set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.TYPE_REFERENCES, ClassIndex.SearchKind.IMPLEMENTORS),EnumSet.of(ClassIndex.SearchScope.SOURCE))); + } else if (linkedKind == ElementKind.METHOD) { + //add all references of overriding methods + allMethods.add(ElementHandle.create((ExecutableElement)linked)); + for (ExecutableElement e:JavaRefactoringUtils.getOverridingMethods((ExecutableElement)linked, info, cancelRequested)) { addMethods(e, set, info, idx); } + //add all references of overriden methods + for (ExecutableElement ov: JavaRefactoringUtils.getOverriddenMethods((ExecutableElement)linked, info)) { + addMethods(ov, set, info, idx); + for (ExecutableElement e:JavaRefactoringUtils.getOverridingMethods( ov,info, cancelRequested)) { + addMethods(e, set, info, idx); + } + } + set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES),EnumSet.of(ClassIndex.SearchScope.SOURCE))); //????? } - set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES),EnumSet.of(ClassIndex.SearchScope.SOURCE))); //????? } } } @@ -623,7 +630,7 @@ public Problem prepare(RefactoringElementsBag elements) { } Set a = getRelevantFiles(); fireProgressListenerStart(AbstractRefactoring.PREPARE, a.size()); - TransformTask transform = new TransformTask(new RenameTransformer(treePathHandle, docTreePathHandle, refactoring, allMethods, refactoring.isSearchInComments()), treePathHandle != null && treePathHandle.getKind() == Tree.Kind.LABELED_STATEMENT ? null : treePathHandle); + 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)); fireProgressListenerStop(); return problem; diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameTransformer.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameTransformer.java index a27dd2972623..c833058dbcb1 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameTransformer.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RenameTransformer.java @@ -59,6 +59,7 @@ public class RenameTransformer extends RefactoringVisitor { private final Set> allMethods; + private final Set recordLinkedDeclarations; private final TreePathHandle handle; private final DocTreePathHandle docHandle; private final String newName; @@ -68,13 +69,14 @@ public class RenameTransformer extends RefactoringVisitor { private Map imports; private List newImports; - public RenameTransformer(TreePathHandle handle, DocTreePathHandle docHandle, RenameRefactoring refactoring, Set> am, boolean renameInComments) { + public RenameTransformer(TreePathHandle handle, DocTreePathHandle docHandle, RenameRefactoring refactoring, Set> am, Set recordLinkedDeclarations, boolean renameInComments) { super(true); this.handle = handle; this.docHandle = docHandle; this.refactoring = refactoring; this.newName = refactoring.getNewName(); this.allMethods = am; + this.recordLinkedDeclarations = recordLinkedDeclarations; this.renameInComments = renameInComments; } @@ -197,6 +199,17 @@ private void renameUsageIfMatch(final TreePath path, Tree tree, Element elementT if (JavaPluginUtils.isSyntheticPath(workingCopy, path) || (handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT)) { return; } + doRenameUsageIfMatch(getCurrentPath(), tree, elementToFind); + for (TreePathHandle h : recordLinkedDeclarations) { + Element linked = h.resolveElement(workingCopy); + + if (linked != null) { + doRenameUsageIfMatch(getCurrentPath(), tree, linked); + } + } + } + + private void doRenameUsageIfMatch(final TreePath path, Tree tree, Element elementToFind) { TreePath elementPath = path; Trees trees = workingCopy.getTrees(); Element el = workingCopy.getTrees().getElement(elementPath); @@ -391,14 +404,17 @@ public Tree visitClass(ClassTree tree, final Element p) { Element el = workingCopy.getTrees().getElement(currentPath); if (el != null && el.getEnclosedElements().contains(p)) { Trees trees = workingCopy.getTrees(); - Scope scope = trees.getScope(trees.getPath(p)); - shadowed = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() { + TreePath pPath = trees.getPath(p); + if (pPath != null) { //may be null for synthetic record accessors + Scope scope = trees.getScope(pPath); + shadowed = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() { - @Override - public boolean accept(Element element, TypeMirror type) { - return !element.equals(p) && element.getKind() == p.getKind() && element.getSimpleName().contentEquals(newName); - } - }); + @Override + public boolean accept(Element element, TypeMirror type) { + return !element.equals(p) && element.getKind() == p.getKind() && element.getSimpleName().contentEquals(newName); + } + }); + } } Tree value = super.visitClass(tree, p); shadowed = null; @@ -421,6 +437,19 @@ private void renameDeclIfMatch(TreePath path, Tree tree, Element elementToFind) if (JavaPluginUtils.isSyntheticPath(workingCopy, path) || (handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT)) { return; } + + doRenameDeclIfMatch(path, tree, elementToFind); + + for (TreePathHandle h : recordLinkedDeclarations) { + Element linked = h.resolveElement(workingCopy); + + if (linked != null) { + doRenameDeclIfMatch(getCurrentPath(), tree, linked); + } + } + } + + private void doRenameDeclIfMatch(TreePath path, Tree tree, Element elementToFind) { Element el = workingCopy.getTrees().getElement(path); if (el==null) { return; diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java new file mode 100644 index 000000000000..43fbba7faa72 --- /dev/null +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java @@ -0,0 +1,418 @@ +/* + * 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.tree.CompilationUnitTree; +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.Task; +import org.netbeans.api.java.source.TestUtilities; +import org.netbeans.api.java.source.TestUtilities.TestInput; +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 RenameRecordTest extends RefactoringTestBase { + + public RenameRecordTest(String name) { + super(name, "17"); + } + + public void testRenameComponent1() throws Exception { + String testCode = """ + package test; + public record Test(int compo|nent) {} + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.component(); + } + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) {} + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + + } + + public void testRenameComponent2() throws Exception { + String testCode = """ + package test; + public record Test(int compo|nent) { + public Test(int component) { + component = -1; + } + public int component() { + return component; + } + public int hashCode() { + return component; + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.component(); + } + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) { + public Test(int newName) { + newName = -1; + } + public int newName() { + return newName; + } + public int hashCode() { + return newName; + } + } + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + + } + + public void testRenameComponent3() throws Exception { + String testCode = """ + package test; + public record Test(int compo|nent) { + public Test { + component = -1; + } + public int component() { + return component; + } + public int hashCode() { + return component; + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.component(); + } + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) { + public Test { + newName = -1; + } + public int newName() { + return newName; + } + public int hashCode() { + return newName; + } + } + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + + } + + public void testRenameComponentStartFromAccessor1() throws Exception { + String useCode = """ + package test; + public class Use { + private void test(Test t) { + int i = t.com|ponent(); + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(useCode); + writeFilesAndWaitForScan(src, + new File("Test.java", + """ + package test; + public record Test(int component) { + public Test { + component = -1; + } + public int component() { + return component; + } + public int hashCode() { + return component; + } + } + """), + new File("Use.java", splitCode.code())); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Use.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) { + public Test { + newName = -1; + } + public int newName() { + return newName; + } + public int hashCode() { + return newName; + } + } + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + + } + + public void testRenameComponentStartFromAccessor2() throws Exception { + String useCode = """ + package test; + public class Use { + private void test(Test t) { + int i = t.com|ponent(); + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(useCode); + writeFilesAndWaitForScan(src, + new File("Test.java", + """ + package test; + public record Test(int component) { + } + """), + new File("Use.java", splitCode.code())); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Use.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) { + } + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + + } + + public void testRenameComponentStartFromConstructorArg() throws Exception { + String testCode = """ + package test; + public record Test(int component) { + public Test { + compo|nent = -1; + } + public int component() { + return component; + } + public int hashCode() { + return component; + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.component(); + } + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "newName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(int newName) { + public Test { + newName = -1; + } + public int newName() { + return newName; + } + public int hashCode() { + return newName; + } + } + """), + new File("Use.java", + """ + package test; + public class Use { + private void test(Test t) { + int i = t.newName(); + } + } + """)); + } + + public void testRenameRecord() throws Exception { + String testCode = """ + package test; + public record Te|st(int component) { + public Test { + component = ""; + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Use.java", + """ + package test; + public class Use { + private Test test() { + return new Test(0); + } + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "NewName", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record NewName(int component) { + public NewName { + component = ""; + } + } + """), + new File("Use.java", + """ + package test; + public class Use { + private NewName test() { + return new NewName(0); + } + } + """)); + + } + 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(new Task() { + + @Override + public void run(CompilationController javac) throws Exception { + javac.toPhase(JavaSource.Phase.RESOLVED); + CompilationUnitTree cut = javac.getCompilationUnit(); + + 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); + } +} diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java index 0c22d6d57808..f2e961fa4af5 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java @@ -28,6 +28,8 @@ import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.TestUtilities; +import org.netbeans.api.java.source.TestUtilities.TestInput; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.modules.refactoring.api.Problem; import org.netbeans.modules.refactoring.api.RefactoringSession; @@ -1573,6 +1575,60 @@ public void testRenameBindingVariableType() throws Exception { } + public void testRenameClassInAnnotation() throws Exception { + TestInput input = TestUtilities.splitCodeAndPos(""" + package t; + public class T|est { + } + """); + + writeFilesAndWaitForScan(src, + new File("t/Test.java", input.code()), + new File("t/Ann.java", + """ + package t; + @interface Ann { + public Class value(); + } + """), + new File("t/Use.java", + """ + package t; + public class Use { + @Ann(Test.class) + void t1() {} + @Ann({Test.class}) + void t2() {} + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("t/Test.java"), input.pos(), "NewName", props, true); + verifyContent(src, + new File("t/Test.java", + """ + package t; + public class NewName { + } + """), + new File("t/Ann.java", + """ + package t; + @interface Ann { + public Class value(); + } + """), + new File("t/Use.java", + """ + package t; + public class Use { + @Ann(NewName.class) + void t1() {} + @Ann({NewName.class}) + void t2() {} + } + """)); + } + private void performRename(FileObject source, final int position, final int position2, final String newname, final JavaRenameProperties props, final boolean searchInComments, Problem... expectedProblems) throws Exception { final RenameRefactoring[] r = new RenameRefactoring[1]; JavaSource.forFileObject(source).runUserActionTask(new Task() {