001: package com.bm.utils;
002:
003: import java.lang.reflect.Constructor;
004: import java.lang.reflect.Method;
005: import java.lang.reflect.Modifier;
006:
007: import junit.framework.Assert;
008:
009: /**
010: * EqualsTester is used to test the equals contract on objects. The contract as
011: * specified by java.lang.Object states that if A.equals(B) is true then
012: * B.equals(A) is also true. It also specifies that if A.equals(B) is true then
013: * A.hashCode() will equals B.hashCode().
014: * <p>
015: *
016: * It is also common practice to implement equals using an instanceof check
017: * which will result in false positives in some cases. Specifically, it will
018: * result in false positives when comparing against a subclass with the same
019: * values. For an in-depth discussion of the common problems when implementing
020: * the equals contract, refer to the book "Practical Java" by Peter Haggar
021: *
022: * <pre>
023: * // WRONG way of implementing equals
024: * public boolean equals( final Object object ) {
025: * if( object instanceof this ) {
026: * // do check
027: * }
028: * return false;
029: * }
030: * </pre>
031: *
032: * The correct way to implement equals is as follows
033: *
034: * <pre>
035: * public boolean equals(final Object object) {
036: * if (object != null && object.getClass() == this.getClass()) {
037: * // do check
038: * }
039: * return false;
040: * }
041: * </pre>
042: *
043: * EqualsTester ensures that the equals() and hashCode() methods have been
044: * implemented correctly.
045: * <p>
046: *
047: * <pre>
048: * final Object a = new Foo(4); // original object
049: * final Object b = new Foo(4); // another object that has the same values as the original
050: * final Object c = new Foo(5); // another object with different values
051: * final Object d = new Foo(4) {
052: * }; // a subclass of Foo with the same values as the original
053: * new EqualsTester(a, b, c, d);
054: * </pre>
055: *
056: * @author Daniel Wiese
057: */
058: public class EqualsTester extends Assert {
059:
060: /**
061: * Perform the test. The act of instantiating one of these will run the
062: * test.
063: *
064: * @param a
065: * The object to be tested
066: * @param b
067: * An object that is equal to A
068: * @param c
069: * An object of the same class that is not equal to A. If it is
070: * not possible to create a different one then pass null.
071: * @param d
072: * A subclass of A with the same values. If A is an instance of a
073: * final class then this must be null
074: * @param checkSubclass -
075: * id a subclass should be checked
076: */
077: public EqualsTester(final Object a, final Object b, final Object c,
078: final Object d, boolean checkSubclass) {
079: assertNotNull(a, "A");
080: assertNotNull(b, "B");
081: assertSameClassAsA(a, b, "B");
082:
083: if (c == null) {
084: assertCAllowedToBeNull(a.getClass());
085: } else {
086: assertSameClassAsA(a, c, "C");
087: }
088:
089: if (isClassFinal(a.getClass())) {
090: assertNull(d, "D");
091: } else if (d == null && checkSubclass) {
092: throw new DetailedNullPointerException("D",
093: "Cannot be null for a non-final class");
094: }
095:
096: if (d != null) {
097: assertDDifferentClassThanA(a, d);
098: }
099:
100: assertAEqualsNull(a);
101: assertAEqualsA(a);
102: assertAEqualsB(a, b);
103: if (c != null) {
104: assertANotEqualC(a, c);
105: }
106: assertClassAndSubclass(a, d);
107: }
108:
109: private boolean isClassFinal(final Class clazz) {
110: final int modifiers = clazz.getModifiers();
111: return Modifier.isFinal(modifiers);
112: }
113:
114: private void assertAEqualsA(final Object a) {
115: assertTrue("A.equals(A)", a.equals(a));
116: }
117:
118: private void assertAEqualsB(final Object a, final Object b) {
119: assertTrue("A.equals(B)", a.equals(b));
120: assertTrue("B.equals(A)", b.equals(a));
121: assertEquals("hashCode", a.hashCode(), b.hashCode());
122: }
123:
124: private void assertANotEqualC(final Object a, final Object c) {
125: assertTrue("a.equals(c)", !a.equals(c));
126: assertTrue("c.equals(a)", !c.equals(a));
127: }
128:
129: private void assertClassAndSubclass(final Object a, final Object d) {
130: if (d != null) {
131: if (a.equals(d)) {
132: fail("a.equals(d)");
133: }
134:
135: if (d.equals(a)) {
136: fail("d.equals(a)");
137: }
138: }
139: }
140:
141: private void assertNotNull(final Object object,
142: final String description) {
143: if (object == null) {
144: fail(description + " is null");
145: }
146: }
147:
148: private void assertNull(final Object object,
149: final String description) {
150: if (object != null) {
151: fail(description + " must be null. Found [" + object + "]");
152: }
153: }
154:
155: /**
156: * C may not be null if it has a public non-default constructor or any
157: * setXX() methods
158: *
159: * @param clazz
160: */
161: private void assertCAllowedToBeNull(final Class clazz) {
162: int i;
163:
164: final Constructor[] constructors = clazz.getConstructors();
165: for (i = 0; i < constructors.length; i++) {
166: if (constructors[i].getParameterTypes().length != 0) {
167: fail("C may not be null because it has a public non-default constructor: "
168: + constructors[i]);
169: }
170: }
171:
172: final Method[] methods = clazz.getMethods();
173: for (i = 0; i < methods.length; i++) {
174: if (methods[i].getName().startsWith("set")) {
175: fail("C may not be null because it has public set methods: "
176: + methods[i]);
177: }
178: }
179: }
180:
181: private void assertSameClassAsA(final Object a,
182: final Object object, final String name) {
183: if (a.getClass() != object.getClass()) {
184: fail(name + " must be of same class as A. A.class=["
185: + a.getClass().getName() + "] " + name + ".class=["
186: + object.getClass().getName() + "]");
187: }
188: }
189:
190: private void assertDDifferentClassThanA(final Object a,
191: final Object d) {
192: if (a.getClass() == d.getClass()) {
193: fail("D must not be of same class as A. A.class=["
194: + a.getClass().getName() + "] d.class=["
195: + d.getClass().getName() + "]");
196: }
197: }
198:
199: private void assertAEqualsNull(final Object a) {
200: try {
201: if (a.equals(null)) {
202: fail("A.equals(null) returned true");
203: }
204: } catch (final NullPointerException e) {
205: fail("a.equals(null) threw a NullPointerException. It should have returned false");
206: }
207: }
208: }
|