/*
* Copyright Technophobia Ltd 2012
*
* This file is part of Substeps.
*
* Substeps is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Substeps is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Substeps. If not, see <http://www.gnu.org/licenses/>.
*/
package com.technophobia.substeps.runner.setupteardown;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
/*
* Copyright Technophobia Ltd 2012
*
* This file is part of Substeps.
*
* Substeps is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Substeps is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Substeps. If not, see <http://www.gnu.org/licenses/>.
*/
public class BeforeAndAfterMethods {
private final Map<MethodState, List<Method>> methodMap;
private final Class<?>[] initialisationClasses;
public BeforeAndAfterMethods(final Class<?>[] initialisationClasses) {
methodMap = Maps.newHashMap();
this.initialisationClasses = initialisationClasses;
locateInitialisationMethods();
}
// from the previous base class
private void sortMethodList() {
// order of execution is base classes first, then the order of
// processorClasses, reversed for tear downs
// build up the order:
final List<Class<?>> hierarchy = new ArrayList<Class<?>>();
if (initialisationClasses != null) {
for (int i = initialisationClasses.length; i > 0; i--) {
hierarchy.addAll(classHierarchyFor(initialisationClasses[i - 1]));
}
}
sortMethodLists(hierarchy);
}
protected List<Method> methodsForState(final MethodState methodState) {
return methodMap.containsKey(methodState) ? methodMap.get(methodState) : Collections
.<Method> emptyList();
}
private void updateMethodMapWith(final Method method) {
for (final MethodState methodState : MethodState.values()) {
updateMethodMapIfPresent(methodState, method);
}
}
private void updateMethodMapIfPresent(final MethodState methodState, final Method method) {
if (method.isAnnotationPresent(methodState.getAnnotationClass())) {
addToMethodMap(methodState, method);
}
}
private void addToMethodMap(final MethodState methodState, final Method method) {
if (!methodMap.containsKey(methodState)) {
methodMap.put(methodState, new ArrayList<Method>());
}
methodMap.get(methodState).add(method);
}
protected List<Class<?>> parentClassHierarchyFor(final Class<?> targetClass) {
final List<Class<?>> classHierarchy = new LinkedList<Class<?>>();
classHierarchy.add(targetClass);
Class<?> tempClass = targetClass;
while (!tempClass.equals(Object.class)) {
tempClass = tempClass.getSuperclass();
// no need to add Object
if (!tempClass.equals(Object.class)) {
classHierarchy.add(tempClass);
}
}
return Collections.unmodifiableList(classHierarchy);
}
private void sortMethodLists(final List<Class<?>> classHierarchy) {
final Comparator<Method> methodComparator = new MethodComparator(classHierarchy);
for (final MethodState methodState : methodMap.keySet()) {
final List<Method> methodsForState = methodMap.get(methodState);
sortMethodList(methodsForState, methodComparator);
// Execute after tests in reverse order
if (!methodState.isBeforeTest()) {
Collections.reverse(methodsForState);
}
}
}
private void sortMethodList(final List<Method> methodsList,
final Comparator<Method> methodComparator) {
Collections.sort(methodsList, methodComparator);
}
public List<Method> getSetupAndTearDownMethods(final MethodState state) {
return methodsForState(state);
}
private void locateInitialisationMethods() {
final Collection<Method> methods = getAllInitialisationClassMethods();
for (final Method method : methods) {
updateMethodMapWith(method);
}
sortMethodList();
}
private List<Method> getAllInitialisationClassMethods() {
final List<Method> methods = new ArrayList<Method>();
// these are the classes that have the before / after annotations
if (initialisationClasses != null) {
for (final Class<?> processorClass : initialisationClasses) {
appendMethodsIn(processorClass, methods);
}
}
return Collections.unmodifiableList(methods);
}
private List<Class<?>> classHierarchyFor(final Class<?> processorClass) {
final List<Class<?>> hierarchy = new ArrayList<Class<?>>();
final List<Class<?>> classHierarchyForProcessor = parentClassHierarchyFor(processorClass);
for (final Class<?> processorClassAncestor : classHierarchyForProcessor) {
int superClassIndex = -1;
for (int i = 0; i < hierarchy.size(); i++) {
if (hierarchy.get(i).isAssignableFrom(processorClassAncestor)) {
superClassIndex = i;
break;
}
}
if (superClassIndex == -1) {
hierarchy.add(processorClassAncestor);
} else {
hierarchy.add(superClassIndex, processorClassAncestor);
}
}
return Collections.unmodifiableList(hierarchy);
}
private void appendMethodsIn(final Class<?> processorClass, final List<Method> methods) {
Collections.addAll(methods, processorClass.getMethods());
}
}