001 /*
002 * $Id: ObjectTester.java,v 1.34 2014/04/30 20:03:11 oboehm Exp $
003 *
004 * Copyright (c) 2010 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 21.07.2010 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.junit;
022
023 import java.io.*;
024 import java.lang.reflect.*;
025 import java.util.*;
026
027 import org.junit.Assert;
028 import org.slf4j.*;
029
030 import patterntesting.runtime.monitor.ClasspathMonitor;
031 import patterntesting.runtime.util.*;
032
033 /**
034 * This is a utility class to check some important methods of a class like the.
035 *
036 * {@link Object#equals(Object)} or {@link Object#hashCode()} method.
037 * Before v1.1 the methods are named "checkEquals" or "checkCompareTo".
038 * Since 1.1 these methods will have now an "assert" prefix ("assertEquals"
039 * or "assertCompareTo").
040 *
041 * @author oliver
042 * @since 1.0.3 (21.07.2010)
043 */
044 public final class ObjectTester {
045
046 private static final Logger log = LoggerFactory.getLogger(ObjectTester.class);
047 private static final ClasspathMonitor classpathMonitor = ClasspathMonitor.getInstance();
048
049 /** Utility class - no need to instantiate it. */
050 private ObjectTester() {}
051
052 /**
053 * Check equality of the given objects. They must be equals otherwise an
054 * AssertionError will be thrown. And if <tt>A == B</tt> also
055 * <tt>B == A</tt> must be true (commutative law, i.e. it is a
056 * symmetrical operation).
057 * <p>
058 * If two objects are equals they must have also the same hash code (but
059 * not the other way around). This condition is also checked here.
060 * </p>
061 * <p>
062 * Often programmers forget that the {@link Object#equals(Object)} method
063 * can be called with <em>null</em> as argument and should return
064 * <em>false</em> as result. So this case is also tested here.
065 * </p>
066 *
067 * @param o1 the 1st object
068 * @param o2 the 2nd object
069 * @throws AssertionError if the check fails
070 * @since 1.1
071 */
072 @SuppressWarnings("unchecked")
073 public static void assertEquals(final Object o1, final Object o2) throws AssertionError {
074 try {
075 Package pkg = (Package) o1;
076 Class<?> c2 = (Class<?>) o2;
077 Class<?>[] excluded = { c2 };
078 assertEquals(pkg, excluded);
079 } catch (ClassCastException cce) {
080 Assert.assertEquals(o1.getClass() + ": objects are not equals!", o1, o2);
081 Assert.assertEquals(o1.getClass() + ": equals not symmetrical (A == B, but B != A)", o2, o1);
082 Assert.assertEquals(o1.getClass() + ": objects are equals but hashCode differs!",
083 o1.hashCode(), o2.hashCode());
084 if (o1 instanceof Comparable<?>) {
085 ComparableTester.assertCompareTo((Comparable<Comparable<?>>) o1,
086 (Comparable<Comparable<?>>) o1);
087 }
088 assertEqualsWithNull(o1);
089 }
090 log.info("equals/hashCode implementation of " + o1.getClass() + " seems to be ok");
091 }
092
093
094 /**
095 * Null as argument for the equals method should always return 'false' and
096 * should not end with a NullPointerException.
097 *
098 * @param obj the obj
099 */
100 private static void assertEqualsWithNull(final Object obj) {
101 try {
102 Assert.assertFalse(obj.getClass().getName() + ".equals(null) should return 'false'", obj.equals(null));
103 } catch (RuntimeException re) {
104 AssertionError error = new AssertionError(obj.getClass().getName()
105 + ".equals(..) implementation does not check (correct) for null argument");
106 error.initCause(re);
107 throw error;
108 }
109 }
110
111 /**
112 * The given object will be serialized and deserialized to get a copy of
113 * that object. The copy must be equals to the original object.
114 *
115 * @param obj the object
116 * @throws AssertionError if the check fails
117 * @throws NotSerializableException if obj is not serializable
118 * @since 1.1
119 */
120 public static void assertEquals(final Serializable obj)
121 throws AssertionError, NotSerializableException {
122 Object clone = clone(obj);
123 assertEquals(obj, clone);
124 }
125
126 /**
127 * The given object will be cloned to get a copy of that object.
128 * The copy must be equals to the original object.
129 *
130 * @param obj the obj
131 * @throws AssertionError the assertion error
132 * @since 1.1
133 */
134 public static void assertEquals(final Cloneable obj) throws AssertionError {
135 Object clone = CloneableTester.getCloneOf(obj);
136 assertEquals(obj, clone);
137 }
138
139 /**
140 * This method will create two objects of the given class using the
141 * default constructor. So three preconditions must be true:
142 * <ol>
143 * <li>the class must not be abstract</li>
144 * <li>there must be a (public) default constructor</li>
145 * <li>it must be Cloneable, Serializable or
146 * return always the same object
147 * </li>
148 * </ol>
149 * That a constructor creates equals objects is not true for all classes.
150 * For example the default constructor
151 * of the Date class will generate objects with different timestamps
152 * which are not equal. But most classes should meet the precondition.
153 *
154 * @param clazz the clazz
155 * @throws AssertionError if the check fails
156 * @since 1.1
157 */
158 public static void assertEquals(final Class<?> clazz) throws AssertionError {
159 log.trace("checking {}.equals...", clazz);
160 Object o1 = newInstanceOf(clazz);
161 try {
162 assertEquals((Cloneable) o1);
163 } catch (ClassCastException cce) {
164 try {
165 assertEquals((Serializable) o1);
166 } catch (NotSerializableException nse) {
167 throw new AssertionError(nse);
168 } catch (ClassCastException e) {
169 Object o2 = newInstanceOf(clazz);
170 assertEquals(o1, o2);
171 }
172 }
173 }
174
175 /**
176 * This method will create two objects of the given class by trying to
177 * clone them in different ways. The last try to get a copy will be a
178 * cloning of all attributes. But this is normally not very significant
179 * because most (wrong) equals implementation depends on a simple
180 * comparison of the object reference.
181 *
182 * @param clazz the clazz
183 * @throws AssertionError if the check fails
184 * @since 1.1
185 */
186 public static void assertEqualsWithClone(final Class<?> clazz) throws AssertionError {
187 if (log.isTraceEnabled()) {
188 log.trace("checking " + clazz.getName() + ".equals...");
189 }
190 Object o1 = newInstanceOf(clazz);
191 try {
192 assertEquals((Cloneable) o1);
193 } catch (ClassCastException cce) {
194 try {
195 assertEquals((Serializable) o1);
196 } catch (NotSerializableException nse) {
197 throw new AssertionError(nse);
198 } catch (ClassCastException e) {
199 Object o2 = clone(o1);
200 assertEquals(o1, o2);
201 }
202 }
203 }
204
205 /**
206 * Check for each class in the given collection if the equals() method
207 * is implemented correct.
208 *
209 * @param classes the classes
210 * @throws Failures the collected assertion errors
211 * @since 1.1
212 */
213 public static void assertEquals(final Collection<Class<?>> classes) throws Failures {
214 Failures failures = new Failures();
215 for (Class<?> clazz : classes) {
216 try {
217 assertEquals(clazz);
218 } catch (AssertionError e) {
219 log.warn("equals/hashCode implementation of " + clazz + " is NOT OK ("
220 + e.getMessage() + ")");
221 failures.add(clazz, e);
222 }
223 }
224 if (failures.hasErrors()) {
225 throw failures;
226 }
227 }
228
229 /**
230 * Check for each class in the given package if the equals() method
231 * is implemented correct.
232 * <br/>
233 * To get a name of a package call {@link Package#getPackage(String)}.
234 * But be sure that you can't get <em>null</em> as result. In this case
235 * use {@link #assertEqualsOfPackage(String)}.
236 *
237 * @param pkg the package e.g. "patterntesting.runtime"
238 * @see #assertEqualsOfPackage(String)
239 * @since 1.1
240 */
241 public static void assertEquals(final Package pkg) {
242 assert pkg!= null;
243 assertEqualsOfPackage(pkg.getName());
244 }
245
246 /**
247 * Check for each class in the given package if the equals() method
248 * is implemented correct.
249 * <br/>
250 * To get a name of a package call {@link Package#getPackage(String)}.
251 * But be sure that you can't get <em>null</em> as result. In this case
252 * use {@link #assertEqualsOfPackage(String, Class...)}.
253 *
254 * @param pkg the package e.g. "patterntesting.runtime"
255 * @param excluded classes which are excluded from the check
256 * @see #assertEqualsOfPackage(String, Class...)
257 * @since 1.1
258 */
259 public static void assertEquals(final Package pkg, final Class<?>... excluded) {
260 assert pkg!= null;
261 assertEqualsOfPackage(pkg.getName(), excluded);
262 }
263
264 /**
265 * Check for each class in the given package if the equals() method
266 * is implemented correct.
267 * <br/>
268 * To get a name of a package call {@link Package#getPackage(String)}.
269 * But be sure that you can't get <em>null</em> as result. In this case
270 * use {@link #assertEqualsOfPackage(String, List)}.
271 *
272 * @param pkg the package e.g. "patterntesting.runtime"
273 * @param excluded classes which should be excluded from the check
274 * @see #assertEqualsOfPackage(String, List)
275 * @since 1.1
276 */
277 public static void assertEqualsOfPackage(final Package pkg, final List<Class<?>> excluded) {
278 assert pkg!= null;
279 assertEqualsOfPackage(pkg.getName(), excluded);
280 }
281
282 /**
283 * Check for each class in the given package if the equals() method
284 * is implemented correct.
285 * <br/>
286 * This method does the same as {@link #assertEquals(Package)} but was
287 * introduced by {@link Package#getPackage(String)} sometimes return null
288 * if no class of this package is loaded.
289 *
290 * @param packageName the package name e.g. "patterntesting.runtime"
291 * @see #assertEquals(Package)
292 * @since 1.1
293 */
294 public static void assertEqualsOfPackage(final String packageName) {
295 Collection<Class<?>> classes = getClassesWithDeclaredEquals(packageName);
296 assertEquals(classes);
297 }
298
299 /**
300 * Check for each class in the given package if the equals() method
301 * is implemented correct.
302 *
303 * @param packageName the package name e.g. "patterntesting.runtime"
304 * @param excluded classes which should be excluded from the check
305 * @see #assertEqualsOfPackage(String)
306 * @since 1.1
307 */
308 public static void assertEqualsOfPackage(final String packageName, final Class<?>... excluded) {
309 List<Class<?>> excludedList = Arrays.asList(excluded);
310 assertEqualsOfPackage(packageName, excludedList);
311 }
312
313 /**
314 * Check for each class in the given package if the equals() method
315 * is implemented correct.
316 *
317 * @param packageName the package name e.g. "patterntesting.runtime"
318 * @param excluded classes which should be excluded from the check
319 * @see #assertEqualsOfPackage(String)
320 * @since 1.1
321 */
322 public static void assertEqualsOfPackage(final String packageName, final List<Class<?>> excluded) {
323 Collection<Class<?>> classes = getClassesWithDeclaredEquals(packageName);
324 log.debug(excluded + " will be excluded from check");
325 removeClasses(classes, excluded);
326 assertEquals(classes);
327 }
328
329 private static Collection<Class<?>> getClassesWithDeclaredEquals(final String packageName) {
330 assert packageName != null;
331 Collection<Class<?>> concreteClasses = classpathMonitor.getConcreteClassList(packageName);
332 Collection<Class<?>> classes = new ArrayList<Class<?>>(concreteClasses.size());
333 for (Class<?> clazz : concreteClasses) {
334 if (!hasEqualsDeclared(clazz)) {
335 log.debug(clazz + " will be ignored (equals(..) not overwritten)");
336 continue;
337 }
338 classes.add(clazz);
339 }
340 return classes;
341 }
342
343 /**
344 * If you want to know if a class (or one of its super classes, except
345 * object) has overwritten the equals method you
346 * can use this method here.
347 *
348 * @param clazz the clazz
349 * @return true, if successful
350 */
351 public static boolean hasEqualsDeclared(final Class<?> clazz) {
352 try {
353 Method method = clazz.getMethod("equals", Object.class);
354 Class<?> declaring = method.getDeclaringClass();
355 return !declaring.equals(Object.class);
356 } catch (SecurityException e) {
357 log.info("can't get equals(..) method of " + clazz, e);
358 return false;
359 } catch (NoSuchMethodException e) {
360 return false;
361 }
362 }
363
364 /**
365 * Check equality of the given objects by using the compareTo() method.
366 * Because casting an object to the expected Comparable is awesome we
367 * provide this additional method here
368 *
369 * @param o1 the first object (must be of type Comparable)
370 * @param o2 the second object (must be of type Comparable)
371 * @throws AssertionError if the check fails
372 * @see ComparableTester#assertCompareTo(Comparable, Comparable)
373 * @since 1.1
374 */
375 @SuppressWarnings("unchecked")
376 public static void assertCompareTo(final Object o1, final Object o2) throws AssertionError {
377 ComparableTester.assertCompareTo((Comparable<Comparable<?>>) o1,
378 (Comparable<Comparable<?>>) o2);
379 }
380
381 /**
382 * If a object is only partially initalized it sometimes can happen, that
383 * calling the toString() method will result in a NullPointerException.
384 * This should not happen so there are several check methods available
385 * where you can proof it.
386 *
387 * @param obj the object to be checked
388 * @since 1.1
389 */
390 public static void assertToString(final Object obj) {
391 if (hasToStringDefaultImpl(obj)) {
392 log.info(obj.getClass() + " has default implementation of toString()");
393 }
394 }
395
396 /**
397 * Normally you should overwrite the toString() method for better logging
398 * and debugging. This is the method to check it.
399 *
400 * @param obj the object to be checked
401 * @return true, if object has default implementation
402 */
403 public static boolean hasToStringDefaultImpl(final Object obj) {
404 try {
405 String s = obj.toString();
406 return s.startsWith(obj.getClass().getName() + "@");
407 } catch (Throwable t) {
408 log.info("The toString implementation of " + obj.getClass()
409 + " seems to be overwritten because error happens.", t);
410 return false;
411 }
412 }
413
414 /**
415 * Normally you should overwrite the toString() method for better logging
416 * and debugging. This is the method to check it.
417 *
418 * @param clazz the clazz
419 * @return true, if object has default implementation
420 */
421 public static boolean hasToStringDefaultImpl(final Class<?> clazz) {
422 Object obj = newInstanceOf(clazz);
423 return hasToStringDefaultImpl(obj);
424 }
425
426 /**
427 * Starts all known checks like checkEquals(..), checks from the
428 * SerializableTester (if the given class is serializable) or from
429 * other classes.
430 *
431 * @param clazz the clazz to be checked.
432 * @since 1.1
433 */
434 @SuppressWarnings("unchecked")
435 public static void assertAll(final Class<?> clazz) {
436 if (log.isTraceEnabled()) {
437 log.trace("checking all of " + clazz + "...");
438 }
439 if (hasEqualsDeclared(clazz)) {
440 assertEquals(clazz);
441 }
442 if (hasToStringDefaultImpl(clazz)) {
443 log.info(clazz + " has default implementation of toString()");
444 }
445 if (clazz.isAssignableFrom(Serializable.class)) {
446 try {
447 SerializableTester.assertSerialization(clazz);
448 } catch (NotSerializableException e) {
449 throw new AssertionError(e);
450 }
451 }
452 if (clazz.isAssignableFrom(Cloneable.class)) {
453 CloneableTester.assertCloning((Class<Cloneable>) clazz);
454 }
455 }
456
457 /**
458 * Check all.
459 *
460 * @param classes the classes to be checked
461 * @since 1.1
462 */
463 public static void assertAll(final Collection<Class<?>> classes) {
464 for (Class<?> clazz : classes) {
465 assertAll(clazz);
466 }
467 }
468
469 /**
470 * Starts all known checks for all classes of the given package.
471 * <br/>
472 * To get a name of a package call {@link Package#getPackage(String)}.
473 * But be sure that you can't get <em>null</em> as result. In this case
474 * use {@link #assertAllOfPackage(String)}.
475 *
476 * @param pkg the package e.g. "patterntesting.runtime"
477 * @since 1.1
478 */
479 public static void assertAll(final Package pkg) {
480 assert pkg != null;
481 assertAllOfPackage(pkg.getName());
482 }
483
484 /**
485 * Starts all known checks for all classes of the given package except for
486 * the "excluded" classes.
487 * <br/>
488 * To get a name of a package call {@link Package#getPackage(String)}.
489 * But be sure that you can't get <em>null</em> as result. In this case
490 * use {@link #assertEqualsOfPackage(String, Class...)}.
491 *
492 * @param pkg the package e.g. "patterntesting.runtime"
493 * @param excluded classes which are excluded from the check
494 * @see #assertAllOfPackage(String, Class...)
495 * @since 1.1
496 */
497 public static void assertAll(final Package pkg, final Class<?>... excluded) {
498 assert pkg!= null;
499 assertAllOfPackage(pkg.getName(), excluded);
500 }
501
502 /**
503 * Starts all known checks for all classes of the given package.
504 *
505 * @param packageName the package e.g. "patterntesting.runtime"
506 * @since 1.1
507 */
508 public static void assertAllOfPackage(final String packageName) {
509 assert packageName != null;
510 assertAllOfPackage(packageName, new ArrayList<Class<?>>());
511 }
512
513 /**
514 * Starts all known checks for all classes of the given package but not
515 * for the "excluded" classes.
516 *
517 * @param packageName the package name e.g. "patterntesting.runtime"
518 * @param excluded classes which should be excluded from the check
519 * @see #assertAllOfPackage(String)
520 * @since 1.1
521 */
522 public static void assertAllOfPackage(final String packageName, final Class<?>... excluded) {
523 List<Class<?>> excludedList = Arrays.asList(excluded);
524 assertAllOfPackage(packageName, excludedList);
525 }
526
527 /**
528 * Starts all known checks for all classes of the given package but not
529 * for the "excluded" classes.
530 *
531 * @param packageName the package name e.g. "patterntesting.runtime"
532 * @param excluded classes which should be excluded from the check
533 * @see #assertEqualsOfPackage(String)
534 * @since 1.1
535 */
536 public static void assertAllOfPackage(final String packageName, final List<Class<?>> excluded) {
537 assert packageName != null;
538 Collection<Class<?>> classes = classpathMonitor.getConcreteClassList(packageName);
539 log.debug("{} will be excluded from check", excluded);
540 classes.removeAll(excluded);
541 Collection<Class<?>> memberClasses = new ArrayList<Class<?>>();
542 for (Class<?> clazz : classes) {
543 if (clazz.isMemberClass()) {
544 memberClasses.add(clazz);
545 }
546 }
547 log.debug("{} will be also excluded from check (member class)", memberClasses);
548 classes.removeAll(memberClasses);
549 assertAll(classes);
550 }
551
552 /**
553 * New instance of.
554 *
555 * @param clazz the clazz
556 * @return the object
557 */
558 static Object newInstanceOf(final Class<?> clazz) {
559 try {
560 return clazz.newInstance();
561 } catch (InstantiationException e) {
562 throw new IllegalArgumentException("can't instantiate " + clazz, e);
563 } catch (IllegalAccessException e) {
564 throw new IllegalArgumentException("can't access ctor of " + clazz, e);
565 }
566 }
567
568 /**
569 * Clone.
570 *
571 * @param orig the orig
572 * @return the serializable
573 * @throws NotSerializableException the not serializable exception
574 */
575 static Serializable clone(final Serializable orig) throws NotSerializableException {
576 byte[] bytes = Converter.serialize(orig);
577 try {
578 return Converter.deserialize(bytes);
579 } catch (ClassNotFoundException canthappen) {
580 throw new RuntimeException("ups, something strange happened",
581 canthappen);
582 }
583 }
584
585 /**
586 * Clone.
587 *
588 * @param orig the orig
589 * @return the object
590 */
591 static Object clone(final Object orig) {
592 if (orig instanceof Cloneable) {
593 return CloneableTester.getCloneOf((Cloneable) orig);
594 }
595 try {
596 return clone((Serializable) orig);
597 } catch (ClassCastException e) {
598 log.trace("{} is not serializable - fallback to attribute cloning", orig.getClass(), e);
599 } catch (NotSerializableException nse) {
600 log.warn("can't serialize {} - fallback to attribute cloning", orig.getClass(), nse);
601 }
602 Object clone = newInstanceOf(orig.getClass());
603 Field[] fields = orig.getClass().getDeclaredFields();
604 for (int i = 0; i < fields.length; i++) {
605 fields[i].setAccessible(true);
606 if (ReflectionHelper.isStatic(fields[i])) {
607 continue;
608 }
609 try {
610 Object value = fields[i].get(orig);
611 fields[i].set(clone, value);
612 } catch (IllegalAccessException e) {
613 log.debug("ignore " + fields[i] + " (" + e.getMessage() + ")");
614 }
615 }
616 return clone;
617 }
618
619 /**
620 * Removes "excluded" from the given classes. If one of the "excluded"
621 * class is an interface or abstract class all implementing or subclasses
622 * will be excluded.
623 *
624 * @param classes the classes
625 * @param excluded the excluded
626 */
627 static void removeClasses(final Collection<?> classes, final List<?> excluded) {
628 classes.removeAll(excluded);
629 for (Object obj : excluded) {
630 Class<?> clazz = (Class<?>) obj;
631 if (clazz.isInterface() || isAbstract(clazz)) {
632 removeAssignableClasses(classes, clazz);
633 }
634 }
635 }
636
637 private static boolean isAbstract(final Class<?> clazz) {
638 return Modifier.isAbstract(clazz.getModifiers());
639 }
640
641 private static void removeAssignableClasses(final Collection<?> classes, final Class<?> superclass) {
642 Collection<Class<?>> toBeDeleted = new ArrayList<Class<?>>();
643 for (Object obj : classes) {
644 Class<?> clazz = (Class<?>) obj;
645 if (superclass.isAssignableFrom(clazz)) {
646 if (log.isTraceEnabled()) {
647 log.trace("removing " + clazz + " from list of classes...");
648 }
649 toBeDeleted.add(clazz);
650 }
651 }
652 classes.removeAll(toBeDeleted);
653 }
654
655 }
656