Skip to content

Commit 0839006

Browse files
authored
Validation option (#3)
* Add options to generator params * Add validate method option
1 parent f2ef10c commit 0839006

File tree

8 files changed

+195
-65
lines changed

8 files changed

+195
-65
lines changed

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
# An opinionated Java Inner Builder Generator
44

5-
This is a simple Java Inner Builder Generator IntelliJ plugin that generates a
5+
This is an opinionated but simple Java Inner Builder Generator IntelliJ plugin that generates a
66
builder for a given class. The builder is an inner class of the class it is building.
77

88
Based from [InnerBuilder](https://github.com/analytically/innerbuilder) with stripped down features.
9+
10+
Generates a builder class for a given class with the following features:
11+
12+
1. Generates builder method for final fields that are not initialized and/or static fields
13+
2. Generates static `builder()` method inside the parent class
14+
3. Uses field names as setters in the builder
15+
16+
Optional features:
17+
18+
1. Generates `toBuilder()` method to convert the object to a builder
19+
2. Generates `validate()` method to validate the fields before building the object
20+
921
<!-- Plugin description end -->
1022

1123
```java
@@ -25,7 +37,6 @@ public class Person {
2537
return new Builder();
2638
}
2739

28-
2940
public static final class Builder {
3041
private int age;
3142
private String lastName;
@@ -43,14 +54,18 @@ public class Person {
4354
return this;
4455
}
4556

57+
private void validate() {
58+
}
59+
4660
public Person build() {
61+
validate();
4762
return new Person(this);
4863
}
4964
}
5065
}
5166
```
5267

53-
Supports Java record classes and automatically detects the class visibility
68+
Supports Java record classes
5469

5570
```java
5671
record Address(String street, String city, String state, String country) {

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

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.junkfactory.innerbuilder;
22

3+
import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption;
34
import com.intellij.codeInsight.generation.PsiFieldMember;
45
import com.intellij.psi.PsiClass;
56
import com.intellij.psi.PsiElement;
@@ -11,7 +12,6 @@
1112
import org.jetbrains.annotations.Nullable;
1213

1314
import java.util.ArrayList;
14-
import java.util.List;
1515
import java.util.Objects;
1616
import java.util.stream.Collectors;
1717
import java.util.stream.Stream;
@@ -34,31 +34,47 @@ class BuilderClassGenerator extends AbstractGenerator {
3434

3535
@Override
3636
public void run() {
37-
var selectedFields = generatorParams.selectedFields();
37+
//builder constructor
38+
var builderConstructor = generateBuilderConstructor();
39+
addMethod(builderClass, null, builderConstructor, false);
40+
41+
//build setters
42+
var selectedFields = generatorParams.psi().selectedFields();
3843
var fieldMembers = new ArrayList<PsiFieldMember>();
3944
PsiElement lastAddedField = null;
4045
for (var fieldMember : selectedFields) {
4146
lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField);
4247
fieldMembers.add(fieldMember);
4348
}
44-
//builder constructor
45-
var builderConstructor = generateBuilderConstructor();
46-
addMethod(builderClass, null, builderConstructor, false);
4749

48-
// builder methods
4950
PsiElement lastAddedElement = null;
5051
for (var member : fieldMembers) {
5152
var setterMethod = generateBuilderSetter(builderType, member);
5253
lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
5354
}
5455

56+
//build validate method
57+
var options = generatorParams.options();
58+
if (options.contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) {
59+
var validateMethod = generateValidateMethod();
60+
addMethod(builderClass, lastAddedElement, validateMethod, false);
61+
}
62+
5563
// builder.build() method
56-
var buildMethod = generateBuildMethod(targetClass, selectedFields);
57-
addMethod(builderClass, lastAddedElement, buildMethod, targetClass.isRecord());
64+
var buildMethod = generateBuildMethod();
65+
addMethod(builderClass, null, buildMethod, targetClass.isRecord());
66+
}
67+
68+
private PsiMethod generateValidateMethod() {
69+
var psiElementFactory = generatorParams.psi().factory();
70+
var voidType = psiElementFactory.createPrimitiveType("void");
71+
var validateMethod = psiElementFactory.createMethod("validate", voidType);
72+
PsiUtil.setModifierProperty(validateMethod, PsiModifier.PRIVATE, true);
73+
return validateMethod;
5874
}
5975

6076
private PsiMethod generateBuilderConstructor() {
61-
var builderConstructor = generatorParams.psiElementFactory().createConstructor(BUILDER_CLASS_NAME);
77+
var builderConstructor = generatorParams.psi().factory().createConstructor(BUILDER_CLASS_NAME);
6278
PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true);
6379
return builderConstructor;
6480
}
@@ -70,7 +86,7 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel
7086
var fieldName = Utils.hasOneLetterPrefix(field.getName()) ?
7187
Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName();
7288

73-
var psiElementFactory = generatorParams.psiElementFactory();
89+
var psiElementFactory = generatorParams.psi().factory();
7490
var setterMethod = psiElementFactory.createMethod(fieldName, builderType);
7591

7692
setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
@@ -86,8 +102,8 @@ private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFiel
86102
return setterMethod;
87103
}
88104

89-
private PsiMethod generateBuildMethod(final PsiClass targetClass, final List<PsiFieldMember> selectedFields) {
90-
var psiElementFactory = generatorParams.psiElementFactory();
105+
private PsiMethod generateBuildMethod() {
106+
var psiElementFactory = generatorParams.psi().factory();
91107
var targetClassType = psiElementFactory.createType(targetClass);
92108
var buildMethod = psiElementFactory.createMethod("build", targetClassType);
93109

@@ -98,9 +114,14 @@ private PsiMethod generateBuildMethod(final PsiClass targetClass, final List<Psi
98114
.ifPresent(modifier -> PsiUtil.setModifierProperty(buildMethod, modifier, true));
99115

100116
var buildMethodBody = Objects.requireNonNull(buildMethod.getBody());
117+
if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_VALIDATE_METHOD)) {
118+
var validateCall = psiElementFactory.createStatementFromText("validate();", buildMethod);
119+
buildMethodBody.add(validateCall);
120+
}
121+
101122
final PsiStatement returnStatement;
102123
if (targetClass.isRecord()) {
103-
var recordParameters = selectedFields.stream()
124+
var recordParameters = generatorParams.psi().selectedFields().stream()
104125
.map(m -> m.getElement().getName())
105126
.collect(Collectors.joining(", "));
106127
returnStatement = psiElementFactory.createStatementFromText(String.format(
@@ -109,6 +130,7 @@ private PsiMethod generateBuildMethod(final PsiClass targetClass, final List<Psi
109130
returnStatement = psiElementFactory.createStatementFromText(String.format(
110131
"return new %s(this);", targetClass.getName()), buildMethod);
111132
}
133+
112134
buildMethodBody.add(returnStatement);
113135
return buildMethod;
114136
}
@@ -119,12 +141,11 @@ private PsiElement findOrCreateField(final PsiClass builderClass, final PsiField
119141
var fieldName = field.getName();
120142
var fieldType = field.getType();
121143
var existingField = builderClass.findFieldByName(fieldName, false);
122-
if (existingField == null ||
123-
Utils.areTypesPresentableNotEqual(existingField.getType(), fieldType)) {
144+
if (existingField == null || Utils.areTypesPresentableNotEqual(existingField.getType(), fieldType)) {
124145
if (existingField != null) {
125146
existingField.delete();
126147
}
127-
var newField = generatorParams.psiElementFactory().createField(fieldName, fieldType);
148+
var newField = generatorParams.psi().factory().createField(fieldName, fieldType);
128149
if (last != null) {
129150
return builderClass.addAfter(newField, last);
130151
} else {
Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,51 @@
11
package com.github.junkfactory.innerbuilder;
22

3-
import com.intellij.codeInsight.generation.PsiFieldMember;
3+
import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption;
44
import com.intellij.openapi.editor.Editor;
55
import com.intellij.openapi.project.Project;
6-
import com.intellij.psi.PsiElementFactory;
7-
import com.intellij.psi.PsiFile;
86

9-
import java.util.List;
7+
import java.util.Set;
108

119
record GeneratorParams(Project project,
12-
PsiFile file,
1310
Editor editor,
14-
List<PsiFieldMember> selectedFields,
15-
PsiElementFactory psiElementFactory) {
11+
PsiParams psi,
12+
Set<JavaInnerBuilderOption> options) {
13+
14+
public static Builder builder() {
15+
return new Builder();
16+
}
17+
18+
public static final class Builder {
19+
private Project project;
20+
private Editor editor;
21+
private PsiParams psi;
22+
private Set<JavaInnerBuilderOption> options;
23+
24+
private Builder() {
25+
}
26+
27+
public Builder project(Project project) {
28+
this.project = project;
29+
return this;
30+
}
31+
32+
public Builder editor(Editor editor) {
33+
this.editor = editor;
34+
return this;
35+
}
36+
37+
public Builder psi(PsiParams psi) {
38+
this.psi = psi;
39+
return this;
40+
}
41+
42+
public Builder options(Set<JavaInnerBuilderOption> options) {
43+
this.options = options;
44+
return this;
45+
}
46+
47+
GeneratorParams build() {
48+
return new GeneratorParams(project, editor, psi, options);
49+
}
50+
}
1651
}

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ class InnerBuilderGenerator extends AbstractGenerator {
2424

2525
@Override
2626
public void run() {
27-
var file = generatorParams.file();
27+
var file = generatorParams.psi().file();
2828
var targetClass = Utils.getStaticOrTopLevelClass(file, generatorParams.editor());
29-
if (targetClass == null) {
29+
if (targetClass == null || BUILDER_CLASS_NAME.equals(targetClass.getName())) {
3030
return;
3131
}
32-
var psiElementFactory = generatorParams.psiElementFactory();
32+
var psiElementFactory = generatorParams.psi().factory();
3333
var builderClass = findOrCreateBuilderClass(targetClass);
3434
var builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, null);
3535

@@ -42,9 +42,9 @@ public void run() {
4242
addMethod(targetClass, null, newBuilderMethod, false);
4343

4444
// toBuilder method
45-
var options = JavaInnerBuilderOption.currentOptions();
46-
if (options.contains(JavaInnerBuilderOption.TO_BUILDER)) {
47-
var toBuilderMethod = generateToBuilderMethod(builderType, generatorParams.selectedFields());
45+
var options = generatorParams.options();
46+
if (options.contains(JavaInnerBuilderOption.WITH_TO_BUILDER_METHOD)) {
47+
var toBuilderMethod = generateToBuilderMethod(builderType, generatorParams.psi().selectedFields());
4848
addMethod(targetClass, null, toBuilderMethod, true);
4949
}
5050

@@ -57,7 +57,7 @@ public void run() {
5757

5858
private PsiMethod generateToBuilderMethod(final PsiType builderType,
5959
final Collection<PsiFieldMember> fields) {
60-
var psiElementFactory = generatorParams.psiElementFactory();
60+
var psiElementFactory = generatorParams.psi().factory();
6161
var toBuilderMethod = psiElementFactory.createMethod(TO_BUILDER_NAME, builderType);
6262
PsiUtil.setModifierProperty(toBuilderMethod, PsiModifier.PUBLIC, true);
6363
var toBuilderBody = Objects.requireNonNull(toBuilderMethod.getBody());
@@ -74,14 +74,14 @@ private void addCopyBody(final Collection<PsiFieldMember> fields, final PsiMetho
7474
var methodBody = Objects.requireNonNull(method.getBody());
7575
for (final PsiFieldMember member : fields) {
7676
var field = member.getElement();
77-
var assignStatement = generatorParams.psiElementFactory().createStatementFromText(String.format(
77+
var assignStatement = generatorParams.psi().factory().createStatementFromText(String.format(
7878
"%s%2$s = this.%3$s;", "builder.", field.getName(), field.getName()), method);
7979
methodBody.add(assignStatement);
8080
}
8181
}
8282

8383
private PsiMethod generateStaticBuilderMethod(final PsiType builderType) {
84-
var psiElementFactory = generatorParams.psiElementFactory();
84+
var psiElementFactory = generatorParams.psi().factory();
8585
var newBuilderMethod = psiElementFactory.createMethod(BUILDER_METHOD_NAME, builderType);
8686
PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true);
8787
PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true);
@@ -94,15 +94,15 @@ private PsiMethod generateStaticBuilderMethod(final PsiType builderType) {
9494
}
9595

9696
private PsiMethod generateConstructor(final PsiClass targetClass, final PsiType builderType) {
97-
var psiElementFactory = generatorParams.psiElementFactory();
97+
var psiElementFactory = generatorParams.psi().factory();
9898
var constructor = psiElementFactory.createConstructor(Objects.requireNonNull(targetClass.getName()));
9999
constructor.getModifierList().setModifierProperty(PsiModifier.PRIVATE, true);
100100

101101
var builderParameter = psiElementFactory.createParameter(BUILDER_METHOD_NAME, builderType);
102102
constructor.getParameterList().add(builderParameter);
103103

104104
var constructorBody = Objects.requireNonNull(constructor.getBody());
105-
for (var member : generatorParams.selectedFields()) {
105+
for (var member : generatorParams.psi().selectedFields()) {
106106
var field = member.getElement();
107107
var setterPrototype = PropertyUtilBase.generateSetterPrototype(field);
108108
var setter = targetClass.findMethodBySignature(setterPrototype, true);
@@ -143,7 +143,7 @@ private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) {
143143

144144
@NotNull
145145
private PsiClass createBuilderClass(final PsiClass targetClass) {
146-
var builderClass = (PsiClass) targetClass.add(generatorParams.psiElementFactory()
146+
var builderClass = (PsiClass) targetClass.add(generatorParams.psi().factory()
147147
.createClass(BUILDER_CLASS_NAME));
148148
PsiUtil.setModifierProperty(builderClass, PsiModifier.STATIC, true);
149149
PsiUtil.setModifierProperty(builderClass, PsiModifier.FINAL, true);

src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderHandler.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.junkfactory.innerbuilder;
22

3+
import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption;
4+
import com.intellij.ide.util.PropertiesComponent;
35
import com.intellij.lang.LanguageCodeInsightActionHandler;
46
import com.intellij.openapi.application.ApplicationManager;
57
import com.intellij.openapi.editor.Editor;
@@ -12,6 +14,9 @@
1214
import com.intellij.psi.PsiJavaFile;
1315
import org.jetbrains.annotations.NotNull;
1416

17+
import java.util.EnumSet;
18+
import java.util.Set;
19+
1520
import static com.github.junkfactory.innerbuilder.FieldCollector.collectFields;
1621
import static com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOptionSelector.selectFieldsAndOptions;
1722

@@ -64,11 +69,39 @@ public void invoke(@NotNull final Project project, @NotNull final Editor editor,
6469
if (selectedFields.isEmpty()) {
6570
return;
6671
}
67-
var generatorParams = new GeneratorParams(project, file, editor, selectedFields,
68-
JavaPsiFacade.getElementFactory(project));
72+
var psiParams = PsiParams.builder()
73+
.file(file)
74+
.selectedFields(selectedFields)
75+
.factory(JavaPsiFacade.getElementFactory(project))
76+
.build();
77+
var generatorParams = GeneratorParams.builder()
78+
.project(project)
79+
.editor(editor)
80+
.psi(psiParams)
81+
.options(currentOptions())
82+
.build();
6983
var builderGenerator = new InnerBuilderGenerator(generatorParams);
7084
ApplicationManager.getApplication().runWriteAction(builderGenerator);
7185
}
7286
}
7387

88+
private Set<JavaInnerBuilderOption> currentOptions() {
89+
final var options = EnumSet.noneOf(JavaInnerBuilderOption.class);
90+
final var propertiesComponent = PropertiesComponent.getInstance();
91+
for (var option : JavaInnerBuilderOption.values()) {
92+
93+
if (Boolean.TRUE.equals(option.isBooleanProperty())) {
94+
final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false);
95+
if (currentSetting) {
96+
options.add(option);
97+
}
98+
} else {
99+
String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty()));
100+
if (currentValue != null) {
101+
JavaInnerBuilderOption.findValue(currentValue).ifPresent(options::add);
102+
}
103+
}
104+
}
105+
return options;
106+
}
74107
}

0 commit comments

Comments
 (0)