Skip to content

Commit b2e0f7b

Browse files
committed
A few cleanup tasks
1 parent b56ccfd commit b2e0f7b

File tree

13 files changed

+424
-349
lines changed

13 files changed

+424
-349
lines changed

LICENSE.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Copyright (c) <year> <copyright holders>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a
4+
copy of this software and associated documentation files (the "Software"),
5+
to deal in the Software without restriction, including without limitation
6+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
and/or sell copies of the Software, and to permit persons to whom the Software
8+
is furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included
11+
in all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This is a simple Java Inner Builder Generator IntelliJ plugin that generates a
44
builder for a given class. The builder is an inner class of the class it is building.
55

6-
Automatically detects the target class access modifier
6+
Based from [InnerBuilder](https://github.com/analytically/innerbuilder) with stripped down features.
77

88
```java
99
public class Person {
@@ -27,6 +27,9 @@ public class Person {
2727
private int age;
2828
private String lastName;
2929

30+
private Builder() {
31+
}
32+
3033
public Builder age(int age) {
3134
this.age = age;
3235
return this;
@@ -42,10 +45,9 @@ public class Person {
4245
}
4346
}
4447
}
45-
4648
```
4749

48-
Supports Java record classes
50+
Supports Java record classes and automatically detects the class visibility
4951

5052
```java
5153
record Address(String street, String city, String state, String country) {
@@ -71,6 +73,9 @@ record Address(String street, String city, String state, String country) {
7173
private String state;
7274
private String country;
7375

76+
private Builder() {
77+
}
78+
7479
public Builder street(String street) {
7580
this.street = street;
7681
return this;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.github.junkfactory.innerbuilder;
2+
3+
import com.intellij.psi.PsiClass;
4+
import com.intellij.psi.PsiElement;
5+
import com.intellij.psi.PsiMethod;
6+
import org.jetbrains.annotations.NonNls;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
abstract class AbstractGenerator implements Runnable {
11+
12+
@NonNls
13+
protected static final String BUILDER_CLASS_NAME = "Builder";
14+
@NonNls
15+
protected static final String BUILDER_METHOD_NAME = "builder";
16+
@NonNls
17+
protected static final String TO_BUILDER_NAME = "toBuilder";
18+
19+
protected GeneratorParams generatorParams;
20+
21+
protected AbstractGenerator(GeneratorParams generatorParams) {
22+
this.generatorParams = generatorParams;
23+
}
24+
25+
protected PsiElement addMethod(@NotNull final PsiClass target, @Nullable final PsiElement after,
26+
@NotNull final PsiMethod newMethod, final boolean replace) {
27+
var existingMethod = target.findMethodBySignature(newMethod, false);
28+
if (existingMethod == null && newMethod.isConstructor()) {
29+
for (final PsiMethod constructor : target.getConstructors()) {
30+
if (Utils.areParameterListsEqual(constructor.getParameterList(),
31+
newMethod.getParameterList())) {
32+
existingMethod = constructor;
33+
break;
34+
}
35+
}
36+
}
37+
if (existingMethod == null) {
38+
if (after != null) {
39+
return target.addAfter(newMethod, after);
40+
} else {
41+
return target.add(newMethod);
42+
}
43+
} else if (replace) {
44+
existingMethod.replace(newMethod);
45+
}
46+
return existingMethod;
47+
}
48+
49+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.github.junkfactory.innerbuilder;
2+
3+
import com.intellij.codeInsight.generation.PsiFieldMember;
4+
import com.intellij.psi.PsiClass;
5+
import com.intellij.psi.PsiElement;
6+
import com.intellij.psi.PsiMethod;
7+
import com.intellij.psi.PsiModifier;
8+
import com.intellij.psi.PsiStatement;
9+
import com.intellij.psi.PsiType;
10+
import com.intellij.psi.util.PsiUtil;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.Objects;
16+
import java.util.stream.Collectors;
17+
import java.util.stream.Stream;
18+
19+
class BuilderClassGenerator extends AbstractGenerator {
20+
21+
private final PsiClass targetClass;
22+
private final PsiClass builderClass;
23+
private final PsiType builderType;
24+
25+
BuilderClassGenerator(GeneratorParams generatorParams,
26+
PsiClass targetClass,
27+
PsiClass builderClass,
28+
PsiType builderType) {
29+
super(generatorParams);
30+
this.targetClass = targetClass;
31+
this.builderClass = builderClass;
32+
this.builderType = builderType;
33+
}
34+
35+
@Override
36+
public void run() {
37+
var selectedFields = generatorParams.selectedFields();
38+
var fieldMembers = new ArrayList<PsiFieldMember>();
39+
PsiElement lastAddedField = null;
40+
for (var fieldMember : selectedFields) {
41+
lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField);
42+
fieldMembers.add(fieldMember);
43+
}
44+
//builder constructor
45+
var builderConstructor = generateBuilderConstructor();
46+
addMethod(builderClass, null, builderConstructor, false);
47+
48+
// builder methods
49+
PsiElement lastAddedElement = null;
50+
for (var member : fieldMembers) {
51+
var setterMethod = generateBuilderSetter(builderType, member);
52+
lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
53+
}
54+
55+
// builder.build() method
56+
var buildMethod = generateBuildMethod(targetClass, selectedFields);
57+
addMethod(builderClass, lastAddedElement, buildMethod, targetClass.isRecord());
58+
}
59+
60+
private PsiMethod generateBuilderConstructor() {
61+
var builderConstructor = generatorParams.psiElementFactory().createConstructor(BUILDER_CLASS_NAME);
62+
PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true);
63+
return builderConstructor;
64+
}
65+
66+
private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember member) {
67+
68+
var field = member.getElement();
69+
var fieldType = field.getType();
70+
var fieldName = Utils.hasOneLetterPrefix(field.getName()) ?
71+
Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName();
72+
73+
var psiElementFactory = generatorParams.psiElementFactory();
74+
var setterMethod = psiElementFactory.createMethod(fieldName, builderType);
75+
76+
setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
77+
var setterParameter = psiElementFactory.createParameter(fieldName, fieldType);
78+
79+
setterMethod.getParameterList().add(setterParameter);
80+
var setterMethodBody = Objects.requireNonNull(setterMethod.getBody());
81+
var actualFieldName = "this." + fieldName;
82+
var assignStatement = psiElementFactory.createStatementFromText(String.format(
83+
"%s = %s;", actualFieldName, fieldName), setterMethod);
84+
setterMethodBody.add(assignStatement);
85+
setterMethodBody.add(Utils.createReturnThis(psiElementFactory, setterMethod));
86+
return setterMethod;
87+
}
88+
89+
private PsiMethod generateBuildMethod(final PsiClass targetClass, final List<PsiFieldMember> selectedFields) {
90+
var psiElementFactory = generatorParams.psiElementFactory();
91+
var targetClassType = psiElementFactory.createType(targetClass);
92+
var buildMethod = psiElementFactory.createMethod("build", targetClassType);
93+
94+
var targetModifierList = Objects.requireNonNull(targetClass.getModifierList());
95+
Stream.of(PsiModifier.PUBLIC, PsiModifier.PACKAGE_LOCAL)
96+
.filter(targetModifierList::hasModifierProperty)
97+
.findFirst()
98+
.ifPresent(modifier -> PsiUtil.setModifierProperty(buildMethod, modifier, true));
99+
100+
var buildMethodBody = Objects.requireNonNull(buildMethod.getBody());
101+
final PsiStatement returnStatement;
102+
if (targetClass.isRecord()) {
103+
var recordParameters = selectedFields.stream()
104+
.map(m -> m.getElement().getName())
105+
.collect(Collectors.joining(", "));
106+
returnStatement = psiElementFactory.createStatementFromText(String.format(
107+
"return new %s(%s);", targetClass.getName(), recordParameters), buildMethod);
108+
} else {
109+
returnStatement = psiElementFactory.createStatementFromText(String.format(
110+
"return new %s(this);", targetClass.getName()), buildMethod);
111+
}
112+
buildMethodBody.add(returnStatement);
113+
return buildMethod;
114+
}
115+
116+
private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member,
117+
@Nullable final PsiElement last) {
118+
var field = member.getElement();
119+
var fieldName = field.getName();
120+
var fieldType = field.getType();
121+
var existingField = builderClass.findFieldByName(fieldName, false);
122+
if (existingField == null ||
123+
Utils.areTypesPresentableNotEqual(existingField.getType(), fieldType)) {
124+
if (existingField != null) {
125+
existingField.delete();
126+
}
127+
var newField = generatorParams.psiElementFactory().createField(fieldName, fieldType);
128+
if (last != null) {
129+
return builderClass.addAfter(newField, last);
130+
} else {
131+
return builderClass.add(newField);
132+
}
133+
}
134+
return existingField;
135+
}
136+
137+
}

src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderCollector.java renamed to src/main/java/com/github/junkfactory/innerbuilder/FieldCollector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222
import static com.intellij.openapi.util.text.StringUtil.hasLowerCaseChar;
2323

24-
public class JavaInnerBuilderCollector {
24+
public class FieldCollector {
2525

26-
private JavaInnerBuilderCollector() {
26+
private FieldCollector() {
2727
}
2828

2929
@NotNull
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.junkfactory.innerbuilder;
2+
3+
import com.intellij.codeInsight.generation.PsiFieldMember;
4+
import com.intellij.openapi.editor.Editor;
5+
import com.intellij.openapi.project.Project;
6+
import com.intellij.psi.PsiElementFactory;
7+
import com.intellij.psi.PsiFile;
8+
9+
import java.util.List;
10+
11+
record GeneratorParams(Project project,
12+
PsiFile file,
13+
Editor editor,
14+
List<PsiFieldMember> selectedFields,
15+
PsiElementFactory psiElementFactory) {
16+
}

src/main/java/com/github/junkfactory/innerbuilder/JavaInnerBuilderAction.java renamed to src/main/java/com/github/junkfactory/innerbuilder/InnerBuilderAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import com.intellij.psi.PsiFile;
88
import org.jetbrains.annotations.NotNull;
99

10-
public class JavaInnerBuilderAction extends BaseCodeInsightAction {
10+
public class InnerBuilderAction extends BaseCodeInsightAction {
1111
private final JavaInnerBuilderHandler handler = new JavaInnerBuilderHandler();
1212

1313
@NotNull

0 commit comments

Comments
 (0)