View Javadoc
1   package org.pojomatic.internal;
2   
3   import static org.testng.Assert.*;
4   
5   import org.testng.annotations.Test;
6   import java.util.*;
7   
8   import org.pojomatic.PropertyElement;
9   import org.pojomatic.TestUtils;
10  import org.pojomatic.annotations.*;
11  import org.pojomatic.internal.a.C1;
12  import org.pojomatic.internal.b.C2;
13  import org.pojomatic.internal.b.C4;
14  import org.pojomatic.internal.factory.PojoClassFactory;
15  import org.pojomatic.internal.factory.PojoDescriptor;
16  import org.pojomatic.internal.factory.PropertyDescriptor;
17  
18  public class ClassPropertiesTest {
19    @Test public void testForClass() {
20      ClassProperties interfaceProperties = ClassProperties.forClass(Interface.class);
21      assertSame(ClassProperties.forClass(Interface.class), interfaceProperties);
22    }
23  
24    @Test
25    public void testAnnotatedFields() throws Exception {
26      final PropertyElement privateStringField = TestUtils.field(FieldPojo.class, "privateString");
27      final PropertyElement publicIntField = TestUtils.field(FieldPojo.class, "publicInt");
28      final PropertyElement onlyForStringField = TestUtils.field(FieldPojo.class, "onlyForToString");
29      final PropertyElement forEqualsAndToString =
30        TestUtils.field(FieldPojo.class, "forEqualsAndToString");
31  
32      ClassProperties classProperties = ClassProperties.forClass(FieldPojo.class);
33  
34      assertEquals(asSet(classProperties.getEqualsProperties()), asSet(privateStringField, publicIntField, forEqualsAndToString));
35      assertEquals(asSet(classProperties.getHashCodeProperties()), asSet(privateStringField, publicIntField));
36      assertEquals(asSet(classProperties.getToStringProperties()), asSet(privateStringField, publicIntField, onlyForStringField, forEqualsAndToString));
37  
38      assertEquals(classProperties.getAllProperties(), asSet(privateStringField, publicIntField, onlyForStringField, forEqualsAndToString));
39    }
40  
41    @Test
42    public void testAutoFields() throws Exception {
43  
44      final PropertyElement stringField = TestUtils.field(AutoFieldPojo.class, "string");
45      final PropertyElement allInDoubleField = TestUtils.field(AutoFieldPojo.class, "allInDouble");
46  
47      ClassProperties classProperties = ClassProperties.forClass(AutoFieldPojo.class);
48  
49      assertEquals(asSet(classProperties.getEqualsProperties()), asSet(allInDoubleField));
50      assertEquals(asSet(classProperties.getHashCodeProperties()), asSet(allInDoubleField));
51      assertEquals(asSet(classProperties.getToStringProperties()), asSet(stringField, allInDoubleField));
52    }
53  
54    @Test
55    public void testAutoFieldWithSynthetic() throws Exception {
56      Set<PropertyElement> properties = asSet(TestUtils.field(AuthFieldPojoWithSyntheticEnclosingThis.class, "findMe"));
57      ClassProperties classProperties = ClassProperties.forClass(AuthFieldPojoWithSyntheticEnclosingThis.class);
58  
59      assertEquals(classProperties.getEqualsProperties(), properties);
60      assertEquals(classProperties.getHashCodeProperties(), properties);
61      assertEquals(classProperties.getToStringProperties(), properties);
62    }
63  
64    @Test
65    public void testAnnotatedMethods() throws Exception {
66      class MethodPojo {
67        @Property public int getInt() { return 0; }
68        @Property private String privateString() { return null; }
69        @Property(policy=PojomaticPolicy.EQUALS) public double onlyForEquals() { return 0.0; }
70      }
71  
72      final PropertyElement getIntMethod = TestUtils.method(MethodPojo.class, "getInt");
73      final PropertyElement privateStringMethod = TestUtils.method(MethodPojo.class, "privateString");
74      final PropertyElement onlyForEqualsMethod = TestUtils.method(MethodPojo.class, "onlyForEquals");
75  
76      ClassProperties classProperties = ClassProperties.forClass(MethodPojo.class);
77  
78      assertEquals(asSet(classProperties.getEqualsProperties()), asSet(getIntMethod, privateStringMethod, onlyForEqualsMethod));
79      assertEquals(asSet(classProperties.getHashCodeProperties()), asSet(getIntMethod, privateStringMethod));
80      assertEquals(asSet(classProperties.getToStringProperties()), asSet(getIntMethod, privateStringMethod));
81    }
82  
83    @Test
84    public void testAutoMethods() throws Exception {
85      final Set<PropertyElement> commonProperties = asSet(
86        TestUtils.method(AutoMethodPojo.class, "getInt"),
87        TestUtils.method(AutoMethodPojo.class, "isBoolean"),
88        TestUtils.method(AutoMethodPojo.class, "is_boolean"),
89        TestUtils.method(AutoMethodPojo.class, "get_int"));
90      final Set<PropertyElement> equalsHashCodeProperties = new HashSet<>(commonProperties);
91      equalsHashCodeProperties.add(TestUtils.method(AutoMethodPojo.class, "getHashCodeAndEquals"));
92  
93      ClassProperties classProperties = ClassProperties.forClass(AutoMethodPojo.class);
94  
95      assertEquals(asSet(classProperties.getEqualsProperties()), equalsHashCodeProperties);
96      assertEquals(asSet(classProperties.getHashCodeProperties()), equalsHashCodeProperties);
97      assertEquals(asSet(classProperties.getToStringProperties()), commonProperties);
98    }
99  
100   @Test
101   public void testAutoMethodsWithSynthetic() throws Exception {
102     Class<?> pojoClass = new PojoClassFactory().generateClass(
103       new PojoDescriptor(
104         new PropertyDescriptor(String.class).withName("getX").asMethod(),
105         new PropertyDescriptor(Object.class).withName("getY").asMethod().asSynthetic())
106       .withAutoDetectPolicy(AutoDetectPolicy.METHOD));
107     final Set<PropertyElement> properties = asSet(TestUtils.method(pojoClass, "getX"));
108 
109     ClassProperties classProperties = ClassProperties.forClass(pojoClass);
110 
111     assertEquals(asSet(classProperties.getEqualsProperties()), properties);
112     assertEquals(asSet(classProperties.getHashCodeProperties()), properties);
113     assertEquals(asSet(classProperties.getToStringProperties()), properties);
114   }
115 
116   @Test(expectedExceptions=IllegalArgumentException.class)
117   public void testAnnotatedMethodReturningVoid() {
118     class MethodReturnsVoidPojo { @Property public void noReturn() {} }
119     ClassProperties.forClass(MethodReturnsVoidPojo.class);
120   }
121 
122   @Test(expectedExceptions=IllegalArgumentException.class)
123   public void testAnnotatedMethodTakingArgs() {
124     class MethodTakesArgsPojo {
125       @Property public int takesArgs(String death) {
126         return death.length(); }
127     }
128     ClassProperties.forClass(MethodTakesArgsPojo.class);
129   }
130 
131   @Test
132   public void testAnnotatedInheritance() throws Exception {
133     Set<PropertyElement> expectedParent = asSet(TestUtils.method(ParentPojo.class, "getFoo"));
134     ClassProperties parentClassProperties = ClassProperties.forClass(ParentPojo.class);
135     assertEquals(asSet(parentClassProperties.getEqualsProperties()), expectedParent);
136     assertEquals(asSet(parentClassProperties.getHashCodeProperties()), expectedParent);
137     assertEquals(asSet(parentClassProperties.getToStringProperties()), expectedParent);
138 
139     ClassProperties childClassProperties = ClassProperties.forClass(ChildPojo.class);
140     Set<PropertyElement> expectedChild = asSet(
141       TestUtils.method(ParentPojo.class, "getFoo"), TestUtils.field(ChildPojo.class, "other"));
142     assertEquals(asSet(childClassProperties.getEqualsProperties()), expectedChild);
143     assertEquals(asSet(childClassProperties.getHashCodeProperties()), expectedChild);
144     assertEquals(asSet(childClassProperties.getToStringProperties()), expectedChild);
145   }
146 
147   @Test
148   public void testAutoInheritanceBothAuto() throws Exception {
149     Set<PropertyElement> expectedParent = asSet(TestUtils.method(ParentAutoPojo.class, "getFoo"));
150     ClassProperties parentClassProperties = ClassProperties.forClass(ParentAutoPojo.class);
151     assertEquals(asSet(parentClassProperties.getEqualsProperties()), expectedParent);
152     assertEquals(asSet(parentClassProperties.getHashCodeProperties()), Collections.EMPTY_SET);
153     assertEquals(asSet(parentClassProperties.getToStringProperties()), Collections.EMPTY_SET);
154 
155     ClassProperties childClassProperties = ClassProperties.forClass(ChildAutoFieldPojo.class);
156     Set<PropertyElement> expectedChild = asSet(
157       TestUtils.field(ChildAutoFieldPojo.class, "other"));
158     assertEquals(asSet(childClassProperties.getEqualsProperties()), expectedParent);
159     assertEquals(asSet(childClassProperties.getHashCodeProperties()), Collections.EMPTY_SET);
160     assertEquals(asSet(childClassProperties.getToStringProperties()), expectedChild);
161   }
162 
163   @Test
164   public void testAutoInheritanceWithOverride() throws Exception {
165     @AutoProperty(autoDetect=AutoDetectPolicy.METHOD)
166     class ChildAutoMethodPojo extends ParentPojo {
167       @Override public int getFoo() { return 2; }
168       @SuppressWarnings("unused") public int getBar() { return 2; }
169     }
170 
171     ClassProperties childClassProperties = ClassProperties.forClass(ChildAutoMethodPojo.class);
172     Set<PropertyElement> expected = asSet(
173       TestUtils.method(ParentPojo.class, "getFoo"),
174       TestUtils.method(ChildAutoMethodPojo.class, "getBar"));
175     assertEquals(asSet(childClassProperties.getEqualsProperties()), expected);
176     assertEquals(asSet(childClassProperties.getHashCodeProperties()), expected);
177     assertEquals(asSet(childClassProperties.getToStringProperties()), expected);
178   }
179 
180   @Test
181   public void testAutoInheritanceAnnotatedParent() throws Exception {
182     @AutoProperty(autoDetect=AutoDetectPolicy.METHOD)
183     class ChildExtendsAnnotatedPojo extends ParentPojo {
184       @Override public int getFoo() { return 0; }
185       @SuppressWarnings("unused") public String getMyString() { return "foo"; }
186     }
187 
188     Set<PropertyElement> expectedParent = asSet(TestUtils.method(ParentPojo.class, "getFoo"));
189     ClassProperties parentClassProperties = ClassProperties.forClass(ParentPojo.class);
190     assertEquals(asSet(parentClassProperties.getEqualsProperties()), expectedParent);
191     assertEquals(asSet(parentClassProperties.getHashCodeProperties()), expectedParent);
192     assertEquals(asSet(parentClassProperties.getToStringProperties()), expectedParent);
193 
194     ClassProperties childClassProperties = ClassProperties.forClass(ChildExtendsAnnotatedPojo.class);
195     Set<PropertyElement> expectedChild = asSet(
196       TestUtils.method(ParentPojo.class, "getFoo"),
197       TestUtils.method(ChildExtendsAnnotatedPojo.class, "getMyString"));
198     assertEquals(asSet(childClassProperties.getEqualsProperties()), expectedChild);
199     assertEquals(asSet(childClassProperties.getHashCodeProperties()), expectedChild);
200     assertEquals(asSet(childClassProperties.getToStringProperties()), expectedChild);
201   }
202 
203   @Test
204   public void testAutoInheritanceAutoParentAnnotatedChild() throws Exception {
205     class ChildExtendsAutoPojo extends ParentAutoPojo {
206       @Property public String other;
207       @Override public int getFoo() { return 2; }
208       @SuppressWarnings("unused") public String getBar() { return ""; }
209     }
210 
211     Set<PropertyElement> expectedParent = asSet(TestUtils.method(ParentAutoPojo.class, "getFoo"));
212     ClassProperties parentClassProperties = ClassProperties.forClass(ParentAutoPojo.class);
213     assertEquals(asSet(parentClassProperties.getEqualsProperties()), expectedParent);
214     assertEquals(asSet(parentClassProperties.getHashCodeProperties()), Collections.EMPTY_SET);
215     assertEquals(asSet(parentClassProperties.getToStringProperties()), Collections.EMPTY_SET);
216 
217     ClassProperties childClassProperties = ClassProperties.forClass(ChildExtendsAutoPojo.class);
218     Set<PropertyElement> expectedChildEquals = asSet(
219       TestUtils.method(ParentAutoPojo.class, "getFoo"),
220       TestUtils.field(ChildExtendsAutoPojo.class, "other"));
221     assertEquals(asSet(childClassProperties.getEqualsProperties()), expectedChildEquals);
222     Set<PropertyElement> expectedChild = asSet(
223       TestUtils.field(ChildExtendsAutoPojo.class, "other"));
224     assertEquals(asSet(childClassProperties.getHashCodeProperties()), expectedChild);
225     assertEquals(asSet(childClassProperties.getToStringProperties()), expectedChild);
226   }
227 
228   @Test
229   public void testOverriddenMethods() throws Exception {
230     ClassProperties classProperties = ClassProperties.forClass(C4.class);
231     assertEquals(asSet(classProperties.getEqualsProperties()), asSet(
232     TestUtils.method(C1.class, "packagePrivate"),
233     TestUtils.method(C1.class, "packagePrivateOverriddenProtected"),
234     TestUtils.method(C1.class, "packagePrivateOverriddenPublic"),
235     TestUtils.method(C1.class, "protectedMethod"),
236     TestUtils.method(C1.class, "publicMethod"),
237     TestUtils.method(C2.class, "packagePrivate"),
238     TestUtils.method(C2.class, "packagePrivateOverriddenProtected"),
239     TestUtils.method(C2.class, "packagePrivateOverriddenPublic")));
240   }
241 
242   @Test
243   public void testAnnotatedStaticField() {
244     try {
245       ClassProperties.forClass(StaticField.class);
246       fail("Exception expected");
247     }
248     catch (IllegalArgumentException e) {
249       assertEquals(e.getMessage(), "Static field " + StaticField.class.getName() + ".a is annotated with @Property");
250     }
251   }
252 
253   @Test
254   public void testSyntheticMethod() throws Exception {
255     assertEquals (
256       asSet(ClassProperties.forClass(Synthetic.class).getEqualsProperties()),
257       asSet(TestUtils.method(Synthetic.class, "getA")));
258   }
259 
260   @Test
261   public void testAnnotatedStaticMethod() {
262     try {
263       ClassProperties.forClass(StaticMethod.class);
264       fail("Exception expected");
265     }
266     catch (IllegalArgumentException e) {
267       assertEquals(e.getMessage(), "Static method " + StaticMethod.class.getName() + ".a() is annotated with @Property");
268     }
269   }
270 
271   @Test public void testInterface() throws Exception {
272     ClassProperties classProperties = ClassProperties.forClass(Interface.class);
273     PropertyElement getFoo = TestUtils.method(Interface.class, "getFoo");
274     PropertyElement baz = TestUtils.method(Interface.class, "baz");
275     assertEquals(asSet(classProperties.getHashCodeProperties()), asSet(getFoo));
276     assertEquals(asSet(classProperties.getToStringProperties()), asSet(getFoo));
277     assertEquals(asSet(classProperties.getEqualsProperties()), asSet(getFoo, baz));
278   }
279 
280   @Test public void testIsCompatibleForEquals() {
281     class Parent { @Property int getX() { return 3; } }
282     class NonContributingChild extends Parent {}
283     @OverridesEquals class AnnotatedNonContributingChild extends Parent {}
284     class ContributingChild extends Parent{ @Property int getY() { return 3; } }
285     class ChildOfContributingChild extends ContributingChild{}
286 
287     List<List<Class<?>>> partitions = Arrays.asList(
288       Arrays.<Class<?>>asList(Parent.class, NonContributingChild.class),
289       Arrays.<Class<?>>asList(ContributingChild.class, ChildOfContributingChild.class),
290       Arrays.<Class<?>>asList(AnnotatedNonContributingChild.class),
291       Arrays.<Class<?>>asList(Interface.class));
292     for (List<Class<?>> partition: partitions) {
293       for (Class<?> clazz: partition) {
294         for(List<Class<?>> otherPartition: partitions) {
295           for(Class<?> otherClazz: otherPartition) {
296             if (partition == otherPartition) {
297               assertTrue(ClassProperties.forClass(clazz).isCompatibleForEquals(otherClazz));
298             }
299             else {
300               assertFalse(ClassProperties.forClass(clazz).isCompatibleForEquals(otherClazz));
301             }
302           }
303         }
304       }
305     }
306   }
307 
308   @Test public void testSubclassCannotOverrideEquals() {
309     class ChildOfInterface implements Interface {
310       @Override
311       public int getFoo() { return 0; }
312       @Override
313       public int bar() { return 0; }
314       @Override
315       public int baz() { return 0; }
316     }
317 
318     assertTrue(ClassProperties.forClass(Interface.class).isCompatibleForEquals(ChildOfInterface.class));
319 
320     @SubclassCannotOverrideEquals class A { @Property int x; }
321     class B extends A { @Property int y; }
322     assertTrue(ClassProperties.forClass(A.class).isCompatibleForEquals(B.class));
323     assertFalse(ClassProperties.forClass(B.class).isCompatibleForEquals(A.class));
324   }
325 
326   @Test
327   public void testMissingClassBytes() throws Exception {
328     class Bean {
329       @Property int a, b, c;
330     }
331 
332     ClassOnlyClassLoader classLoader = new ClassOnlyClassLoader(Bean.class.getClassLoader());
333     Class<?> beanClass = classLoader.loadClass(Bean.class.getName());
334     try {
335       ClassProperties.forClass(beanClass);
336       fail("Exception expected");
337     }
338     catch (RuntimeException e) {
339       assertEquals(e.getMessage(), "no class bytes for class " + beanClass.getName());
340     }
341   }
342 
343   //Not all classes can be made internal.  In particular, autodetect=FIELD classes cannot, because of the synthetic
344   //$this, and classes requiring static elements cannot.
345 
346   public static class FieldPojo {
347     @Property
348     private String privateString;
349 
350     @Property
351     public int publicInt;
352 
353     @Property(policy=PojomaticPolicy.TO_STRING)
354     private int onlyForToString;
355 
356     @Property(policy=PojomaticPolicy.EQUALS_TO_STRING)
357     private int forEqualsAndToString;
358   }
359 
360   @AutoProperty(policy=DefaultPojomaticPolicy.TO_STRING)
361   public static class AutoFieldPojo {
362     public String string;
363 
364     @Property(policy=PojomaticPolicy.NONE)
365     public int ignoredInt;
366 
367     @Property(policy=PojomaticPolicy.ALL)
368     public double allInDouble;
369 
370     /* Methods are not auto-detected */
371     public float getNotDetected() { return 1f; }
372 
373     /* static fields are not detected */
374     public static String staticField;
375   }
376 
377   @AutoProperty(autoDetect=AutoDetectPolicy.FIELD)
378   public class AuthFieldPojoWithSyntheticEnclosingThis {
379     @SuppressWarnings("unused")
380     private String findMe;
381   }
382 
383   @AutoProperty(autoDetect=AutoDetectPolicy.METHOD, policy=DefaultPojomaticPolicy.ALL)
384   public static class AutoMethodPojo {
385     /* Fields are not auto-detected */
386     int notDetected;
387 
388     @Property(policy=PojomaticPolicy.NONE)
389     public String getIgnored() { return null; }
390 
391     @Property(policy=PojomaticPolicy.HASHCODE_EQUALS)
392     public double getHashCodeAndEquals() { return 0.0; }
393 
394     // getter variants
395     public int getInt() { return 0; }
396     public boolean isBoolean() { return true; }
397     public boolean is_boolean() { return true; }
398     public int get_int() { return 0; }
399 
400     // not getters
401     public boolean isaEnabled() { return true; }
402     public int gettyIsAMuseum() { return 1; }
403     public String thisIsNotAGetter() { return "really, it's not"; }
404 
405     // some methods we should not grab
406     public void getHello() {}
407     public int getTriple(int arg) { return arg * 3; }
408 
409 
410     /* static fields are not detected */
411     public static String getStatic() { return null; }
412   }
413 
414   private static abstract class ParentPojo {
415     @Property
416     public abstract int getFoo();
417   }
418 
419   public static class ChildPojo extends ParentPojo {
420     @Property
421     public String other;
422 
423     @Override public int getFoo() { return 2; }
424   }
425 
426   @AutoProperty(autoDetect=AutoDetectPolicy.METHOD, policy=DefaultPojomaticPolicy.EQUALS)
427   private static abstract class ParentAutoPojo {
428     public abstract int getFoo();
429   }
430 
431   @AutoProperty(autoDetect=AutoDetectPolicy.FIELD, policy=DefaultPojomaticPolicy.TO_STRING)
432   public static class ChildAutoFieldPojo extends ParentAutoPojo {
433     public String other;
434 
435     @Override public int getFoo() { return 2; }
436   }
437 
438   public static class ParentOfSynthetic {
439     public Number getA() { return null; }
440   }
441 
442   public static class Synthetic extends ParentOfSynthetic {
443     @Override @Property public Integer getA() { return 3; }
444   }
445   @AutoProperty(autoDetect=AutoDetectPolicy.METHOD)
446   public static interface Interface {
447     int getFoo();
448     int bar();
449     @Property(policy=PojomaticPolicy.EQUALS) int baz();
450   }
451 
452   public static class StaticField {
453     @Property public static int a;
454   }
455 
456   public static class StaticMethod {
457     @Property public static int a() { return 1; }
458   }
459 
460   private static Set<PropertyElement> asSet(PropertyElement... elements) {
461     return new HashSet<>(Arrays.asList(elements));
462   }
463 
464   private static Set<PropertyElement> asSet(Collection<PropertyElement> elements) {
465     return new HashSet<>(elements);
466   }
467 }
468