From cd1131331ede6da097a67530b1f6a2f8f2fc6998 Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 13:34:24 -0700 Subject: [PATCH 1/2] Add options to generator params --- .../innerbuilder/BuilderClassGenerator.java | 10 ++-- .../innerbuilder/GeneratorParams.java | 49 ++++++++++++++++--- .../innerbuilder/InnerBuilderGenerator.java | 20 ++++---- .../innerbuilder/JavaInnerBuilderHandler.java | 37 +++++++++++++- .../junkfactory/innerbuilder/PsiParams.java | 44 +++++++++++++++++ .../ui/JavaInnerBuilderOption.java | 23 --------- 6 files changed, 136 insertions(+), 47 deletions(-) create mode 100644 src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java index 2026c52..eedf3fd 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java @@ -34,7 +34,7 @@ class BuilderClassGenerator extends AbstractGenerator { @Override public void run() { - var selectedFields = generatorParams.selectedFields(); + var selectedFields = generatorParams.psi().selectedFields(); var fieldMembers = new ArrayList(); PsiElement lastAddedField = null; for (var fieldMember : selectedFields) { @@ -58,7 +58,7 @@ public void run() { } private PsiMethod generateBuilderConstructor() { - var builderConstructor = generatorParams.psiElementFactory().createConstructor(BUILDER_CLASS_NAME); + var builderConstructor = generatorParams.psi().factory().createConstructor(BUILDER_CLASS_NAME); PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true); return builderConstructor; } @@ -70,7 +70,7 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel var fieldName = Utils.hasOneLetterPrefix(field.getName()) ? Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName(); - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var setterMethod = psiElementFactory.createMethod(fieldName, builderType); setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true); @@ -87,7 +87,7 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel } private PsiMethod generateBuildMethod(final PsiClass targetClass, final List selectedFields) { - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var targetClassType = psiElementFactory.createType(targetClass); var buildMethod = psiElementFactory.createMethod("build", targetClassType); @@ -124,7 +124,7 @@ private PsiElement findOrCreateField(final PsiClass builderClass, final PsiField if (existingField != null) { existingField.delete(); } - var newField = generatorParams.psiElementFactory().createField(fieldName, fieldType); + var newField = generatorParams.psi().factory().createField(fieldName, fieldType); if (last != null) { return builderClass.addAfter(newField, last); } else { diff --git a/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java b/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java index 6893b8a..54d61e7 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/GeneratorParams.java @@ -1,16 +1,51 @@ package com.github.junkfactory.innerbuilder; -import com.intellij.codeInsight.generation.PsiFieldMember; +import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElementFactory; -import com.intellij.psi.PsiFile; -import java.util.List; +import java.util.Set; record GeneratorParams(Project project, - PsiFile file, Editor editor, - List selectedFields, - PsiElementFactory psiElementFactory) { + PsiParams psi, + Set options) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Project project; + private Editor editor; + private PsiParams psi; + private Set options; + + private Builder() { + } + + public Builder project(Project project) { + this.project = project; + return this; + } + + public Builder editor(Editor editor) { + this.editor = editor; + return this; + } + + public Builder psi(PsiParams psi) { + this.psi = psi; + return this; + } + + public Builder options(Set options) { + this.options = options; + return this; + } + + 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/InnerBuilderGenerator.java index adc7ef5..1cd1dec 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderGenerator.java @@ -24,12 +24,12 @@ class InnerBuilderGenerator extends AbstractGenerator { @Override public void run() { - var file = generatorParams.file(); + var file = generatorParams.psi().file(); var targetClass = Utils.getStaticOrTopLevelClass(file, generatorParams.editor()); if (targetClass == null) { return; } - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var builderClass = findOrCreateBuilderClass(targetClass); var builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, null); @@ -42,9 +42,9 @@ public void run() { addMethod(targetClass, null, newBuilderMethod, false); // toBuilder method - var options = JavaInnerBuilderOption.currentOptions(); + var options = generatorParams.options(); if (options.contains(JavaInnerBuilderOption.TO_BUILDER)) { - var toBuilderMethod = generateToBuilderMethod(builderType, generatorParams.selectedFields()); + var toBuilderMethod = generateToBuilderMethod(builderType, generatorParams.psi().selectedFields()); addMethod(targetClass, null, toBuilderMethod, true); } @@ -57,7 +57,7 @@ public void run() { private PsiMethod generateToBuilderMethod(final PsiType builderType, final Collection fields) { - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var toBuilderMethod = psiElementFactory.createMethod(TO_BUILDER_NAME, builderType); PsiUtil.setModifierProperty(toBuilderMethod, PsiModifier.PUBLIC, true); var toBuilderBody = Objects.requireNonNull(toBuilderMethod.getBody()); @@ -74,14 +74,14 @@ private void addCopyBody(final Collection fields, final PsiMetho var methodBody = Objects.requireNonNull(method.getBody()); for (final PsiFieldMember member : fields) { var field = member.getElement(); - var assignStatement = generatorParams.psiElementFactory().createStatementFromText(String.format( + var assignStatement = generatorParams.psi().factory().createStatementFromText(String.format( "%s%2$s = this.%3$s;", "builder.", field.getName(), field.getName()), method); methodBody.add(assignStatement); } } private PsiMethod generateStaticBuilderMethod(final PsiType builderType) { - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var newBuilderMethod = psiElementFactory.createMethod(BUILDER_METHOD_NAME, builderType); PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true); PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true); @@ -94,7 +94,7 @@ private PsiMethod generateStaticBuilderMethod(final PsiType builderType) { } private PsiMethod generateConstructor(final PsiClass targetClass, final PsiType builderType) { - var psiElementFactory = generatorParams.psiElementFactory(); + var psiElementFactory = generatorParams.psi().factory(); var constructor = psiElementFactory.createConstructor(Objects.requireNonNull(targetClass.getName())); constructor.getModifierList().setModifierProperty(PsiModifier.PRIVATE, true); @@ -102,7 +102,7 @@ private PsiMethod generateConstructor(final PsiClass targetClass, final PsiType constructor.getParameterList().add(builderParameter); var constructorBody = Objects.requireNonNull(constructor.getBody()); - for (var member : generatorParams.selectedFields()) { + for (var member : generatorParams.psi().selectedFields()) { var field = member.getElement(); var setterPrototype = PropertyUtilBase.generateSetterPrototype(field); var setter = targetClass.findMethodBySignature(setterPrototype, true); @@ -143,7 +143,7 @@ private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) { @NotNull private PsiClass createBuilderClass(final PsiClass targetClass) { - var builderClass = (PsiClass) targetClass.add(generatorParams.psiElementFactory() + var builderClass = (PsiClass) targetClass.add(generatorParams.psi().factory() .createClass(BUILDER_CLASS_NAME)); PsiUtil.setModifierProperty(builderClass, PsiModifier.STATIC, true); PsiUtil.setModifierProperty(builderClass, PsiModifier.FINAL, true); diff --git a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java index 70a1869..cbdf0bc 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java @@ -1,5 +1,7 @@ package com.github.junkfactory.innerbuilder; +import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageCodeInsightActionHandler; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; @@ -12,6 +14,9 @@ import com.intellij.psi.PsiJavaFile; import org.jetbrains.annotations.NotNull; +import java.util.EnumSet; +import java.util.Set; + import static com.github.junkfactory.innerbuilder.FieldCollector.collectFields; import static com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOptionSelector.selectFieldsAndOptions; @@ -64,11 +69,39 @@ public void invoke(@NotNull final Project project, @NotNull final Editor editor, if (selectedFields.isEmpty()) { return; } - var generatorParams = new GeneratorParams(project, file, editor, selectedFields, - JavaPsiFacade.getElementFactory(project)); + 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); } } + private Set currentOptions() { + final var options = EnumSet.noneOf(JavaInnerBuilderOption.class); + final var propertiesComponent = PropertiesComponent.getInstance(); + for (var option : JavaInnerBuilderOption.values()) { + + if (Boolean.TRUE.equals(option.isBooleanProperty())) { + final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false); + if (currentSetting) { + options.add(option); + } + } else { + String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty())); + if (currentValue != null) { + JavaInnerBuilderOption.findValue(currentValue).ifPresent(options::add); + } + } + } + return options; + } } diff --git a/src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java b/src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java new file mode 100644 index 0000000..6b6bbfa --- /dev/null +++ b/src/main/java/com/github/junkfactory/innerbuilder/PsiParams.java @@ -0,0 +1,44 @@ +package com.github.junkfactory.innerbuilder; + +import com.intellij.codeInsight.generation.PsiFieldMember; +import com.intellij.psi.PsiElementFactory; +import com.intellij.psi.PsiFile; + +import java.util.List; + +public record PsiParams(PsiFile file, + List selectedFields, + PsiElementFactory factory) { + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private PsiFile file; + private List selectedFields; + private PsiElementFactory factory; + + private Builder() { + } + + public Builder file(PsiFile file) { + this.file = file; + return this; + } + + public Builder selectedFields(List selectedFields) { + this.selectedFields = selectedFields; + return this; + } + + public Builder factory(PsiElementFactory factory) { + this.factory = factory; + return this; + } + + public PsiParams build() { + return new PsiParams(file, selectedFields, factory); + } + } +} diff --git a/src/main/java/com/github/junkfactory/innerbuilder/ui/JavaInnerBuilderOption.java b/src/main/java/com/github/junkfactory/innerbuilder/ui/JavaInnerBuilderOption.java index 46f2f6f..16c6aad 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/ui/JavaInnerBuilderOption.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/ui/JavaInnerBuilderOption.java @@ -1,12 +1,8 @@ package com.github.junkfactory.innerbuilder.ui; -import com.intellij.ide.util.PropertiesComponent; - import java.util.Arrays; -import java.util.EnumSet; import java.util.Objects; import java.util.Optional; -import java.util.Set; public enum JavaInnerBuilderOption { TO_BUILDER("toBuilder", "Generate 'toBuilder' method"); @@ -43,23 +39,4 @@ public static Optional findValue(String value) { .findFirst(); } - public static Set currentOptions() { - final var options = EnumSet.noneOf(JavaInnerBuilderOption.class); - final var propertiesComponent = PropertiesComponent.getInstance(); - for (var option : JavaInnerBuilderOption.values()) { - - if (Boolean.TRUE.equals(option.isBooleanProperty())) { - final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false); - if (currentSetting) { - options.add(option); - } - } else { - String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty())); - if (currentValue != null) { - JavaInnerBuilderOption.findValue(currentValue).ifPresent(options::add); - } - } - } - return options; - } } From 57c97251efa08b062e6bf5011d8a5339edb278a3 Mon Sep 17 00:00:00 2001 From: junkfactory <2998269+junkfactory@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:32:29 -0700 Subject: [PATCH 2/2] Add validate method option --- README.md | 21 +++++++-- .../innerbuilder/BuilderClassGenerator.java | 43 ++++++++++++++----- .../innerbuilder/InnerBuilderGenerator.java | 4 +- .../ui/JavaInnerBuilderOption.java | 3 +- .../ui/JavaInnerBuilderOptionSelector.java | 6 ++- 5 files changed, 59 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 67a6b0e..29b220b 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,22 @@ # An opinionated Java Inner Builder Generator -This is a simple Java Inner Builder Generator IntelliJ plugin that generates a +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. 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 +2. Generates static `builder()` method inside the parent class +3. Uses field names as setters in the builder + +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 + ```java @@ -25,7 +37,6 @@ public class Person { return new Builder(); } - public static final class Builder { private int age; private String lastName; @@ -43,14 +54,18 @@ public class Person { return this; } + private void validate() { + } + public Person build() { + validate(); return new Person(this); } } } ``` -Supports Java record classes and automatically detects the class visibility +Supports Java record classes ```java record Address(String street, String city, String state, String country) { diff --git a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java index eedf3fd..a44460f 100644 --- a/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java +++ b/src/main/java/com/github/junkfactory/innerbuilder/BuilderClassGenerator.java @@ -1,5 +1,6 @@ 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; @@ -11,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,6 +34,11 @@ class BuilderClassGenerator extends AbstractGenerator { @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; @@ -41,20 +46,31 @@ public void run() { lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField); fieldMembers.add(fieldMember); } - //builder constructor - var builderConstructor = generateBuilderConstructor(); - addMethod(builderClass, null, builderConstructor, false); - // builder methods PsiElement lastAddedElement = null; for (var member : fieldMembers) { 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(targetClass, selectedFields); - addMethod(builderClass, lastAddedElement, buildMethod, targetClass.isRecord()); + 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; } private PsiMethod generateBuilderConstructor() { @@ -86,7 +102,7 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel return setterMethod; } - private PsiMethod generateBuildMethod(final PsiClass targetClass, final List selectedFields) { + private PsiMethod generateBuildMethod() { var psiElementFactory = generatorParams.psi().factory(); var targetClassType = psiElementFactory.createType(targetClass); var buildMethod = psiElementFactory.createMethod("build", targetClassType); @@ -98,9 +114,14 @@ private PsiMethod generateBuildMethod(final PsiClass targetClass, final List 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 = selectedFields.stream() + var recordParameters = generatorParams.psi().selectedFields().stream() .map(m -> m.getElement().getName()) .collect(Collectors.joining(", ")); returnStatement = psiElementFactory.createStatementFromText(String.format( @@ -109,6 +130,7 @@ private PsiMethod generateBuildMethod(final PsiClass targetClass, final List createGeneratorOptions() { var options = new ArrayList(); options.add(new CheckboxSelectorOption( - JavaInnerBuilderOption.TO_BUILDER, + JavaInnerBuilderOption.WITH_TO_BUILDER_METHOD, 'o' )); + options.add(new CheckboxSelectorOption( + JavaInnerBuilderOption.WITH_VALIDATE_METHOD, + 'v' + )); return options; }