aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/org/zwobble/couscous/tests/AnnotationNodeTests.java
blob: 2c17ccb6234b21fb8fe4033005455d7582a4c2cb (plain)
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;
        }
    }
}