001: package com.bm.utils;
002:
003: import java.util.Calendar;
004: import java.util.Collection;
005: import java.util.Date;
006: import java.util.GregorianCalendar;
007: import java.util.Iterator;
008: import java.util.LinkedList;
009: import java.util.List;
010:
011: import javax.persistence.Temporal;
012:
013: import junit.framework.Assert;
014:
015: import org.apache.log4j.Logger;
016:
017: import com.bm.introspectors.Introspector;
018: import com.bm.introspectors.Property;
019:
020: /**
021: * Helper method to test the equality of beans.
022: *
023: * @author Daniel Wiese
024: *
025: */
026: public final class BeanEqualsTester extends Assert {
027:
028: private static final String EQUALS_WRONG = "The implementation of the equals is might incorrect, "
029: + "two Entity-Beans representing the same row should be equal";
030:
031: private static final String HASHCODE_WRONG = "The implementation of the hashCode is might incorrect, "
032: + "two Entity-Beans representing the same row should have the same hashCode";
033:
034: private static final Logger log = Logger
035: .getLogger(BeanEqualsTester.class);
036:
037: private BeanEqualsTester() {
038: // intenionally left emty
039: }
040:
041: /**
042: * Test the collection for the size (if not equal a runntime excetion is.
043: * thrown)
044: *
045: * @param <T> -
046: * the type of the bean
047: *
048: * @param original -
049: * the original collsecion ob beans
050: * @param readed -
051: * the readed collection from the DB
052: */
053: public static <T> void testEqualsOnSize(List<T> original,
054: List<T> readed) {
055:
056: if (original != null && readed != null) {
057: if (original.size() != readed.size()) {
058: throw new RuntimeException(
059: "Bean-Read-test: Not all/too many beans reded from the DB");
060: }
061: } else {
062: throw new RuntimeException(
063: "Bean-Read-test: One of the collections is null");
064: }
065:
066: }
067:
068: /**
069: * Test the collection for equality on persistent fields (if not equal a
070: * runntime excetion is thrown).
071: *
072: * @param <T> -
073: * the type of the bean
074: *
075: * @param original -
076: * the original collsecion ob beans
077: * @param readed -
078: * the readed collection from the DB
079: * @param intro -
080: * the introspector
081: */
082: public static <T> void testEqualsOnPersistentFields(
083: List<T> original, List<T> readed, Introspector<T> intro) {
084:
085: testEqualsOnSize(original, readed);
086:
087: for (int i = 0; i < original.size(); i++) {
088: T origB = original.get(i);
089: T readB = readed.get(i);
090:
091: for (Property akt : intro.getPersitentProperties()) {
092: try {
093: Object valueOrig = intro.getField(origB, akt);
094: Object valueRead = intro.getField(readB, akt);
095: if (valueOrig == null && valueRead == null) {
096: // both are null >ok
097: log.debug("Both values are null> field:"
098: + akt.getName());
099: } else {
100: if (valueOrig == null) {
101: fail("The property ("
102: + akt
103: + ") is null, but the DB value is not null ("
104: + valueRead + ")");
105: } else if (valueRead == null) {
106: fail("The DB value is null");
107: } else if (valueOrig instanceof Collection
108: && valueRead instanceof Collection) {
109: // test collections independend from the order
110: testColletionsForEqual(
111: (Collection<?>) valueOrig,
112: (Collection<?>) valueRead);
113:
114: } else if (!isEqual(akt, valueOrig, valueRead)) {
115: // usual equals failed
116: if (valueOrig instanceof String) {
117: printErrorForUneqalStrings(
118: (String) valueOrig,
119: (String) valueRead);
120: }
121: fail("The values of the field ("
122: + akt.getName() + ") in class ("
123: + akt.getDeclaringClass()
124: + ") are not equal");
125: }
126: }
127: } catch (IllegalAccessException e) {
128: throw new RuntimeException(
129: "Canīt read the value of a field");
130: }
131:
132: }
133: }
134:
135: }
136:
137: /**
138: * Assert that the two collections are the same irrespective of order.
139: *
140: * @param firstColl
141: * The first collection
142: * @param secondColl
143: * The second collection
144: * @param <T> -
145: * the type of the collection
146: */
147: public static <T> void testColletionsForEqual(
148: Collection<? extends T> firstColl,
149: Collection<? extends T> secondColl) {
150: if (firstColl == null || secondColl == null) {
151: assertNull("Second collection is null, first not",
152: firstColl);
153: assertNull("First collection is null, second not",
154: secondColl);
155: } else if (firstColl.size() == secondColl.size()) {
156: final Collection<T> copyOfSecond = new LinkedList<T>(
157: secondColl);
158: // check if all elements of a are in b and vice versa.
159: final Iterator iterator = firstColl.iterator();
160: while (iterator.hasNext()) {
161: final Object object = iterator.next();
162: if (!copyOfSecond.contains(object)) {
163: fail("Object ("
164: + object
165: + ") is missing in second collection (second parameter)");
166: }
167:
168: copyOfSecond.remove(object);
169: }
170:
171: if (!copyOfSecond.isEmpty()) {
172: fail("Second collection has elements that aren't in the first collection: "
173: + copyOfSecond);
174: }
175: } else {
176: fail("The size of the collections is not equal, fist size ("
177: + firstColl.size()
178: + "), second size ("
179: + secondColl.size() + ")");
180: }
181: }
182:
183: /**
184: * Test the collection for equality by calling the equals method (if not
185: * equal a runntime excetion is thrown).
186: *
187: *
188: *
189: * The preconditions are: We expect that the list do not contain duplicate
190: * elements - every element should represent a different row in the
191: * database!<br>
192: * Additional preconditions are:<br> - 2 Entitybeans representing different
193: * database-rows are NEVER equal. <br> - Every bean instance representing
194: * the same DB row is always equal. <br> - 2 equal beans MUST have the same
195: * hash code.<br> - N bans (e.g 30) should have different hash codes.<br>
196: *
197: * @param <T> -
198: * the type of the bean
199: *
200: * @param original -
201: * the original collection ob beans
202: * @param readed -
203: * the readed collection from the DB
204: */
205: public static <T> void testEqualsImplementationForEntityBeans(
206: List<T> original, List<T> readed) {
207:
208: int firstHashCode = 0;
209: boolean differentHashCode = true;
210: // make the hashcode test only with minimum 30 elements
211: if (original.size() > 30) {
212: firstHashCode = original.get(0).hashCode();
213: differentHashCode = false;
214: }
215:
216: for (int i = 0; i < original.size(); i++) {
217: T origB = original.get(i);
218: T readB = readed.get(i);
219:
220: // thest the equals method
221: if (!(origB.equals(readB) && readB.equals(origB))) {
222: log
223: .error("The implementation of the equals Method is wrong");
224: log
225: .error("Two Entity-Beans ("
226: + origB.getClass().getName()
227: + ") representing the same row should be equal");
228: fail(EQUALS_WRONG);
229: }
230: // test the hash code
231: if (origB.hashCode() != readB.hashCode()) {
232: log
233: .error("The implementation of the hash-code Method is wrong");
234: log
235: .error("Two Entity-Beans ("
236: + origB.getClass().getName()
237: + ") representing the same row should have the same hash-code");
238: fail(HASHCODE_WRONG);
239:
240: }
241:
242: // to avoid n-square complexity compare the n bean with the n+1 bean
243: if (i + 1 < original.size()) {
244: T nextOrigB = original.get(i + 1);
245: T nextReadB = readed.get(i + 1);
246:
247: if ((origB.equals(nextReadB) || readB.equals(nextOrigB))) {
248: log
249: .error("The implementation of the equals Method is wrong");
250: log
251: .error("Two Entity-Beans ("
252: + origB.getClass().getName()
253: + ") representing the different row should NEVER be equal");
254: fail(EQUALS_WRONG);
255: }
256: }
257:
258: if (!differentHashCode && origB.hashCode() != firstHashCode) {
259: // different hash code found
260: differentHashCode = true;
261: }
262: }
263:
264: // throw a exception if all hash codes are the same
265: if (!differentHashCode) {
266: fail("All tested beans have the same hash code!");
267: }
268:
269: // check if null and not equal with other class instance is implemented
270: if (original.isEmpty()) {
271: T first = original.get(0);
272: assertFalse(first.equals(null));
273: assertFalse(first.equals("NOT EXISTING"));
274: }
275:
276: }
277:
278: private static boolean isEqual(Property prop, Object obj1,
279: Object obj2) {
280: boolean back = false;
281: if (obj1 == null && obj2 == null) {
282: back = true;
283: } else if ((obj1 == null && obj2 != null)
284: || (obj1 != null && obj2 == null)) {
285: back = false;
286: } else {
287: // both object are not null
288: if (obj1 instanceof Date && obj2 instanceof Date) {
289: Temporal temporal = prop.getAnnotation(Temporal.class);
290: if (temporal != null) {
291: switch (temporal.value()) {
292: case DATE:
293: back = dateEqual((Date) obj1, (Date) obj2);
294: break;
295: case TIME:
296: back = timeEqual((Date) obj1, (Date) obj2);
297: break;
298: case TIMESTAMP:
299: back = timeStampEqual((Date) obj1, (Date) obj2);
300: break;
301:
302: }
303: } else {
304: back = dateEqual((Date) obj1, (Date) obj2);
305: }
306: } else {
307: back = obj1.equals(obj2);
308: }
309: }
310:
311: return back;
312: }
313:
314: private static boolean dateEqual(Date obj1, Date obj2) {
315: boolean back;
316: Calendar c1 = new GregorianCalendar();
317: c1.setTime((Date) obj1);
318: Calendar c2 = new GregorianCalendar();
319: c2.setTime((Date) obj2);
320: back = c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR);
321: back = back && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
322: back = back
323: && c1.get(Calendar.DAY_OF_MONTH) == c2
324: .get(Calendar.DAY_OF_MONTH);
325: return back;
326: }
327:
328: private static boolean timeEqual(Date obj1, Date obj2) {
329: boolean back;
330: Calendar c1 = new GregorianCalendar();
331: c1.setTime((Date) obj1);
332: Calendar c2 = new GregorianCalendar();
333: c2.setTime((Date) obj2);
334: back = c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR);
335: back = back && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
336: back = back
337: && c1.get(Calendar.DAY_OF_MONTH) == c2
338: .get(Calendar.DAY_OF_MONTH);
339: back = back
340: && c1.get(Calendar.HOUR_OF_DAY) == c2
341: .get(Calendar.HOUR_OF_DAY);
342: back = back
343: && c1.get(Calendar.MINUTE) == c2.get(Calendar.MINUTE);
344: return back;
345: }
346:
347: private static boolean timeStampEqual(Date obj1, Date obj2) {
348: java.sql.Timestamp d1 = new java.sql.Timestamp(((Date) obj1)
349: .getTime());
350: java.sql.Timestamp d2 = new java.sql.Timestamp(((Date) obj2)
351: .getTime());
352: return d1.equals(d2);
353: }
354:
355: private static void printErrorForUneqalStrings(String origin,
356: String other) {
357: log.error("The readed string from the database "
358: + "is not Eqal to the origin");
359: log.error("Origin : " + origin);
360: log.error("From DB: " + other);
361: log.error("-----------------------------------");
362: log.error("Origin Length : " + origin.length());
363: log.error("From DB Length: " + other.length());
364:
365: }
366:
367: }
|