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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
package org.zwobble.couscous.tests;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.Function;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.zwobble.couscous.ast.AnnotationNode;
import org.zwobble.couscous.util.ExtraArrays;
import com.google.common.base.Joiner;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.zwobble.couscous.util.ExtraArrays.stream;
@RunWith(Parameterized.class)
public class ValueObjectTests {
private final Class<?> clazz;
public ValueObjectTests(Class<?> clazz) {
this.clazz = clazz;
}
@Parameters(name="{0} is value object")
public static Iterable<Object[]> valueObjectTypes() {
return asList(new Object[][] {
{AnnotationNode.class}
});
}
@Test
public <T> void toStringIncludesAllFields() {
GeneratedValue value = generateValue(clazz, ValueObjectTests::generateInstance);
Field[] fields = clazz.getDeclaredFields();
Object[] fieldStrings = IntStream.range(0, fields.length)
.mapToObj(index -> String.format("%s=%s", fields[index].getName(), value.arguments[index]))
.toArray();
assertEquals(
String.format("%s(%s)",
clazz.getSimpleName(),
Joiner.on(", ").join(fieldStrings)),
value.instance.toString());
}
@Test
public void equalityIncludesAllFields() {
assertEquals(generateInstance(clazz), generateInstance(clazz));
assertNotEquals(
generateFirstInstance(clazz),
generateSecondInstance(clazz));
}
@Test
public void hashCodeIncludesAllFields() {
assertEquals(generateInstance(clazz).hashCode(), generateInstance(clazz).hashCode());
assertNotEquals(
generateFirstInstance(clazz).hashCode(),
generateSecondInstance(clazz).hashCode());
}
private static Object generateFirstInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string 1]";
} else {
return generateValue(type, ValueObjectTests::generateFirstInstance).instance;
}
}
private static Object generateSecondInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string ]";
} else {
return generateValue(type, ValueObjectTests::generateSecondInstance).instance;
}
}
private static Object generateInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string]";
} else {
return generateValue(type, ValueObjectTests::generateInstance).instance;
}
}
private static GeneratedValue generateValue(Class<?> type, Function<Class<?>, Object> generate) {
try {
Field[] fields = type.getDeclaredFields();
Class<?>[] fieldTypes = ExtraArrays.map(fields, field -> field.getType())
.toArray(Class<?>[]::new);
Method constructor = findStaticConstructor(type, fieldTypes);
Object[] arguments = ExtraArrays.map(fieldTypes, fieldType -> generate.apply(fieldType))
.toArray(Object[]::new);
Object instance = constructor.invoke(null, arguments);
return new GeneratedValue(instance, arguments);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Method findStaticConstructor(Class<?> type, Class<?>[] fieldTypes) {
return stream(type.getDeclaredMethods())
.filter(method -> isStaticConstructor(type, fieldTypes, method))
.findAny()
.orElseThrow(() -> new RuntimeException("Could not find static constructor for " + type));
}
private static boolean isStaticConstructor(Class<?> type, Class<?>[] fieldTypes, Method method) {
return method.getReturnType().equals(type) &&
asList(fieldTypes).equals(asList(method.getParameterTypes()));
}
private static class GeneratedValue {
private final Object instance;
private final Object[] arguments;
public GeneratedValue(Object value, Object[] arguments) {
this.instance = value;
this.arguments = arguments;
}
}
}
|