package org.test4j.spec.scenario.step;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.test4j.spec.ISpec;
import org.test4j.spec.annotations.Named;
import org.test4j.spec.inner.IScenarioStep;
import org.test4j.spec.inner.ISpecMethod;
import org.test4j.spec.inner.StepType;
import org.test4j.tools.commons.AnnotationHelper;
import org.test4j.tools.reflector.MethodAccessor;
@SuppressWarnings("rawtypes")
public class SpecMethod implements ISpecMethod {
private final MethodAccessor accessor;
private final String clazzName;
private final String methodName;
private final List<String> paraNameds;
private final List<Type> paraTypes;
public SpecMethod(Method method) {
this.accessor = new MethodAccessor(method);
this.clazzName = method.getDeclaringClass().getName();
this.methodName = method.getName();
this.paraNameds = this.getParaAnnotationNames(method);
this.paraTypes = this.getParaTypes(method);
if (this.paraNameds.size() != this.paraTypes.size()) {
throw new RuntimeException(
"the size of Parameter Named Annotation should be equal to the size of Parameter type.");
}
}
@Override
public String getClazzName() {
return clazzName;
}
@Override
public String getMethodName() {
return methodName;
}
List<Type> getParaTypes(Method method) {
List<Type> types = new ArrayList<Type>();
Type[] paras = method.getGenericParameterTypes();
for (Type para : paras) {
types.add(para);
}
return types;
}
List<String> getParaAnnotationNames(Method method) {
List<String> names = new ArrayList<String>();
Annotation[][] arrays = method.getParameterAnnotations();
for (Annotation[] annotations : arrays) {
String name = this.annotationName(annotations);
if (name == null) {
throw new RuntimeException(String.format("the argument of method[%s] missing Named annotation.",
method.getName()));
} else {
names.add(name);
}
}
return names;
}
private String annotationName(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAssignableFrom(Named.class)) {
return ((Named) annotation).value();
}
}
return null;
}
@Override
public Object execute(ISpec spec, IScenarioStep step) {
try {
Object[] args = step.getArguments(paraNameds, paraTypes);
Object instance = spec.getStepsInstance(this.getClazzName());
Object o = this.accessor.invokeUnThrow(instance == null ? spec : instance, args);
return o;
} catch (Throwable e) {
e.printStackTrace();
String err = String.format("Invoke spec method[%s] error:%s", step.getMethod(), e.getMessage());
throw new RuntimeException(err, e);
}
}
public static Map<SpecMethodID, ISpecMethod> findMethods(Class claz) {
Map<SpecMethodID, ISpecMethod> map = new HashMap<SpecMethodID, ISpecMethod>();
Set<Method> methods = findAllStepMethods(claz);
for (Method method : methods) {
String methodName = method.getName();
int count = method.getParameterTypes().length;
SpecMethodID id = new SpecMethodID(methodName, count);
if (map.containsKey(id)) {
String msg = String.format("the class[%s] has contain a method named %s and with %d arguments.",
claz.getName(), methodName, count);
throw new RuntimeException(msg);
} else {
SpecMethod specMethod = new SpecMethod(method);
map.put(id, specMethod);
}
}
return map;
}
private static Set<Method> findAllStepMethods(Class claz) {
Set<Method> methods = new HashSet<Method>();
for (StepType type : StepType.values()) {
Set<Method> found = AnnotationHelper.getMethodsAnnotatedWith(claz, type.getAnnotatonClaz());
methods.addAll(found);
}
return methods;
}
@Override
public String toString() {
return clazzName + "." + methodName;
}
}