Java: care about generic class when replacing constructor with builder (IDEA-359490)

GitOrigin-RevId: 7aab29e572eb6da07d2fec202c886500fd944f6a
This commit is contained in:
Bas Leijdekkers
2024-09-24 10:04:30 +00:00
committed by intellij-monorepo-bot
parent 84a8688289
commit ba54dfd721
8 changed files with 69 additions and 42 deletions
@@ -13,6 +13,7 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
@@ -40,6 +41,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
* @author anna
@@ -146,7 +149,7 @@ public class ReplaceConstructorWithBuilderProcessor extends FixableUsagesRefacto
super.performRefactoring(usageInfos);
final PsiMethod method = createMethodSignature(createMethodName());
final PsiMethod method = createBuildMethod(createMethodName());
if (builderClass.findMethodBySignature(method, false) == null) {
builderClass.add(method);
}
@@ -225,18 +228,44 @@ public class ReplaceConstructorWithBuilderProcessor extends FixableUsagesRefacto
}
}
private PsiMethod createMethodSignature(String createMethodName) {
private PsiMethod createBuildMethod(String createMethodName) {
JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(myProject);
final StringBuilder buf = new StringBuilder();
final PsiClass aClass = myConstructors[0].getContainingClass();
assert aClass != null;
PsiType[] typeArguments = Stream.of(aClass.getTypeParameters())
.map(myElementFactory::createType)
.toArray(PsiType[]::new);
PsiMethod method = myElementFactory.createMethod(createMethodName, myElementFactory.createType(aClass, typeArguments));
final StringBuilder bodyText = new StringBuilder();
final PsiMethod constructor = getWorkingConstructor();
bodyText.append("{\n return new ").append(constructor.getName());
if (typeArguments.length > 0) {
if (PsiUtil.isAvailable(JavaFeature.DIAMOND_TYPES, aClass)) {
bodyText.append("<>");
}
else {
bodyText.append('<');
boolean comma = false;
for (PsiType argument : typeArguments) {
bodyText.append(comma ? ", " : "").append(argument.getCanonicalText());
comma = true;
}
bodyText.append('>');
}
}
bodyText.append("(");
boolean comma = false;
for (PsiParameter parameter : constructor.getParameterList().getParameters()) {
final String pureParamName = styleManager.variableNameToPropertyName(parameter.getName(), VariableKind.PARAMETER);
if (!buf.isEmpty()) buf.append(", ");
buf.append(myParametersMap.get(pureParamName).getFieldName());
bodyText.append(comma ? ", " : "").append(myParametersMap.get(pureParamName).getFieldName());
comma = true;
}
return myElementFactory.createMethodFromText("public " + constructor.getName() + " " + createMethodName + "(){" +
"\n return new " + constructor.getName() + "(" + buf + ");" +
"\n}", constructor);
bodyText.append(");\n}");
PsiCodeBlock body = myElementFactory.createCodeBlockFromText(bodyText.toString(), method);
Objects.requireNonNull(method.getBody()).replace(body);
return method;
}
private PsiMethod getWorkingConstructor() {
@@ -1,19 +1,4 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed 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.
*/
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.replaceConstructorWithBuilder.usageInfo;
import com.intellij.openapi.util.Comparing;
@@ -21,11 +6,13 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.refactoring.replaceConstructorWithBuilder.ParameterData;
import com.intellij.refactoring.util.FixableUsageInfo;
import com.intellij.util.IncorrectOperationException;
import java.util.Map;
import java.util.Objects;
public class ReplaceConstructorWithSettersChainInfo extends FixableUsageInfo {
private final String myBuilderClass;
@@ -65,9 +52,11 @@ public class ReplaceConstructorWithSettersChainInfo extends FixableUsageInfo {
buf.append(data.getSetterName()).append("(").append(arg).append(").");
}
}
PsiNewExpression expression = (PsiNewExpression)PsiDiamondTypeUtil.expandTopLevelDiamondsInside(expr);
PsiReferenceParameterList typeArguments = Objects.requireNonNull(expression.getClassReference()).getParameterList();
assert typeArguments != null;
final PsiExpression settersChain = elementFactory.createExpressionFromText(
"new " + myBuilderClass + "()." + buf + "create" + StringUtil.capitalize(constructor.getName()) + "()",
"new " + myBuilderClass + typeArguments.getText() + "()." + buf + "create" + StringUtil.capitalize(constructor.getName()) + "()",
null);
styleManager.shortenClassReferences(expr.replace(settersChain));
@@ -1,4 +1,4 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.util;
import com.intellij.codeInsight.AnnotationTargetUtil;
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable;
import java.beans.Introspector;
import java.util.*;
import java.util.stream.Stream;
public class PropertyUtilBase {
@@ -604,9 +605,17 @@ public class PropertyUtilBase {
VariableKind kind = codeStyleManager.getVariableKind(field);
String propertyName = codeStyleManager.variableNameToPropertyName(name, kind);
String setName = suggestSetterName(field);
PsiMethod setMethod = factory
.createMethodFromText(factory.createMethod(setName, returnSelf ? factory.createType(containingClass) : PsiTypes.voidType()).getText(),
field);
PsiMethod setMethod;
if (returnSelf) {
PsiType[] typeArguments = Stream.of(containingClass.getTypeParameters())
.map(factory::createType)
.toArray(PsiType[]::new);
setMethod = factory.createMethod(setName, factory.createType(containingClass, typeArguments));
}
else {
setMethod = factory.createMethod(setName, PsiTypes.voidType());
}
String parameterName = codeStyleManager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER);
PsiParameter param = factory.createParameter(parameterName, AnnotationTargetUtil.keepStrictlyTypeUseAnnotations(field.getModifierList(), field.getType()));
@@ -1,12 +1,12 @@
public class Builder<T> {
private T t;
public Builder setT(T t) {
public Builder<T> setT(T t) {
this.t = t;
return this;
}
public Test createTest() {
return new Test(t);
public Test<T> createTest() {
return new Test<>(t);
}
}
@@ -1,7 +1,7 @@
public class Test<T> {
public Test(T t){}
void foo(){}
public static void main(T args){
new Builder().setT(args).createTest().foo();
public static void main(String[] args){
new Builder<String>().setT(args[0]).createTest().foo();
}
}
@@ -1,7 +1,7 @@
public class Test<T> {
public Test(T t){}
void foo(){}
public static void main(T args){
new Test(args).foo();
public static void main(String[] args){
new Test<>(args[0]).foo();
}
}
@@ -4,17 +4,17 @@ public class Builder<T extends Serializable> {
private String name;
private Class<T> clazz;
public Builder setName(String name) {
public Builder<T> setName(String name) {
this.name = name;
return this;
}
public Builder setClazz(Class<T> clazz) {
public Builder<T> setClazz(Class<T> clazz) {
this.clazz = clazz;
return this;
}
public Test createTest() {
return new Test(name, clazz);
public Test<T> createTest() {
return new Test<>(name, clazz);
}
}
@@ -9,5 +9,5 @@ public class Test<T extends Serializable> {
this.clazz = clazz;
}
public static final Test<Boolean> BOOLEAN = new Builder().setName("b").setClazz(Boolean.class).createTest();
public static final Test<Boolean> BOOLEAN = new Builder<Boolean>().setName("b").setClazz(Boolean.class).createTest();
}