001/*
002 * #%L
003 * Netarchivesuite - common - test
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.testutils;
025
026import java.lang.reflect.Field;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032
033import dk.netarkivet.common.exceptions.PermissionDenied;
034import dk.netarkivet.common.utils.CleanupIF;
035import junit.framework.TestCase;
036
037/**
038 * Utility class containing various method for making assertions on Class objects.
039 */
040@SuppressWarnings({"unchecked"})
041public class ClassAsserts {
042
043    /**
044     * Tests the class has a static factory method getInstance()
045     *
046     * @param c the class to test
047     */
048    @SuppressWarnings({"rawtypes"})
049    public static void assertHasFactoryMethod(Class c) {
050        Method m = null;
051        try {
052            m = c.getDeclaredMethod("getInstance", (Class[]) null);
053        } catch (NoSuchMethodException e) {
054            TestCase.fail("Expect to find getInstance() method in class " + c.getName());
055        }
056        TestCase.assertEquals("getInstance() method should return " + "an object of the same class: ", c,
057                m.getReturnType());
058        TestCase.assertTrue("getInstance() method should be static:", Modifier.isStatic(m.getModifiers()));
059    }
060
061    /**
062     * Tests that a class has a static factory method getInstance() and that it acts as a singleton. NB This method will
063     * create an instance of the class. It is your responsibility to clean up after yourself.
064     *
065     * @param c the class to test
066     * @return the singleton
067     */
068    public static <T> T assertSingleton(Class<T> c) {
069        assertHasFactoryMethod(c);
070        assertPrivateConstructor(c);
071        T o1 = null;
072        T o2 = null;
073        try {
074            Method m = c.getDeclaredMethod("getInstance");
075            o1 = (T) m.invoke(null);
076            o2 = (T) m.invoke(null);
077        } catch (Exception e) {
078            throw new PermissionDenied("Unexpected error in unit test ", e);
079        }
080        TestCase.assertSame("Expect to find only one distinct instance of " + "class " + c.getName(), o1, o2);
081        if (o1 instanceof CleanupIF) {
082            ((CleanupIF) o1).cleanup();
083        }
084        return o1;
085    }
086
087    /**
088     * Tests if there are any public constructors. Will fail on any public constructor, and simply return otherwise.
089     *
090     * @param c A class to test.
091     */
092    public static <T> void assertPrivateConstructor(Class<T> c) {
093        TestCase.assertEquals("Expect to find no public constructors for " + "class " + c.getName(), 0,
094                c.getConstructors().length);
095    }
096
097    /**
098     * Tests that a class' equals method notice changed fields. Also performs some testing of the hashCode method, but
099     * not as comprehensive.
100     *
101     * @param o1 An object to test on.
102     * @param o2 Another object with all fields to be tested set to different values from o1
103     * @param excludedFields A list of field names of fields that should not be included in the test.
104     * @throws IllegalAccessException
105     */
106    @SuppressWarnings("rawtypes")
107    public static void assertEqualsValid(Object o1, Object o2, List excludedFields) throws IllegalAccessException {
108        TestCase.fail("Unsafe to use with final fields - pending rethought");
109        Class c = o1.getClass();
110        TestCase.assertSame("Class must be the same for both objects", c, o2.getClass());
111        Map<String, Object> fieldValues1 = new HashMap<String, Object>();
112        Map<String, Object> fieldValues2 = new HashMap<String, Object>();
113        Field[] fields = c.getDeclaredFields();
114        for (final Field field : fields) {
115            if (!excludedFields.contains(field.getName()) && !Modifier.isStatic(field.getModifiers())) {
116                field.setAccessible(true);
117                Object fieldValue1 = field.get(o1);
118                fieldValues1.put(field.getName(), fieldValue1);
119                final Object fieldValue2 = field.get(o2);
120                fieldValues2.put(field.getName(), fieldValue2);
121                TestCase.assertNotSame("Values for field " + field + " should not be the same", fieldValue1,
122                        fieldValue2);
123                field.set(o2, fieldValue1);
124            }
125        }
126        for (final Field field : fields) {
127            if (!excludedFields.contains(field.getName()) && !Modifier.isStatic(field.getModifiers())) {
128                TestCase.assertEquals("Objects must be same with all fields equals", o1, o2);
129                field.set(o2, fieldValues2.get(field.getName()));
130                TestCase.assertFalse("Changing field " + field + " should cause non-equals", o1.equals(o2));
131                field.set(o2, fieldValues1.get(field.getName()));
132                TestCase.assertEquals("Objects must have same hashcode with all fields equals", o1.hashCode(),
133                        o2.hashCode());
134            }
135        }
136    }
137}