Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2023 IBM Corporation and others.
* Copyright (c) 2019, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -337,6 +337,40 @@ public static boolean isConvertAnonymousAvailable(final IType type) throws JavaM
return false;
}

public static boolean isConvertToRecordAvailable(final IType type) throws JavaModelException {
if (Checks.isAvailable(type)) {
if (type.isImplicitlyDeclared() || type.isBinary() || type.isLambda() || type.isAnnotation()) {
return false;
}
if (type.isClass()) {
IField[] fields= type.getFields();
if (fields.length == 0) {
return false;
}
for (IField field : fields) {
if (!(Flags.isPrivate(field.getFlags()))) {
return false;
}
}
IMethod[] methods= type.getMethods();
boolean hasConstructor= false;
for (IMethod method : methods) {
if (method.isConstructor()) {
if (hasConstructor) {
return false;
}
hasConstructor= true;
if (method.getNumberOfParameters() < fields.length) {
return false;
}
}
}
}
return type.isClass();
}
return false;
}

public static boolean isDeleteAvailable(final IResource resource) {
if (!resource.exists() || resource.isPhantom())
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,16 @@ public final class RefactoringCoreMessages extends NLS {

public static String ConvertToRecordRefactoring_cannot_extend;

public static String ConvertToRecordRefactoring_fields_initialized;

public static String ConvertToRecordRefactoring_multiple_constructors;

public static String ConvertToRecordRefactoring_nonstandard_constructor;

public static String ConvertToRecordRefactoring_not_implicit_getter;

public static String ConvertToRecordRefactoring_setter_found;

static {
NLS.initializeMessages(BUNDLE_NAME, RefactoringCoreMessages.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
Expand Down Expand Up @@ -79,7 +82,6 @@
import org.eclipse.jdt.internal.corext.codemanipulation.GetterSetterUtil;
import org.eclipse.jdt.internal.corext.dom.ASTFlattener;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.AbortSearchException;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.dom.SelectionAnalyzer;
Expand Down Expand Up @@ -136,7 +138,20 @@ public String getName() {
return RefactoringCoreMessages.ConvertToRecordRefactoring_name;
}


public String getClassName() {
IJavaElement element= null;
try {
element= fCu.getElementAt(fSelectionStart);
} catch (JavaModelException e) {
// ignore
}
if (element instanceof IType type)
return type.getElementName();
if (element instanceof IMember member) {
return member.getDeclaringType().getElementName();
}
return "class"; //$NON-NLS-1$
}

@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
Expand All @@ -147,6 +162,12 @@ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws Core
if (result.hasFatalError())
return result;

if (fASTRoot == null) {
fASTRoot= Checks.convertICUtoCU(fCu);
}
if (fASTRoot == null) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_unexpected_error);
}
ASTNode selected= getSelectedNode(fASTRoot, fSelectionStart, fSelectionLength);

ASTNode selectedType= ASTNodes.getFirstAncestorOrNull(selected,
Expand Down Expand Up @@ -201,25 +222,35 @@ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws Core
IMethod[] methods= sourceType.getMethods();
for (IVariableBinding field : fields) {
IMethodBinding getter= findGetter(fTypeBinding, field);
if (getter == null) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_not_simple_case);
if (getter != null) {
fGetterMap.put(field, getter);
}
IMethodBinding setter= findSetter(fTypeBinding, field);
if (setter != null) {
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.ConvertToRecordRefactoring_setter_found, field.getName()));
}
fGetterMap.put(field, getter);
}

if (methods.length < fields.length + 1) {
return RefactoringStatus.createWarningStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_not_enough_getters);
if (fGetterMap.size() < fields.length) {
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_not_enough_getters));
} else if (methods.length > fields.length + 1) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_not_simple_case);
}
class VisitException extends RuntimeException {
private static final long serialVersionUID= 1L;

public VisitException(String message) {
super(message);
}
}
try {
fTypeDeclaration.accept(new ASTVisitor() {
@Override
public boolean visit(FieldDeclaration node) {
List<VariableDeclarationFragment> fragments= node.fragments();
for (VariableDeclarationFragment fragment : fragments) {
if (fragment.getInitializer() != null) {
throw new AbortSearchException();
throw new VisitException(RefactoringCoreMessages.ConvertToRecordRefactoring_fields_initialized);
}
}
return false;
Expand All @@ -229,7 +260,7 @@ public boolean visit(MethodDeclaration node) {
List<Statement> statements= ASTNodes.asList(node.getBody());
if (node.isConstructor()) {
if (fConstructor != null) {
throw new AbortSearchException();
throw new VisitException(RefactoringCoreMessages.ConvertToRecordRefactoring_multiple_constructors);
}
fConstructor= node;
for (Statement statement : statements) {
Expand All @@ -239,24 +270,33 @@ public boolean visit(MethodDeclaration node) {
Expression rightHandSide= assignment.getRightHandSide();
if (rightHandSide instanceof SimpleName simpleName) {
IBinding binding= simpleName.resolveBinding();
if (binding instanceof IVariableBinding simpleNameBinding) {
if (binding instanceof IVariableBinding simpleNameBinding && simpleNameBinding.isParameter()) {
String getterName= GetterSetterUtil.getGetterName(fCu.getJavaProject(), simpleNameBinding.getName(), simpleNameBinding.getModifiers(), isBoolean(simpleNameBinding), null);
if (isGetterField(leftHandSide, getterName)) {
continue;
}
if (leftHandSide instanceof SimpleName leftHandName) {
IBinding leftBinding= leftHandName.resolveBinding();
if (leftBinding instanceof IVariableBinding varBinding
&& varBinding.isField()) {
continue;
}
} else if (leftHandSide instanceof FieldAccess) {
continue;
}
}
}
}
}
throw new AbortSearchException();
throw new VisitException(RefactoringCoreMessages.ConvertToRecordRefactoring_nonstandard_constructor);
}
} else {
if (statements.size() != 1 || !(statements.get(0) instanceof ReturnStatement retStmt)) {
throw new AbortSearchException();
throw new VisitException(RefactoringCoreMessages.ConvertToRecordRefactoring_not_implicit_getter);
} else {
Expression exp= retStmt.getExpression();
if (!isGetterField(exp, node.getName().getFullyQualifiedName())) {
throw new AbortSearchException();
throw new VisitException(RefactoringCoreMessages.ConvertToRecordRefactoring_not_implicit_getter);
}
}
}
Expand All @@ -282,8 +322,8 @@ private boolean isGetterField(Expression exp, String name) {
return false;
}
});
} catch (AbortSearchException e) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ConvertToRecordRefactoring_not_simple_case);
} catch (VisitException e) {
return RefactoringStatus.createFatalErrorStatus(e.getMessage());
}
}
return result;
Expand Down Expand Up @@ -312,6 +352,14 @@ private IMethodBinding findGetter(ITypeBinding declaringType, IVariableBinding v
return null;
}

private IMethodBinding findSetter(ITypeBinding declaringType, IVariableBinding variableBinding) {
ITypeBinding fieldType= variableBinding.getType();
String setterName= GetterSetterUtil.getSetterName(variableBinding, fCu.getJavaProject(), null, isBoolean(variableBinding));
if (declaringType == null)
return null;
IMethodBinding setter= Bindings.findMethodInHierarchy(declaringType, setterName, new ITypeBinding[] { fieldType });
return setter;
}

public static ASTNode getSelectedNode(CompilationUnit cu, int selectionOffset, int selectionLength) {
SelectionAnalyzer analyzer= new SelectionAnalyzer(Selection.createFromStartLength(selectionOffset, selectionLength), false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -879,13 +879,18 @@ ConvertToRecordRefactoring_unexpected_error=Unexpected error occurred
ConvertToRecordRefactoring_not_private=Type has non-private instance field
ConvertToRecordRefactoring_cannot_convert_fields=Cannot convert all non-static fields to components
ConvertToRecordRefactoring_has_constructors=Type has more than one constructor
ConvertToRecordRefactoring_not_simple_case=Type is not simple scenario
ConvertToRecordRefactoring_not_simple_case=Conversion of this class is not supported
ConvertToRecordRefactoring_progress_updating_references=Updating references
ConvertToRecordRefactoring_group_replace_getters=Replace getter calls
ConvertToRecordRefactoring_descriptor_description_short=Convert ''{0}'' to record
ConvertToRecordRefactoring_descriptor_description=Convert ''{0}'' to record
ConvertToRecordRefactoring_not_enough_getters=Missing at least one getter method for private field
ConvertToRecordRefactoring_not_enough_getters=At least one field without getter will become exposed publicly via accessor
ConvertToRecordRefactoring_cannot_extend=Cannot convert to record if there is a super class
ConvertToRecordRefactoring_fields_initialized=Private field has initializer
ConvertToRecordRefactoring_multiple_constructors=More than one constructor
ConvertToRecordRefactoring_nonstandard_constructor=Constructor is non-standard and cannot be implicit for record
ConvertToRecordRefactoring_not_implicit_getter=Method found that cannot be replaced by implicit accessor
ConvertToRecordRefactoring_setter_found=Cannot convert because setter method found for field: ''{0}''

UseSupertypeWherePossibleRefactoring_name=Use Supertype Where Possible
UseSuperTypeProcessor_name=Use Supertype
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package p;
// Class A
public class A {

private final int a;
private final String b;

public A(int a, String b) {
this.a= a;
this.b= b;
}

public int getA() {
return a;
}

public String getB() {
return b;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package p;
// Class A
public record A (int a,String b) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package p;
// Class A
public class A {

/**
* Inner
*/
static class Inner {
private final int a;
private final String b;

public Inner(int a, String b) {
this.a= a;
this.b= b;
}

public int getA() {
return a;
}

public String getB() {
return b;
}
}

public void foo() {
Inner x= new Inner(3, "abc");
System.out.println(x.getA());
System.out.println(x.getB());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package p;
// Class A
public class A {

/**
* Inner
*/
static record Inner (int a, String b) {}

public void foo() {
Inner x= new Inner(3, "abc");
System.out.println(x.a());
System.out.println(x.b());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package p;
// Class A
public class A {

/**
* Inner
*/
private static class Inner extends Object {
private final int a;
private final String b;

public Inner(int a, String b) {
this.a= a;
this.b= b;
}

public int getA() {
return a;
}

public String getB() {
return b;
}
}

public void foo() {
Inner x= new Inner(3, "abc");
System.out.println(x.getA());
System.out.println(x.getB());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package p;
// Class A
public class A {

/**
* Inner
*/
private static record Inner (int a, String b) {}

public void foo() {
Inner x= new Inner(3, "abc");
System.out.println(x.a());
System.out.println(x.b());
}
}
Loading
Loading