diff --git a/build.gradle.kts b/build.gradle.kts index f06ef01..c238e36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,7 @@ tasks { patchPluginXml { sinceBuild.set("241") - untilBuild.set("243.*") + untilBuild.set("251.*") } signPlugin { diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java index 46ca2d1..acd15c9 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java @@ -66,6 +66,11 @@ protected boolean addImport(PsiType psiType) { return generatorParams.psi().codeStyleManager().addImport((PsiJavaFile) generatorParams.psi().file(), psiClass); } + protected PsiMethod findFirstConstructor(PsiClass target) { + var constructors = target.getConstructors(); + return constructors.length > 0 ? constructors[0] : null; + } + private PsiMethod findConstructor(PsiClass target, PsiMethod newMethod) { for (var constructor : target.getConstructors()) { if (Utils.areParameterListsEqual(constructor.getParameterList(), newMethod.getParameterList())) { diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClass.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClass.java new file mode 100644 index 0000000..e736ecb --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClass.java @@ -0,0 +1,11 @@ +package com.github.junkfactory.innerbuilder.generators; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiType; + +public record BuilderClass(PsiClass psiClass, + PsiType builderType, + BuilderClassName builderClassName, + boolean genericType) { +} + diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java index 86050a9..b35b280 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java @@ -20,7 +20,7 @@ public GenerationResult generate() { //builder constructor var builderClass = builderClassParams.builderClass(); var builderConstructor = generateBuilderConstructor(); - addMethod(builderClass, null, builderConstructor, false); + addMethod(builderClass.psiClass(), null, builderConstructor, false); var fieldsGenerator = generatorFactory.createBuilderFieldsGenerator(generatorParams, builderClassParams); fieldsGenerator.generate(); diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassName.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassName.java new file mode 100644 index 0000000..1e5c9e8 --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassName.java @@ -0,0 +1,5 @@ +package com.github.junkfactory.innerbuilder.generators; + +public record BuilderClassName(String className, String instanceClassName) { +} + diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java index bd2761e..488ec26 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java @@ -1,9 +1,9 @@ package com.github.junkfactory.innerbuilder.generators; import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiType; -public record BuilderClassParams(PsiClass targetClass, PsiClass builderClass, PsiType builderType) { +public record BuilderClassParams(PsiClass targetClass, + BuilderClass builderClass) { public static Builder builder() { return new Builder(); @@ -11,8 +11,7 @@ public static Builder builder() { public static final class Builder { private PsiClass targetClass; - private PsiClass builderClass; - private PsiType builderType; + private BuilderClass builderClass; private Builder() { } @@ -22,18 +21,13 @@ public Builder targetClass(PsiClass targetClass) { return this; } - public Builder builderClass(PsiClass builderClass) { + public Builder builderClass(BuilderClass builderClass) { this.builderClass = builderClass; return this; } - public Builder builderType(PsiType builderType) { - this.builderType = builderType; - return this; - } - public BuilderClassParams build() { - return new BuilderClassParams(targetClass, builderClass, builderType); + return new BuilderClassParams(targetClass, builderClass); } } } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java index c0d9c16..1d58116 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java @@ -30,10 +30,11 @@ public List getFields() { public GenerationResult generate() { PsiField lastAddedField = null; for (var fieldMember : generatorParams.psi().selectedFields()) { - lastAddedField = createOrUpdateField(builderClassParams.builderClass(), fieldMember, lastAddedField); + lastAddedField = + createOrUpdateField(builderClassParams.builderClass().psiClass(), fieldMember, lastAddedField); fields.add(lastAddedField); } - cleanupFields(builderClassParams.builderClass()); + cleanupFields(builderClassParams.builderClass().psiClass()); return GenerationResult.NO_RESULT; } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java index 85eff58..2af5eab 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java @@ -34,21 +34,21 @@ public GenerationResult generate() { var targetClass = builderClassParams.targetClass(); var targetModifierList = Objects.requireNonNull(targetClass.getModifierList()); isPublic = targetModifierList.hasModifierProperty(PsiModifier.PUBLIC); - PsiElement lastAddedElement = null; + PsiElement lastAddedElement = findFirstConstructor(builderClass.psiClass()); for (var field : fieldsGenerator.getFields()) { var setterMethod = generateFieldMethod(field); field.putCopyableUserData(UserDataKey.METHOD_REF, setterMethod.getName()); - lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); + lastAddedElement = addMethod(builderClass.psiClass(), lastAddedElement, setterMethod, false); } var options = generatorParams.options(); if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { var validateMethod = generateValidateMethod(); - addMethod(builderClass, lastAddedElement, validateMethod, false); + lastAddedElement = addMethod(builderClass.psiClass(), lastAddedElement, validateMethod, false); } var buildMethod = generateBuildMethod(targetClass); - addMethod(builderClass, null, buildMethod, builderClassParams.targetClass().isRecord()); + addMethod(builderClass.psiClass(), lastAddedElement, buildMethod, builderClassParams.targetClass().isRecord()); return generationResult; } @@ -92,7 +92,7 @@ private PsiMethod generatePutToMap(PsiField field, PsiMethod fieldPutMethod) { if (isPublic) { methodText.append(PsiModifier.PUBLIC).append(' '); } - methodText.append(BUILDER_CLASS_NAME) + methodText.append(builderClassParams.builderClass().builderType().getPresentableText()) .append(' ') .append(methodName) .append('(') @@ -130,7 +130,7 @@ private PsiMethod generateAddToCollection(PsiField field, PsiMethod fieldAddMeth if (isPublic) { methodText.append(PsiModifier.PUBLIC).append(' '); } - methodText.append(BUILDER_CLASS_NAME) + methodText.append(builderClassParams.builderClass().builderType().getPresentableText()) .append(' ') .append(methodName) .append('(') @@ -159,7 +159,7 @@ private PsiMethod generateBuilderSetter(PsiField field) { if (isPublic) { methodText.append(PsiModifier.PUBLIC).append(' '); } - methodText.append(BUILDER_CLASS_NAME) + methodText.append(builderClassParams.builderClass().builderType().getPresentableText()) .append(' ') .append(fieldName) .append('(') @@ -180,10 +180,11 @@ private PsiMethod generateBuilderSetter(PsiField field) { } private PsiMethod generateBuildMethod(PsiClass targetClass) { + var targetClassName = Utils.buildClassName(targetClass.getName(), targetClass); var buildMethod = new StringBuilder() .append(isPublic ? PsiModifier.PUBLIC : EMPTY) .append(isPublic ? SPACE : EMPTY) - .append(targetClass.getName()) + .append(targetClassName.className()) .append(" build() {"); if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { buildMethod.append("validate();"); @@ -193,13 +194,13 @@ private PsiMethod generateBuildMethod(PsiClass targetClass) { .map(PsiField::getName) .collect(Collectors.joining(", ")); buildMethod.append("return new ") - .append(targetClass.getName()) + .append(targetClassName.instanceClassName()) .append("(") .append(recordParameters) .append(");"); } else { buildMethod.append("return new ") - .append(targetClass.getName()) + .append(targetClassName.instanceClassName()) .append("(this);"); } buildMethod.append("}"); diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java index 6268380..a42dfdd 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java @@ -7,7 +7,6 @@ import com.intellij.psi.PsiJavaFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiType; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PropertyUtilBase; import com.intellij.psi.util.PsiUtil; @@ -33,22 +32,20 @@ public GenerationResult generate() { if (targetClass == null || BUILDER_CLASS_NAME.equals(targetClass.getName())) { return NO_RESULT; } - var psiElementFactory = generatorParams.psi().factory(); var builderClass = findOrCreateBuilderClass(targetClass); - var builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, targetClass); if (!targetClass.isRecord()) { - var constructor = generateTargetConstructor(targetClass, builderType); + var constructor = generateTargetConstructor(targetClass, builderClass); addMethod(targetClass, null, constructor, true); } - var newBuilderMethod = generateStaticBuilderMethod(targetClass, builderType); + var newBuilderMethod = generateStaticBuilderMethod(targetClass, builderClass); addMethod(targetClass, null, newBuilderMethod, false); // toBuilder method var options = generatorParams.options(); if (options.contains(JavaInnerBuilderOption.WITH_TO_BUILDER_METHOD)) { - var toBuilderMethod = generateToBuilderMethod(targetClass, builderType, + var toBuilderMethod = generateToBuilderMethod(targetClass, builderClass, generatorParams.psi().selectedFields()); addMethod(targetClass, null, toBuilderMethod, true); } @@ -56,31 +53,30 @@ public GenerationResult generate() { var params = BuilderClassParams.builder() .targetClass(targetClass) .builderClass(builderClass) - .builderType(builderType) .build(); var result = generatorFactory.createBuilderClassGenerator(generatorParams, params).generate(); generationResult.merge(result); var codeStyleManager = generatorParams.psi().codeStyleManager(); generationResult.when(ANNOTATIONS_ADDED, () -> codeStyleManager.shortenClassReferences(targetClass)); generationResult.when(IMPORTS_ADDED, () -> codeStyleManager.removeRedundantImports((PsiJavaFile) file)); - CodeStyleManager.getInstance(generatorParams.project()).reformat(builderClass); + CodeStyleManager.getInstance(generatorParams.project()).reformat(builderClass.psiClass()); return generationResult; } private PsiMethod generateToBuilderMethod(PsiClass targetClass, - PsiType builderType, + BuilderClass builderClass, Collection fields) { var targetModifierList = Objects.requireNonNull(targetClass.getModifierList()); boolean isPublic = targetModifierList.hasModifierProperty(PsiModifier.PUBLIC); var toBuilderMethod = new StringBuilder() .append(isPublic ? PsiModifier.PUBLIC : EMPTY) .append(isPublic ? SPACE : EMPTY) - .append(builderType.getPresentableText()) + .append(builderClass.builderType().getPresentableText()) .append(SPACE) .append(TO_BUILDER_NAME) .append("() {") .append("var builder = new ") - .append(builderType.getPresentableText()) + .append(builderClass.builderType().getPresentableText()) .append("();"); for (var member : fields) { var field = member.getElement(); @@ -97,9 +93,10 @@ private PsiMethod generateToBuilderMethod(PsiClass targetClass, return psiElementFactory.createMethodFromText(toBuilderMethod.toString(), targetClass); } - private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, PsiType builderType) { + private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, BuilderClass builderClass) { var psiElementFactory = generatorParams.psi().factory(); - var newBuilderMethod = psiElementFactory.createMethod(BUILDER_METHOD_NAME, builderType); + var methodName = Utils.buildBuilderMethodName(builderClass); + var newBuilderMethod = psiElementFactory.createMethodFromText(methodName, targetClass); PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true); PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true); @@ -108,18 +105,18 @@ private PsiMethod generateStaticBuilderMethod(PsiClass targetClass, PsiType buil existingMethod = newBuilderMethod; var newBuilderMethodBody = Objects.requireNonNull(existingMethod.getBody()); var newStatement = psiElementFactory.createStatementFromText(String.format( - "return new %s();", builderType.getPresentableText()), newBuilderMethod); + "return new %s();", builderClass.builderClassName().instanceClassName()), newBuilderMethod); newBuilderMethodBody.add(newStatement); } return existingMethod; } - private PsiMethod generateTargetConstructor(final PsiClass targetClass, final PsiType builderType) { + private PsiMethod generateTargetConstructor(final PsiClass targetClass, BuilderClass builderClass) { var constructor = new StringBuilder() .append("private ") .append(targetClass.getName()) .append("(") - .append(builderType.getPresentableText()) + .append(builderClass.builderType().getPresentableText()) .append(" builder) {"); for (var member : generatorParams.psi().selectedFields()) { @@ -151,19 +148,22 @@ private PsiMethod generateTargetConstructor(final PsiClass targetClass, final Ps } @NotNull - private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) { - var builderClass = targetClass.findInnerClassByName(BUILDER_CLASS_NAME, false); - if (builderClass == null) { - return (PsiClass) targetClass.add(createBuilderClass(targetClass)); + private BuilderClass findOrCreateBuilderClass(final PsiClass targetClass) { + var builderClassName = Utils.buildClassName(BUILDER_CLASS_NAME, targetClass); + var psiClass = targetClass.findInnerClassByName(BUILDER_CLASS_NAME, false); + if (psiClass == null) { + psiClass = (PsiClass) targetClass.add(createBuilderClass(targetClass, builderClassName.className())); } - return builderClass; + var psiElementFactory = generatorParams.psi().factory(); + var builderType = psiElementFactory.createTypeFromText(builderClassName.className(), targetClass); + return new BuilderClass(psiClass, builderType, builderClassName, Utils.isGenericType(builderType)); } @NotNull - private PsiClass createBuilderClass(final PsiClass targetClass) { - String classDef = "public static final class " + - BUILDER_CLASS_NAME + + private PsiClass createBuilderClass(final PsiClass targetClass, String builderClassName) { + var classDef = "public static final class " + + builderClassName + " {}" + System.lineSeparator(); return generatorParams.psi().factory().createClassFromText(classDef, targetClass) diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java index 097574a..8d26892 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java @@ -2,6 +2,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; @@ -19,6 +20,8 @@ import java.util.Optional; import java.util.function.Predicate; +import static com.github.junkfactory.innerbuilder.generators.AbstractGenerator.BUILDER_METHOD_NAME; + public class Utils { @NonNls static final String JAVA_DOT_LANG = "java.lang."; @@ -147,9 +150,49 @@ public static List stringToList(String str) { .toList(); } - public static String parseType(String text) { - var parenthesisIndex = text.indexOf('('); - return parenthesisIndex == -1 ? text : text.substring(0, parenthesisIndex); + public static boolean isGenericType(PsiType psiType) { + // Check if the type is a PsiClassType + if (psiType instanceof PsiClassType classType) { + // Check if it has type parameters + return classType.getParameters().length > 0; + } + return false; + } + + public static BuilderClassName buildClassName(String className, PsiClass targetClass) { + var builderClassName = new StringBuilder(className); + var typeParameters = targetClass.getTypeParameters(); + if (typeParameters.length > 0) { + builderClassName.append('<'); + for (int i = 0, l = typeParameters.length; i < l; i++) { + var typeParameter = typeParameters[i]; + builderClassName.append(typeParameter.getName()); + if (i < l - 1) { + builderClassName.append(", "); + } + } + builderClassName.append('>'); + return new BuilderClassName(builderClassName.toString(), "%s<>".formatted(className)); + } + return new BuilderClassName(builderClassName.toString(), className); + } + + public static String buildBuilderMethodName(BuilderClass builderClass) { + var psiClassType = (PsiClassType) builderClass.builderType(); + if (builderClass.genericType()) { + var typeParameters = psiClassType.getParameters(); + var typeParameterNames = new StringBuilder(); + for (int i = 0, l = typeParameters.length; i < l; i++) { + var typeParameter = typeParameters[i]; + typeParameterNames.append(typeParameter.getPresentableText()); + if (i < l - 1) { + typeParameterNames.append(", "); + } + } + return String.format("<%s> %s %s(){}", typeParameterNames, + psiClassType.getPresentableText(), BUILDER_METHOD_NAME); + } + return String.format("%s %s(){}", psiClassType.getPresentableText(), BUILDER_METHOD_NAME); } }