Skip to content

Commit 9496213

Browse files
Merge pull request #47 from oubaydos/27-support-generic-objects
Support generic objects
2 parents fd9e2ec + 94b0c1e commit 9496213

File tree

9 files changed

+167
-29
lines changed

9 files changed

+167
-29
lines changed

src/main/java/io/javarig/RandomInstanceGenerator.java

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ public class RandomInstanceGenerator {
1717
private final TypeGeneratorFactory typeGeneratorFactory = new TypeGeneratorFactory();
1818

1919
@SuppressWarnings({"unchecked"})
20-
private synchronized <T> T generate(Type type, Consumer<CollectionGenerator> collectionSizeSetter) throws InstanceGenerationException {
20+
private synchronized <T> T generate(Type type, Consumer<TypeGenerator> generatorSetup) throws InstanceGenerationException {
2121
checkForRecursion(type);
2222
objectStack.push(type);
2323
TypeGenerator generator = typeGeneratorFactory.getGenerator(type, this);
24-
generator = setCollectionSize(generator, collectionSizeSetter);
24+
generatorSetup.accept(generator);
2525
T generated = (T) generator.generate();
2626
objectStack.pop();
2727
return generated;
@@ -32,7 +32,7 @@ private synchronized <T> T generate(Type type, Consumer<CollectionGenerator> col
3232
*
3333
* @return the generated object
3434
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
35-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
35+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
3636
*/
3737
public <T> T generate(@NonNull Type objectType) throws InstanceGenerationException {
3838
return generate(objectType, ignore -> {
@@ -45,14 +45,18 @@ public <T> T generate(@NonNull Type objectType) throws InstanceGenerationExcepti
4545
* @param collectionSize the size of the collection to generate
4646
* @return the generated object
4747
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
48-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
48+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
4949
*/
5050
public <T> T generate(
5151
@NonNull Type type,
5252
int collectionSize
5353
) throws InstanceGenerationException {
5454
validateSize(collectionSize);
55-
return generate(type, collectionGenerator -> collectionGenerator.setSize(collectionSize));
55+
return generate(type, generator -> {
56+
if(generator instanceof CollectionGenerator collectionGenerator){
57+
collectionGenerator.setSize(collectionSize);
58+
}
59+
});
5660
}
5761

5862
/**
@@ -61,16 +65,18 @@ public <T> T generate(
6165
* @param <T> the generic type of the object to generate
6266
* @return the generated object
6367
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
64-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
68+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
6569
*/
6670
public <T> T generate(@NonNull Type objectType,
6771
int minSizeInclusive,
6872
int maxSizeExclusive
6973
) throws InstanceGenerationException {
7074
validateSize(minSizeInclusive, maxSizeExclusive);
71-
return generate(objectType, collectionGenerator -> {
72-
collectionGenerator.setMinSizeInclusive(minSizeInclusive);
73-
collectionGenerator.setMaxSizeExclusive(maxSizeExclusive);
75+
return generate(objectType, generator -> {
76+
if(generator instanceof CollectionGenerator collectionGenerator){
77+
collectionGenerator.setMinSizeInclusive(minSizeInclusive);
78+
collectionGenerator.setMaxSizeExclusive(maxSizeExclusive);
79+
}
7480
});
7581
}
7682

@@ -80,7 +86,7 @@ public <T> T generate(@NonNull Type objectType,
8086
* @param genericTypes types of generic parameters
8187
* @return the generated object
8288
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
83-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
89+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
8490
*/
8591
public <T> T generate(
8692
@NonNull Type objectType,
@@ -90,13 +96,14 @@ public <T> T generate(
9096
return generate(parameterizedType);
9197
}
9298

99+
93100
/**
94101
* generate a random instance of a generic collection with a fixed size
95102
*
96103
* @param genericTypes types of generic parameters
97104
* @return the generated object
98105
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
99-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
106+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
100107
*/
101108
public <T> T generate(
102109
@NonNull Type type,
@@ -113,7 +120,7 @@ public <T> T generate(
113120
* @param genericTypes types of generic parameters
114121
* @return the generated object
115122
* @throws InstanceGenerationException if the instance cannot be generated for some reason (class doesn't have
116-
* default constructor , class have a non-public default constructor , setter cannot be invoked ... )
123+
* a default constructor , class have a non-public default constructor , setter cannot be invoked ... )
117124
*/
118125
public <T> T generate(
119126
@NonNull Type type,
@@ -138,14 +145,6 @@ private void checkForRecursion(Type type) {
138145
}
139146
}
140147

141-
private TypeGenerator setCollectionSize(TypeGenerator generator, Consumer<CollectionGenerator> collectionSizeSetter) {
142-
if (generator instanceof CollectionGenerator collectionGenerator) {
143-
collectionSizeSetter.accept(collectionGenerator);
144-
return (TypeGenerator) collectionGenerator;
145-
}
146-
return generator;
147-
}
148-
149148
private void validateSize(int minSizeInclusive, int maxSizeExclusive) {
150149
Validate.isTrue(maxSizeExclusive > minSizeInclusive, "Start value must be smaller than end value.");
151150
Validate.isTrue(minSizeInclusive >= 0, "Both range values must be non-negative.");

src/main/java/io/javarig/generator/ObjectGenerator.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.javarig.generator;
22

3+
import io.javarig.util.GenericTypes;
4+
import io.javarig.ParameterizedTypeImpl;
35
import io.javarig.RandomInstanceGenerator;
46
import io.javarig.exception.AbstractClassInstantiationException;
57
import io.javarig.exception.InstanceGenerationException;
@@ -10,12 +12,10 @@
1012
import lombok.Setter;
1113
import lombok.extern.slf4j.Slf4j;
1214

13-
import java.lang.reflect.Field;
14-
import java.lang.reflect.InvocationTargetException;
15-
import java.lang.reflect.Method;
16-
import java.lang.reflect.Type;
17-
import java.util.Arrays;
18-
import java.util.List;
15+
import java.lang.reflect.*;
16+
import java.util.*;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.IntStream;
1919

2020
import static io.javarig.util.Utils.getOwnOrInheritedFieldByName;
2121

@@ -24,21 +24,39 @@
2424
@Slf4j
2525
public class ObjectGenerator extends TypeGenerator {
2626
private static final String SETTER_PREFIX = "set";
27+
private Map<String,Type> genericTypesMap = new HashMap<>();
2728

2829
public ObjectGenerator(Type type, RandomInstanceGenerator randomInstanceGenerator) {
2930
super(type, randomInstanceGenerator);
3031
}
3132

3233
@Override
3334
public Object generate() throws InstanceGenerationException {
34-
Class<?> objectClass = (Class<?>) getType();
35+
Type objectType = getType();
36+
Class<?> objectClass;
37+
if(objectType instanceof ParameterizedType parameterizedType){
38+
objectClass = (Class<?>) parameterizedType.getRawType();
39+
constructGenericTypesMap(objectClass, parameterizedType);
40+
} else {
41+
objectClass = (Class<?>) objectType;
42+
}
3543
Object generatedObject = getNewObjectInstance(objectClass);
3644
log.info("generating object of type {} ...", objectClass.getName());
3745
generateFields(generatedObject, objectClass);
3846
log.info("created object {}", generatedObject);
3947
return generatedObject;
4048
}
4149

50+
private void constructGenericTypesMap(Class<?> objectClass, ParameterizedType parameterizedType) {
51+
List<Type> typeParametersValues = Arrays.asList(parameterizedType.getActualTypeArguments());
52+
List<String> typeParametersKeys = Arrays.stream(objectClass.getTypeParameters())
53+
.map(TypeVariable::getTypeName)
54+
.toList();
55+
genericTypesMap = IntStream.range(0, typeParametersKeys.size())
56+
.boxed()
57+
.collect(Collectors.toMap(typeParametersKeys::get, typeParametersValues::get));
58+
}
59+
4260
private void generateFields(Object generatedObject, Class<?> objectClass) throws InstanceGenerationException {
4361
List<Method> setters = getSetters(objectClass);
4462
setters.forEach((setter) -> generateFieldWithSetter(generatedObject, objectClass, setter));
@@ -62,7 +80,10 @@ private void generateFieldWithSetter(Object generatedObject, Class<?> objectClas
6280

6381
private void generateField(Object generatedObject, Method setter, Field field) throws InstanceGenerationException {
6482
Type type = field.getGenericType();
65-
Object generatedField = getRandomInstanceGenerator().generate(type);
83+
if(type instanceof ParameterizedType parameterizedType){
84+
type = resolveTypeArguments(parameterizedType);
85+
}
86+
Object generatedField = getRandomInstanceGenerator().generate(GenericTypes.resolve(type, genericTypesMap));
6687
try {
6788
setter.invoke(generatedObject, generatedField);
6889
} catch (IllegalAccessException ignore) {
@@ -74,6 +95,16 @@ private void generateField(Object generatedObject, Method setter, Field field) t
7495
}
7596
}
7697

98+
private Type resolveTypeArguments(Type type) {
99+
ParameterizedType parameterizedType = (ParameterizedType) type;
100+
List<Type> typeArguments = Arrays.stream(parameterizedType.getActualTypeArguments())
101+
.map((typeArgument -> GenericTypes.resolve(typeArgument, genericTypesMap)))
102+
.toList();
103+
Type[] typeArgumentsArray = new Type[typeArguments.size()];
104+
return new ParameterizedTypeImpl(typeArguments.toArray(typeArgumentsArray),
105+
(Class<?>) ((ParameterizedType) type).getRawType());
106+
}
107+
77108
private Object getNewObjectInstance(Class<?> objectClass) throws InstanceGenerationException {
78109
try {
79110
return objectClass.getConstructor().newInstance();

src/main/java/io/javarig/generator/TypeGenerator.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public abstract class TypeGenerator {
1919
private final Random random = new Random();
2020
private final Type type;
2121
private final RandomInstanceGenerator randomInstanceGenerator;
22-
2322
/**
2423
* generates a random object, its type is known from the extending class
2524
*/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.javarig.util;
2+
3+
import java.lang.reflect.Type;
4+
import java.util.Map;
5+
6+
public class GenericTypes {
7+
public static Type resolve(Type type, Map<String, Type> genericTypesMap){
8+
if(genericTypesMap.containsKey(type.getTypeName())){
9+
return genericTypesMap.get(type.getTypeName());
10+
}
11+
return type;
12+
}
13+
}

src/test/java/io/javarig/generation/ObjectGenerationTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import lombok.extern.slf4j.Slf4j;
88
import org.junit.jupiter.api.BeforeEach;
99
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.ValueSource;
1012

1113
import java.lang.reflect.InvocationTargetException;
1214
import java.lang.reflect.Type;
@@ -189,4 +191,51 @@ public void shouldGenerateBaseFieldsAndInheritedFieldsWhenGivenClassExtendingAno
189191
.isInstanceOf(Float.class);
190192
}
191193

194+
@ParameterizedTest
195+
@ValueSource(classes = {String.class, Integer.class, Double.class, BaseClass.class})
196+
public void shouldGenerateAGenericClass(Type genericType) {
197+
//given
198+
Object generatedObject = randomInstanceGenerator.generate(GenericTestClass.class, genericType);
199+
// then
200+
assertThat(generatedObject)
201+
.isNotNull()
202+
.isInstanceOf(GenericTestClass.class);
203+
GenericTestClass<String> genericTestClass = (GenericTestClass) generatedObject;
204+
assertThat(genericTestClass)
205+
.extracting(GenericTestClass::getList)
206+
.asList()
207+
.isNotNull()
208+
.hasSizeBetween(CollectionGenerator.DEFAULT_MIN_SIZE_INCLUSIVE, CollectionGenerator.DEFAULT_MAX_SIZE_EXCLUSIVE)// could be better if we had access to collectionGenerator defaultMin defaultMax size as public static fields
209+
.element(0)
210+
.isInstanceOf((Class<?>) genericType);
211+
}
212+
213+
@Test
214+
public void shouldGenerateAGenericClassWithRightOrderTypes() {
215+
//given
216+
Class<String> classParam1 = String.class;
217+
Class<Integer> classParam2 = Integer.class;
218+
Object generatedObject = randomInstanceGenerator.generate(GenericTestClass2.class, classParam1, classParam2);
219+
// then
220+
assertThat(generatedObject)
221+
.isNotNull()
222+
.isInstanceOf(GenericTestClass2.class);
223+
224+
GenericTestClass2<String, Integer> genericTestClass2 = (GenericTestClass2) generatedObject;
225+
assertThat(genericTestClass2)
226+
.extracting(GenericTestClass2::getGenericClass3)
227+
.isNotNull()
228+
.isInstanceOf(GenericTestClass3.class)
229+
.extracting(GenericTestClass3::getT)
230+
.isNotNull()
231+
.isInstanceOf(classParam2);
232+
233+
assertThat(genericTestClass2)
234+
.extracting(GenericTestClass2::getGenericClass3)
235+
.isNotNull()
236+
.isInstanceOf(GenericTestClass3.class)
237+
.extracting(GenericTestClass3::getK)
238+
.isNotNull()
239+
.isInstanceOf(classParam1);
240+
}
192241
}

src/test/java/io/javarig/testclasses/BaseClass.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import lombok.Getter;
44
import lombok.NoArgsConstructor;
55
import lombok.Setter;
6+
import lombok.ToString;
67

78
@NoArgsConstructor
89
@Getter
910
@Setter
11+
@ToString
1012
public class BaseClass extends SuperClass{
1113
int baseField;
1214
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javarig.testclasses;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
import lombok.Setter;
6+
import lombok.ToString;
7+
8+
import java.util.List;
9+
10+
@NoArgsConstructor
11+
@Getter
12+
@Setter
13+
@ToString
14+
public class GenericTestClass<T> {
15+
private List<T> list;
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.javarig.testclasses;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
import lombok.Setter;
6+
import lombok.ToString;
7+
8+
@NoArgsConstructor
9+
@Getter
10+
@Setter
11+
@ToString
12+
public class GenericTestClass2<T,K> {
13+
private GenericTestClass3<K,T> genericClass3;
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javarig.testclasses;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
import lombok.Setter;
6+
import lombok.ToString;
7+
8+
@NoArgsConstructor
9+
@Getter
10+
@Setter
11+
@ToString
12+
public class GenericTestClass3<T,K> {
13+
private T t;
14+
private K k;
15+
}

0 commit comments

Comments
 (0)