Skip to content

Commit 0b5b0e1

Browse files
Merge pull request #953 from dewthefifth/features/builders-as-inner-classes
Features/builders as inner classes
2 parents 8a06edf + 3361f44 commit 0b5b0e1

File tree

23 files changed

+1188
-455
lines changed

23 files changed

+1188
-455
lines changed

jsonschema2pojo-ant/src/main/java/org/jsonschema2pojo/ant/Jsonschema2PojoTask.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public class Jsonschema2PojoTask extends Task implements GenerationConfig {
6969

7070
private boolean generateBuilders;
7171

72+
private boolean useInnerClassBuilders = false;
73+
7274
private boolean includeConstructors = false;
7375

7476
private boolean usePrimitives;
@@ -1201,5 +1203,18 @@ public Language getTargetLanguage() {
12011203
public Map<String, String> getFormatTypeMapping() {
12021204
return formatTypeMapping;
12031205
}
1204-
1206+
1207+
@Override
1208+
public boolean isUseInnerClassBuilders() {
1209+
return useInnerClassBuilders;
1210+
}
1211+
1212+
/**
1213+
* Sets the 'useInnerClassBuilders' property of this class
1214+
*
1215+
* @param useInnerClassBuilders determines whether builders will be chainable setters or embedded classes when {@link #isGenerateBuilders()} used
1216+
*/
1217+
public void setUseInnerClassBuilders(boolean useInnerClassBuilders) {
1218+
this.useInnerClassBuilders = useInnerClassBuilders;
1219+
}
12051220
}

jsonschema2pojo-ant/src/site/Jsonschema2PojoTask.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ <h3>Parameters</h3>
156156
</td>
157157
<td align="center" valign="top">No (default <code>false</code>)</td>
158158
</tr>
159+
<tr>
160+
<td valign="top">useInnerClassBuilders</td>
161+
<td valign="top">Determines whether builders will be chainable setters or embedded classes when generateBuilders is used</td>
162+
<td align="center" valign="top">No (default <code>false</code>)</td>
163+
</tr>
159164
<tr>
160165
<td valign="top">includeGetters</td>
161166
<td valign="top">Whether to include getters or to omit these accessor methods and create public fields instead.</td>

jsonschema2pojo-cli/src/main/java/org/jsonschema2pojo/cli/Arguments.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public class Arguments implements GenerationConfig {
6363
@Parameter(names = { "-b", "--generate-builders" }, description = "Generate builder-style methods as well as setters")
6464
private boolean generateBuilderMethods = false;
6565

66+
@Parameter(names = { "--use-inner-class-builders" }, description = "Generate an inner class with builder-style methods")
67+
private boolean useInnerClassBuilders = false;
68+
6669
@Parameter(names = { "-c", "--generate-constructors" }, description = "Generate constructors")
6770
private boolean generateConstructors = false;
6871

@@ -278,6 +281,11 @@ public boolean isGenerateBuilders() {
278281
return generateBuilderMethods;
279282
}
280283

284+
@Override
285+
public boolean isUseInnerClassBuilders() {
286+
return useInnerClassBuilders;
287+
}
288+
281289
@Override
282290
public boolean isUsePrimitives() {
283291
return usePrimitives;

jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/DefaultGenerationConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,5 +444,7 @@ public Language getTargetLanguage() {
444444
public Map<String, String> getFormatTypeMapping() {
445445
return Collections.emptyMap();
446446
}
447-
447+
448+
@Override
449+
public boolean isUseInnerClassBuilders() { return false; }
448450
}

jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/GenerationConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,4 +571,13 @@ public interface GenerationConfig {
571571
* fully qualified type name (e.g. 'java.net.URI').
572572
*/
573573
Map<String, String> getFormatTypeMapping();
574+
575+
/**
576+
* If set to true, then the gang of four builder pattern will be used to generate builders on generated classes. Note: This property works
577+
* in collaboration with the {@link #isGenerateBuilders()} method. If the {@link #isGenerateBuilders()} is false,
578+
* then this property will not do anything.
579+
* @return whether to include the gang of four builder patter on the generated classes. The default value for this is false.
580+
*/
581+
default boolean isUseInnerClassBuilders(){ return false;}
582+
574583
}

jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/ScalaSingleStreamCodeWriter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@
1616

1717
package org.jsonschema2pojo;
1818

19-
import java.io.ByteArrayOutputStream;
20-
import java.io.FilterOutputStream;
21-
import java.io.IOException;
22-
import java.io.OutputStream;
23-
2419
import com.mysema.scalagen.ConversionSettings;
2520
import com.mysema.scalagen.Converter;
2621
import com.sun.codemodel.JPackage;
2722
import com.sun.codemodel.writer.SingleStreamCodeWriter;
23+
import java.io.ByteArrayOutputStream;
24+
import java.io.FilterOutputStream;
25+
import java.io.IOException;
26+
import java.io.OutputStream;
2827

2928
public class ScalaSingleStreamCodeWriter extends SingleStreamCodeWriter {
3029

jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/AdditionalPropertiesRule.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
import java.util.HashMap;
2020
import java.util.Map;
2121

22+
import java.util.Optional;
23+
import java.util.Spliterator;
24+
import java.util.Spliterators;
25+
import java.util.stream.StreamSupport;
26+
import org.apache.commons.collections15.CollectionUtils;
2227
import org.jsonschema2pojo.Schema;
2328

2429
import com.fasterxml.jackson.databind.JsonNode;
@@ -160,7 +165,19 @@ private JMethod addGetter(JDefinedClass jclass, JFieldVar field) {
160165
return getter;
161166
}
162167

163-
private void addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
168+
private JMethod addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
169+
170+
JMethod result = null;
171+
if(ruleFactory.getGenerationConfig().isUseInnerClassBuilders()) {
172+
result = addInnerBuilder(jclass, propertyType, field);
173+
} else {
174+
result = addLegacyBuilder(jclass, propertyType, field);
175+
}
176+
177+
return result;
178+
}
179+
180+
private JMethod addLegacyBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
164181
JMethod builder = jclass.method(JMod.PUBLIC, jclass, "withAdditionalProperty");
165182

166183
JVar nameParam = builder.param(String.class, "name");
@@ -171,6 +188,32 @@ private void addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar fiel
171188
mapInvocation.arg(nameParam);
172189
mapInvocation.arg(valueParam);
173190
body._return(JExpr._this());
191+
192+
return builder;
193+
}
194+
195+
private JMethod addInnerBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
196+
Optional<JDefinedClass> builderClass = StreamSupport
197+
.stream(Spliterators.spliteratorUnknownSize(jclass.classes(), Spliterator.ORDERED), false)
198+
.filter(definedClass -> definedClass.name().equals(getBuilderClassName(jclass)))
199+
.findFirst();
200+
201+
JMethod builder = builderClass.get().method(JMod.PUBLIC, builderClass.get(), "withAdditionalProperty");
202+
203+
JVar nameParam = builder.param(String.class, "name");
204+
JVar valueParam = builder.param(propertyType, "value");
205+
206+
JBlock body = builder.body();
207+
JInvocation mapInvocation = body.invoke(JExpr.ref(JExpr.cast(jclass, JExpr._this().ref("instance")), field), "put");
208+
mapInvocation.arg(nameParam);
209+
mapInvocation.arg(valueParam);
210+
body._return(JExpr._this());
211+
212+
return builder;
213+
}
214+
215+
private String getBuilderClassName(JDefinedClass c) {
216+
return ruleFactory.getNameHelper().getBuilderClassName(c);
174217
}
175218

176219
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Copyright © 2010-2017 Nokia
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.jsonschema2pojo.rules;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
import com.sun.codemodel.JAnnotationUse;
20+
import com.sun.codemodel.JBlock;
21+
import com.sun.codemodel.JClass;
22+
import com.sun.codemodel.JClassAlreadyExistsException;
23+
import com.sun.codemodel.JConditional;
24+
import com.sun.codemodel.JDefinedClass;
25+
import com.sun.codemodel.JExpr;
26+
import com.sun.codemodel.JFieldVar;
27+
import com.sun.codemodel.JInvocation;
28+
import com.sun.codemodel.JMethod;
29+
import com.sun.codemodel.JMod;
30+
import com.sun.codemodel.JTypeVar;
31+
import com.sun.codemodel.JVar;
32+
import java.util.Objects;
33+
import org.jsonschema2pojo.Schema;
34+
import org.jsonschema2pojo.util.ReflectionHelper;
35+
36+
public class BuilderRule implements Rule<JDefinedClass, JDefinedClass> {
37+
38+
private RuleFactory ruleFactory;
39+
private ReflectionHelper reflectionHelper;
40+
41+
BuilderRule(RuleFactory ruleFactory, ReflectionHelper reflectionHelper) {
42+
this.ruleFactory = ruleFactory;
43+
this.reflectionHelper = reflectionHelper;
44+
}
45+
46+
@Override
47+
public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass instanceClass, Schema currentSchema) {
48+
49+
// Create the inner class for the builder
50+
JDefinedClass builderClass;
51+
52+
try {
53+
String builderName = ruleFactory.getNameHelper().getBuilderClassName(instanceClass);
54+
builderClass = instanceClass._class(JMod.PUBLIC + JMod.STATIC, builderName);
55+
} catch (JClassAlreadyExistsException e) {
56+
return e.getExistingClass();
57+
}
58+
59+
// Determine which builder (if any) this builder should inherit from
60+
JClass parentBuilderClass = null;
61+
JClass parentClass = instanceClass._extends();
62+
if (!(parentClass.isPrimitive() || reflectionHelper.isFinal(parentClass) || Objects.equals(parentClass.fullName(), "java.lang.Object"))) {
63+
parentBuilderClass = reflectionHelper.getBuilderClass(parentClass);
64+
}
65+
66+
// Determine the generic type 'T' that the builder will create instances of
67+
JTypeVar instanceType = builderClass.generify("T", instanceClass);
68+
69+
// For new builders we need to create an instance variable and 'build' method
70+
// for inheriting builders we'll receive these from the superType
71+
if (parentBuilderClass == null) {
72+
73+
// Create the instance variable
74+
JFieldVar instanceField = builderClass.field(JMod.PROTECTED, instanceType, "instance");
75+
76+
// Create the actual "build" method
77+
JMethod buildMethod = builderClass.method(JMod.PUBLIC, instanceType, "build");
78+
79+
JBlock body = buildMethod.body();
80+
JVar result = body.decl(instanceType, "result");
81+
body.assign(result, JExpr._this().ref(instanceField));
82+
body.assign(JExpr._this().ref(instanceField), JExpr._null());
83+
body._return(result);
84+
85+
// Create the noargs builder constructor
86+
generateNoArgsBuilderConstructor(instanceClass, builderClass);
87+
} else {
88+
// Declare the inheritance
89+
builderClass._extends(parentBuilderClass);
90+
91+
// Create the noargs builder constructor
92+
generateNoArgsBuilderConstructor(instanceClass, builderClass);
93+
}
94+
95+
return builderClass;
96+
}
97+
98+
private void generateNoArgsBuilderConstructor(JDefinedClass instanceClass, JDefinedClass builderClass) {
99+
JMethod noargsConstructor = builderClass.constructor(JMod.PUBLIC);
100+
JAnnotationUse warningSuppression = noargsConstructor.annotate(SuppressWarnings.class);
101+
warningSuppression.param("value", "unchecked");
102+
103+
JBlock constructorBlock = noargsConstructor.body();
104+
105+
JFieldVar instanceField = reflectionHelper.searchClassAndSuperClassesForField("instance", builderClass);
106+
107+
// Determine if we need to invoke the super() method for our parent builder
108+
JClass parentClass = builderClass._extends();
109+
if (!(parentClass.isPrimitive() || reflectionHelper.isFinal(parentClass) || Objects.equals(parentClass.fullName(), "java.lang.Object"))) {
110+
constructorBlock.invoke("super");
111+
}
112+
113+
// Only initialize the instance if the object being constructed is actually this class
114+
// if it's a subtype then ignore the instance initialization since the subclass will initialize it
115+
constructorBlock.directStatement("// Skip initialization when called from subclass");
116+
JInvocation comparison = JExpr._this().invoke("getClass").invoke("equals").arg(JExpr.dotclass(builderClass));
117+
JConditional ifNotSubclass = constructorBlock._if(comparison);
118+
ifNotSubclass._then().assign(JExpr._this().ref(instanceField), JExpr.cast(instanceField.type(), JExpr._new(instanceClass)));
119+
}
120+
121+
}

0 commit comments

Comments
 (0)