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