001 /*
002 * $Id: CloneableTester.java,v 1.13 2014/01/04 19:28:54 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 06.08.2010 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.junit;
022
023 import java.lang.reflect.*;
024 import java.util.*;
025
026 import org.slf4j.*;
027
028 import patterntesting.runtime.exception.DetailedAssertionError;
029 import patterntesting.runtime.monitor.ClasspathMonitor;
030
031 /**
032 * This tester checks class which implements Clonable and has therefore
033 * the clone method implemented. According {@link Object#clone()} the
034 * following conditions should be true:
035 * <blockquote>
036 * <pre>
037 * x.clone() != x</pre></blockquote>
038 * will be true, and that the expression:
039 * <blockquote>
040 * <pre>
041 * x.clone().getClass() == x.getClass()</pre></blockquote>
042 * will be <tt>true</tt>, but these are not absolute requirements.
043 * While it is typically the case that:
044 * <blockquote>
045 * <pre>
046 * x.clone().equals(x)</pre></blockquote>
047 * will be <tt>true</tt>, this is not an absolute requirement.
048 * <p>
049 * So the equals method will be only checked if it is overwritten.
050 * <br/>
051 * NOTE: In the future this class will be perhaps part of the ObjectTester
052 * class.
053 * <br/>
054 * Before v1.1 the methods are named "checkCloning". Since 1.1 these
055 * methods will have now an "assert" prefix ("assertCloning").
056 *
057 * @author oliver
058 * @since 1.0.2 (06.08.2010)
059 */
060 public final class CloneableTester {
061
062 private static final Logger log = LoggerFactory.getLogger(CloneableTester.class);
063 private static final ClasspathMonitor classpathMonitor = ClasspathMonitor.getInstance();
064
065 /** Utility class - no need to instantiate it. */
066 private CloneableTester() {}
067
068 /**
069 * Check cloning.
070 *
071 * @param clazz the clazz
072 * @since 1.1
073 */
074 public static void assertCloning(final Class<? extends Cloneable> clazz) {
075 try {
076 assertCloning(clazz.newInstance());
077 } catch (InstantiationException e) {
078 throw new AssertionError("can't instantiate " + clazz + ":" + e);
079 } catch (IllegalAccessException e) {
080 throw new AssertionError("can't access " + clazz + ":" + e);
081 }
082 }
083
084 /**
085 * We call the clone method of the given orig paramter.Because the
086 * clone method is normally "protected" we use reflection to call it.
087 * Then we compare the orig and cloned object which should be equals.
088 *
089 * @param orig the original object
090 * @since 1.1
091 */
092 public static void assertCloning(final Cloneable orig) {
093 Cloneable clone = getCloneOf(orig);
094 if (ObjectTester.hasEqualsDeclared(orig.getClass())) {
095 ObjectTester.assertEquals(orig, clone);
096 }
097 }
098
099 /**
100 * Gets the clone of the given Cloneable object.
101 *
102 * @param orig the orig
103 * @return the clone of
104 * @throws AssertionError the assertion error
105 */
106 public static Cloneable getCloneOf(final Cloneable orig) throws AssertionError {
107 try {
108 Method cloneMethod = getPublicCloneMethod(orig.getClass());
109 Cloneable clone = (Cloneable) cloneMethod.invoke(orig);
110 if (clone == orig) {
111 throw new AssertionError(clone + " must have another reference as original object");
112 }
113 return clone;
114 } catch (SecurityException e) {
115 throw new DetailedAssertionError("can't access clone method of " + orig.getClass(), e);
116 } catch (IllegalAccessException e) {
117 throw new DetailedAssertionError("can't access clone method of " + orig.getClass(), e);
118 } catch (InvocationTargetException e) {
119 throw new DetailedAssertionError("clone of " + orig.getClass() + " failed", e);
120 }
121 }
122
123 private static Method getPublicCloneMethod(final Class<? extends Cloneable> cloneClass) throws AssertionError {
124 try {
125 return cloneClass.getMethod("clone");
126 } catch (NoSuchMethodException nsme) {
127 if (hasCloneMethod(cloneClass)) {
128 throw new AssertionError("clone() is not public in " + cloneClass);
129 }
130 AssertionError error = new AssertionError("no clone method found in " + cloneClass);
131 error.initCause(nsme);
132 throw error;
133 }
134 }
135
136 private static boolean hasCloneMethod(final Class<? extends Cloneable> cloneClass) {
137 try {
138 Method m = cloneClass.getDeclaredMethod("clone");
139 if (log.isTraceEnabled()) {
140 log.trace(m + " found in " + cloneClass);
141 }
142 return true;
143 } catch (NoSuchMethodException e) {
144 return false;
145 }
146 }
147
148 /**
149 * Check for each class in the given collection if it can be cloned
150 * correct.
151 *
152 * @param classes a collection of classes to be checked
153 * @since 1.1
154 */
155 public static void assertCloning(final Collection<Class<Cloneable>> classes) {
156 for (Class<Cloneable> clazz : classes) {
157 assertCloning(clazz);
158 }
159 }
160
161 /**
162 * Check for each class in the given package if it can be cloned
163 * correct.
164 * <br/>
165 * To get a name of a package call {@link Package#getPackage(String)}.
166 * But be sure that you can't get <em>null</em> as result. In this case
167 * use {@link #assertCloningOfPackage(String)}.
168 *
169 * @param pkg the package e.g. "patterntesting.runtime"
170 * @see #assertCloningOfPackage(String)
171 * @since 1.1
172 */
173 public static void assertCloning(final Package pkg) {
174 assert pkg!= null;
175 assertCloningOfPackage(pkg.getName());
176 }
177
178 /**
179 * Check for each class in the given package if it can be cloned
180 * correct.
181 *
182 * @param packageName the package name e.g. "patterntesting.runtime"
183 * @since 1.1
184 */
185 public static void assertCloningOfPackage(final String packageName) {
186 assert packageName != null;
187 Collection<Class<Cloneable>> cloneables = getCloneableClasses(packageName);
188 assertCloning(cloneables);
189 }
190
191 /**
192 * Check for each class in the given package if the clone method is
193 * implemented correct.
194 *
195 * @param packageName the package name e.g. "patterntesting.runtime"
196 * @param excluded classes which should be excluded from the check
197 * @see #assertCloningOfPackage(String)
198 * @since 1.1
199 */
200 @SuppressWarnings("unchecked")
201 public static void assertCloningOfPackage(final String packageName,
202 final Class<? extends Cloneable>... excluded) {
203 List<Class<Cloneable>> excludedList = Arrays.asList((Class<Cloneable>[]) excluded);
204 assertCloningOfPackage(packageName, excludedList);
205 }
206
207 /**
208 * Check for each class in the given package if the clone method is
209 * implemented correct.
210 *
211 * @param packageName the package name e.g. "patterntesting.runtime"
212 * @param excluded classes which should be excluded from the check
213 * @see #assertCloningOfPackage(String)
214 * @since 1.1
215 */
216 public static void assertCloningOfPackage(final String packageName,
217 final List<Class<Cloneable>> excluded) {
218 Collection<Class<Cloneable>> classes = getCloneableClasses(packageName);
219 log.debug(excluded + " will be excluded from check");
220 ObjectTester.removeClasses(classes, excluded);
221 assertCloning(classes);
222 }
223
224 private static Collection<Class<Cloneable>> getCloneableClasses(final String packageName) {
225 Collection<Class<Cloneable>> cloneables = classpathMonitor.getClassList(packageName,
226 Cloneable.class);
227 return cloneables;
228 }
229
230 }
231