View Javadoc
1   package org.pojomatic.internal;
2   
3   import static org.testng.Assert.*;
4   
5   import java.lang.annotation.Annotation;
6   import java.lang.reflect.Array;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.List;
10  import java.util.Objects;
11  
12  import org.pojomatic.annotations.SkipArrayCheck;
13  import org.pojomatic.diff.Difference;
14  import org.pojomatic.diff.Differences;
15  import org.pojomatic.diff.NoDifferences;
16  import org.pojomatic.diff.PropertyDifferences;
17  import org.pojomatic.diff.ValueDifference;
18  import org.pojomatic.internal.factory.PojoDescriptor;
19  import org.pojomatic.internal.factory.PojoFactory;
20  import org.pojomatic.internal.factory.PropertyDescriptor;
21  import org.testng.annotations.Test;
22  
23  import com.google.common.collect.Iterables;
24  
25  public class PropertyTypeTest {
26  
27    @Test(dataProvider = "types", dataProviderClass = TypeProviders.class)
28    public void testHashCode(Type type) {
29      PojoFactory pojoFactory = new PojoFactory(new PojoDescriptor(new PropertyDescriptor(type.getClazz())));
30      for (Object value: type.getSampleValues()) {
31        checkHashCode(pojoFactory, value, type.hashCode(value));
32      }
33    }
34  
35    @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
36    public void testArrayAsObjectHashCode(Type type, boolean skipArrayCheck) {
37      PojoFactory pojoFactory = new PojoFactory(
38        new PojoDescriptor(new PropertyDescriptor(Object.class, extraAnnotations(skipArrayCheck))));
39      for (Object value: type.getSampleValues()) {
40        int propertyHashCode = skipArrayCheck
41          ? Objects.hashCode(value)
42          : type.deepHashCode(value);
43  
44        checkHashCode(pojoFactory, value, propertyHashCode);
45      }
46    }
47  
48    @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
49    public void testArrayAsArrayHashCode(Type type, boolean skipArrayCheck) {
50      PojoFactory pojoFactory = new PojoFactory(
51        new PojoDescriptor(new PropertyDescriptor(type.getClazz(), extraAnnotations(skipArrayCheck))));
52      for (Object value: type.getSampleValues()) {
53        checkHashCode(pojoFactory, value, type.deepHashCode(value));
54      }
55    }
56  
57    @Test(dataProvider = "types", dataProviderClass = TypeProviders.class)
58    public void testToString(Type type) {
59      PojoFactory pojoFactory = new PojoFactory(new PojoDescriptor(new PropertyDescriptor(type.getClazz())));
60      for (Object value: type.getSampleValues()) {
61        checkToString(pojoFactory, value, type.toString(value));
62      }
63    }
64  
65    @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
66    public void testArrayAsObjectToString(Type type, boolean skipArrayCheck) {
67      PojoFactory pojoFactory = new PojoFactory(
68        new PojoDescriptor(new PropertyDescriptor(Object.class, extraAnnotations(skipArrayCheck))));
69      for (Object value: type.getSampleValues()) {
70        String expectedPropertyValue = skipArrayCheck ? Objects.toString(value) : type.deepToString(value);
71        checkToString(pojoFactory, value, expectedPropertyValue);
72      }
73    }
74  
75    @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
76    public void testArrayAsArrayToString(Type type, boolean skipArrayCheck) {
77      PojoFactory pojoFactory = new PojoFactory(
78        new PojoDescriptor(new PropertyDescriptor(type.getClazz(), extraAnnotations(skipArrayCheck))));
79      for (Object value: type.getSampleValues()) {
80        checkToString(pojoFactory, value, type.deepToString(value));
81      }
82    }
83  
84    @Test(dataProvider = "types", dataProviderClass = TypeProviders.class)
85    public void testEqualsAndDiff(Type type) {
86      PojoFactory pojoFactory = new PojoFactory(new PojoDescriptor(new PropertyDescriptor(type.getClazz())));
87      for (Object value1: type.getSampleValues()) {
88        Object pojo1 = pojoFactory.create(value1);
89        for (Object value2: type.getSampleValues()) {
90          boolean expectedToBeEqual = value1 == null ? value2 == null : value1.equals(value2);
91          Object pojo2 = pojoFactory.create(value2);
92          checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
93  
94        }
95        assertFalse(
96          pojoFactory.pojomator().doEquals(pojo1, null),
97          "type: " + type.getClazz() + ", value1: " + value1);
98      }
99    }
100 
101   @Test(dataProvider = "annotations", dataProviderClass = TypeProviders.class)
102   public void testMixedTypesAsObjectEqualsAndDiff(boolean skipArrayCheck) {
103     PojoFactory pojoFactory = new PojoFactory(
104       new PojoDescriptor(new PropertyDescriptor(Object.class, extraAnnotations(skipArrayCheck))));
105     Iterable<Type> allTypes =
106       Iterables.concat(Arrays.asList(BaseType.OBJECT), TypeProviders.simpleArrays(), TypeProviders.doubleArrays());
107     List<Object> allValues = new ArrayList<>();
108     for (Type type: allTypes) {
109       allValues.addAll(type.getSampleValues());
110     }
111     // Ideally, this would be a data provider. However, as there are 90 different possible values, and we're doing a
112     // self-cartesian product, it would add 8100 test cases, and just be a pain. Instead, delegate the real work
113     // to a sub method, so that if we have problems, we can do a drop-to-frame in that method to diagnose.
114     for (Object value1: allValues) {
115       Object pojo1 = pojoFactory.create(value1);
116       for (Object value2: allValues) {
117         testMixedTypesAsObjectEqualsAndDiffWorker(skipArrayCheck, pojoFactory, value1, pojo1, value2);
118       }
119     }
120   }
121 
122   private void testMixedTypesAsObjectEqualsAndDiffWorker(boolean skipArrayCheck,
123     PojoFactory pojoFactory, Object value1, Object pojo1, Object value2) {
124     Object value2PossibleClone = maybeCloneObject(value2);
125     Object pojo2 = pojoFactory.create(value2PossibleClone);;
126     boolean expectedToBeEqual = Objects.equals(value1, value2PossibleClone)
127       || !skipArrayCheck && value1 == value2;
128 
129     checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2PossibleClone, pojo1, pojo2);
130   }
131 
132   @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
133   public void testArrayAsObjectEqualsAndDiff(Type type, boolean skipArrayCheck) {
134     PojoFactory pojoFactory = new PojoFactory(
135       new PojoDescriptor(new PropertyDescriptor(Object.class, extraAnnotations(skipArrayCheck))));
136     for (Object value1: type.getSampleValues()) {
137       Object pojo1 = pojoFactory.create(value1);
138       for (Object value2: type.getSampleValues()) {
139         // Equality of different arrays should only be detected if SkipArrayCheck is not present
140         // Note that in this test, we only clone the inner array if skipArrayCheck is false.
141         boolean expectedToBeEqual = (value1 == value2) && (value1 == null || (!skipArrayCheck));
142         Object pojo2 = pojoFactory.create(cloneArray(value2, !skipArrayCheck));
143         checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
144         if (skipArrayCheck) {
145           // however, even if SkipArrayCheck is mpresent, identical arrays should still match
146           checkEqualsAndDiff(value1 == value2, pojoFactory, value1, value2, pojo1, pojoFactory.create(value2));
147         }
148       }
149       assertFalse(
150         pojoFactory.pojomator().doEquals(pojo1, null),
151         "type: " + type.getClazz() + ", value1: " + value1);
152     }
153   }
154 
155   @Test(dataProvider = "arrayTypes", dataProviderClass = TypeProviders.class)
156   public void testArrayAsArrayEqualsAndDiff(Type type, boolean skipArrayCheck) { // skipArrayCheck shouldn't matter here
157     PojoFactory pojoFactory = new PojoFactory(
158       new PojoDescriptor(new PropertyDescriptor(type.getClazz(), extraAnnotations(skipArrayCheck))));
159     for (Object value1: type.getSampleValues()) {
160       for (Object value2: type.getSampleValues()) {
161         Object pojo1 = pojoFactory.create(value1);
162         Object pojo2 = pojoFactory.create(cloneArray(value2, true));
163         boolean expectedToBeEqual = (value1 == value2);
164         checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
165       }
166     }
167   }
168 
169   /**
170    * Verify that doEquals honors the {@link SkipArrayCheck} annotation on properties of type {@link Object}.
171    * @param type
172    * @param skipArrayCheck
173    */
174   @Test(dataProvider = "deepArrayTypes", dataProviderClass = TypeProviders.class)
175   public void testDeepArrayAsObjectEqualsAndDiff(Type type, boolean skipArrayCheck) {
176     PojoFactory pojoFactory = new PojoFactory(
177       new PojoDescriptor(new PropertyDescriptor(Object.class, extraAnnotations(skipArrayCheck))));
178     for (Object value1: type.getSampleValues()) {
179       for (Object value2: type.getSampleValues()) {
180         // equality of different arrays should only be detected if SkipArrayCheck is not present
181         // If value1 != value 2, then doEquals should always return false.
182         // If value1 == value2, but skipArrayCheck is false, then the only way doEquals can return true is if value1 == null
183         boolean expectedToBeEqual = (value1 == value2) && (value1 == null || ! skipArrayCheck);
184         Object pojo1 = pojoFactory.create(value1);
185         Object pojo2 = pojoFactory.create(cloneArray(value2, true));
186         checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
187       }
188     }
189   }
190 
191   /**
192    * Verify that doEquals honors the {@link SkipArrayCheck} annotation on properties of array type
193    * @param type
194    * @param skipArrayCheck
195    */
196   @Test(dataProvider = "deepArrayTypes", dataProviderClass = TypeProviders.class)
197   public void testDeepArrayAsArrayEquals(Type type, boolean skipArrayCheck) {
198     PojoFactory pojoFactory = new PojoFactory(
199       new PojoDescriptor(new PropertyDescriptor(type.getClazz(), extraAnnotations(skipArrayCheck))));
200     for (Object value1: type.getSampleValues()) {
201       for (Object value2: type.getSampleValues()) {
202         // If value1 != value 2, then doEquals should always return false.
203         // If value1 == value2, but canBeArray is false, then the only way doEquals can return true is if value1 == null
204         // If value1 == value2 != null and canBeArray is true, then without deepArray, they can only be equal if
205         //  there are no second level arrays that can be cloned.
206         Object pojo1 = pojoFactory.create(value1);
207         Object pojo2 = pojoFactory.create(cloneArray(value2, true));
208         boolean expectedToBeEqual = value1 == value2;
209         checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
210       }
211     }
212   }
213 
214   @Test(dataProvider = "deepArrayTypes", dataProviderClass = TypeProviders.class)
215   public void testDeepArraysAsShallowArraysEqualsAndDiff(Type type, boolean skipArrayCheck) {
216     PojoFactory pojoFactory = new PojoFactory(
217       new PojoDescriptor(new PropertyDescriptor(Object[].class, extraAnnotations(skipArrayCheck))));
218     for (Object value1: type.getSampleValues()) {
219       for (Object value2: type.getSampleValues()) {
220         // If value1 != value 2, then doEquals should always return false.
221         // If value1 == value2, but canBeArray is false, then the only way doEquals can return true is if value1 == null
222         // If value1 == value2 != null and canBeArray is true, then without deepArray, they can only be equal if
223         //  there are no second level arrays that can be cloned.
224         Object pojo1 = pojoFactory.create(value1);
225         Object pojo2 = pojoFactory.create(cloneArray(value2, true));
226         boolean expectedToBeEqual = value1 == value2;
227         checkEqualsAndDiff(expectedToBeEqual, pojoFactory, value1, value2, pojo1, pojo2);
228       }
229     }
230   }
231 
232   @Test(dataProvider = "deepArrayTypes", dataProviderClass = TypeProviders.class)
233   public void testDeepArraysAsShallowArraysToString(Type type, boolean skipArrayCheck) {
234     PojoFactory pojoFactory = new PojoFactory(
235       new PojoDescriptor(new PropertyDescriptor(Object[].class, extraAnnotations(skipArrayCheck))));
236     for (Object value: type.getSampleValues()) {
237       checkToString(pojoFactory, value, type.deepToString(value));
238     }
239   }
240 
241   @Test(dataProvider = "annotations", dataProviderClass = TypeProviders.class)
242   public void testMixedTypesAsObjectArrayEqualsAndDiff(boolean skipArrayCheck) {
243     PojoFactory pojoFactory = new PojoFactory(
244       new PojoDescriptor(new PropertyDescriptor(Object[].class, extraAnnotations(skipArrayCheck))));
245     Iterable<Type> allTypes =
246       Iterables.concat(Arrays.asList(new ArrayType(BaseType.OBJECT)), TypeProviders.doubleArrays());
247     List<Object> allValues = new ArrayList<>();
248     for (Type type: allTypes) {
249       allValues.addAll(type.getSampleValues());
250     }
251     // Ideally, this would be a data provider. However, as there are 90 different possible values, and we're doing a
252     // self-cartesian product, it would add 8100 test cases, and just be a pain. Instead, delegate the real work
253     // to a sub method, so that if we have problems, we can do a drop-to-frame in that method to diagnose.
254     for (Object value1: allValues) {
255       Object pojo1 = pojoFactory.create(value1);
256       for (Object value2: allValues) {
257         testMixedTypesAsObjectEqualsAndDiffWorker(false, pojoFactory, value1, pojo1, value2);
258       }
259     }
260   }
261 
262 
263   @SuppressWarnings("unchecked")
264   private Class<? extends Annotation>[] extraAnnotations(boolean skipArrayCheck) {
265     List<Class<? extends Annotation>> classes = new ArrayList<>();
266     if (skipArrayCheck) {
267       classes.add(SkipArrayCheck.class);
268     }
269     return classes.toArray(new Class[0]);
270   }
271 
272   private void checkHashCode(PojoFactory pojoFactory, Object value,
273     int propertyHashCode) {
274     assertEquals(
275       pojoFactory.pojomator().doHashCode(pojoFactory.create(value)),
276       31 + propertyHashCode,
277       label(value));
278   }
279 
280   private void checkToString(PojoFactory pojoFactory, Object value,
281     String expectedPropertyValue) {
282     assertEquals(pojoFactory.pojomator().doToString(pojoFactory.create(value)), "Pojo{x: {" + expectedPropertyValue + "}}", label(value));
283   }
284 
285   private void checkEqualsAndDiff(boolean expectedToBeEqual, PojoFactory pojoFactory,
286     Object value1, Object value2, Object pojo1, Object pojo2) {
287     if (pojoFactory.pojomator().doEquals(pojo1, pojo2) != expectedToBeEqual)
288       assertEquals(pojoFactory.pojomator().doEquals(pojo1, pojo2), expectedToBeEqual, label(value1, value2));
289     assertEquals(pojoFactory.pojomator().doDiff(pojo1, pojo2), expectedDifferences(expectedToBeEqual, value1, value2), label(value1, value2));
290   }
291 
292   /**
293    * Return the expected Differences object for a pair of object
294    * @param expectedToBeEqual whether we expect these two objects to be considered equal
295    * @param value1 the first object
296    * @param value2 the second object
297    * @return the Differences we expect between the two
298    */
299   private Differences expectedDifferences(boolean expectedToBeEqual,
300     Object value1, Object value2) {
301     return expectedToBeEqual
302       ? NoDifferences.getInstance()
303       : new PropertyDifferences(Arrays.<Difference>asList(new ValueDifference("x", value1, value2)));
304   }
305 
306   private String label(Object value1, Object value2) {
307     return "value1: " + labelString(value1) + ", value2: " + labelString(value2);
308   }
309 
310   private String label(Object value) {
311     return "value: " + labelString(value);
312   }
313 
314   private String labelString(Object value) {
315     if (value == null) {
316       return "null";
317     }
318     else {
319       return possibleArrayToList(value) + "(" + value.getClass().getName() + ")";
320     }
321   }
322   /**
323    * Convert arrays to lists, leaving other types alone.
324    * @param value
325    * @return {@code value} if value is not an array, or the List equivalent of {@code value} if value is an array
326    */
327   private Object possibleArrayToList(Object value) {
328     if (value == null || ! value.getClass().isArray()) {
329       return value;
330     }
331     List<Object> result = new ArrayList<>();
332     for (int i = 0; i < Array.getLength(value); i++) {
333       result.add(possibleArrayToList(Array.get(value, i)));
334     }
335     return result;
336   }
337 
338   private Object maybeCloneObject(Object object) {
339     if (object == null) {
340       return null;
341     }
342     if (object.getClass().isArray()) {
343       return cloneArray(object, true);
344     }
345     else if (object instanceof String) {
346       return new String((String) object);
347     }
348     else {
349       return object;
350     }
351   }
352 
353   private Object cloneArray(Object array, boolean deep) {
354     if (array == null) {
355       return null;
356     }
357     Object clone = Array.newInstance(array.getClass().getComponentType(), Array.getLength(array));
358     for (int i = 0; i < Array.getLength(array); i++) {
359       Object element = Array.get(array, i);
360       if (deep && element != null && element.getClass().isArray()) {
361         element = cloneArray(element, deep);
362       }
363       Array.set(clone, i, element);
364     }
365     return clone;
366   }
367 }