| /******************************************************************************* |
| * 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_MILLIS, DatatypeConverter.parseDateTime(lastModified).getTimeInMillis()); |
| 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)); |
| } |
| } |
| } |