Skip to content

Commit 72050f6

Browse files
Merge pull request #1553 from unkish/issues/1550
Prevent generating duplicate constructors when all properties are required; and both includeAllPropertiesConstructor and includeRequiredPropertiesConstructor are true
2 parents 6c8602a + c8711a9 commit 72050f6

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Objects;
2929
import java.util.Set;
3030
import java.util.StringJoiner;
31+
import java.util.stream.Stream;
3132

3233
import org.apache.commons.lang3.StringUtils;
3334
import org.jsonschema2pojo.GenerationConfig;
@@ -48,6 +49,8 @@
4849
import com.sun.codemodel.JMod;
4950
import com.sun.codemodel.JVar;
5051

52+
import static java.util.stream.Collectors.toSet;
53+
5154
public class ConstructorRule implements Rule<JDefinedClass, JDefinedClass> {
5255

5356
private final RuleFactory ruleFactory;
@@ -139,6 +142,10 @@ private void handleMultiChoiceConstructorConfiguration(JsonNode node, JDefinedCl
139142
}
140143

141144
private void addFieldsConstructor(JDefinedClass instanceClass, Map<String, String> classProperties, Map<String, String> combinedSuperProperties) {
145+
if (isConstructorAlreadyAdded(instanceClass, classProperties, combinedSuperProperties)) {
146+
return;
147+
}
148+
142149
GenerationConfig generationConfig = ruleFactory.getGenerationConfig();
143150

144151
// Generate the constructor with the properties which were located
@@ -153,6 +160,17 @@ private void addFieldsConstructor(JDefinedClass instanceClass, Map<String, Strin
153160
}
154161
}
155162

163+
private boolean isConstructorAlreadyAdded(JDefinedClass instanceClass, Map<String, String> classProperties, Map<String, String> combinedSuperProperties) {
164+
final Set<String> allProperties = Stream.of(classProperties.keySet(), combinedSuperProperties.keySet()).flatMap(Set::stream).collect(toSet());
165+
for (Iterator<JMethod> constructorIterator = instanceClass.constructors(); constructorIterator.hasNext(); ) {
166+
final Set<String> constructorParams = constructorIterator.next().params().stream().map(JVar::name).collect(toSet());
167+
if (constructorParams.equals(allProperties)) {
168+
return true;
169+
}
170+
}
171+
return false;
172+
}
173+
156174
private void addCopyConstructor(JDefinedClass instanceClass, Map<String, String> classProperties, Map<String, String> combinedSuperProperties) {
157175
GenerationConfig generationConfig = ruleFactory.getGenerationConfig();
158176

jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/ConstructorsIT.java

+18
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,24 @@ public void testAllFieldsConstructorAssignsFields() throws Exception {
267267
assertEquals("provider", getValue(instance, "getProvider"));
268268
assertEquals("startTime", getValue(instance, "getStarttime"));
269269
}
270+
271+
/**
272+
* Test that duplicate constructors are not generated (compile time error is not thrown) when:
273+
* <ul>
274+
* <li>all properties are required</li>
275+
* <li>{@code includeAllPropertiesConstructor} configuration property is {@code true}</li>
276+
* <li>{@code includeRequiredPropertiesConstructor} configuration property is {@code true}</li>
277+
*/
278+
@Test
279+
public void testGeneratesConstructorWithAllPropertiesRequired() throws Exception {
280+
classSchemaRule.generate(
281+
"/schema/constructors/allPropertiesRequiredConstructor.json",
282+
"com.example",
283+
config("includeConstructors", true, "includeAllPropertiesConstructor", true, "includeRequiredPropertiesConstructor", true));
284+
Class<?> type = classSchemaRule.compile().loadClass("com.example.AllPropertiesRequiredConstructor");
285+
assertHasModifier(JMod.PUBLIC, getAllPropertiesConstructor(type).getModifiers(), "public");
286+
}
287+
270288
}
271289

272290
/**

jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/config/UseInnerClassBuildersIT.java

+42-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public void innerBuilderWithAllPropertyConstructor()
195195
* with the required properties will be created
196196
*/
197197
@Test
198-
public void innerBuilderWithRequiredPropertyConstructor()
198+
public void innerBuilderWithRequiredPropertyOnlyConstructor()
199199
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
200200
ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema.useInnerClassBuilders/child.json", "com.example",
201201
config("generateBuilders", true, "useInnerClassBuilders", true, "includeConstructors", true, "constructorsRequiredPropertiesOnly", true));
@@ -225,6 +225,47 @@ public void innerBuilderWithRequiredPropertyConstructor()
225225
assertEquals(sharedProperty, getSharedProperty.invoke(childObject));
226226
}
227227

228+
/**
229+
* This method confirms that duplicate constructors are not generated (compile time error is not thrown) when:
230+
* <ul>
231+
* <li>all properties are required</li>
232+
* <li>{@code includeAllPropertiesConstructor} configuration property is {@code true}</li>
233+
* <li>{@code includeRequiredPropertiesConstructor} configuration property is {@code true}</li>
234+
*/
235+
@Test
236+
public void innerBuilderWithRequiredPropertyConstructor() throws ReflectiveOperationException {
237+
ClassLoader resultsClassLoader = schemaRule.generateAndCompile(
238+
"/schema.useInnerClassBuilders/child_parent_all_required.json",
239+
"com.example",
240+
config("generateBuilders", true,
241+
"useInnerClassBuilders", true,
242+
"includeConstructors", true,
243+
"includeAllPropertiesConstructor", true,
244+
"includeRequiredPropertiesConstructor", true));
245+
246+
Class<?> builderClass = resultsClassLoader.loadClass("com.example.ChildParentAllRequired$ChildParentAllRequiredBuilder");
247+
Constructor<?> constructor = builderClass.getConstructor(String.class, String.class);
248+
Method buildMethod = builderClass.getMethod("build");
249+
Method withChildProperty = builderClass.getMethod("withChildProperty", Integer.class);
250+
251+
int childProperty = 1;
252+
String parentProperty = "parentProperty";
253+
String sharedProperty = "sharedProperty";
254+
255+
Object builder = constructor.newInstance(sharedProperty, parentProperty);
256+
withChildProperty.invoke(builder, childProperty);
257+
Object childObject = buildMethod.invoke(builder);
258+
259+
Class<?> childClass = resultsClassLoader.loadClass("com.example.ChildParentAllRequired");
260+
Method getChildProperty = childClass.getMethod("getChildProperty");
261+
Method getParentProperty = childClass.getMethod("getParentProperty");
262+
Method getSharedProperty = childClass.getMethod("getSharedProperty");
263+
264+
assertEquals(childProperty, getChildProperty.invoke(childObject));
265+
assertEquals(parentProperty, getParentProperty.invoke(childObject));
266+
assertEquals(sharedProperty, getSharedProperty.invoke(childObject));
267+
}
268+
228269
/**
229270
* This method confirms that if innerBuilders are used, a "builder" method is created on the class that returns an instance of the builder
230271
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"type": "object",
3+
"extends": {
4+
"$ref": "parent_all_required.json"
5+
},
6+
"properties": {
7+
"childProperty": {
8+
"type": "integer"
9+
},
10+
"sharedProperty": {
11+
"type": "string",
12+
"required": true
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"type" : "object",
3+
"properties" : {
4+
"parentProperty" : { "type" : "string" },
5+
"sharedProperty" : { "type" : "string" }
6+
},
7+
"required" : ["parentProperty", "sharedProperty"]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"type" : "object",
3+
"properties" : {
4+
"type" : {
5+
"type" : "string",
6+
"default" : "event",
7+
"required": true
8+
},
9+
"id" : {
10+
"type" : "integer",
11+
"required": true
12+
},
13+
"has_tickets" : {
14+
"type" : "boolean",
15+
"required": true
16+
},
17+
"provider" : {
18+
"type" : "string",
19+
"required": true
20+
},
21+
"starttime" : {
22+
"type" : "string",
23+
"required": true
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)