From 68462e3e7218726b0fbfed41a0ca3d98cd3d86ea Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:35:52 -0700 Subject: [PATCH 1/4] Remove unused var --- .../junkfactory/innerbuilder/BuilderClassGenerator.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java index a44460f..6b8b566 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java @@ -11,7 +11,6 @@ import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,15 +39,13 @@ public void run() { //build setters var selectedFields = generatorParams.psi().selectedFields(); - var fieldMembers = new ArrayList(); PsiElement lastAddedField = null; for (var fieldMember : selectedFields) { lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField); - fieldMembers.add(fieldMember); } PsiElement lastAddedElement = null; - for (var member : fieldMembers) { + for (var member : selectedFields) { var setterMethod = generateBuilderSetter(builderType, member); lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); } @@ -137,6 +134,7 @@ private PsiMethod generateBuildMethod() { private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member, @Nullable final PsiElement last) { + var psiFactory = generatorParams.psi().factory(); var field = member.getElement(); var fieldName = field.getName(); var fieldType = field.getType(); @@ -145,7 +143,7 @@ private PsiElement findOrCreateField(final PsiClass builderClass, final PsiField if (existingField != null) { existingField.delete(); } - var newField = generatorParams.psi().factory().createField(fieldName, fieldType); + var newField = psiFactory.createField(fieldName, fieldType); if (last != null) { return builderClass.addAfter(newField, last); } else { From 59e50c9bf6d5baabfa28d6d0e5a8edbc7d33be9f Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:50:26 -0700 Subject: [PATCH 2/4] Adding builder classes --- .../innerbuilder/BuilderClassGenerator.java | 135 +----------------- .../innerbuilder/BuilderClassParams.java | 39 +++++ .../innerbuilder/BuilderFieldsGenerator.java | 51 +++++++ .../innerbuilder/BuilderMethodsGenerator.java | 110 ++++++++++++++ .../innerbuilder/InnerBuilderGenerator.java | 7 +- 5 files changed, 212 insertions(+), 130 deletions(-) create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java index 6b8b566..3c14c28 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java @@ -1,73 +1,27 @@ package com.github.junkfactory.innerbuilder; -import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; -import com.intellij.codeInsight.generation.PsiFieldMember; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiUtil; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; class BuilderClassGenerator extends AbstractGenerator { - private final PsiClass targetClass; - private final PsiClass builderClass; - private final PsiType builderType; + private final BuilderClassParams builderClassParams; - BuilderClassGenerator(GeneratorParams generatorParams, - PsiClass targetClass, - PsiClass builderClass, - PsiType builderType) { + BuilderClassGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { super(generatorParams); - this.targetClass = targetClass; - this.builderClass = builderClass; - this.builderType = builderType; + this.builderClassParams = builderClassParams; } @Override public void run() { //builder constructor + var builderClass = builderClassParams.builderClass(); var builderConstructor = generateBuilderConstructor(); addMethod(builderClass, null, builderConstructor, false); - //build setters - var selectedFields = generatorParams.psi().selectedFields(); - PsiElement lastAddedField = null; - for (var fieldMember : selectedFields) { - lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField); - } - - PsiElement lastAddedElement = null; - for (var member : selectedFields) { - var setterMethod = generateBuilderSetter(builderType, member); - lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); - } - - //build validate method - var options = generatorParams.options(); - if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { - var validateMethod = generateValidateMethod(); - addMethod(builderClass, lastAddedElement, validateMethod, false); - } - - // builder.build() method - var buildMethod = generateBuildMethod(); - addMethod(builderClass, null, buildMethod, targetClass.isRecord()); - } - - private PsiMethod generateValidateMethod() { - var psiElementFactory = generatorParams.psi().factory(); - var voidType = psiElementFactory.createPrimitiveType("void"); - var validateMethod = psiElementFactory.createMethod("validate", voidType); - PsiUtil.setModifierProperty(validateMethod, PsiModifier.PRIVATE, true); - return validateMethod; + new BuilderFieldsGenerator(generatorParams, builderClassParams).run(); + new BuilderMethodsGenerator(generatorParams, builderClassParams).run(); } private PsiMethod generateBuilderConstructor() { @@ -76,81 +30,4 @@ private PsiMethod generateBuilderConstructor() { return builderConstructor; } - private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember member) { - - var field = member.getElement(); - var fieldType = field.getType(); - var fieldName = Utils.hasOneLetterPrefix(field.getName()) ? - Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName(); - - var psiElementFactory = generatorParams.psi().factory(); - var setterMethod = psiElementFactory.createMethod(fieldName, builderType); - - setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true); - var setterParameter = psiElementFactory.createParameter(fieldName, fieldType); - - setterMethod.getParameterList().add(setterParameter); - var setterMethodBody = Objects.requireNonNull(setterMethod.getBody()); - var actualFieldName = "this." + fieldName; - var assignStatement = psiElementFactory.createStatementFromText(String.format( - "%s = %s;", actualFieldName, fieldName), setterMethod); - setterMethodBody.add(assignStatement); - setterMethodBody.add(Utils.createReturnThis(psiElementFactory, setterMethod)); - return setterMethod; - } - - private PsiMethod generateBuildMethod() { - var psiElementFactory = generatorParams.psi().factory(); - var targetClassType = psiElementFactory.createType(targetClass); - var buildMethod = psiElementFactory.createMethod("build", targetClassType); - - var targetModifierList = Objects.requireNonNull(targetClass.getModifierList()); - Stream.of(PsiModifier.PUBLIC, PsiModifier.PACKAGE_LOCAL) - .filter(targetModifierList::hasModifierProperty) - .findFirst() - .ifPresent(modifier -> PsiUtil.setModifierProperty(buildMethod, modifier, true)); - - var buildMethodBody = Objects.requireNonNull(buildMethod.getBody()); - if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { - var validateCall = psiElementFactory.createStatementFromText("validate();", buildMethod); - buildMethodBody.add(validateCall); - } - - final PsiStatement returnStatement; - if (targetClass.isRecord()) { - var recordParameters = generatorParams.psi().selectedFields().stream() - .map(m -> m.getElement().getName()) - .collect(Collectors.joining(", ")); - returnStatement = psiElementFactory.createStatementFromText(String.format( - "return new %s(%s);", targetClass.getName(), recordParameters), buildMethod); - } else { - returnStatement = psiElementFactory.createStatementFromText(String.format( - "return new %s(this);", targetClass.getName()), buildMethod); - } - - buildMethodBody.add(returnStatement); - return buildMethod; - } - - private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member, - @Nullable final PsiElement last) { - var psiFactory = generatorParams.psi().factory(); - var field = member.getElement(); - var fieldName = field.getName(); - var fieldType = field.getType(); - var existingField = builderClass.findFieldByName(fieldName, false); - if (existingField == null || Utils.areTypesPresentableNotEqual(existingField.getType(), fieldType)) { - if (existingField != null) { - existingField.delete(); - } - var newField = psiFactory.createField(fieldName, fieldType); - if (last != null) { - return builderClass.addAfter(newField, last); - } else { - return builderClass.add(newField); - } - } - return existingField; - } - } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java new file mode 100644 index 0000000..056fa0c --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java @@ -0,0 +1,39 @@ +package com.github.junkfactory.innerbuilder; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiType; + +public record BuilderClassParams(PsiClass targetClass, PsiClass builderClass, PsiType builderType) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private PsiClass targetClass; + private PsiClass builderClass; + private PsiType builderType; + + private Builder() { + } + + public Builder targetClass(PsiClass targetClass) { + this.targetClass = targetClass; + return this; + } + + public Builder builderClass(PsiClass 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); + } + } +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java new file mode 100644 index 0000000..03d2925 --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java @@ -0,0 +1,51 @@ +package com.github.junkfactory.innerbuilder; + +import com.intellij.codeInsight.generation.PsiFieldMember; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; + +class BuilderFieldsGenerator extends AbstractGenerator { + + private final BuilderClassParams builderClassParams; + private PsiElement lastAddedField; + + BuilderFieldsGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { + super(generatorParams); + this.builderClassParams = builderClassParams; + } + + public PsiElement getLastAddedField() { + return lastAddedField; + } + + @Override + public void run() { + var selectedFields = generatorParams.psi().selectedFields(); + for (var fieldMember : selectedFields) { + lastAddedField = findOrCreateField(builderClassParams.builderClass(), fieldMember, lastAddedField); + } + } + + private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member, + @Nullable final PsiElement last) { + var psiFactory = generatorParams.psi().factory(); + var field = member.getElement(); + var fieldName = field.getName(); + var fieldType = field.getType(); + var existingField = builderClass.findFieldByName(fieldName, false); + if (existingField == null || Utils.areTypesPresentableNotEqual(existingField.getType(), fieldType)) { + if (existingField != null) { + existingField.delete(); + } + var newField = psiFactory.createField(fieldName, fieldType); + if (last != null) { + return builderClass.addAfter(newField, last); + } else { + return builderClass.add(newField); + } + } + return existingField; + } + +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java new file mode 100644 index 0000000..2b8d2eb --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java @@ -0,0 +1,110 @@ +package com.github.junkfactory.innerbuilder; + +import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; +import com.intellij.codeInsight.generation.PsiFieldMember; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiStatement; +import com.intellij.psi.PsiType; +import com.intellij.psi.util.PsiUtil; + +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class BuilderMethodsGenerator extends AbstractGenerator { + + private final BuilderClassParams builderClassParams; + + public BuilderMethodsGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { + super(generatorParams); + this.builderClassParams = builderClassParams; + } + + @Override + public void run() { + var builderClass = builderClassParams.builderClass(); + PsiElement lastAddedElement = null; + for (var member : generatorParams.psi().selectedFields()) { + var setterMethod = generateBuilderSetter(builderClassParams.builderType(), member); + lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); + } + + //build validate method + var options = generatorParams.options(); + if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { + var validateMethod = generateValidateMethod(); + addMethod(builderClass, lastAddedElement, validateMethod, false); + } + + // builder.build() method + var buildMethod = generateBuildMethod(); + addMethod(builderClass, null, buildMethod, builderClassParams.builderClass().isRecord()); + } + + private PsiMethod generateValidateMethod() { + var psiElementFactory = generatorParams.psi().factory(); + var voidType = psiElementFactory.createPrimitiveType("void"); + var validateMethod = psiElementFactory.createMethod("validate", voidType); + PsiUtil.setModifierProperty(validateMethod, PsiModifier.PRIVATE, true); + return validateMethod; + } + + private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember member) { + + var field = member.getElement(); + var fieldType = field.getType(); + var fieldName = Utils.hasOneLetterPrefix(field.getName()) ? + Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName(); + + var psiElementFactory = generatorParams.psi().factory(); + var setterMethod = psiElementFactory.createMethod(fieldName, builderType); + + setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true); + var setterParameter = psiElementFactory.createParameter(fieldName, fieldType); + + setterMethod.getParameterList().add(setterParameter); + var setterMethodBody = Objects.requireNonNull(setterMethod.getBody()); + var actualFieldName = "this." + fieldName; + var assignStatement = psiElementFactory.createStatementFromText(String.format( + "%s = %s;", actualFieldName, fieldName), setterMethod); + setterMethodBody.add(assignStatement); + setterMethodBody.add(Utils.createReturnThis(psiElementFactory, setterMethod)); + return setterMethod; + } + + private PsiMethod generateBuildMethod() { + var targetClass = builderClassParams.targetClass(); + var psiElementFactory = generatorParams.psi().factory(); + var targetClassType = psiElementFactory.createType(targetClass); + var buildMethod = psiElementFactory.createMethod("build", targetClassType); + + var targetModifierList = Objects.requireNonNull(targetClass.getModifierList()); + Stream.of(PsiModifier.PUBLIC, PsiModifier.PACKAGE_LOCAL) + .filter(targetModifierList::hasModifierProperty) + .findFirst() + .ifPresent(modifier -> PsiUtil.setModifierProperty(buildMethod, modifier, true)); + + var buildMethodBody = Objects.requireNonNull(buildMethod.getBody()); + if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { + var validateCall = psiElementFactory.createStatementFromText("validate();", buildMethod); + buildMethodBody.add(validateCall); + } + + final PsiStatement returnStatement; + if (targetClass.isRecord()) { + var recordParameters = generatorParams.psi().selectedFields().stream() + .map(m -> m.getElement().getName()) + .collect(Collectors.joining(", ")); + returnStatement = psiElementFactory.createStatementFromText(String.format( + "return new %s(%s);", targetClass.getName(), recordParameters), buildMethod); + } else { + returnStatement = psiElementFactory.createStatementFromText(String.format( + "return new %s(this);", targetClass.getName()), buildMethod); + } + + buildMethodBody.add(returnStatement); + return buildMethod; + } +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java index 714bc2d..b5ea062 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java @@ -48,7 +48,12 @@ public void run() { addMethod(targetClass, null, toBuilderMethod, true); } - new BuilderClassGenerator(generatorParams, targetClass, builderClass, builderType).run(); + var params = BuilderClassParams.builder() + .targetClass(targetClass) + .builderClass(builderClass) + .builderType(builderType) + .build(); + new BuilderClassGenerator(generatorParams, params).run(); var project = generatorParams.project(); JavaCodeStyleManager.getInstance(project).shortenClassReferences(file); From 4eaa19c62f3249a81c4b9c43440686a606646be6 Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:13:38 -0700 Subject: [PATCH 3/4] Add GeneratorFactory --- .../innerbuilder/InnerBuilderAction.java | 2 +- .../innerbuilder/JavaInnerBuilderHandler.java | 13 ++++++-- .../{ => generators}/AbstractGenerator.java | 14 +++++---- .../BuilderClassGenerator.java | 16 +++++++--- .../{ => generators}/BuilderClassParams.java | 2 +- .../BuilderFieldsGenerator.java | 17 +++++----- .../BuilderMethodsGenerator.java | 10 +++--- .../{ => generators}/FieldCollector.java | 31 +++++++++++-------- .../generators/GeneratorFactory.java | 19 ++++++++++++ .../{ => generators}/GeneratorParams.java | 12 +++---- .../InnerBuilderGenerator.java | 8 ++--- .../InnerBuilderGeneratorFactory.java | 26 ++++++++++++++++ .../{ => generators}/PsiParams.java | 2 +- .../innerbuilder/{ => generators}/Utils.java | 6 ++-- 14 files changed, 120 insertions(+), 58 deletions(-) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/AbstractGenerator.java (74%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/BuilderClassGenerator.java (55%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/BuilderClassParams.java (94%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/BuilderFieldsGenerator.java (77%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/BuilderMethodsGenerator.java (94%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/FieldCollector.java (74%) create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorFactory.java rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/GeneratorParams.java (77%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/InnerBuilderGenerator.java (96%) create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGeneratorFactory.java rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/PsiParams.java (95%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/Utils.java (94%) diff --git a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderAction.java b/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderAction.java index 033308e..a81416e 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderAction.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderAction.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; public class InnerBuilderAction extends BaseCodeInsightAction { - private final JavaInnerBuilderHandler handler = new JavaInnerBuilderHandler(); + private static final JavaInnerBuilderHandler handler = new JavaInnerBuilderHandler(); @NotNull @Override diff --git a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java index cbdf0bc..f5daf5f 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java @@ -1,5 +1,9 @@ package com.github.junkfactory.innerbuilder; +import com.github.junkfactory.innerbuilder.generators.GeneratorFactory; +import com.github.junkfactory.innerbuilder.generators.GeneratorParams; +import com.github.junkfactory.innerbuilder.generators.PsiParams; +import com.github.junkfactory.innerbuilder.generators.Utils; import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageCodeInsightActionHandler; @@ -17,10 +21,13 @@ import java.util.EnumSet; import java.util.Set; -import static com.github.junkfactory.innerbuilder.FieldCollector.collectFields; +import static com.github.junkfactory.innerbuilder.generators.FieldCollector.collectFields; import static com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOptionSelector.selectFieldsAndOptions; -public class JavaInnerBuilderHandler implements LanguageCodeInsightActionHandler { +class JavaInnerBuilderHandler implements LanguageCodeInsightActionHandler { + + private static final GeneratorFactory generatorFactory = GeneratorFactory.create(); + @Override public boolean isValidFor(final Editor editor, final PsiFile file) { if (!(file instanceof PsiJavaFile)) { @@ -80,7 +87,7 @@ public void invoke(@NotNull final Project project, @NotNull final Editor editor, .psi(psiParams) .options(currentOptions()) .build(); - var builderGenerator = new InnerBuilderGenerator(generatorParams); + var builderGenerator = generatorFactory.createInnerBuilderGenerator(generatorParams); ApplicationManager.getApplication().runWriteAction(builderGenerator); } } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/AbstractGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java similarity index 74% rename from src/main/java/com/github/junkfactory/innerbuilder/AbstractGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java index 2287821..3ae73c0 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/AbstractGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; @@ -10,15 +10,17 @@ abstract class AbstractGenerator implements Runnable { @NonNls - protected static final String BUILDER_CLASS_NAME = "Builder"; + static final String BUILDER_CLASS_NAME = "Builder"; @NonNls - protected static final String BUILDER_METHOD_NAME = "builder"; + static final String BUILDER_METHOD_NAME = "builder"; @NonNls - protected static final String TO_BUILDER_NAME = "toBuilder"; + static final String TO_BUILDER_NAME = "toBuilder"; - protected GeneratorParams generatorParams; + protected final GeneratorFactory generatorFactory; + protected final GeneratorParams generatorParams; - protected AbstractGenerator(GeneratorParams generatorParams) { + protected AbstractGenerator(GeneratorFactory generatorFactory, GeneratorParams generatorParams) { + this.generatorFactory = generatorFactory; this.generatorParams = generatorParams; } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java similarity index 55% rename from src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java index 3c14c28..6946a62 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; @@ -7,10 +7,16 @@ class BuilderClassGenerator extends AbstractGenerator { private final BuilderClassParams builderClassParams; + private final Runnable fieldsGenerator; + private final Runnable methodsGenerator; - BuilderClassGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { - super(generatorParams); + BuilderClassGenerator(GeneratorFactory generatorFactory, + GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + super(generatorFactory, generatorParams); this.builderClassParams = builderClassParams; + this.fieldsGenerator = generatorFactory.createBuilderFieldsGenerator(generatorParams, builderClassParams); + this.methodsGenerator = generatorFactory.createBuilderMethodsGenerator(generatorParams, builderClassParams); } @Override @@ -20,8 +26,8 @@ public void run() { var builderConstructor = generateBuilderConstructor(); addMethod(builderClass, null, builderConstructor, false); - new BuilderFieldsGenerator(generatorParams, builderClassParams).run(); - new BuilderMethodsGenerator(generatorParams, builderClassParams).run(); + fieldsGenerator.run(); + methodsGenerator.run(); } private PsiMethod generateBuilderConstructor() { diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java similarity index 94% rename from src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java index 056fa0c..bd2761e 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassParams.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiType; diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java similarity index 77% rename from src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java index 03d2925..897c804 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderFieldsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.codeInsight.generation.PsiFieldMember; import com.intellij.psi.PsiClass; @@ -8,21 +8,18 @@ class BuilderFieldsGenerator extends AbstractGenerator { private final BuilderClassParams builderClassParams; - private PsiElement lastAddedField; - BuilderFieldsGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { - super(generatorParams); + BuilderFieldsGenerator(GeneratorFactory generatorFactory, + GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + super(generatorFactory, generatorParams); this.builderClassParams = builderClassParams; } - public PsiElement getLastAddedField() { - return lastAddedField; - } - @Override public void run() { - var selectedFields = generatorParams.psi().selectedFields(); - for (var fieldMember : selectedFields) { + PsiElement lastAddedField = null; + for (var fieldMember : generatorParams.psi().selectedFields()) { lastAddedField = findOrCreateField(builderClassParams.builderClass(), fieldMember, lastAddedField); } } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java similarity index 94% rename from src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java index 2b8d2eb..aae7e63 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderMethodsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.codeInsight.generation.PsiFieldMember; @@ -17,8 +17,10 @@ class BuilderMethodsGenerator extends AbstractGenerator { private final BuilderClassParams builderClassParams; - public BuilderMethodsGenerator(GeneratorParams generatorParams, BuilderClassParams builderClassParams) { - super(generatorParams); + BuilderMethodsGenerator(GeneratorFactory generatorFactory, + GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + super(generatorFactory, generatorParams); this.builderClassParams = builderClassParams; } @@ -31,14 +33,12 @@ public void run() { lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); } - //build validate method var options = generatorParams.options(); if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) { var validateMethod = generateValidateMethod(); addMethod(builderClass, lastAddedElement, validateMethod, false); } - // builder.build() method var buildMethod = generateBuildMethod(); addMethod(builderClass, null, buildMethod, builderClassParams.builderClass().isRecord()); } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/FieldCollector.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java similarity index 74% rename from src/main/java/com/github/junkfactory/innerbuilder/FieldCollector.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java index 6266e50..56f4d32 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/FieldCollector.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.codeInsight.generation.PsiFieldMember; import com.intellij.openapi.editor.Editor; @@ -23,6 +23,8 @@ public class FieldCollector { + private static final String OBJECT_CLASS_NAME = "Object"; + private FieldCollector() { } @@ -43,36 +45,39 @@ public static List collectFields(final PsiFile file, final Edito PsiClass classToExtractFieldsFrom = clazz; while (classToExtractFieldsFrom != null) { - final List classFieldMembers = collectFieldsInClass(element, clazz, - classToExtractFieldsFrom); + var classFieldMembers = collectFieldsInClass(element, clazz, classToExtractFieldsFrom); allFields.addAll(0, classFieldMembers); - classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperClass(); } return allFields; } - private static List collectFieldsInClass(final PsiElement element, final PsiClass accessObjectClass, - final PsiClass clazz) { - var helper = JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper(); - return Arrays.stream(clazz.getFields()) - .filter(field -> helper.isAccessible(field, clazz, accessObjectClass) || - hasSetter(clazz, field.getName())) + private static List collectFieldsInClass(final PsiElement element, + final PsiClass accessObjectClass, + final PsiClass classToExtractFieldsFrom) { + if (AbstractGenerator.BUILDER_CLASS_NAME.equals(classToExtractFieldsFrom.getName()) || + OBJECT_CLASS_NAME.equals(classToExtractFieldsFrom.getName())) { + return List.of(); + } + var helper = JavaPsiFacade.getInstance(classToExtractFieldsFrom.getProject()).getResolveHelper(); + return Arrays.stream(classToExtractFieldsFrom.getFields()) + .filter(field -> helper.isAccessible(field, classToExtractFieldsFrom, accessObjectClass) || + hasSetter(classToExtractFieldsFrom, field.getName())) .filter(field -> !PsiTreeUtil.isAncestor(field, element, false)) .filter(field -> !field.hasModifierProperty(PsiModifier.STATIC)) .filter(field -> hasLowerCaseChar(field.getName())) .filter(field -> { if (field.hasModifierProperty(PsiModifier.FINAL)) { - if (field.getInitializer() != null) { + if (field.hasInitializer()) { return false; } - return accessObjectClass.isEquivalentTo(clazz); + return accessObjectClass.isEquivalentTo(classToExtractFieldsFrom); } return true; }) .filter(field -> Objects.nonNull(field.getContainingClass())) - .map(field -> buildFieldMember(field, field.getContainingClass(), clazz)) + .map(field -> buildFieldMember(field, field.getContainingClass(), classToExtractFieldsFrom)) .toList(); } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorFactory.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorFactory.java new file mode 100644 index 0000000..d94a3fd --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorFactory.java @@ -0,0 +1,19 @@ +package com.github.junkfactory.innerbuilder.generators; + +public interface GeneratorFactory { + + static GeneratorFactory create() { + return new InnerBuilderGeneratorFactory(); + } + + Runnable createInnerBuilderGenerator(GeneratorParams generatorParams); + + Runnable createBuilderClassGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams); + + Runnable createBuilderFieldsGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams); + + Runnable createBuilderMethodsGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams); +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorParams.java similarity index 77% rename from src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorParams.java index 54d61e7..83ee197 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/GeneratorParams.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.openapi.editor.Editor; @@ -6,10 +6,10 @@ import java.util.Set; -record GeneratorParams(Project project, - Editor editor, - PsiParams psi, - Set options) { +public record GeneratorParams(Project project, + Editor editor, + PsiParams psi, + Set options) { public static Builder builder() { return new Builder(); @@ -44,7 +44,7 @@ public Builder options(Set options) { return this; } - GeneratorParams build() { + public GeneratorParams build() { return new GeneratorParams(project, editor, psi, options); } } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java similarity index 96% rename from src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java index b5ea062..9a15935 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.codeInsight.generation.PsiFieldMember; @@ -18,8 +18,8 @@ class InnerBuilderGenerator extends AbstractGenerator { - InnerBuilderGenerator(GeneratorParams generatorParams) { - super(generatorParams); + InnerBuilderGenerator(GeneratorFactory generatorFactory, GeneratorParams generatorParams) { + super(generatorFactory, generatorParams); } @Override @@ -53,7 +53,7 @@ public void run() { .builderClass(builderClass) .builderType(builderType) .build(); - new BuilderClassGenerator(generatorParams, params).run(); + generatorFactory.createBuilderClassGenerator(generatorParams, params).run(); var project = generatorParams.project(); JavaCodeStyleManager.getInstance(project).shortenClassReferences(file); diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGeneratorFactory.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGeneratorFactory.java new file mode 100644 index 0000000..b4c4a1d --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGeneratorFactory.java @@ -0,0 +1,26 @@ +package com.github.junkfactory.innerbuilder.generators; + +class InnerBuilderGeneratorFactory implements GeneratorFactory { + @Override + public Runnable createInnerBuilderGenerator(GeneratorParams generatorParams) { + return new InnerBuilderGenerator(this, generatorParams); + } + + @Override + public Runnable createBuilderClassGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + return new BuilderClassGenerator(this, generatorParams, builderClassParams); + } + + @Override + public Runnable createBuilderFieldsGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + return new BuilderFieldsGenerator(this, generatorParams, builderClassParams); + } + + @Override + public Runnable createBuilderMethodsGenerator(GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + return new BuilderMethodsGenerator(this, generatorParams, builderClassParams); + } +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/PsiParams.java similarity index 95% rename from src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/PsiParams.java index 6b6bbfa..a30e4e0 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/PsiParams.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.codeInsight.generation.PsiFieldMember; import com.intellij.psi.PsiElementFactory; diff --git a/src/main/java/com/github/junkfactory/innerbuilder/Utils.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java similarity index 94% rename from src/main/java/com/github/junkfactory/innerbuilder/Utils.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java index 8ddb3dc..b3a7b30 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/Utils.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java @@ -1,4 +1,4 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiClass; @@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -class Utils { +public class Utils { @NonNls static final String JAVA_DOT_LANG = "java.lang."; @@ -67,7 +67,7 @@ static boolean areTypesPresentableNotEqual(PsiType type1, PsiType type2) { * @return psiClass if class is static or top level. Otherwise returns {@code null} */ @Nullable - static PsiClass getStaticOrTopLevelClass(PsiFile file, Editor editor) { + public static PsiClass getStaticOrTopLevelClass(PsiFile file, Editor editor) { var offset = editor.getCaretModel().getOffset(); var element = file.findElementAt(offset); if (element == null) { From cbb658327bb094ac3c4f1d459a8959d9adc4bf1b Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:43:47 -0700 Subject: [PATCH 4/4] Handle addToCollection --- README.md | 123 ++++++++++++++---- .../innerbuilder/JavaInnerBuilderHandler.java | 39 +++--- .../generators/BuilderFieldsGenerator.java | 4 + .../generators/BuilderMethodsGenerator.java | 45 ++++++- .../generators/FieldCollector.java | 9 -- .../innerbuilder/generators/Utils.java | 22 ++++ 6 files changed, 183 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 29b220b..67c6de2 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,98 @@ # An opinionated Java Inner Builder Generator -This is an opinionated but simple Java Inner Builder Generator IntelliJ plugin that generates a -builder for a given class. The builder is an inner class of the class it is building. +This is an opinionated but simple Java Inner Builder Generator IntelliJ plugin that generates an +inner builder for a given class. Based from [InnerBuilder](https://github.com/analytically/innerbuilder) with stripped down features. -Generates a builder class for a given class with the following features: - -1. Generates builder method for final fields that are not initialized and/or static fields +1. Generates builder method for final fields that are not initialized, static fields are excluded 2. Generates static `builder()` method inside the parent class 3. Uses field names as setters in the builder +4. Detects collection types and generates `addTo...` method for them if they are initialized Optional features: 1. Generates `toBuilder()` method to convert the object to a builder 2. Generates `validate()` method to validate the fields before building the object - +### Example + +Initial ```java public class Person { + static String m = "ME"; private static final Logger logger = Logger.getLogger("test"); + private final String name; + private final int age; + private String lastName; + + private List list; - private final String name = "1"; + private Set set; + + private final List
addresses = new ArrayList<>(); + +} +``` + +Generates + +```java +public class Person { + static String m = "ME"; + private static final Logger logger = Logger.getLogger("test"); + private final String name; private final int age; private String lastName; + private List list; + + private Set set; + + private final List
addresses; + private Person(Builder builder) { + name = builder.name; age = builder.age; lastName = builder.lastName; + list = builder.list; + set = builder.set; + addresses = builder.addresses; } public static Builder builder() { return new Builder(); } + public Builder toBuilder() { + Builder builder = new Builder(); + builder.name = this.name; + builder.age = this.age; + builder.lastName = this.lastName; + builder.list = this.list; + builder.set = this.set; + builder.addresses = this.addresses; + return builder; + } + public static final class Builder { + private String name; private int age; private String lastName; + private List list; + private Set set; + private List
addresses = new ArrayList<>(); private Builder() { } + public Builder name(String name) { + this.name = name; + return this; + } + public Builder age(int age) { this.age = age; return this; @@ -54,6 +104,21 @@ public class Person { return this; } + public Builder list(List list) { + this.list = list; + return this; + } + + public Builder set(Set set) { + this.set = set; + return this; + } + + public Builder addToAddresses(Address e) { + this.addresses.add(e); + return this; + } + private void validate() { } @@ -68,60 +133,66 @@ public class Person { Supports Java record classes ```java -record Address(String street, String city, String state, String country) { +record Address(Person person, List streets, String city) { + static String m = "ME"; + +} +``` + +Generates + +```java +record Address(Person person, List streets, String city) { static String m = "ME"; public static Builder builder() { return new Builder(); } - //optional toBuilder method public Builder toBuilder() { Builder builder = new Builder(); - builder.street = this.street; + builder.person = this.person; + builder.streets = this.streets; builder.city = this.city; - builder.state = this.state; - builder.country = this.country; return builder; } public static final class Builder { - private String street; + private Person person; + private List streets; private String city; - private String state; - private String country; private Builder() { } - public Builder street(String street) { - this.street = street; + public Builder person(Person person) { + this.person = person; return this; } - public Builder city(String city) { - this.city = city; + public Builder streets(List streets) { + this.streets = streets; return this; } - public Builder state(String state) { - this.state = state; + public Builder city(String city) { + this.city = city; return this; } - public Builder country(String country) { - this.country = country; - return this; + private void validate() { } - //follows the containing class visibility Address build() { - return new Address(street, city, state, country); + validate(); + return new Address(person, streets, city); } } } ``` + + ## Installation 1. Download plugin zip file from [Releases](https://github.com/junkfactory/java-inner-builder/releases) diff --git a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java index f5daf5f..dd4554e 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java @@ -71,25 +71,28 @@ public void invoke(@NotNull final Project project, @NotNull final Editor editor, } var existingFields = collectFields(file, editor); - if (!existingFields.isEmpty()) { - var selectedFields = selectFieldsAndOptions(existingFields, project); - if (selectedFields.isEmpty()) { - return; - } - var psiParams = PsiParams.builder() - .file(file) - .selectedFields(selectedFields) - .factory(JavaPsiFacade.getElementFactory(project)) - .build(); - var generatorParams = GeneratorParams.builder() - .project(project) - .editor(editor) - .psi(psiParams) - .options(currentOptions()) - .build(); - var builderGenerator = generatorFactory.createInnerBuilderGenerator(generatorParams); - ApplicationManager.getApplication().runWriteAction(builderGenerator); + if (existingFields.isEmpty()) { + return; + } + + var selectedFields = selectFieldsAndOptions(existingFields, project); + if (selectedFields.isEmpty()) { + return; } + + var psiParams = PsiParams.builder() + .file(file) + .selectedFields(selectedFields) + .factory(JavaPsiFacade.getElementFactory(project)) + .build(); + var generatorParams = GeneratorParams.builder() + .project(project) + .editor(editor) + .psi(psiParams) + .options(currentOptions()) + .build(); + var builderGenerator = generatorFactory.createInnerBuilderGenerator(generatorParams); + ApplicationManager.getApplication().runWriteAction(builderGenerator); } private Set currentOptions() { 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 897c804..7f6b064 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java @@ -36,6 +36,10 @@ private PsiElement findOrCreateField(final PsiClass builderClass, final PsiField existingField.delete(); } var newField = psiFactory.createField(fieldName, fieldType); + newField.setInitializer(field.getInitializer()); + if (!builderClassParams.targetClass().isRecord()) { + field.setInitializer(null); + } if (last != null) { return builderClass.addAfter(newField, last); } else { 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 aae7e63..c7346e4 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java @@ -2,11 +2,11 @@ import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.codeInsight.generation.PsiFieldMember; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiUtil; import java.util.Objects; @@ -29,7 +29,7 @@ public void run() { var builderClass = builderClassParams.builderClass(); PsiElement lastAddedElement = null; for (var member : generatorParams.psi().selectedFields()) { - var setterMethod = generateBuilderSetter(builderClassParams.builderType(), member); + var setterMethod = generateFieldMethod(member); lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false); } @@ -51,7 +51,40 @@ private PsiMethod generateValidateMethod() { return validateMethod; } - private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember member) { + private PsiMethod generateFieldMethod(PsiFieldMember member) { + var addMethod = Utils.findFieldAddMethod(builderClassParams.builderClass(), member); + if (null != addMethod) { + return generateAddToCollection(member, addMethod); + } + return generateBuilderSetter(member); + } + + private PsiMethod generateAddToCollection(PsiFieldMember member, PsiMethod fieldAddMethod) { + var field = member.getElement(); + //resolve the generic type of the collection via the parameter type of the add method + var param = Objects.requireNonNull(fieldAddMethod.getParameterList().getParameter(0)); + var paramType = PsiUtil.resolveGenericsClassInType(field.getType()) + .getSubstitutor() + .substitute(param.getType()); + + //now build the add method + var fieldName = "addTo" + StringUtil.capitalize(field.getName()); + var psiElementFactory = generatorParams.psi().factory(); + var addMethod = psiElementFactory.createMethod(fieldName, builderClassParams.builderType()); + PsiUtil.setModifierProperty(addMethod, PsiModifier.PUBLIC, true); + + var addParameter = psiElementFactory.createParameter(param.getName().toLowerCase(), paramType); + addMethod.getParameterList().add(addParameter); + + var addMethodBody = Objects.requireNonNull(addMethod.getBody()); + var addBody = psiElementFactory.createStatementFromText(String.format( + "this.%s.add(%s);", field.getName(), param.getName()), addMethod); + addMethodBody.add(addBody); + addMethodBody.add(Utils.createReturnThis(psiElementFactory, addMethod)); + return addMethod; + } + + private PsiMethod generateBuilderSetter(final PsiFieldMember member) { var field = member.getElement(); var fieldType = field.getType(); @@ -59,12 +92,12 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName(); var psiElementFactory = generatorParams.psi().factory(); - var setterMethod = psiElementFactory.createMethod(fieldName, builderType); - + var setterMethod = psiElementFactory.createMethod(fieldName, builderClassParams.builderType()); setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true); - var setterParameter = psiElementFactory.createParameter(fieldName, fieldType); + var setterParameter = psiElementFactory.createParameter(fieldName, fieldType); setterMethod.getParameterList().add(setterParameter); + var setterMethodBody = Objects.requireNonNull(setterMethod.getBody()); var actualFieldName = "this." + fieldName; var assignStatement = psiElementFactory.createStatementFromText(String.format( diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java index 56f4d32..aa206ab 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java @@ -67,15 +67,6 @@ private static List collectFieldsInClass(final PsiElement elemen .filter(field -> !PsiTreeUtil.isAncestor(field, element, false)) .filter(field -> !field.hasModifierProperty(PsiModifier.STATIC)) .filter(field -> hasLowerCaseChar(field.getName())) - .filter(field -> { - if (field.hasModifierProperty(PsiModifier.FINAL)) { - if (field.hasInitializer()) { - return false; - } - return accessObjectClass.isEquivalentTo(classToExtractFieldsFrom); - } - return true; - }) .filter(field -> Objects.nonNull(field.getContainingClass())) .map(field -> buildFieldMember(field, field.getContainingClass(), classToExtractFieldsFrom)) .toList(); 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 b3a7b30..862053c 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java @@ -1,10 +1,12 @@ package com.github.junkfactory.innerbuilder.generators; +import com.intellij.codeInsight.generation.PsiFieldMember; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementFactory; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiStatement; @@ -15,6 +17,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Objects; +import java.util.Optional; + public class Utils { @NonNls static final String JAVA_DOT_LANG = "java.lang."; @@ -87,4 +92,21 @@ public static PsiClass getStaticOrTopLevelClass(PsiFile file, Editor editor) { static PsiStatement createReturnThis(@NotNull PsiElementFactory psiElementFactory, @Nullable PsiElement context) { return psiElementFactory.createStatementFromText("return this;", context); } + + static PsiMethod findFieldAddMethod(PsiClass builderClass, PsiFieldMember member) { + var field = builderClass.findFieldByName(member.getElement().getName(), false); + if (!Objects.requireNonNull(field).hasInitializer()) { + return null; + } + var fieldClass = PsiUtil.resolveClassInClassTypeOnly(field.getType()); + var methods = Optional.ofNullable(fieldClass) + .map(PsiClass::getAllMethods) + .orElseGet(() -> new PsiMethod[0]); + for (var method : methods) { + if (method.getName().equals("add") && method.getParameterList().getParametersCount() == 1) { + return method; + } + } + return null; + } }