Skip to content

Commit ae4264c

Browse files
authored
Builder annotations (#10)
* adding builder annotation option UI * Hookup annotation options to Builder generation * Add validation of annotations * Fix type parsing for annotations with parameters
1 parent 268af6d commit ae4264c

13 files changed

+264
-53
lines changed

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.intellij.psi.JavaPsiFacade;
1818
import com.intellij.psi.PsiFile;
1919
import com.intellij.psi.PsiJavaFile;
20+
import com.intellij.psi.PsiManager;
2021
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
2122
import com.intellij.util.AstLoadingFilter;
2223
import org.jetbrains.annotations.NotNull;
@@ -91,6 +92,7 @@ public void invoke(@NotNull final Project project, @NotNull final Editor editor,
9192
.selectedFields(selectedFields)
9293
.factory(JavaPsiFacade.getElementFactory(project))
9394
.codeStyleManager(JavaCodeStyleManager.getInstance(project))
95+
.psiManager(PsiManager.getInstance(project))
9496
.build();
9597
var generatorParams = GeneratorParams.builder()
9698
.project(project)
@@ -108,17 +110,21 @@ private Set<JavaInnerBuilderOption> currentOptions() {
108110
final var options = EnumSet.noneOf(JavaInnerBuilderOption.class);
109111
final var propertiesComponent = PropertiesComponent.getInstance();
110112
for (var option : JavaInnerBuilderOption.values()) {
111-
112-
if (Boolean.TRUE.equals(option.isBooleanProperty())) {
113-
final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false);
114-
if (currentSetting) {
115-
options.add(option);
116-
}
117-
} else {
118-
String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty()));
119-
if (currentValue != null) {
113+
switch (option.getType()) {
114+
case BOOLEAN:
115+
if (propertiesComponent.getBoolean(option.getProperty(), false)) {
116+
options.add(option);
117+
}
118+
break;
119+
case LIST:
120+
var list = propertiesComponent.getList(option.getProperty());
121+
if (null != list && !list.isEmpty()) {
122+
options.add(option);
123+
}
124+
break;
125+
default:
126+
String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty()));
120127
JavaInnerBuilderOption.findValue(currentValue).ifPresent(options::add);
121-
}
122128
}
123129
}
124130
return options;

src/main/java/com/github/junkfactory/innerbuilder/generators/AbstractGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ abstract class AbstractGenerator {
3131

3232
protected final GeneratorFactory generatorFactory;
3333
protected final GeneratorParams generatorParams;
34+
protected final GenerationResult generationResult;
3435

3536
protected AbstractGenerator(GeneratorFactory generatorFactory, GeneratorParams generatorParams) {
3637
this.generatorFactory = generatorFactory;
3738
this.generatorParams = generatorParams;
39+
this.generationResult = new GenerationResult();
3840
}
3941

4042
protected PsiElement addElement(PsiElement target, PsiElement element, PsiElement after) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public GenerationResult generate() {
2727

2828
var methodsGenerator = generatorFactory.createBuilderMethodsGenerator(generatorParams,
2929
builderClassParams, fieldsGenerator);
30-
return methodsGenerator.generate();
30+
return generationResult.merge(methodsGenerator.generate());
3131
}
3232

3333
private PsiMethod generateBuilderConstructor() {

src/main/java/com/github/junkfactory/innerbuilder/generators/BuilderMethodsGenerator.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class BuilderMethodsGenerator extends AbstractGenerator implements MethodsGenera
1616

1717
private final BuilderClassParams builderClassParams;
1818
private final FieldsGenerator fieldsGenerator;
19-
private final GenerationResult generationResult;
2019

2120
private boolean isPublic;
2221

@@ -27,7 +26,6 @@ class BuilderMethodsGenerator extends AbstractGenerator implements MethodsGenera
2726
super(generatorFactory, generatorParams);
2827
this.builderClassParams = builderClassParams;
2928
this.fieldsGenerator = fieldsGenerator;
30-
this.generationResult = new GenerationResult();
3129
}
3230

3331
@Override

src/main/java/com/github/junkfactory/innerbuilder/generators/GenerationResult.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ public void when(Code code, Runnable runnable) {
3434
runnable.run();
3535
}
3636
}
37+
38+
public GenerationResult merge(GenerationResult other) {
39+
result.or(other.result);
40+
return this;
41+
}
3742
}

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.junkfactory.innerbuilder.ui.JavaInnerBuilderOption;
44
import com.intellij.codeInsight.generation.PsiFieldMember;
5+
import com.intellij.ide.util.PropertiesComponent;
56
import com.intellij.psi.PsiClass;
67
import com.intellij.psi.PsiField;
78
import com.intellij.psi.PsiJavaFile;
@@ -59,12 +60,13 @@ public GenerationResult generate() {
5960
.builderType(builderType)
6061
.build();
6162
var result = generatorFactory.createBuilderClassGenerator(generatorParams, params).generate();
62-
63+
generationResult.merge(result);
64+
targetClass.add(builderClass);
6365
var codeStyleManager = generatorParams.psi().codeStyleManager();
64-
result.when(ANNOTATIONS_ADDED, () -> codeStyleManager.shortenClassReferences(file));
65-
result.when(IMPORTS_ADDED, () -> codeStyleManager.removeRedundantImports((PsiJavaFile) file));
66+
generationResult.when(ANNOTATIONS_ADDED, () -> codeStyleManager.shortenClassReferences(targetClass));
67+
generationResult.when(IMPORTS_ADDED, () -> codeStyleManager.removeRedundantImports((PsiJavaFile) file));
6668
CodeStyleManager.getInstance(generatorParams.project()).reformat(builderClass);
67-
return result;
69+
return generationResult;
6870
}
6971

7072
private PsiMethod generateToBuilderMethod(PsiClass targetClass,
@@ -162,11 +164,22 @@ private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) {
162164

163165
@NotNull
164166
private PsiClass createBuilderClass(final PsiClass targetClass) {
165-
var builderClass = (PsiClass) targetClass.add(generatorParams.psi().factory()
166-
.createClass(BUILDER_CLASS_NAME));
167-
PsiUtil.setModifierProperty(builderClass, PsiModifier.STATIC, true);
168-
PsiUtil.setModifierProperty(builderClass, PsiModifier.FINAL, true);
169-
return builderClass;
167+
var classDef = new StringBuilder();
168+
if (generatorParams.options().contains(JavaInnerBuilderOption.WITH_BUILDER_CLASS_ANNOTATIONS)) {
169+
var propertiesComponent = PropertiesComponent.getInstance();
170+
var annotationOptions =
171+
propertiesComponent.getList(JavaInnerBuilderOption.WITH_BUILDER_CLASS_ANNOTATIONS.getProperty());
172+
if (annotationOptions != null) {
173+
annotationOptions.forEach(a -> classDef.append('@').append(a).append(System.lineSeparator()));
174+
generationResult.set(GenerationResult.Code.ANNOTATIONS_ADDED);
175+
}
176+
}
177+
classDef.append("public static final class ")
178+
.append(BUILDER_CLASS_NAME)
179+
.append(" {}")
180+
.append(System.lineSeparator());
181+
return generatorParams.psi().factory().createClassFromText(classDef.toString(), targetClass)
182+
.getInnerClasses()[0];
170183
}
171184

172185
}

src/main/java/com/github/junkfactory/innerbuilder/generators/PsiParams.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import com.intellij.codeInsight.generation.PsiFieldMember;
44
import com.intellij.psi.PsiElementFactory;
55
import com.intellij.psi.PsiFile;
6+
import com.intellij.psi.PsiManager;
67
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
78

89
import java.util.List;
910

1011
public record PsiParams(PsiFile file,
1112
List<PsiFieldMember> selectedFields,
1213
PsiElementFactory factory,
13-
JavaCodeStyleManager codeStyleManager) {
14+
JavaCodeStyleManager codeStyleManager,
15+
PsiManager psiManager) {
1416

1517
public static Builder builder() {
1618
return new Builder();
@@ -21,6 +23,7 @@ public static final class Builder {
2123
private List<PsiFieldMember> selectedFields;
2224
private PsiElementFactory factory;
2325
private JavaCodeStyleManager codeStyleManager;
26+
private PsiManager psiManager;
2427

2528
private Builder() {
2629
}
@@ -45,8 +48,13 @@ public Builder codeStyleManager(JavaCodeStyleManager codeStyleManager) {
4548
return this;
4649
}
4750

51+
public Builder psiManager(PsiManager psiManager) {
52+
this.psiManager = psiManager;
53+
return this;
54+
}
55+
4856
public PsiParams build() {
49-
return new PsiParams(file, selectedFields, factory, codeStyleManager);
57+
return new PsiParams(file, selectedFields, factory, codeStyleManager, psiManager);
5058
}
5159
}
5260
}

src/main/java/com/github/junkfactory/innerbuilder/generators/Utils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import org.jetbrains.annotations.NonNls;
1515
import org.jetbrains.annotations.Nullable;
1616

17+
import java.util.Arrays;
18+
import java.util.List;
1719
import java.util.Optional;
20+
import java.util.function.Predicate;
1821

1922
public class Utils {
2023
@NonNls
@@ -134,5 +137,19 @@ public static boolean isFieldInitializedWithImmutableInstance(PsiField field) {
134137
return null != initializerClass && initializerClass.hasModifierProperty(PsiModifier.ABSTRACT);
135138
}
136139

140+
public static List<String> stringToList(String str) {
141+
if (null == str || str.isBlank()) {
142+
return List.of();
143+
}
144+
return Arrays.stream(str.split(System.lineSeparator()))
145+
.map(String::trim)
146+
.filter(Predicate.not(String::isBlank))
147+
.toList();
148+
}
149+
150+
public static String parseType(String text) {
151+
var parenthesisIndex = text.indexOf('(');
152+
return parenthesisIndex == -1 ? text : text.substring(0, parenthesisIndex);
153+
}
137154

138155
}
Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,96 @@
11
package com.github.junkfactory.innerbuilder.ui;
22

3+
import com.github.junkfactory.innerbuilder.generators.Utils;
4+
import com.intellij.openapi.Disposable;
5+
import com.intellij.openapi.ui.ComponentValidator;
6+
import com.intellij.openapi.ui.LabeledComponent;
7+
import com.intellij.openapi.ui.ValidationInfo;
8+
import com.intellij.psi.PsiManager;
9+
import com.intellij.psi.util.ClassUtil;
10+
import com.intellij.ui.JBColor;
11+
import com.intellij.ui.border.CustomLineBorder;
12+
import com.intellij.util.ui.JBUI;
13+
14+
import javax.swing.JComponent;
15+
import javax.swing.JTextArea;
316
import java.util.Arrays;
417
import java.util.Objects;
518
import java.util.Optional;
619

720
public enum JavaInnerBuilderOption {
8-
WITH_TO_BUILDER_METHOD("toBuilder", "Generate 'toBuilder()' method"),
9-
WITH_VALIDATE_METHOD("validate", "Generate 'validate()' method");
21+
WITH_TO_BUILDER_METHOD("JavaInnerBuilderOption.toBuilder",
22+
"Generate 'toBuilder()' method",
23+
Type.BOOLEAN),
24+
WITH_VALIDATE_METHOD("JavaInnerBuilderOption.validate",
25+
"Generate 'validate()' method",
26+
Type.BOOLEAN),
27+
WITH_BUILDER_CLASS_ANNOTATIONS("JavaInnerBuilderOption.builderClassAnnotations",
28+
"Generate annotations for the builder class",
29+
Type.LIST,
30+
(p, d, j) -> new ComponentValidator(d).withValidator(() -> {
31+
if (j instanceof JTextArea textArea) {
32+
var errors = new StringBuilder();
33+
var annotations = Utils.stringToList(textArea.getText());
34+
for (var annotationText : annotations) {
35+
var annotation = Utils.parseType(annotationText);
36+
if (ClassUtil.findPsiClass(p, annotation) == null) {
37+
errors.append(" - ").append(annotation).append("\n");
38+
}
39+
}
40+
if (!errors.isEmpty()) {
41+
textArea.setBorder(new CustomLineBorder(JBColor.RED, JBUI.insets(1)));
42+
return new ValidationInfo(errors.insert(0, "Annotations not found")
43+
.append(System.lineSeparator()).toString(), textArea);
44+
}
45+
textArea.setBorder(new CustomLineBorder(JBColor.border(), JBUI.insets(1)));
46+
}
47+
return null;
48+
}).installOn(j));
1049

1150
private final String property;
1251
private final String description;
13-
private final Boolean booleanProperty;
52+
private final Type type;
53+
private final OptionValidatorFactory validatorFactory;
1454

15-
JavaInnerBuilderOption(final String property, String description) {
16-
this(property, description, true);
55+
JavaInnerBuilderOption(String property, String description, Type type) {
56+
this(property, description, type, (p, d, j) -> {
57+
});
1758
}
1859

19-
JavaInnerBuilderOption(final String property, String description, final Boolean booleanProperty) {
60+
JavaInnerBuilderOption(String property, String description, Type type,
61+
OptionValidatorFactory validatorFactory) {
2062
this.property = String.format("JavaInnerBuilder.%s", property);
2163
this.description = description;
22-
this.booleanProperty = booleanProperty;
64+
this.type = type;
65+
this.validatorFactory = validatorFactory;
2366
}
2467

2568
public String getProperty() {
2669
return property;
2770
}
2871

29-
public Boolean isBooleanProperty() {
30-
return booleanProperty;
72+
public Type getType() {
73+
return type;
3174
}
3275

3376
public String getDescription() {
3477
return description;
3578
}
3679

80+
public void createValidator(PsiManager psiManager, Disposable disposable, JComponent component) {
81+
if (component instanceof LabeledComponent<?> labeledComponent) {
82+
component = labeledComponent.getComponent();
83+
}
84+
validatorFactory.create(psiManager, disposable, component);
85+
}
86+
3787
public static Optional<JavaInnerBuilderOption> findValue(String value) {
3888
return Arrays.stream(values())
3989
.filter(it -> Objects.equals(it.getProperty(), value))
4090
.findFirst();
4191
}
4292

93+
public enum Type {
94+
BOOLEAN, LIST
95+
}
4396
}

0 commit comments

Comments
 (0)