blob: e208753d6d9e4f7d5c3c92326f1195a435337412 [file] [log] [blame]
/*******************************************************************************
* 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));
}
}
}