package net.dossot.jbound.exercise;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.dossot.jbound.api.EXERCISE;
/**
* When we exercise a class, we do not care about the actual result of equal,
* hashcode, toString or bean properties, we just check they are not throwing generic
* exceptions.
*
* @author David Dossot (david@dossot.net)
*/
final class Runner implements Runnable
{
private final Set<String> accepted;
private final List<Class<?>> exercisedClasses;
private final Set<EXERCISE> skipped;
private final Map<Class<?>, Object[]> customTestData;
public Runner(final List<Class<?>> exercisedClasses,
final Set<EXERCISE> skipped,
final Set<String> accepted,
final Map<Class<?>, Object[]> customTestData)
{
this.exercisedClasses = exercisedClasses;
this.skipped = skipped;
this.accepted = accepted;
this.customTestData = customTestData;
}
private void acceptOrRethrow(final AccessibleObject context, final InvocationTargetException ite)
{
final Throwable iteCause = ite.getCause();
if (iteCause == null)
{
// this is a problem with JBound doing a particular exercise
ite.printStackTrace();
return;
}
if (iteCause.getMessage() != null)
{
// we got an exception but it has a message, so it is probably a
// user crafted one, hence we accept it silently
return;
}
final String contextAsString = context.toString();
if (accepted.contains(contextAsString))
{
// we have be told to accept this as a source of generic exceptions
return;
}
// this is an exception without a message, so most probably a generic
// one
final AssertionError assertionError = new AssertionError("Received a generic " + iteCause.getClass()
+ " when calling: " + contextAsString);
assertionError.initCause(iteCause);
throw assertionError;
}
/**
* Recursively build constructor parameters until it can be invoked.
*/
private List<Object> newInstancesFrom(final Constructor<?> constructor, final List<Object> parameters)
{
final List<Object> result = new ArrayList<Object>();
if (parameters.size() >= constructor.getParameterTypes().length)
{
final Object newInstance = instantiateClass(constructor, parameters);
if (newInstance != null)
{
result.add(newInstance);
}
}
else
{
final Class<?> parameterType = constructor.getParameterTypes()[parameters.size()];
for (final Object testValue : getTestDataFor(parameterType))
{
final List<Object> nextParameters = new ArrayList<>(parameters);
nextParameters.add(testValue);
result.addAll(newInstancesFrom(constructor, nextParameters));
}
}
return result;
}
private Object instantiateClass(final Constructor<?> constructor, final List<Object> parameters)
{
try
{
return constructor.newInstance(parameters.toArray());
}
catch (final IllegalArgumentException e)
{
Support.handleInternalException(e);
}
catch (final InstantiationException e)
{
Support.handleInternalException(e);
}
catch (final IllegalAccessException e)
{
Support.handleInternalException(e);
}
catch (final InvocationTargetException e)
{
acceptOrRethrow(constructor, e);
}
return null;
}
private List<Object> newInstancesOf(final Class<?> exercisedClass)
{
final List<Object> result = new ArrayList<Object>();
for (final Constructor<?> constructor : exercisedClass.getConstructors())
{
result.addAll(newInstancesFrom(constructor, new ArrayList<>()));
}
return Collections.unmodifiableList(result);
}
public void run()
{
for (final Class<?> exercisedClass : exercisedClasses)
{
exerciseClass(exercisedClass);
}
}
private void exerciseClass(final Class<?> exercisedClass)
{
System.out.println("Exercising class: " + exercisedClass);
exerciseObjects(exercisedClass, newInstancesOf(exercisedClass));
}
private void exerciseObjects(final Class<?> exercisedClass, final List<Object> exercisedObjects)
{
for (final Object exercisedObject : exercisedObjects)
{
exerciseObject(exercisedClass, exercisedObjects, exercisedObject);
}
}
private void exerciseObject(final Class<?> exercisedClass,
final List<Object> exercisedObjects,
final Object exercisedObject)
{
exerciseEquals(exercisedObjects, exercisedObject);
exerciseHashCode(exercisedObject);
exerciseToString(exercisedObject);
exerciseBeanProperties(exercisedClass, exercisedObject);
}
private void exerciseBeanProperties(final Class<?> exercisedClass, final Object exercisedObject)
{
if (!skipped.contains(EXERCISE.BEAN_PROPERTIES))
{
try
{
final PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(exercisedClass)
.getPropertyDescriptors();
if (propertyDescriptors != null)
{
exerciseBeanProperties(exercisedObject, propertyDescriptors);
}
}
catch (final IntrospectionException e)
{
Support.handleInternalException(e);
}
}
}
private void exerciseBeanProperties(final Object exercisedObject,
final PropertyDescriptor[] propertyDescriptors)
{
for (final PropertyDescriptor propertyDescriptor : propertyDescriptors)
{
exercisedReadMethod(exercisedObject, propertyDescriptor);
exercisedWriteMethod(exercisedObject, propertyDescriptor);
}
}
private void exercisedWriteMethod(final Object exercisedObject,
final PropertyDescriptor propertyDescriptor)
{
final Method writeMethod = propertyDescriptor.getWriteMethod();
if (writeMethod != null)
{
final Class<?> setterParameterClass = writeMethod.getParameterTypes()[0];
for (final Object testValue : getTestDataFor(setterParameterClass))
{
try
{
writeMethod.invoke(exercisedObject, testValue);
}
catch (final IllegalAccessException e)
{
Support.handleInternalException(e);
}
catch (final IllegalArgumentException e)
{
Support.handleInternalException(e);
}
catch (final InvocationTargetException e)
{
acceptOrRethrow(writeMethod, e);
}
}
}
}
private Object[] getTestDataFor(final Class<?> targetClass)
{
final Object[] testData = customTestData.get(targetClass);
if (testData != null)
{
return testData;
}
return Data.getTestDataFor(targetClass);
}
private void exercisedReadMethod(final Object exercisedObject, final PropertyDescriptor propertyDescriptor)
{
final Method readMethod = propertyDescriptor.getReadMethod();
if (readMethod != null)
{
try
{
readMethod.invoke(exercisedObject);
}
catch (final IllegalArgumentException e)
{
Support.handleInternalException(e);
}
catch (final InvocationTargetException e)
{
acceptOrRethrow(readMethod, e);
}
catch (final IllegalAccessException e)
{
Support.handleInternalException(e);
}
}
}
private void exerciseToString(final Object exercisedObject)
{
if (!skipped.contains(EXERCISE.TO_STRING))
{
exercisedObject.toString();
}
}
private void exerciseHashCode(final Object exercisedObject)
{
if (!skipped.contains(EXERCISE.HASHCODE))
{
exercisedObject.hashCode();
}
}
private void exerciseEquals(final List<Object> exercisedObjects, final Object exercisedObject)
{
if (!skipped.contains(EXERCISE.EQUALS))
{
exercisedObject.equals(null);
exercisedObject.equals(new Object());
for (final Object exercisedObjectBis : exercisedObjects)
{
exercisedObject.equals(exercisedObjectBis);
}
}
}
}