aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/org/zwobble/couscous/tests/ValueObjectTests.java
blob: f31ec65d62160d543804004dba95ee5140382e29 (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
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;
        }
    }
}