patterntesting.runtime.junit.CloneableTester Maven / Gradle / Ivy
Show all versions of patterntesting-rt Show documentation
/*
* $Id: CloneableTester.java,v 1.13 2014/01/04 19:28:54 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 06.08.2010 by oliver ([email protected])
*/
package patterntesting.runtime.junit;
import java.lang.reflect.*;
import java.util.*;
import org.slf4j.*;
import patterntesting.runtime.exception.DetailedAssertionError;
import patterntesting.runtime.monitor.ClasspathMonitor;
/**
* This tester checks class which implements Clonable and has therefore
* the clone method implemented. According {@link Object#clone()} the
* following conditions should be true:
*
*
* x.clone() != x
* will be true, and that the expression:
*
*
* x.clone().getClass() == x.getClass()
* will be true, but these are not absolute requirements.
* While it is typically the case that:
*
*
* x.clone().equals(x)
* will be true, this is not an absolute requirement.
*
* So the equals method will be only checked if it is overwritten.
*
* NOTE: In the future this class will be perhaps part of the ObjectTester
* class.
*
* Before v1.1 the methods are named "checkCloning". Since 1.1 these
* methods will have now an "assert" prefix ("assertCloning").
*
* @author oliver
* @since 1.0.2 (06.08.2010)
*/
public final class CloneableTester {
private static final Logger log = LoggerFactory.getLogger(CloneableTester.class);
private static final ClasspathMonitor classpathMonitor = ClasspathMonitor.getInstance();
/** Utility class - no need to instantiate it. */
private CloneableTester() {}
/**
* Check cloning.
*
* @param clazz the clazz
* @since 1.1
*/
public static void assertCloning(final Class extends Cloneable> clazz) {
try {
assertCloning(clazz.newInstance());
} catch (InstantiationException e) {
throw new AssertionError("can't instantiate " + clazz + ":" + e);
} catch (IllegalAccessException e) {
throw new AssertionError("can't access " + clazz + ":" + e);
}
}
/**
* We call the clone method of the given orig paramter.Because the
* clone method is normally "protected" we use reflection to call it.
* Then we compare the orig and cloned object which should be equals.
*
* @param orig the original object
* @since 1.1
*/
public static void assertCloning(final Cloneable orig) {
Cloneable clone = getCloneOf(orig);
if (ObjectTester.hasEqualsDeclared(orig.getClass())) {
ObjectTester.assertEquals(orig, clone);
}
}
/**
* Gets the clone of the given Cloneable object.
*
* @param orig the orig
* @return the clone of
* @throws AssertionError the assertion error
*/
public static Cloneable getCloneOf(final Cloneable orig) throws AssertionError {
try {
Method cloneMethod = getPublicCloneMethod(orig.getClass());
Cloneable clone = (Cloneable) cloneMethod.invoke(orig);
if (clone == orig) {
throw new AssertionError(clone + " must have another reference as original object");
}
return clone;
} catch (SecurityException e) {
throw new DetailedAssertionError("can't access clone method of " + orig.getClass(), e);
} catch (IllegalAccessException e) {
throw new DetailedAssertionError("can't access clone method of " + orig.getClass(), e);
} catch (InvocationTargetException e) {
throw new DetailedAssertionError("clone of " + orig.getClass() + " failed", e);
}
}
private static Method getPublicCloneMethod(final Class extends Cloneable> cloneClass) throws AssertionError {
try {
return cloneClass.getMethod("clone");
} catch (NoSuchMethodException nsme) {
if (hasCloneMethod(cloneClass)) {
throw new AssertionError("clone() is not public in " + cloneClass);
}
AssertionError error = new AssertionError("no clone method found in " + cloneClass);
error.initCause(nsme);
throw error;
}
}
private static boolean hasCloneMethod(final Class extends Cloneable> cloneClass) {
try {
Method m = cloneClass.getDeclaredMethod("clone");
if (log.isTraceEnabled()) {
log.trace(m + " found in " + cloneClass);
}
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
/**
* Check for each class in the given collection if it can be cloned
* correct.
*
* @param classes a collection of classes to be checked
* @since 1.1
*/
public static void assertCloning(final Collection> classes) {
for (Class clazz : classes) {
assertCloning(clazz);
}
}
/**
* Check for each class in the given package if it can be cloned
* 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 #assertCloningOfPackage(String)}.
*
* @param pkg the package e.g. "patterntesting.runtime"
* @see #assertCloningOfPackage(String)
* @since 1.1
*/
public static void assertCloning(final Package pkg) {
assert pkg!= null;
assertCloningOfPackage(pkg.getName());
}
/**
* Check for each class in the given package if it can be cloned
* correct.
*
* @param packageName the package name e.g. "patterntesting.runtime"
* @since 1.1
*/
public static void assertCloningOfPackage(final String packageName) {
assert packageName != null;
Collection> cloneables = getCloneableClasses(packageName);
assertCloning(cloneables);
}
/**
* Check for each class in the given package if the clone method is
* implemented correct.
*
* @param packageName the package name e.g. "patterntesting.runtime"
* @param excluded classes which should be excluded from the check
* @see #assertCloningOfPackage(String)
* @since 1.1
*/
@SuppressWarnings("unchecked")
public static void assertCloningOfPackage(final String packageName,
final Class extends Cloneable>... excluded) {
List> excludedList = Arrays.asList((Class[]) excluded);
assertCloningOfPackage(packageName, excludedList);
}
/**
* Check for each class in the given package if the clone method is
* implemented correct.
*
* @param packageName the package name e.g. "patterntesting.runtime"
* @param excluded classes which should be excluded from the check
* @see #assertCloningOfPackage(String)
* @since 1.1
*/
public static void assertCloningOfPackage(final String packageName,
final List> excluded) {
Collection> classes = getCloneableClasses(packageName);
log.debug(excluded + " will be excluded from check");
ObjectTester.removeClasses(classes, excluded);
assertCloning(classes);
}
private static Collection> getCloneableClasses(final String packageName) {
Collection> cloneables = classpathMonitor.getClassList(packageName,
Cloneable.class);
return cloneables;
}
}