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