Skip to content

Commit fd0ffe2

Browse files
authored
Add generic class/record support (#13)
* Add generic class/record support * Fix find existing builder class * Fix positions and refactor to separate files
1 parent d1be444 commit fd0ffe2

File tree

10 files changed

+112
-52
lines changed

10 files changed

+112
-52
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ tasks {
3232

3333
patchPluginXml {
3434
sinceBuild.set("241")
35-
untilBuild.set("243.*")
35+
untilBuild.set("251.*")
3636
}
3737

3838
signPlugin {

src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ protected boolean addImport(PsiType psiType) {
6666
return generatorParams.psi().codeStyleManager().addImport((PsiJavaFile) generatorParams.psi().file(), psiClass);
6767
}
6868

69+
protected PsiMethod findFirstConstructor(PsiClass target) {
70+
var constructors = target.getConstructors();
71+
return constructors.length > 0 ? constructors[0] : null;
72+
}
73+
6974
private PsiMethod findConstructor(PsiClass target, PsiMethod newMethod) {
7075
for (var constructor : target.getConstructors()) {
7176
if (Utils.areParameterListsEqual(constructor.getParameterList(), newMethod.getParameterList())) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.junkfactory.innerbuilder.generators;
2+
3+
import com.intellij.psi.PsiClass;
4+
import com.intellij.psi.PsiType;
5+
6+
public record BuilderClass(PsiClass psiClass,
7+
PsiType builderType,
8+
BuilderClassName builderClassName,
9+
boolean genericType) {
10+
}
11+

src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public GenerationResult generate() {
2020
//builder constructor
2121
var builderClass = builderClassParams.builderClass();
2222
var builderConstructor = generateBuilderConstructor();
23-
addMethod(builderClass, null, builderConstructor, false);
23+
addMethod(builderClass.psiClass(), null, builderConstructor, false);
2424

2525
var fieldsGenerator = generatorFactory.createBuilderFieldsGenerator(generatorParams, builderClassParams);
2626
fieldsGenerator.generate();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.github.junkfactory.innerbuilder.generators;
2+
3+
public record BuilderClassName(String className, String instanceClassName) {
4+
}
5+
Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package com.github.junkfactory.innerbuilder.generators;
22

33
import com.intellij.psi.PsiClass;
4-
import com.intellij.psi.PsiType;
54

6-
public record BuilderClassParams(PsiClass targetClass, PsiClass builderClass, PsiType builderType) {
5+
public record BuilderClassParams(PsiClass targetClass,
6+
BuilderClass builderClass) {
77

88
public static Builder builder() {
99
return new Builder();
1010
}
1111

1212
public static final class Builder {
1313
private PsiClass targetClass;
14-
private PsiClass builderClass;
15-
private PsiType builderType;
14+
private BuilderClass builderClass;
1615

1716
private Builder() {
1817
}
@@ -22,18 +21,13 @@ public Builder targetClass(PsiClass targetClass) {
2221
return this;
2322
}
2423

25-
public Builder builderClass(PsiClass builderClass) {
24+
public Builder builderClass(BuilderClass builderClass) {
2625
this.builderClass = builderClass;
2726
return this;
2827
}
2928

30-
public Builder builderType(PsiType builderType) {
31-
this.builderType = builderType;
32-
return this;
33-
}
34-
3529
public BuilderClassParams build() {
36-
return new BuilderClassParams(targetClass, builderClass, builderType);
30+
return new BuilderClassParams(targetClass, builderClass);
3731
}
3832
}
3933
}

src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ public List<PsiField> getFields() {
3030
public GenerationResult generate() {
3131
PsiField lastAddedField = null;
3232
for (var fieldMember : generatorParams.psi().selectedFields()) {
33-
lastAddedField = createOrUpdateField(builderClassParams.builderClass(), fieldMember, lastAddedField);
33+
lastAddedField =
34+
createOrUpdateField(builderClassParams.builderClass().psiClass(), fieldMember, lastAddedField);
3435
fields.add(lastAddedField);
3536
}
36-
cleanupFields(builderClassParams.builderClass());
37+
cleanupFields(builderClassParams.builderClass().psiClass());
3738
return GenerationResult.NO_RESULT;
3839
}
3940

src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@ public GenerationResult generate() {
3434
var targetClass = builderClassParams.targetClass();
3535
var targetModifierList = Objects.requireNonNull(targetClass.getModifierList());
3636
isPublic = targetModifierList.hasModifierProperty(PsiModifier.PUBLIC);
37-
PsiElement lastAddedElement = null;
37+
PsiElement lastAddedElement = findFirstConstructor(builderClass.psiClass());
3838
for (var field : fieldsGenerator.getFields()) {
3939
var setterMethod = generateFieldMethod(field);
4040
field.putCopyableUserData(UserDataKey.METHOD_REF, setterMethod.getName());
41-
lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
41+
lastAddedElement = addMethod(builderClass.psiClass(), lastAddedElement, setterMethod, false);
4242
}
4343

4444
var options = generatorParams.options();
4545
if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) {
4646
var validateMethod = generateValidateMethod();
47-
addMethod(builderClass, lastAddedElement, validateMethod, false);
47+
lastAddedElement = addMethod(builderClass.psiClass(), lastAddedElement, validateMethod, false);
4848
}
4949

5050
var buildMethod = generateBuildMethod(targetClass);
51-
addMethod(builderClass, null, buildMethod, builderClassParams.targetClass().isRecord());
51+
addMethod(builderClass.psiClass(), lastAddedElement, buildMethod, builderClassParams.targetClass().isRecord());
5252
return generationResult;
5353
}
5454

@@ -92,7 +92,7 @@ private PsiMethod generatePutToMap(PsiField field, PsiMethod fieldPutMethod) {
9292
if (isPublic) {
9393
methodText.append(PsiModifier.PUBLIC).append(' ');
9494
}
95-
methodText.append(BUILDER_CLASS_NAME)
95+
methodText.append(builderClassParams.builderClass().builderType().getPresentableText())
9696
.append(' ')
9797
.append(methodName)
9898
.append('(')
@@ -130,7 +130,7 @@ private PsiMethod generateAddToCollection(PsiField field, PsiMethod fieldAddMeth
130130
if (isPublic) {
131131
methodText.append(PsiModifier.PUBLIC).append(' ');
132132
}
133-
methodText.append(BUILDER_CLASS_NAME)
133+
methodText.append(builderClassParams.builderClass().builderType().getPresentableText())
134134
.append(' ')
135135
.append(methodName)
136136
.append('(')
@@ -159,7 +159,7 @@ private PsiMethod generateBuilderSetter(PsiField field) {
159159
if (isPublic) {
160160
methodText.append(PsiModifier.PUBLIC).append(' ');
161161
}
162-
methodText.append(BUILDER_CLASS_NAME)
162+
methodText.append(builderClassParams.builderClass().builderType().getPresentableText())
163163
.append(' ')
164164
.append(fieldName)
165165
.append('(')
@@ -180,10 +180,11 @@ private PsiMethod generateBuilderSetter(PsiField field) {
180180
}
181181

182182
private PsiMethod generateBuildMethod(PsiClass targetClass) {
183+
var targetClassName = Utils.buildClassName(targetClass.getName(), targetClass);
183184
var buildMethod = new StringBuilder()
184185
.append(isPublic ? PsiModifier.PUBLIC : EMPTY)
185186
.append(isPublic ? SPACE : EMPTY)
186-
.append(targetClass.getName())
187+
.append(targetClassName.className())
187188
.append(" build() {");
188189
if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) {
189190
buildMethod.append("validate();");
@@ -193,13 +194,13 @@ private PsiMethod generateBuildMethod(PsiClass targetClass) {
193194
.map(PsiField::getName)
194195
.collect(Collectors.joining(", "));
195196
buildMethod.append("return new ")
196-
.append(targetClass.getName())
197+
.append(targetClassName.instanceClassName())
197198
.append("(")
198199
.append(recordParameters)
199200
.append(");");
200201
} else {
201202
buildMethod.append("return new ")
202-
.append(targetClass.getName())
203+
.append(targetClassName.instanceClassName())
203204
.append("(this);");
204205
}
205206
buildMethod.append("}");

src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.intellij.psi.PsiJavaFile;
88
import com.intellij.psi.PsiMethod;
99
import com.intellij.psi.PsiModifier;
10-
import com.intellij.psi.PsiType;
1110
import com.intellij.psi.codeStyle.CodeStyleManager;
1211
import com.intellij.psi.util.PropertyUtilBase;
1312
import com.intellij.psi.util.PsiUtil;
@@ -33,54 +32,51 @@ public GenerationResult generate() {
3332
if (targetClass == null || BUILDER_CLASS_NAME.equals(targetClass.getName())) {
3433
return NO_RESULT;
3534
}
36-
var psiElementFactory = generatorParams.psi().factory();
3735
var builderClass = findOrCreateBuilderClass(targetClass);
38-
var builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, targetClass);
3936

4037
if (!targetClass.isRecord()) {
41-
var constructor = generateTargetConstructor(targetClass, builderType);
38+
var constructor = generateTargetConstructor(targetClass, builderClass);
4239
addMethod(targetClass, null, constructor, true);
4340
}
4441

45-
var newBuilderMethod = generateStaticBuilderMethod(targetClass, builderType);
42+
var newBuilderMethod = generateStaticBuilderMethod(targetClass, builderClass);
4643
addMethod(targetClass, null, newBuilderMethod, false);
4744

4845
// toBuilder method
4946
var options = generatorParams.options();
5047
if (options.contains(JavaInnerBuilderOption.WITH_TO_BUILDER_METHOD)) {
51-
var toBuilderMethod = generateToBuilderMethod(targetClass, builderType,
48+
var toBuilderMethod = generateToBuilderMethod(targetClass, builderClass,
5249
generatorParams.psi().selectedFields());
5350
addMethod(targetClass, null, toBuilderMethod, true);
5451
}
5552

5653
var params = BuilderClassParams.builder()
5754
.targetClass(targetClass)
5855
.builderClass(builderClass)
59-
.builderType(builderType)
6056
.build();
6157
var result = generatorFactory.createBuilderClassGenerator(generatorParams, params).generate();
6258
generationResult.merge(result);
6359
var codeStyleManager = generatorParams.psi().codeStyleManager();
6460
generationResult.when(ANNOTATIONS_ADDED, () -> codeStyleManager.shortenClassReferences(targetClass));
6561
generationResult.when(IMPORTS_ADDED, () -> codeStyleManager.removeRedundantImports((PsiJavaFile) file));
66-
CodeStyleManager.getInstance(generatorParams.project()).reformat(builderClass);
62+
CodeStyleManager.getInstance(generatorParams.project()).reformat(builderClass.psiClass());
6763
return generationResult;
6864
}
6965

7066
private PsiMethod generateToBuilderMethod(PsiClass targetClass,
71-
PsiType builderType,
67+
BuilderClass builderClass,
7268
Collection<PsiFieldMember> fields) {
7369
var targetModifierList = Objects.requireNonNull(targetClass.getModifierList());
7470
boolean isPublic = targetModifierList.hasModifierProperty(PsiModifier.PUBLIC);
7571
var toBuilderMethod = new StringBuilder()
7672
.append(isPublic ? PsiModifier.PUBLIC : EMPTY)
7773
.append(isPublic ? SPACE : EMPTY)
78-
.append(builderType.getPresentableText())
74+
.append(builderClass.builderType().getPresentableText())
7975
.append(SPACE)
8076
.append(TO_BUILDER_NAME)
8177
.append("() {")
8278
.append("var builder = new ")
83-
.append(builderType.getPresentableText())
79+
.append(builderClass.builderType().getPresentableText())
8480
.append("();");
8581
for (var member : fields) {
8682
var field = member.getElement();
@@ -97,9 +93,10 @@ private PsiMethod generateToBuilderMethod(PsiClass targetClass,
9793
return psiElementFactory.createMethodFromText(toBuilderMethod.toString(), targetClass);
9894
}
9995

100-
private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, PsiType builderType) {
96+
private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, BuilderClass builderClass) {
10197
var psiElementFactory = generatorParams.psi().factory();
102-
var newBuilderMethod = psiElementFactory.createMethod(BUILDER_METHOD_NAME, builderType);
98+
var methodName = Utils.buildBuilderMethodName(builderClass);
99+
var newBuilderMethod = psiElementFactory.createMethodFromText(methodName, targetClass);
103100
PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true);
104101
PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true);
105102

@@ -108,18 +105,18 @@ private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, PsiType buil
108105
existingMethod = newBuilderMethod;
109106
var newBuilderMethodBody = Objects.requireNonNull(existingMethod.getBody());
110107
var newStatement = psiElementFactory.createStatementFromText(String.format(
111-
"return new %s();", builderType.getPresentableText()), newBuilderMethod);
108+
"return new %s();", builderClass.builderClassName().instanceClassName()), newBuilderMethod);
112109
newBuilderMethodBody.add(newStatement);
113110
}
114111
return existingMethod;
115112
}
116113

117-
private PsiMethod generateTargetConstructor(final PsiClass targetClass, final PsiType builderType) {
114+
private PsiMethod generateTargetConstructor(final PsiClass targetClass, BuilderClass builderClass) {
118115
var constructor = new StringBuilder()
119116
.append("private ")
120117
.append(targetClass.getName())
121118
.append("(")
122-
.append(builderType.getPresentableText())
119+
.append(builderClass.builderType().getPresentableText())
123120
.append(" builder) {");
124121

125122
for (var member : generatorParams.psi().selectedFields()) {
@@ -151,19 +148,22 @@ private PsiMethod generateTargetConstructor(final PsiClass targetClass, final Ps
151148
}
152149

153150
@NotNull
154-
private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) {
155-
var builderClass = targetClass.findInnerClassByName(BUILDER_CLASS_NAME, false);
156-
if (builderClass == null) {
157-
return (PsiClass) targetClass.add(createBuilderClass(targetClass));
151+
private BuilderClass findOrCreateBuilderClass(final PsiClass targetClass) {
152+
var builderClassName = Utils.buildClassName(BUILDER_CLASS_NAME, targetClass);
153+
var psiClass = targetClass.findInnerClassByName(BUILDER_CLASS_NAME, false);
154+
if (psiClass == null) {
155+
psiClass = (PsiClass) targetClass.add(createBuilderClass(targetClass, builderClassName.className()));
158156
}
159157

160-
return builderClass;
158+
var psiElementFactory = generatorParams.psi().factory();
159+
var builderType = psiElementFactory.createTypeFromText(builderClassName.className(), targetClass);
160+
return new BuilderClass(psiClass, builderType, builderClassName, Utils.isGenericType(builderType));
161161
}
162162

163163
@NotNull
164-
private PsiClass createBuilderClass(final PsiClass targetClass) {
165-
String classDef = "public static final class " +
166-
BUILDER_CLASS_NAME +
164+
private PsiClass createBuilderClass(final PsiClass targetClass, String builderClassName) {
165+
var classDef = "public static final class " +
166+
builderClassName +
167167
" {}" +
168168
System.lineSeparator();
169169
return generatorParams.psi().factory().createClassFromText(classDef, targetClass)

src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.openapi.editor.Editor;
44
import com.intellij.psi.PsiClass;
5+
import com.intellij.psi.PsiClassType;
56
import com.intellij.psi.PsiField;
67
import com.intellij.psi.PsiFile;
78
import com.intellij.psi.PsiMethod;
@@ -19,6 +20,8 @@
1920
import java.util.Optional;
2021
import java.util.function.Predicate;
2122

23+
import static com.github.junkfactory.innerbuilder.generators.AbstractGenerator.BUILDER_METHOD_NAME;
24+
2225
public class Utils {
2326
@NonNls
2427
static final String JAVA_DOT_LANG = "java.lang.";
@@ -147,9 +150,49 @@ public static List<String> stringToList(String str) {
147150
.toList();
148151
}
149152

150-
public static String parseType(String text) {
151-
var parenthesisIndex = text.indexOf('(');
152-
return parenthesisIndex == -1 ? text : text.substring(0, parenthesisIndex);
153+
public static boolean isGenericType(PsiType psiType) {
154+
// Check if the type is a PsiClassType
155+
if (psiType instanceof PsiClassType classType) {
156+
// Check if it has type parameters
157+
return classType.getParameters().length > 0;
158+
}
159+
return false;
160+
}
161+
162+
public static BuilderClassName buildClassName(String className, PsiClass targetClass) {
163+
var builderClassName = new StringBuilder(className);
164+
var typeParameters = targetClass.getTypeParameters();
165+
if (typeParameters.length > 0) {
166+
builderClassName.append('<');
167+
for (int i = 0, l = typeParameters.length; i < l; i++) {
168+
var typeParameter = typeParameters[i];
169+
builderClassName.append(typeParameter.getName());
170+
if (i < l - 1) {
171+
builderClassName.append(", ");
172+
}
173+
}
174+
builderClassName.append('>');
175+
return new BuilderClassName(builderClassName.toString(), "%s<>".formatted(className));
176+
}
177+
return new BuilderClassName(builderClassName.toString(), className);
178+
}
179+
180+
public static String buildBuilderMethodName(BuilderClass builderClass) {
181+
var psiClassType = (PsiClassType) builderClass.builderType();
182+
if (builderClass.genericType()) {
183+
var typeParameters = psiClassType.getParameters();
184+
var typeParameterNames = new StringBuilder();
185+
for (int i = 0, l = typeParameters.length; i < l; i++) {
186+
var typeParameter = typeParameters[i];
187+
typeParameterNames.append(typeParameter.getPresentableText());
188+
if (i < l - 1) {
189+
typeParameterNames.append(", ");
190+
}
191+
}
192+
return String.format("<%s> %s %s(){}", typeParameterNames,
193+
psiClassType.getPresentableText(), BUILDER_METHOD_NAME);
194+
}
195+
return String.format("%s %s(){}", psiClassType.getPresentableText(), BUILDER_METHOD_NAME);
153196
}
154197

155198
}

0 commit comments

Comments
 (0)