All Downloads are FREE. Search and download functionalities are using the official Maven repository.

patterntesting.runtime.junit.ObjectTester Maven / Gradle / Ivy

Go to download

PatternTesting Runtime (patterntesting-rt) is the runtime component for the PatternTesting framework. It provides the annotations and base classes for the PatternTesting testing framework (e.g. patterntesting-check, patterntesting-concurrent or patterntesting-exception) but can be also used standalone for classpath monitoring or profiling. It uses AOP and AspectJ to perform this feat.

There is a newer version: 2.4.0
Show newest version
/*
 * $Id: ObjectTester.java,v 1.34 2014/04/30 20:03:11 oboehm Exp $
 *
 * Copyright (c) 2010 by Oliver Boehm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 21.07.2010 by oliver ([email protected])
 */

package patterntesting.runtime.junit;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.junit.Assert;
import org.slf4j.*;

import patterntesting.runtime.monitor.ClasspathMonitor;
import patterntesting.runtime.util.*;

/**
 * This is a utility class to check some important methods of a class like the.
 *
 * {@link Object#equals(Object)} or {@link Object#hashCode()} method.
 * Before v1.1 the methods are named "checkEquals" or "checkCompareTo".
 * Since 1.1 these methods will have now an "assert" prefix ("assertEquals"
 * or "assertCompareTo").
 *
 * @author oliver
 * @since 1.0.3 (21.07.2010)
 */
public final class ObjectTester {

    private static final Logger log = LoggerFactory.getLogger(ObjectTester.class);
    private static final ClasspathMonitor classpathMonitor = ClasspathMonitor.getInstance();

    /** Utility class - no need to instantiate it. */
    private ObjectTester() {}

    /**
     * Check equality of the given objects. They must be equals otherwise an
     * AssertionError will be thrown. And if A == B also
     * B == A must be true (commutative law, i.e. it is a
     * symmetrical operation).
     * 

* If two objects are equals they must have also the same hash code (but * not the other way around). This condition is also checked here. *

*

* Often programmers forget that the {@link Object#equals(Object)} method * can be called with null as argument and should return * false as result. So this case is also tested here. *

* * @param o1 the 1st object * @param o2 the 2nd object * @throws AssertionError if the check fails * @since 1.1 */ @SuppressWarnings("unchecked") public static void assertEquals(final Object o1, final Object o2) throws AssertionError { try { Package pkg = (Package) o1; Class c2 = (Class) o2; Class[] excluded = { c2 }; assertEquals(pkg, excluded); } catch (ClassCastException cce) { Assert.assertEquals(o1.getClass() + ": objects are not equals!", o1, o2); Assert.assertEquals(o1.getClass() + ": equals not symmetrical (A == B, but B != A)", o2, o1); Assert.assertEquals(o1.getClass() + ": objects are equals but hashCode differs!", o1.hashCode(), o2.hashCode()); if (o1 instanceof Comparable) { ComparableTester.assertCompareTo((Comparable>) o1, (Comparable>) o1); } assertEqualsWithNull(o1); } log.info("equals/hashCode implementation of " + o1.getClass() + " seems to be ok"); } /** * Null as argument for the equals method should always return 'false' and * should not end with a NullPointerException. * * @param obj the obj */ private static void assertEqualsWithNull(final Object obj) { try { Assert.assertFalse(obj.getClass().getName() + ".equals(null) should return 'false'", obj.equals(null)); } catch (RuntimeException re) { AssertionError error = new AssertionError(obj.getClass().getName() + ".equals(..) implementation does not check (correct) for null argument"); error.initCause(re); throw error; } } /** * The given object will be serialized and deserialized to get a copy of * that object. The copy must be equals to the original object. * * @param obj the object * @throws AssertionError if the check fails * @throws NotSerializableException if obj is not serializable * @since 1.1 */ public static void assertEquals(final Serializable obj) throws AssertionError, NotSerializableException { Object clone = clone(obj); assertEquals(obj, clone); } /** * The given object will be cloned to get a copy of that object. * The copy must be equals to the original object. * * @param obj the obj * @throws AssertionError the assertion error * @since 1.1 */ public static void assertEquals(final Cloneable obj) throws AssertionError { Object clone = CloneableTester.getCloneOf(obj); assertEquals(obj, clone); } /** * This method will create two objects of the given class using the * default constructor. So three preconditions must be true: *
    *
  1. the class must not be abstract
  2. *
  3. there must be a (public) default constructor
  4. *
  5. it must be Cloneable, Serializable or * return always the same object *
  6. *
* That a constructor creates equals objects is not true for all classes. * For example the default constructor * of the Date class will generate objects with different timestamps * which are not equal. But most classes should meet the precondition. * * @param clazz the clazz * @throws AssertionError if the check fails * @since 1.1 */ public static void assertEquals(final Class clazz) throws AssertionError { log.trace("checking {}.equals...", clazz); Object o1 = newInstanceOf(clazz); try { assertEquals((Cloneable) o1); } catch (ClassCastException cce) { try { assertEquals((Serializable) o1); } catch (NotSerializableException nse) { throw new AssertionError(nse); } catch (ClassCastException e) { Object o2 = newInstanceOf(clazz); assertEquals(o1, o2); } } } /** * This method will create two objects of the given class by trying to * clone them in different ways. The last try to get a copy will be a * cloning of all attributes. But this is normally not very significant * because most (wrong) equals implementation depends on a simple * comparison of the object reference. * * @param clazz the clazz * @throws AssertionError if the check fails * @since 1.1 */ public static void assertEqualsWithClone(final Class clazz) throws AssertionError { if (log.isTraceEnabled()) { log.trace("checking " + clazz.getName() + ".equals..."); } Object o1 = newInstanceOf(clazz); try { assertEquals((Cloneable) o1); } catch (ClassCastException cce) { try { assertEquals((Serializable) o1); } catch (NotSerializableException nse) { throw new AssertionError(nse); } catch (ClassCastException e) { Object o2 = clone(o1); assertEquals(o1, o2); } } } /** * Check for each class in the given collection if the equals() method * is implemented correct. * * @param classes the classes * @throws Failures the collected assertion errors * @since 1.1 */ public static void assertEquals(final Collection> classes) throws Failures { Failures failures = new Failures(); for (Class clazz : classes) { try { assertEquals(clazz); } catch (AssertionError e) { log.warn("equals/hashCode implementation of " + clazz + " is NOT OK (" + e.getMessage() + ")"); failures.add(clazz, e); } } if (failures.hasErrors()) { throw failures; } } /** * Check for each class in the given package if the equals() method * is implemented correct. *
* To get a name of a package call {@link Package#getPackage(String)}. * But be sure that you can't get null as result. In this case * use {@link #assertEqualsOfPackage(String)}. * * @param pkg the package e.g. "patterntesting.runtime" * @see #assertEqualsOfPackage(String) * @since 1.1 */ public static void assertEquals(final Package pkg) { assert pkg!= null; assertEqualsOfPackage(pkg.getName()); } /** * Check for each class in the given package if the equals() method * is implemented correct. *
* To get a name of a package call {@link Package#getPackage(String)}. * But be sure that you can't get null as result. In this case * use {@link #assertEqualsOfPackage(String, Class...)}. * * @param pkg the package e.g. "patterntesting.runtime" * @param excluded classes which are excluded from the check * @see #assertEqualsOfPackage(String, Class...) * @since 1.1 */ public static void assertEquals(final Package pkg, final Class... excluded) { assert pkg!= null; assertEqualsOfPackage(pkg.getName(), excluded); } /** * Check for each class in the given package if the equals() method * is implemented correct. *
* To get a name of a package call {@link Package#getPackage(String)}. * But be sure that you can't get null as result. In this case * use {@link #assertEqualsOfPackage(String, List)}. * * @param pkg the package e.g. "patterntesting.runtime" * @param excluded classes which should be excluded from the check * @see #assertEqualsOfPackage(String, List) * @since 1.1 */ public static void assertEqualsOfPackage(final Package pkg, final List> excluded) { assert pkg!= null; assertEqualsOfPackage(pkg.getName(), excluded); } /** * Check for each class in the given package if the equals() method * is implemented correct. *
* This method does the same as {@link #assertEquals(Package)} but was * introduced by {@link Package#getPackage(String)} sometimes return null * if no class of this package is loaded. * * @param packageName the package name e.g. "patterntesting.runtime" * @see #assertEquals(Package) * @since 1.1 */ public static void assertEqualsOfPackage(final String packageName) { Collection> classes = getClassesWithDeclaredEquals(packageName); assertEquals(classes); } /** * Check for each class in the given package if the equals() method * is implemented correct. * * @param packageName the package name e.g. "patterntesting.runtime" * @param excluded classes which should be excluded from the check * @see #assertEqualsOfPackage(String) * @since 1.1 */ public static void assertEqualsOfPackage(final String packageName, final Class... excluded) { List> excludedList = Arrays.asList(excluded); assertEqualsOfPackage(packageName, excludedList); } /** * Check for each class in the given package if the equals() method * is implemented correct. * * @param packageName the package name e.g. "patterntesting.runtime" * @param excluded classes which should be excluded from the check * @see #assertEqualsOfPackage(String) * @since 1.1 */ public static void assertEqualsOfPackage(final String packageName, final List> excluded) { Collection> classes = getClassesWithDeclaredEquals(packageName); log.debug(excluded + " will be excluded from check"); removeClasses(classes, excluded); assertEquals(classes); } private static Collection> getClassesWithDeclaredEquals(final String packageName) { assert packageName != null; Collection> concreteClasses = classpathMonitor.getConcreteClassList(packageName); Collection> classes = new ArrayList>(concreteClasses.size()); for (Class clazz : concreteClasses) { if (!hasEqualsDeclared(clazz)) { log.debug(clazz + " will be ignored (equals(..) not overwritten)"); continue; } classes.add(clazz); } return classes; } /** * If you want to know if a class (or one of its super classes, except * object) has overwritten the equals method you * can use this method here. * * @param clazz the clazz * @return true, if successful */ public static boolean hasEqualsDeclared(final Class clazz) { try { Method method = clazz.getMethod("equals", Object.class); Class declaring = method.getDeclaringClass(); return !declaring.equals(Object.class); } catch (SecurityException e) { log.info("can't get equals(..) method of " + clazz, e); return false; } catch (NoSuchMethodException e) { return false; } } /** * Check equality of the given objects by using the compareTo() method. * Because casting an object to the expected Comparable is awesome we * provide this additional method here * * @param o1 the first object (must be of type Comparable) * @param o2 the second object (must be of type Comparable) * @throws AssertionError if the check fails * @see ComparableTester#assertCompareTo(Comparable, Comparable) * @since 1.1 */ @SuppressWarnings("unchecked") public static void assertCompareTo(final Object o1, final Object o2) throws AssertionError { ComparableTester.assertCompareTo((Comparable>) o1, (Comparable>) o2); } /** * If a object is only partially initalized it sometimes can happen, that * calling the toString() method will result in a NullPointerException. * This should not happen so there are several check methods available * where you can proof it. * * @param obj the object to be checked * @since 1.1 */ public static void assertToString(final Object obj) { if (hasToStringDefaultImpl(obj)) { log.info(obj.getClass() + " has default implementation of toString()"); } } /** * Normally you should overwrite the toString() method for better logging * and debugging. This is the method to check it. * * @param obj the object to be checked * @return true, if object has default implementation */ public static boolean hasToStringDefaultImpl(final Object obj) { try { String s = obj.toString(); return s.startsWith(obj.getClass().getName() + "@"); } catch (Throwable t) { log.info("The toString implementation of " + obj.getClass() + " seems to be overwritten because error happens.", t); return false; } } /** * Normally you should overwrite the toString() method for better logging * and debugging. This is the method to check it. * * @param clazz the clazz * @return true, if object has default implementation */ public static boolean hasToStringDefaultImpl(final Class clazz) { Object obj = newInstanceOf(clazz); return hasToStringDefaultImpl(obj); } /** * Starts all known checks like checkEquals(..), checks from the * SerializableTester (if the given class is serializable) or from * other classes. * * @param clazz the clazz to be checked. * @since 1.1 */ @SuppressWarnings("unchecked") public static void assertAll(final Class clazz) { if (log.isTraceEnabled()) { log.trace("checking all of " + clazz + "..."); } if (hasEqualsDeclared(clazz)) { assertEquals(clazz); } if (hasToStringDefaultImpl(clazz)) { log.info(clazz + " has default implementation of toString()"); } if (clazz.isAssignableFrom(Serializable.class)) { try { SerializableTester.assertSerialization(clazz); } catch (NotSerializableException e) { throw new AssertionError(e); } } if (clazz.isAssignableFrom(Cloneable.class)) { CloneableTester.assertCloning((Class) clazz); } } /** * Check all. * * @param classes the classes to be checked * @since 1.1 */ public static void assertAll(final Collection> classes) { for (Class clazz : classes) { assertAll(clazz); } } /** * Starts all known checks for all classes of the given package. *
* To get a name of a package call {@link Package#getPackage(String)}. * But be sure that you can't get null as result. In this case * use {@link #assertAllOfPackage(String)}. * * @param pkg the package e.g. "patterntesting.runtime" * @since 1.1 */ public static void assertAll(final Package pkg) { assert pkg != null; assertAllOfPackage(pkg.getName()); } /** * Starts all known checks for all classes of the given package except for * the "excluded" classes. *
* To get a name of a package call {@link Package#getPackage(String)}. * But be sure that you can't get null as result. In this case * use {@link #assertEqualsOfPackage(String, Class...)}. * * @param pkg the package e.g. "patterntesting.runtime" * @param excluded classes which are excluded from the check * @see #assertAllOfPackage(String, Class...) * @since 1.1 */ public static void assertAll(final Package pkg, final Class... excluded) { assert pkg!= null; assertAllOfPackage(pkg.getName(), excluded); } /** * Starts all known checks for all classes of the given package. * * @param packageName the package e.g. "patterntesting.runtime" * @since 1.1 */ public static void assertAllOfPackage(final String packageName) { assert packageName != null; assertAllOfPackage(packageName, new ArrayList>()); } /** * Starts all known checks for all classes of the given package but not * for the "excluded" classes. * * @param packageName the package name e.g. "patterntesting.runtime" * @param excluded classes which should be excluded from the check * @see #assertAllOfPackage(String) * @since 1.1 */ public static void assertAllOfPackage(final String packageName, final Class... excluded) { List> excludedList = Arrays.asList(excluded); assertAllOfPackage(packageName, excludedList); } /** * Starts all known checks for all classes of the given package but not * for the "excluded" classes. * * @param packageName the package name e.g. "patterntesting.runtime" * @param excluded classes which should be excluded from the check * @see #assertEqualsOfPackage(String) * @since 1.1 */ public static void assertAllOfPackage(final String packageName, final List> excluded) { assert packageName != null; Collection> classes = classpathMonitor.getConcreteClassList(packageName); log.debug("{} will be excluded from check", excluded); classes.removeAll(excluded); Collection> memberClasses = new ArrayList>(); for (Class clazz : classes) { if (clazz.isMemberClass()) { memberClasses.add(clazz); } } log.debug("{} will be also excluded from check (member class)", memberClasses); classes.removeAll(memberClasses); assertAll(classes); } /** * New instance of. * * @param clazz the clazz * @return the object */ static Object newInstanceOf(final Class clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { throw new IllegalArgumentException("can't instantiate " + clazz, e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("can't access ctor of " + clazz, e); } } /** * Clone. * * @param orig the orig * @return the serializable * @throws NotSerializableException the not serializable exception */ static Serializable clone(final Serializable orig) throws NotSerializableException { byte[] bytes = Converter.serialize(orig); try { return Converter.deserialize(bytes); } catch (ClassNotFoundException canthappen) { throw new RuntimeException("ups, something strange happened", canthappen); } } /** * Clone. * * @param orig the orig * @return the object */ static Object clone(final Object orig) { if (orig instanceof Cloneable) { return CloneableTester.getCloneOf((Cloneable) orig); } try { return clone((Serializable) orig); } catch (ClassCastException e) { log.trace("{} is not serializable - fallback to attribute cloning", orig.getClass(), e); } catch (NotSerializableException nse) { log.warn("can't serialize {} - fallback to attribute cloning", orig.getClass(), nse); } Object clone = newInstanceOf(orig.getClass()); Field[] fields = orig.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { fields[i].setAccessible(true); if (ReflectionHelper.isStatic(fields[i])) { continue; } try { Object value = fields[i].get(orig); fields[i].set(clone, value); } catch (IllegalAccessException e) { log.debug("ignore " + fields[i] + " (" + e.getMessage() + ")"); } } return clone; } /** * Removes "excluded" from the given classes. If one of the "excluded" * class is an interface or abstract class all implementing or subclasses * will be excluded. * * @param classes the classes * @param excluded the excluded */ static void removeClasses(final Collection classes, final List excluded) { classes.removeAll(excluded); for (Object obj : excluded) { Class clazz = (Class) obj; if (clazz.isInterface() || isAbstract(clazz)) { removeAssignableClasses(classes, clazz); } } } private static boolean isAbstract(final Class clazz) { return Modifier.isAbstract(clazz.getModifiers()); } private static void removeAssignableClasses(final Collection classes, final Class superclass) { Collection> toBeDeleted = new ArrayList>(); for (Object obj : classes) { Class clazz = (Class) obj; if (superclass.isAssignableFrom(clazz)) { if (log.isTraceEnabled()) { log.trace("removing " + clazz + " from list of classes..."); } toBeDeleted.add(clazz); } } classes.removeAll(toBeDeleted); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy