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
|
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.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;
public class AnnotationNodeTests {
@Test
public void annotationNodeIsValueObject() throws Exception {
assertIsValueObject(AnnotationNode.class);
}
private <T> void assertIsValueObject(Class<T> clazz) throws Exception {
assertToStringIncludesAllFields(clazz);
assertEqualityIncludesAllFields(clazz);
assertHashCodeIncludesAllFields(clazz);
}
private <T> void assertToStringIncludesAllFields(Class<T> clazz) {
GeneratedValue value = generateValue(clazz, this::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());
}
private void assertEqualityIncludesAllFields(Class<?> clazz) {
assertEquals(generateInstance(clazz), generateInstance(clazz));
assertNotEquals(
generateFirstInstance(clazz),
generateSecondInstance(clazz));
}
private void assertHashCodeIncludesAllFields(Class<?> clazz) {
assertEquals(generateInstance(clazz).hashCode(), generateInstance(clazz).hashCode());
assertNotEquals(
generateFirstInstance(clazz).hashCode(),
generateSecondInstance(clazz).hashCode());
}
private Object generateFirstInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string 1]";
} else {
return generateValue(type, this::generateFirstInstance).instance;
}
}
private Object generateSecondInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string ]";
} else {
return generateValue(type, this::generateSecondInstance).instance;
}
}
private Object generateInstance(Class<?> type) {
if (type.equals(String.class)) {
return "[string]";
} else {
return generateValue(type, this::generateInstance).instance;
}
}
private 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 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 boolean isStaticConstructor(Class<?> type, Class<?>[] fieldTypes, Method method) {
return method.getReturnType().equals(type) &&
asList(fieldTypes).equals(asList(method.getParameterTypes()));
}
private class GeneratedValue {
private final Object instance;
private final Object[] arguments;
public GeneratedValue(Object value, Object[] arguments) {
this.instance = value;
this.arguments = arguments;
}
}
}
|