java: introduce parameter object: use records when applicable (IDEA-264907)

GitOrigin-RevId: 18963c681bcc93e2ed9ba72cec75a026aab1b345
This commit is contained in:
Anna Kozlova
2021-03-22 20:09:07 +00:00
committed by intellij-monorepo-bot
parent 1418aaa7fb
commit d265a0c1cd
24 changed files with 249 additions and 319 deletions
@@ -15,6 +15,7 @@
*/
package com.intellij.refactoring.introduceparameterobject;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
import com.intellij.ide.highlighter.JavaFileType;
@@ -129,14 +130,21 @@ public class JavaIntroduceParameterObjectClassDescriptor extends IntroduceParame
}
@Override
public String getGetterName(ParameterInfoImpl paramInfo, @NotNull PsiElement context) {
public String getGetterName(ParameterInfoImpl paramInfo,
@NotNull PsiElement context,
ReadWriteAccessDetector.Access access) {
final ParameterBean bean = getBean(paramInfo);
@NonNls String getter = bean != null ? bean.getGetter() : null;
if (getter == null) {
getter = bean != null && bean.getField() != null ? GenerateMembersUtil.suggestGetterName(bean.getField())
: GenerateMembersUtil
.suggestGetterName(paramInfo.getName(), paramInfo.getTypeWrapper().getType(context),
context.getProject());
if (bean != null && bean.getField() != null) {
getter = GenerateMembersUtil.suggestGetterName(bean.getField());
}
else if (bean == null && access == ReadWriteAccessDetector.Access.Read && HighlightingFeature.RECORDS.isAvailable(context)) {
getter = paramInfo.getName();
}
else {
getter = GenerateMembersUtil.suggestGetterName(paramInfo.getName(), paramInfo.getTypeWrapper().getType(context), context.getProject());
}
}
return getter;
}
@@ -184,34 +184,51 @@ public class JavaIntroduceParameterObjectDelegate
final PsiParameter parameter = params[parameterInfo.getOldIndex()];
final ReadWriteAccessDetector detector = ReadWriteAccessDetector.findDetector(parameter);
assert detector != null;
final String setter = classDescriptor.getSetterName(parameterInfo, overridingMethod);
final String getter = classDescriptor.getGetterName(parameterInfo, overridingMethod);
final ReadWriteAccessDetector.Access[] accessor = new ReadWriteAccessDetector.Access[]{null};
final List<PsiReferenceExpression> readUsages = new ArrayList<>();
final List<PsiReferenceExpression> readWriteUsages = new ArrayList<>();
final List<PsiReferenceExpression> writeUsages = new ArrayList<>();
ReferencesSearch.search(parameter, localSearchScope).forEach(reference -> {
final PsiElement refElement = reference.getElement();
if (refElement instanceof PsiReferenceExpression) {
final PsiReferenceExpression paramUsage = (PsiReferenceExpression)refElement;
final ReadWriteAccessDetector.Access access = detector.getExpressionAccess(refElement);
if (access == ReadWriteAccessDetector.Access.Read) {
usages.add(new ReplaceParameterReferenceWithCall(paramUsage, mergedParamName, getter));
if (accessor[0] == null) {
accessor[0] = ReadWriteAccessDetector.Access.Read;
}
}
else {
if (access == ReadWriteAccessDetector.Access.ReadWrite) {
usages.add(new ReplaceParameterIncrementDecrement(paramUsage, mergedParamName, setter, getter));
}
else {
usages.add(new ReplaceParameterAssignmentWithCall(paramUsage, mergedParamName, setter, getter));
}
accessor[0] = ReadWriteAccessDetector.Access.Write;
switch (detector.getExpressionAccess(refElement)) {
case Read:
readUsages.add(paramUsage);
break;
case ReadWrite:
readWriteUsages.add(paramUsage);
break;
case Write:
writeUsages.add(paramUsage);
break;
}
}
return true;
}
);
return accessor[0];
ReadWriteAccessDetector.Access access =
readWriteUsages.isEmpty() && writeUsages.isEmpty() ? ReadWriteAccessDetector.Access.Read : ReadWriteAccessDetector.Access.Write;
if (access == ReadWriteAccessDetector.Access.Read && readUsages.isEmpty()) {
return null;
}
final String setter = classDescriptor.getSetterName(parameterInfo, overridingMethod);
final String getter = classDescriptor.getGetterName(parameterInfo, overridingMethod, access);
readUsages.stream()
.map(paramUsage -> new ReplaceParameterReferenceWithCall(paramUsage, mergedParamName, getter)).forEach(usages::add);
readWriteUsages.stream()
.map(paramUsage -> new ReplaceParameterIncrementDecrement(paramUsage, mergedParamName, setter, getter))
.forEach(usages::add);
writeUsages.stream()
.map(paramUsage -> new ReplaceParameterAssignmentWithCall(paramUsage, mergedParamName, setter, getter))
.forEach(usages::add);
return access;
}
@Override
@@ -16,14 +16,17 @@
package com.intellij.refactoring.introduceparameterobject;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -33,34 +36,35 @@ import java.util.Iterator;
import java.util.List;
class ParameterObjectBuilder {
private String className;
private String packageName;
private final List<ParameterSpec> fields = new ArrayList<>(5);
private final List<PsiTypeParameter> typeParams = new ArrayList<>();
private Project myProject;
private PsiFile myFile;
private JavaCodeStyleManager myJavaCodeStyleManager ;
private String className;
private String packageName;
private final List<ParameterSpec> fields = new ArrayList<>(5);
private final List<PsiTypeParameter> typeParams = new ArrayList<>();
private Project myProject;
private PsiFile myFile;
private JavaCodeStyleManager myJavaCodeStyleManager;
private String myVisibility;
public void setClassName(String className) {
this.className = className;
}
this.className = className;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void addField(PsiParameter variable, String name, PsiType type, boolean setterRequired) {
final String propertyName = myJavaCodeStyleManager.variableNameToPropertyName(name, VariableKind.PARAMETER);
final ParameterSpec field = new ParameterSpec(variable, myJavaCodeStyleManager.propertyNameToVariableName(propertyName, VariableKind.FIELD),
type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type, setterRequired);
fields.add(field);
}
public void addField(PsiParameter variable, String name, PsiType type, boolean setterRequired) {
final String propertyName = myJavaCodeStyleManager.variableNameToPropertyName(name, VariableKind.PARAMETER);
final ParameterSpec field =
new ParameterSpec(variable, myJavaCodeStyleManager.propertyNameToVariableName(propertyName, VariableKind.FIELD),
type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type, setterRequired);
fields.add(field);
}
public void setTypeArguments(Collection<PsiTypeParameter> typeParams) {
this.typeParams.clear();
this.typeParams.addAll(typeParams);
}
this.typeParams.clear();
this.typeParams.addAll(typeParams);
}
public void setProject(final Project project) {
myProject = project;
@@ -72,144 +76,170 @@ class ParameterObjectBuilder {
}
public String buildBeanClass() {
@NonNls final StringBuffer out = new StringBuffer(1024);
if (packageName.length() > 0) out.append("package " + packageName + ';');
out.append('\n');
out.append(myVisibility + " class " + className);
if (!typeParams.isEmpty()) {
out.append('<');
boolean first = true;
for (PsiTypeParameter typeParam : typeParams) {
if (!first) {
out.append(',');
}
out.append(typeParam.getText());
first = false;
}
out.append('>');
boolean recordsAvailable = HighlightingFeature.RECORDS.isAvailable(myFile) &&
!ContainerUtil.exists(fields, ParameterSpec::isSetterRequired);
@NonNls final StringBuilder out = new StringBuilder(1024);
if (packageName.length() > 0) out.append("package ").append(packageName).append(';');
out.append('\n');
out.append(myVisibility).append(" ");
out.append(recordsAvailable ? PsiKeyword.RECORD : PsiKeyword.CLASS);
out.append(" ").append(className);
if (!typeParams.isEmpty()) {
out.append('<');
boolean first = true;
for (PsiTypeParameter typeParam : typeParams) {
if (!first) {
out.append(',');
}
out.append('\n');
out.append('{');
outputFields(out);
outputConstructor(out);
outputGetters(out);
outputSetters(out);
out.append("}\n");
return out.toString();
out.append(typeParam.getText());
first = false;
}
out.append('>');
}
private void outputSetters(StringBuffer out) {
for (final ParameterSpec field : fields) {
outputSetter(field, out);
}
}
private void outputGetters(StringBuffer out) {
for (final ParameterSpec field : fields) {
outputGetter(field, out);
if (recordsAvailable) {
out.append("(");
fields.stream().map(param -> param.getType().getCanonicalText(true) + " " + param.getName());
StringUtil.join(fields, param -> {
PsiType type = param.getType();
if (param.getParameter().isVarArgs() && type instanceof PsiArrayType) {
type = new PsiEllipsisType(((PsiArrayType)type).getComponentType(), type.getAnnotations());
}
return type.getCanonicalText(true) + " " + param.getName();
}, ", ", out);
out.append("){}");
}
else {
private void outputFields(StringBuffer out) {
for (final ParameterSpec field : fields) {
outputField(field, out);
}
out.append('\n');
out.append('{');
outputFields(out);
outputConstructor(out);
outputGetters(out);
outputSetters(out);
out.append("}\n");
}
return out.toString();
}
private void outputSetter(ParameterSpec field, @NonNls StringBuffer out) {
if (!field.isSetterRequired()) {
return;
}
out.append(GenerateMembersUtil.generateSetterPrototype(JavaPsiFacade.getElementFactory(myProject).createField(field.getName(), field.getType())).getText());
}
private static void generateFieldAssignment(final @NonNls StringBuffer out, final String parameterName, final String fieldName) {
if (fieldName.equals(parameterName)) {
out.append("\t\tthis.").append(fieldName).append(" = ").append(parameterName).append(";\n");
} else {
out.append("\t\t").append(fieldName).append(" = ").append(parameterName).append(";\n");
private void outputSetters(@NonNls StringBuilder out) {
for (final ParameterSpec field : fields) {
outputSetter(field, out);
}
}
private void outputGetter(ParameterSpec field, @NonNls StringBuffer out) {
out.append(GenerateMembersUtil.generateGetterPrototype(JavaPsiFacade.getElementFactory(myProject).createField(field.getName(), field.getType())).getText());
private void outputGetters(@NonNls StringBuilder out) {
for (final ParameterSpec field : fields) {
outputGetter(field, out);
}
}
private void outputFields(StringBuilder out) {
for (final ParameterSpec field : fields) {
outputField(field, out);
}
}
private void outputSetter(ParameterSpec field, @NonNls StringBuilder out) {
if (!field.isSetterRequired()) {
return;
}
out.append(
GenerateMembersUtil.generateSetterPrototype(JavaPsiFacade.getElementFactory(myProject).createField(field.getName(), field.getType()))
.getText());
}
private static void generateFieldAssignment(final @NonNls StringBuilder out, final String parameterName, final String fieldName) {
if (fieldName.equals(parameterName)) {
out.append("\t\tthis.").append(fieldName).append(" = ").append(parameterName).append(";\n");
}
else {
out.append("\t\t").append(fieldName).append(" = ").append(parameterName).append(";\n");
}
}
private void outputGetter(ParameterSpec field, @NonNls StringBuilder out) {
out.append(
GenerateMembersUtil.generateGetterPrototype(JavaPsiFacade.getElementFactory(myProject).createField(field.getName(), field.getType()))
.getText());
}
@NotNull
private CodeStyleSettings getSettings() {
return myFile != null ? CodeStyle.getSettings(myFile) : CodeStyle.getProjectOrDefaultSettings(myProject);
}
private void outputConstructor(@NonNls StringBuffer out) {
out.append("\t" + myVisibility + " " + className + '(');
for (Iterator<ParameterSpec> iterator = fields.iterator(); iterator.hasNext();) {
final ParameterSpec field = iterator.next();
final PsiParameter parameter = field.getParameter();
outputAnnotationString(parameter, out);
out.append(getSettings().getCustomSettings(JavaCodeStyleSettings.class).GENERATE_FINAL_PARAMETERS ?
" final " : "");
final String parameterName = parameter.getName();
final PsiType type = field.getType();
final PsiType fieldType = parameter.isVarArgs() && type instanceof PsiArrayType ?
new PsiEllipsisType(((PsiArrayType)type).getComponentType()) : type;
out.append(' ' + fieldType.getCanonicalText() + ' ' + parameterName);
if (iterator.hasNext()) {
out.append(", ");
}
}
out.append(")\n");
out.append("\t{\n");
for (final ParameterSpec field : fields) {
generateFieldAssignment(out, field.getParameter().getName(), field.getName());
}
out.append("\t}\n");
private void outputConstructor(@NonNls StringBuilder out) {
out.append("\t").append(myVisibility).append(" ").append(className).append('(');
for (Iterator<ParameterSpec> iterator = fields.iterator(); iterator.hasNext(); ) {
final ParameterSpec field = iterator.next();
final PsiParameter parameter = field.getParameter();
outputAnnotationString(parameter, out);
out.append(getSettings().getCustomSettings(JavaCodeStyleSettings.class).GENERATE_FINAL_PARAMETERS ?
" final " : "");
final String parameterName = parameter.getName();
final PsiType type = field.getType();
final PsiType fieldType = parameter.isVarArgs() && type instanceof PsiArrayType ?
new PsiEllipsisType(((PsiArrayType)type).getComponentType()) : type;
out.append(' ').append(fieldType.getCanonicalText()).append(' ').append(parameterName);
if (iterator.hasNext()) {
out.append(", ");
}
}
out.append(")\n");
out.append("\t{\n");
for (final ParameterSpec field : fields) {
generateFieldAssignment(out, field.getParameter().getName(), field.getName());
}
out.append("\t}\n");
}
private void outputField(ParameterSpec field, StringBuffer out) {
final PsiParameter parameter = field.getParameter();
final PsiDocComment docComment = getJavadocForVariable(parameter);
if (docComment != null) {
out.append(docComment.getText());
out.append('\n');
}
final PsiType type = field.getType();
final String typeText = type.getCanonicalText();
final String name = field.getName();
@NonNls String modifierString = "private ";
if (!field.isSetterRequired()) {
modifierString += "final ";
}
outputAnnotationString(parameter, out);
out.append('\t' + modifierString + typeText + ' ' + name + ";\n");
private static void outputField(ParameterSpec field, StringBuilder out) {
final PsiParameter parameter = field.getParameter();
final PsiDocComment docComment = getJavadocForVariable(parameter);
if (docComment != null) {
out.append(docComment.getText());
out.append('\n');
}
final PsiType type = field.getType();
final String typeText = type.getCanonicalText();
final String name = field.getName();
@NonNls String modifierString = "private ";
if (!field.isSetterRequired()) {
modifierString += "final ";
}
outputAnnotationString(parameter, out);
out.append('\t').append(modifierString).append(typeText).append(' ').append(name).append(";\n");
}
private void outputAnnotationString(PsiParameter parameter, StringBuffer out) {
final PsiModifierList modifierList = parameter.getModifierList();
final PsiAnnotation[] annotations = modifierList.getAnnotations();
for (PsiAnnotation annotation : annotations) {
final PsiJavaCodeReferenceElement reference = annotation.getNameReferenceElement();
if (reference == null) {
continue;
}
final PsiClass annotationClass = (PsiClass) reference.resolve();
if (annotationClass != null) {
final PsiAnnotationParameterList parameterList = annotation.getParameterList();
final String annotationText = '@' + annotationClass.getQualifiedName() + parameterList.getText();
out.append(annotationText);
}
}
private static void outputAnnotationString(PsiParameter parameter, StringBuilder out) {
final PsiModifierList modifierList = parameter.getModifierList();
final PsiAnnotation[] annotations = modifierList.getAnnotations();
for (PsiAnnotation annotation : annotations) {
final PsiJavaCodeReferenceElement reference = annotation.getNameReferenceElement();
if (reference == null) {
continue;
}
final PsiClass annotationClass = (PsiClass)reference.resolve();
if (annotationClass != null) {
final PsiAnnotationParameterList parameterList = annotation.getParameterList();
final String annotationText = '@' + annotationClass.getQualifiedName() + parameterList.getText();
out.append(annotationText);
}
}
}
private static PsiDocComment getJavadocForVariable(PsiVariable variable) {
final PsiElement[] children = variable.getChildren();
for (PsiElement child : children) {
if (child instanceof PsiDocComment) {
return (PsiDocComment) child;
}
}
return null;
private static PsiDocComment getJavadocForVariable(PsiVariable variable) {
final PsiElement[] children = variable.getChildren();
for (PsiElement child : children) {
if (child instanceof PsiDocComment) {
return (PsiDocComment)child;
}
}
return null;
}
public void setVisibility(String visibility) {
myVisibility = visibility;
@@ -4,23 +4,11 @@ class Test {
* @param param
*/
void foo(Param param) {
bar(param.getS());
bar(param.s());
}
void bar(String s){}
private static class Param {
private final String s;
/**
* @param s long description
*/
private Param(String s) {
this.s = s;
}
public String getS() {
return s;
}
private static record Param(String s) {
}
}
@@ -5,23 +5,11 @@ class Test {
* @param s1 long1 description1
*/
void foo(Param param, String s1) {
bar(param.getS(), s1);
bar(param.s(), s1);
}
void bar(String s, String s1){}
private static class Param {
private final String s;
/**
* @param s long description
*/
private Param(String s) {
this.s = s;
}
public String getS() {
return s;
}
private static record Param(String s) {
}
}
@@ -1,11 +1,2 @@
public class Param {
private final int[] i;
public Param(int... i) {
this.i = i;
}
public int[] getI() {
return i;
}
}
public record Param(int... i) {
}
@@ -4,7 +4,7 @@ class Test {
}
void foo(Param param) {
if (param.getI().lenght == 0) {
if (param.i().lenght == 0) {
}
}
@@ -1,19 +1,10 @@
class Test {
void foo(Param param) {
bar(param.getS());
bar(param.s());
}
void bar(String s){}
private static class Param {
private final String s;
private Param(String s) {
this.s = s;
}
public String getS() {
return s;
}
private static record Param(String s) {
}
}
@@ -1,16 +1,7 @@
public interface Test {
void foo(Param param);
private static class Param {
private final String s;
private Param(String s) {
this.s = s;
}
public String getS() {
return s;
}
private static record Param(String s) {
}
}
@@ -1,11 +1,2 @@
public class Param<R> {
private final R r;
public Param(R r) {
this.r = r;
}
public R getR() {
return r;
}
}
public record Param<R>(R r) {
}
@@ -1,6 +1,6 @@
class Test<G, R> {
void foo(Param<R> param) {
if (param.getR() == null) {
if (param.r() == null) {
}
}
@@ -1,11 +1,2 @@
public class Param {
private final int i;
public Param(int i) {
this.i = i;
}
public int getI() {
return i;
}
}
public record Param(int i) {
}
@@ -1,6 +1,6 @@
class Test {
void foo(Param param) {
if (param.getI() == 0) {
if (param.i() == 0) {
}
}
@@ -1,17 +1,2 @@
public class Param {
private final String a;
private final String b;
public Param(String a, String b) {
this.a = a;
this.b = b;
}
public String getA() {
return a;
}
public String getB() {
return b;
}
}
public record Param(String a, String b) {
}
@@ -1,11 +1,2 @@
public class Param<R> {
private final R r;
public Param(R r) {
this.r = r;
}
public R getR() {
return r;
}
}
public record Param<R>(R r) {
}
@@ -1,6 +1,6 @@
class Test<R> {
void foo(Param<R> param) {
if (param.getR() == null) {
if (param.r() == null) {
}
}
@@ -3,22 +3,13 @@ import java.util.List;
public class Test<U extends List> {
public Collection foo(Param param) {
return param.getP();
return param.p();
}
public void context1(U p) {
Collection v = foo(new Param(p));
}
private static class Param {
private final Collection p;
private Param(Collection p) {
this.p = p;
}
public Collection getP() {
return p;
}
private static record Param(Collection p) {
}
}
@@ -1,13 +1,4 @@
import java.util.List;
public class Param<T> {
private final List<T> y;
public Param(List<T> y) {
this.y = y;
}
public List<T> getY() {
return y;
}
}
public record Param<T>(List<T> y) {
}
@@ -1,19 +1,10 @@
class Test {
void foo(Param param) {
bar(param.getS());
bar(param.s());
}
void bar(String s){}
static class Param {
private final String s;
Param(String s) {
this.s = s;
}
public String getS() {
return s;
}
static record Param(String s) {
}
}
@@ -1,11 +1,2 @@
public class Param {
private final int[] i;
public Param(int... i) {
this.i = i;
}
public int[] getI() {
return i;
}
}
public record Param(int... i) {
}
@@ -1,6 +1,6 @@
class Test {
void foo(Param param) {
if (param.getI().lenght == 0) {
if (param.i().lenght == 0) {
}
}
@@ -136,7 +136,9 @@ public abstract class IntroduceParameterObjectClassDescriptor<M extends PsiNamed
* Corresponding field accessors how they should appear inside changed method body
*/
public abstract String getSetterName(P paramInfo, @NotNull PsiElement context);
public abstract String getGetterName(P paramInfo, @NotNull PsiElement context);
public abstract String getGetterName(P paramInfo,
@NotNull PsiElement context,
ReadWriteAccessDetector.Access access);
/**
* Called if use existing class is chosen only. Should find constructor to use
@@ -39,7 +39,9 @@ public class GroovyIntroduceObjectClassDescriptor extends IntroduceParameterObje
}
@Override
public String getGetterName(GrParameterInfo paramInfo, @NotNull PsiElement context) {
public String getGetterName(GrParameterInfo paramInfo,
@NotNull PsiElement context,
ReadWriteAccessDetector.Access access) {
throw new UnsupportedOperationException();
}
@@ -117,7 +117,7 @@ public class GroovyIntroduceParameterObjectDelegate
final GrParameter parameter = overridingMethod.getParameterList().getParameters()[oldIndex];
final ReadWriteAccessDetector.Access[] accessors = new ReadWriteAccessDetector.Access[1];
final String setter = classDescriptor.getSetterName(parameterInfo, overridingMethod);
final String getter = classDescriptor.getGetterName(parameterInfo, overridingMethod);
final String getter = classDescriptor.getGetterName(parameterInfo, overridingMethod, ReadWriteAccessDetector.Access.Read);
ReferencesSearch.search(parameter, new LocalSearchScope(overridingMethod)).forEach(reference -> {
final PsiElement element = reference.getElement();
if (element instanceof GrReferenceExpression) {