1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
package au.id.zancanaro.javacheck.object;
import au.id.zancanaro.javacheck.Generator;
import au.id.zancanaro.javacheck.ShrinkTree;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
public class ObjectGenerator<T> implements Generator<T> {
private final Class<T> objectClass;
private final GeneratorProvider provider;
private final List<Generator<?>> constructorGenerators;
private final SortedMap<Field, Generator<?>> fieldGenerators;
private transient Constructor<T> constructor = null;
public ObjectGenerator(Class<T> objectClass) {
this(objectClass, GeneratorProvider.DEFAULT_PROVIDER);
}
public ObjectGenerator(Class<T> objectClass, GeneratorProvider provider) {
this.objectClass = objectClass;
this.provider = provider;
this.constructorGenerators = calculateConstructorGenerators();
this.fieldGenerators = calculateFieldGenerators();
}
@SuppressWarnings("unchecked")
private Constructor<T> getConstructor() {
if (this.constructor == null) {
Constructor<?>[] constructors = objectClass.getConstructors();
if (constructors.length == 1) {
return (Constructor<T>) constructors[0];
} else {
for (Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(UseForGeneration.class)) {
if (this.constructor == null) {
this.constructor = (Constructor<T>) constructor;
} else {
throw new ObjectGenerationException("Multiple constructors are annotated with @UseForGeneration in class" + objectClass);
}
}
}
if (this.constructor == null) {
throw new ObjectGenerationException("Multiple constructors, but none are annotated with @UseForGeneration, in class" + objectClass);
}
}
}
return this.constructor;
}
private List<Generator<?>> calculateConstructorGenerators() {
List<Generator<?>> result = new ArrayList<>();
Constructor<T> constructor = getConstructor();
Type[] parameterTypes = constructor.getGenericParameterTypes();
Annotation[][] annotations = constructor.getParameterAnnotations();
for (int i = 0; i < parameterTypes.length; ++i) {
result.add(provider.getGenerator(parameterTypes[i], annotations[i], provider));
}
return result;
}
private SortedMap<Field, Generator<?>> calculateFieldGenerators() {
Comparator<Field> fieldComparator = Comparator.comparing(Field::getName);
SortedMap<Field, Generator<?>> result = new TreeMap<>(fieldComparator);
for (Field field : objectClass.getFields()) {
if (!Modifier.isFinal(field.getModifiers())
&& !Modifier.isStatic(field.getModifiers())) {
result.put(field, provider.getGenerator(
field.getGenericType(),
field.getAnnotations(),
provider));
}
}
return result;
}
@Override
public ShrinkTree<T> generate(Random random, int size) {
Generator<?>[] parameters = new Generator<?>[constructorGenerators.size()];
parameters = constructorGenerators.toArray(parameters);
Generator<?>[] fields = new Generator<?>[fieldGenerators.size()];
fields = fieldGenerators.values().toArray(fields);
return Generator.tuple(Generator.tuple(parameters), Generator.tuple(fields))
.generate(random, size)
.map(objs -> makeObject(
(List<?>) objs.get(0),
(List<?>) objs.get(1)));
}
private T makeObject(List<?> parameters, List<?> fields) {
try {
T obj = getConstructor().newInstance(parameters.toArray());
int i = 0;
for (Field key : fieldGenerators.keySet()) {
key.set(obj, fields.get(i++));
}
return obj;
} catch (IllegalAccessException | InstantiationException | InvocationTargetException ex) {
throw new ObjectGenerationException(ex);
}
}
}
|