org.eclipse.skalli.testutil.PropertyTestUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.testutil.PropertyTestUtil.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014 SAP AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.skalli.testutil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.bind.DatatypeConverter;

import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.model.Derived;
import org.eclipse.skalli.model.EntityBase;
import org.eclipse.skalli.model.PropertyName;
import org.junit.Assert;

@SuppressWarnings("nls")
public class PropertyTestUtil {

    public static Map<String, Object> getValues() {
        HashMap<String, Object> values = new HashMap<String, Object>();
        values.put(EntityBase.PROPERTY_UUID, TestUUIDs.TEST_UUIDS[0]);
        values.put(EntityBase.PROPERTY_DELETED, Boolean.FALSE);
        TestExtensibleEntityBase parent = new TestExtensibleEntityBase(TestUUIDs.TEST_UUIDS[1]);
        values.put(EntityBase.PROPERTY_PARENT_ENTITY, parent);
        values.put(EntityBase.PROPERTY_PARENT_ENTITY_ID, TestUUIDs.TEST_UUIDS[1]);
        TestExtensibleEntityBase firstChild = new TestExtensibleEntityBase(TestUUIDs.TEST_UUIDS[2]);
        values.put(EntityBase.PROPERTY_FIRST_CHILD, firstChild);
        TestExtensibleEntityBase nextSibling = new TestExtensibleEntityBase(TestUUIDs.TEST_UUIDS[3]);
        values.put(EntityBase.PROPERTY_NEXT_SIBLING, nextSibling);
        Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); //$NON-NLS-1$
        String lastModified = DatatypeConverter.printDateTime(now);
        values.put(EntityBase.PROPERTY_LAST_MODIFIED, lastModified);
        values.put(EntityBase.PROPERTY_LAST_MODIFIED_BY, "homer"); //$NON-NLS-1$
        return values;
    }

    public static Map<Class<?>, String[]> getRequiredProperties() {
        return new HashMap<Class<?>, String[]>();
    }

    public static final void checkPropertyDefinitions(Class<?> classToCheck,
            Map<Class<?>, String[]> requiredProperties, Map<String, Object> values)
            throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException,
            InstantiationException, InvocationTargetException {
        Class<?> clazz = classToCheck;
        while (clazz != null) {

            // assert that the model class under test has a suitable constructor (either
            // a default constructor if requiredProperties is empty, or a constructor with the
            // correct number and type of parameters), and can be instantiated
            Object instance = assertExistsConstructor(clazz, requiredProperties, values);

            for (Field field : clazz.getDeclaredFields()) {
                if (hasAnnotation(field, PropertyName.class)) {

                    // assert that the field is public static final
                    Assert.assertTrue(clazz.getName() + ": constant " + field.getName() + " is not declared STATIC",
                            (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC); //$NON-NLS-1$
                    Assert.assertTrue(clazz.getName() + ": constant " + field.getName() + " is not declared PUBLIC",
                            (field.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC); //$NON-NLS-1$
                    Assert.assertTrue(clazz.getName() + ": constant " + field.getName() + " is not declared FINAL",
                            (field.getModifiers() & Modifier.FINAL) == Modifier.FINAL); //$NON-NLS-1$

                    // assert that the constant if of type String and has a value assigned.
                    Object object = field.get(null);
                    Assert.assertNotNull(clazz.getName() + ": constant " + field.getName() + " is NULL", object); //$NON-NLS-1$
                    Assert.assertTrue(clazz.getName() + ": constants " + field.getName() + " is not of type STRING",
                            object instanceof String); //$NON-NLS-1$

                    // assert that there is a private non-static field with a name matching the
                    // value of the constant unless the property is marked as derived
                    String fieldName = (String) object;
                    if (!hasAnnotation(field, Derived.class)) {
                        Assert.assertTrue(clazz.getName() + ": must have a private field named " + fieldName,
                                hasPrivateField(clazz, fieldName));
                    }

                    // assert that the values argument contains a test value for this field
                    Assert.assertTrue(clazz.getName() + ": no test value for field " + fieldName,
                            values.containsKey(fieldName));

                    Methods methods = new Methods();

                    // assert that the class has a getter for this property
                    methods.getMethod = assertExistsGetMethod(clazz, fieldName);

                    // assert that the class has a setter for this property if it is an optional property;
                    // required properties must be set in the constructor;
                    // skip properties that are annotated as @Derived
                    if (isOptionalProperty(clazz, fieldName, requiredProperties)
                            && !hasAnnotation(field, Derived.class)) {
                        Class<?> returnType = methods.getMethod.getReturnType();
                        if (!Collection.class.isAssignableFrom(returnType)) {
                            methods.setMethod = assertExistsSetMethod(clazz, fieldName, returnType);
                        } else {
                            Class<?> entryType = ((Collection<?>) values.get(fieldName)).iterator().next()
                                    .getClass();
                            methods.addMethod = assertExistsCollectionMethod(clazz, fieldName, entryType, "add");
                            methods.removeMethod = assertExistsCollectionMethod(clazz, fieldName, entryType,
                                    "remove");
                            methods.hasMethod = assertExistsCollectionMethod(clazz, fieldName, entryType, "has");
                        }
                        // call the setter/adder and getter methods with the given test value
                        if (instance != null) {
                            assertChangeReadCycle(clazz, fieldName, methods, instance, values.get(fieldName));
                        }
                    } else {
                        if (instance != null) {
                            assertReadCycle(clazz, methods, instance, values.get(fieldName));
                        }
                    }
                }
            }

            // check the properties of the parent class (EntityBase!)
            clazz = clazz.getSuperclass();
        }
    }

    private static final class Methods {
        public Method getMethod;
        public Method setMethod;
        public Method addMethod;
        public Method removeMethod;
        public Method hasMethod;
    }

    private static final String getGetMethodName(String fieldName) {
        return "get" + StringUtils.capitalize(fieldName);
    }

    private static final String getSetMethodName(String fieldName) {
        return "set" + StringUtils.capitalize(fieldName);
    }

    private static final String getCollectionMethodName(String fieldName, String prefix) {
        return prefix + singular(fieldName);
    }

    private static final String singular(String fieldName) {
        String name = StringUtils.capitalize(fieldName);
        if (name.endsWith("ies")) {
            name = name.substring(0, name.length() - 3) + "y";
        } else if (name.endsWith("s")) {
            name = name.substring(0, name.length() - 1);
        } else {
            Assert.fail(fieldName + ": is not a valid field name for a collection-like "
                    + "property (must end with 's' or 'ies'");
        }
        return name;
    }

    private static final String getBooleanGetMethodName(String fieldName) {
        return "is" + StringUtils.capitalize(fieldName); //$NON-NLS-1$
    }

    private static boolean hasPrivateField(Class<?> clazz, String fieldName) {
        for (Field field : clazz.getDeclaredFields()) {
            if (fieldName.equals(field.getName()) && ((field.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE)
                    && !((field.getModifiers() & Modifier.STATIC) == Modifier.STATIC)) {
                return true;
            }
        }
        return false;
    }

    private static <T extends Annotation> boolean hasAnnotation(Field field, Class<T> annotationClass) {
        return field.getAnnotation(annotationClass) != null;
    }

    private static boolean isOptionalProperty(Class<?> clazz, String fieldName,
            Map<Class<?>, String[]> requiredProperties) {
        String[] props = requiredProperties.get(clazz);
        if (props == null) {
            return true;
        }
        for (String prop : props) {
            if (fieldName.equals(prop)) {
                return false;
            }
        }
        return true;
    }

    private static Object assertExistsConstructor(Class<?> clazz, Map<Class<?>, String[]> requiredProperties,
            Map<String, Object> values) throws IllegalAccessException, IllegalArgumentException,
            InstantiationException, InvocationTargetException {
        Object instance = null;
        String[] params = requiredProperties.get(clazz);
        if (params == null) {
            try {
                instance = clazz.newInstance();
            } catch (InstantiationException ex) {
                Assert.assertTrue(clazz.getName() + ": class without constructor must be abstract",
                        (clazz.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT);
            }
        } else {
            Class<?>[] paramTypes = new Class<?>[params.length];
            Object[] args = new Object[params.length];
            for (int i = 0; i < params.length; ++i) {
                Object arg = values.get(params[i]);
                Assert.assertNotNull(arg);
                args[i] = arg;
                paramTypes[i] = arg.getClass();
            }
            Constructor<?> c;
            try {
                c = clazz.getConstructor(paramTypes);
                instance = c.newInstance(args);
            } catch (NoSuchMethodException e) {
                Assert.fail(clazz.getName() + ": must have constructor " + clazz.getName() + "("
                        + Arrays.toString(paramTypes) + ")");
            } catch (InstantiationException e) {
                Assert.assertTrue(clazz.getName() + ": class is not instantiable",
                        (clazz.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT);
            }
        }
        return instance;
    }

    /**
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private static Method assertExistsGetMethod(Class<?> clazz, String fieldName) {
        boolean found = false;
        Method getter = null;
        String methodName = null;
        try {
            methodName = getGetMethodName(fieldName);
            getter = clazz.getMethod(methodName, new Class[] {});
            found = true;
        } catch (NoSuchMethodException e) {
            found = false;
        }
        if (!found) {
            try {
                methodName = getBooleanGetMethodName(fieldName);
                getter = clazz.getMethod(methodName, new Class[] {});
                if (getter.getReturnType().getName().equals("boolean")) { //$NON-NLS-1$
                    found = true;
                }
            } catch (NoSuchMethodException e) {
                found = false;
            }
        }
        Assert.assertTrue(clazz.getName() + ": must hava a getter for '" + fieldName + "'", found); //$NON-NLS-1$ //$NON-NLS-2$
        Class<?>[] params = getter.getParameterTypes();
        Assert.assertEquals(clazz.getName() + ": getter " + methodName + " must not have parameters", 0,
                params.length);
        return getter;
    }

    /**
     * Ensure that a setter method with a single parameter exists for the given field name,
     * and that the given field type can be assigned to the parameter of the setter.
     */
    private static Method assertExistsSetMethod(Class<?> clazz, String fieldName, Class<?> fieldType) {
        boolean found = false;
        Method setter = null;
        String methodName = null;
        try {
            methodName = getSetMethodName(fieldName);
            setter = getMethod(clazz, methodName, fieldType);
            found = true;
        } catch (NoSuchMethodException e) {
            found = false;
        }

        Assert.assertTrue(clazz.getName() + ": must have a set method for " + fieldName, found);

        Class<?>[] params = setter.getParameterTypes();
        Assert.assertEquals(clazz.getName() + ": " + methodName + " must have a single parameter", 1,
                params.length);

        Assert.assertTrue(clazz.getName() + ": value of type " + fieldType.getName() + " is not assignable to "
                + "parameter of type " + params[0].getName(), fieldType.isAssignableFrom(params[0]));
        return setter;
    }

    private static Method assertExistsCollectionMethod(Class<?> clazz, String fieldName, Class<?> entryType,
            String prefix) {
        boolean found = false;
        Method adder = null;
        String methodName = null;
        while (!found && entryType != null) {
            try {
                methodName = getCollectionMethodName(fieldName, prefix);
                adder = getMethod(clazz, methodName, entryType);
                found = true;
            } catch (NoSuchMethodException e) {
                // try to find a method that matches the superclass
                // of the given entry type
                entryType = entryType.getSuperclass();
                found = false;
            }
        }
        Assert.assertTrue(clazz.getName() + ": must have a " + prefix + " method for " + fieldName, found);
        Class<?>[] params = adder.getParameterTypes();
        Assert.assertEquals(clazz.getName() + ": " + methodName + " must have a single parameter", 1,
                params.length);
        Assert.assertTrue(clazz.getName() + ": value of type " + entryType.getName() + " is not assignable to "
                + " parameter of type " + params[0].getName(), entryType.isAssignableFrom(params[0]));
        return adder;
    }

    private static Method getMethod(Class<?> clazz, String methodName, Class<?> fieldType)
            throws NoSuchMethodException {
        String name = fieldType.getSimpleName();
        if ("Boolean".equals(name)) {
            return clazz.getMethod(methodName, boolean.class);
        }
        if ("Integer".equals(name)) {
            return clazz.getMethod(methodName, int.class);
        }
        if ("Long".equals(name)) {
            return clazz.getMethod(methodName, long.class);
        }
        if ("Float".equals(name)) {
            return clazz.getMethod(methodName, float.class);
        }
        if ("Double".equals(name)) {
            return clazz.getMethod(methodName, double.class);
        }
        if ("Character".equals(name)) {
            return clazz.getMethod(methodName, char.class);
        }
        return clazz.getMethod(methodName, fieldType);
    }

    private static void assertChangeReadCycle(Class<?> clazz, String fieldName, Methods methods, Object instance,
            Object valueSet) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        if (methods.setMethod != null) {
            methods.setMethod.invoke(instance, valueSet);
            Object valueGet = methods.getMethod.invoke(instance);
            Assert.assertEquals(clazz.getName() + "#" + methods.getMethod.getName() + ":", valueSet, valueGet);
        } else if (methods.addMethod != null) {
            Object first = null;
            Collection<?> collectionSet = (Collection<?>) valueSet;
            for (Object o : collectionSet) {
                methods.addMethod.invoke(instance, o);
                if (first == null) {
                    first = o;
                }
            }
            Collection<?> valueGet = (Collection<?>) methods.getMethod.invoke(instance);
            assertEqualsAnyOrder(clazz.getName() + "#" + methods.getMethod.getName(), collectionSet, valueGet);

            // has-remove-has-add-has cycle
            Assert.assertTrue(clazz.getName() + "#" + methods.hasMethod.getName() + " before remove",
                    (Boolean) methods.hasMethod.invoke(instance, first));
            methods.removeMethod.invoke(instance, first);
            Assert.assertFalse(clazz.getName() + "#" + methods.hasMethod.getName() + " after remove",
                    (Boolean) methods.hasMethod.invoke(instance, first));
            methods.addMethod.invoke(instance, first);
            Assert.assertTrue(clazz.getName() + "#" + methods.hasMethod.getName() + " after add",
                    (Boolean) methods.hasMethod.invoke(instance, first));
        } else {
            Assert.fail(clazz.getName() + ": neither a setter nor an adder available for property " + fieldName);
        }
    }

    private static void assertReadCycle(Class<?> clazz, Methods methods, Object instance, Object value)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        String msg = clazz.getName() + "#" + methods.getMethod.getName() + ": ";
        Object valueGet = methods.getMethod.invoke(instance);
        if (value != null) {
            if (Collection.class.isAssignableFrom(clazz)) {
                assertEqualsAnyOrder(msg, (Collection<?>) value, (Collection<?>) valueGet);
            } else {
                Assert.assertEquals(msg, value, valueGet);
            }
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Assert.assertTrue(msg + " - expected empty collection", ((Collection<?>) valueGet).isEmpty());
        } else if (valueGet instanceof String) {
            Assert.assertEquals(msg, "", valueGet);
        } else if (valueGet instanceof Boolean) {
            Assert.assertEquals(msg, Boolean.FALSE, valueGet);
        } else {
            Assert.assertNull(msg, valueGet);
        }
    }

    private static void assertEqualsAnyOrder(String message, Collection<?> collection1, Collection<?> collection2) {
        Assert.assertEquals(message + "[size]", collection1.size(), collection2.size());
        Iterator<?> it1 = collection1.iterator();
        while (it1.hasNext()) {
            Object next1 = it1.next();
            Assert.assertTrue(message + "[" + next1 + " found]", collection2.contains(next1));
        }
    }
}