Skip to content

Commit f7f7651

Browse files
mt657-jpgmatthewtatum1kingthorin
authored
Retrieve the no-argument constructor (#1690)
* Retrieve the no-argument constructor using a method called getParameterlessPublicConstructor This fixes issue #1689 where the no-argument constructor is not always the one you get * Update JavaObjectTransformer.java * Filter for public only constructors Add a test to cover the scenarios where the no-arg constructor is public, protected and private Improve the code slightly that finds tries to find an alternative constructor. This could be improved further to find one that matches the schema. Currently it just finds the first public constructor in the array returned by clazz.getConstructors() --------- Co-authored-by: Matthew Tatum <[email protected]> Co-authored-by: Rick M <[email protected]>
1 parent 9f74fbc commit f7f7651

File tree

2 files changed

+126
-9
lines changed

2 files changed

+126
-9
lines changed

src/main/java/net/datafaker/transformations/JavaObjectTransformer.java

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.OutputStream;
66
import java.lang.reflect.Constructor;
77
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Modifier;
89
import java.lang.reflect.RecordComponent;
910
import java.util.ArrayList;
1011
import java.util.Arrays;
@@ -51,14 +52,22 @@ public Object apply(Object input, Schema<Object, ?> schema) {
5152

5253
result = getObject(schema, result, recordConstructor);
5354
} else if (!hasParameterlessPublicConstructor(clazz)) {
54-
Constructor<?> primaryConstructor = CLASS2CONSTRUCTOR.computeIfAbsent(clazz, c -> c.getDeclaredConstructors()[0]);
55+
Constructor<?> primaryConstructor = CLASS2CONSTRUCTOR.computeIfAbsent(clazz, c -> getAnyPublicConstructor(c).orElse(null));
56+
57+
if (primaryConstructor == null) {
58+
throw new RuntimeException("Failed to initialize class " + clazz.getName() + ", no appropriate public constructor found");
59+
}
5560

5661
result = getObject(schema, result, primaryConstructor);
5762
} else {
5863
if (result == null) {
5964
try {
60-
Constructor<?> primaryConstructor = CLASS2CONSTRUCTOR.computeIfAbsent(clazz, c -> c.getDeclaredConstructors()[0]);
61-
result = primaryConstructor.newInstance();
65+
Constructor<?> primaryConstructor = CLASS2CONSTRUCTOR.computeIfAbsent(clazz, c -> getParameterlessPublicConstructor(c).orElse(null));
66+
if (primaryConstructor != null) {
67+
result = primaryConstructor.newInstance();
68+
} else {
69+
throw new RuntimeException("Failed to locate a parameterless public constructor for class " + clazz.getName());
70+
}
6271
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
6372
throw new RuntimeException(e);
6473
}
@@ -167,11 +176,19 @@ private Object getObject(Schema<Object, ?> schema, Object result, Constructor<?>
167176
}
168177

169178
private boolean hasParameterlessPublicConstructor(Class<?> clazz) {
170-
for (Constructor<?> constructor : clazz.getConstructors()) {
171-
if (constructor.getParameterCount() == 0) {
172-
return true;
173-
}
174-
}
175-
return false;
179+
return getParameterlessPublicConstructor(clazz).isPresent();
180+
}
181+
182+
private Optional<Constructor<?>> getParameterlessPublicConstructor(Class<?> clazz) {
183+
return Arrays.stream(clazz.getConstructors())
184+
.filter(constructor -> constructor.getParameterCount() == 0)
185+
.filter(constructor -> Modifier.isPublic(constructor.getModifiers()))
186+
.findFirst();
187+
}
188+
189+
private Optional<Constructor<?>> getAnyPublicConstructor(Class<?> clazz) {
190+
return Arrays.stream(clazz.getConstructors())
191+
.filter(constructor -> Modifier.isPublic(constructor.getModifiers()))
192+
.findFirst();
176193
}
177194
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package net.datafaker.transformations;
2+
3+
import net.datafaker.Faker;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static net.datafaker.transformations.Field.field;
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
public class JavaObjectTransformerConstructorTest {
10+
11+
public static class Person {
12+
13+
private int id;
14+
15+
public Person() {
16+
}
17+
18+
public Person(int id) {
19+
throw new RuntimeException("This should not be called");
20+
}
21+
}
22+
23+
public static class ProtectedPerson {
24+
25+
private int id;
26+
27+
protected ProtectedPerson() {
28+
throw new RuntimeException("This should not be called");
29+
}
30+
31+
public ProtectedPerson(int id) {
32+
this.id = id;
33+
}
34+
}
35+
36+
public static class PrivatePerson {
37+
38+
private int id;
39+
40+
private PrivatePerson() {
41+
throw new RuntimeException("This should not be called");
42+
}
43+
44+
public PrivatePerson(int id) {
45+
this.id = id;
46+
}
47+
}
48+
49+
private final Faker faker = new Faker();
50+
51+
@Test
52+
void javaNoArgConstructorTest() {
53+
JavaObjectTransformer jTransformer = (new JavaObjectTransformer()).from(Person.class);
54+
Schema<Object, ?> schema = Schema.of(
55+
field("id", () -> faker.number().positive())
56+
);
57+
58+
jTransformer
59+
.generate(schema, 10)
60+
.stream()
61+
.map(object -> (Person) object)
62+
.forEach(person -> {
63+
assertThat(person.id).isNotNull();
64+
});
65+
66+
}
67+
68+
@Test
69+
void javaNoArgConstructorProtectedTest() {
70+
JavaObjectTransformer jTransformer = (new JavaObjectTransformer()).from(ProtectedPerson.class);
71+
Schema<Object, ?> schema = Schema.of(
72+
field("id", () -> faker.number().positive())
73+
);
74+
75+
jTransformer
76+
.generate(schema, 10)
77+
.stream()
78+
.map(object -> (ProtectedPerson) object)
79+
.forEach(person -> {
80+
assertThat(person.id).isNotNull();
81+
});
82+
}
83+
84+
@Test
85+
void javaNoArgConstructorPrivateTest() {
86+
JavaObjectTransformer jTransformer = (new JavaObjectTransformer()).from(PrivatePerson.class);
87+
Schema<Object, ?> schema = Schema.of(
88+
field("id", () -> faker.number().positive())
89+
);
90+
91+
jTransformer
92+
.generate(schema, 10)
93+
.stream()
94+
.map(object -> (PrivatePerson) object)
95+
.forEach(person -> {
96+
assertThat(person.id).isNotNull();
97+
});
98+
99+
}
100+
}

0 commit comments

Comments
 (0)