From 97b236f1136422aa264528d900a5d340da90b0e3 Mon Sep 17 00:00:00 2001 From: "Junk F. Actory" <2998269+junkfactory@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:35:24 -0700 Subject: [PATCH] Create add method for initialized collections (#4) * Remove unused var * Adding builder classes * Add GeneratorFactory * Handle addToCollection --- README.md | 123 ++++++++++++++---- .../innerbuilder/InnerBuilderAction.java | 2 +- .../innerbuilder/JavaInnerBuilderHandler.java | 50 ++++--- .../{ => generators}/AbstractGenerator.java | 14 +- .../generators/BuilderClassGenerator.java | 39 ++++++ .../generators/BuilderClassParams.java | 39 ++++++ .../generators/BuilderFieldsGenerator.java | 52 ++++++++ .../BuilderMethodsGenerator.java} | 113 +++++++--------- .../{ => generators}/FieldCollector.java | 36 +++-- .../generators/GeneratorFactory.java | 19 +++ .../{ => generators}/GeneratorParams.java | 12 +- .../InnerBuilderGenerator.java | 13 +- .../InnerBuilderGeneratorFactory.java | 26 ++++ .../{ => generators}/PsiParams.java | 2 +- .../innerbuilder/{ => generators}/Utils.java | 28 +++- 15 files changed, 417 insertions(+), 151 deletions(-) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/AbstractGenerator.java (74%) create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java rename src/main/java/com/github/junkfactory/innerbuilder/{BuilderClassGenerator.java => generators/BuilderMethodsGenerator.java} (60%) rename src/main/java/com/github/junkfactory/innerbuilder/{ => generators}/FieldCollector.java (73%) 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 (93%) 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 (74%) 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/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..dd4554e 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)) { @@ -64,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 = new InnerBuilderGenerator(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/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/generators/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java new file mode 100644 index 0000000..6946a62 --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassGenerator.java @@ -0,0 +1,39 @@ +package com.github.junkfactory.innerbuilder.generators; + +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.util.PsiUtil; + +class BuilderClassGenerator extends AbstractGenerator { + + private final BuilderClassParams builderClassParams; + private final Runnable fieldsGenerator; + private final Runnable methodsGenerator; + + 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 + public void run() { + //builder constructor + var builderClass = builderClassParams.builderClass(); + var builderConstructor = generateBuilderConstructor(); + addMethod(builderClass, null, builderConstructor, false); + + fieldsGenerator.run(); + methodsGenerator.run(); + } + + private PsiMethod generateBuilderConstructor() { + var builderConstructor = generatorParams.psi().factory().createConstructor(BUILDER_CLASS_NAME); + PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true); + return builderConstructor; + } + +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java new file mode 100644 index 0000000..bd2761e --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderClassParams.java @@ -0,0 +1,39 @@ +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 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/generators/BuilderFieldsGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java new file mode 100644 index 0000000..7f6b064 --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderFieldsGenerator.java @@ -0,0 +1,52 @@ +package com.github.junkfactory.innerbuilder.generators; + +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; + + BuilderFieldsGenerator(GeneratorFactory generatorFactory, + GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + super(generatorFactory, generatorParams); + this.builderClassParams = builderClassParams; + } + + @Override + public void run() { + PsiElement lastAddedField = null; + for (var fieldMember : generatorParams.psi().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); + newField.setInitializer(field.getInitializer()); + if (!builderClassParams.targetClass().isRecord()) { + field.setInitializer(null); + } + 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/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java similarity index 60% rename from src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java index a44460f..c7346e4 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java @@ -1,68 +1,46 @@ -package com.github.junkfactory.innerbuilder; +package com.github.junkfactory.innerbuilder.generators; import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.codeInsight.generation.PsiFieldMember; -import com.intellij.psi.PsiClass; +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 org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; -class BuilderClassGenerator extends AbstractGenerator { +class BuilderMethodsGenerator 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) { - super(generatorParams); - this.targetClass = targetClass; - this.builderClass = builderClass; - this.builderType = builderType; + BuilderMethodsGenerator(GeneratorFactory generatorFactory, + GeneratorParams generatorParams, + BuilderClassParams builderClassParams) { + super(generatorFactory, generatorParams); + this.builderClassParams = builderClassParams; } @Override public void run() { - //builder constructor - var builderConstructor = generateBuilderConstructor(); - addMethod(builderClass, null, builderConstructor, false); - - //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); - } - + var builderClass = builderClassParams.builderClass(); PsiElement lastAddedElement = null; - for (var member : fieldMembers) { - var setterMethod = generateBuilderSetter(builderType, member); + for (var member : generatorParams.psi().selectedFields()) { + var setterMethod = generateFieldMethod(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()); + addMethod(builderClass, null, buildMethod, builderClassParams.builderClass().isRecord()); } private PsiMethod generateValidateMethod() { @@ -73,13 +51,40 @@ private PsiMethod generateValidateMethod() { return validateMethod; } - private PsiMethod generateBuilderConstructor() { - var builderConstructor = generatorParams.psi().factory().createConstructor(BUILDER_CLASS_NAME); - PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true); - return builderConstructor; + private PsiMethod generateFieldMethod(PsiFieldMember member) { + var addMethod = Utils.findFieldAddMethod(builderClassParams.builderClass(), member); + if (null != addMethod) { + return generateAddToCollection(member, addMethod); + } + return generateBuilderSetter(member); } - private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember 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(); @@ -87,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( @@ -103,6 +108,7 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel } private PsiMethod generateBuildMethod() { + var targetClass = builderClassParams.targetClass(); var psiElementFactory = generatorParams.psi().factory(); var targetClassType = psiElementFactory.createType(targetClass); var buildMethod = psiElementFactory.createMethod("build", targetClassType); @@ -134,25 +140,4 @@ private PsiMethod generateBuildMethod() { buildMethodBody.add(returnStatement); return buildMethod; } - - private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member, - @Nullable final PsiElement last) { - 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 = generatorParams.psi().factory().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/FieldCollector.java b/src/main/java/com/github/junkfactory/innerbuilder/generators/FieldCollector.java similarity index 73% 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..aa206ab 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,30 @@ 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) { - return false; - } - return accessObjectClass.isEquivalentTo(clazz); - } - 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 93% rename from src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java rename to src/main/java/com/github/junkfactory/innerbuilder/generators/InnerBuilderGenerator.java index 714bc2d..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 @@ -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(); + 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 74% 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..862053c 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/Utils.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java @@ -1,10 +1,12 @@ -package com.github.junkfactory.innerbuilder; +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,7 +17,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -class Utils { +import java.util.Objects; +import java.util.Optional; + +public class Utils { @NonNls static final String JAVA_DOT_LANG = "java.lang."; @@ -67,7 +72,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) { @@ -87,4 +92,21 @@ 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; + } }