/*******************************************************************************
* Copyright 2014 JHC Systems Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package jhc.redsniff.generation;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hamcrest.generator.FactoryMethod;
import org.hamcrest.generator.ReflectiveFactoryReader;
/**
* custom version for use in this library that:
* accepts a custom parent class (eg Finder, Matcher etc), so that only factory methods for that type will be found
* copes with return types having more than one parameterised type - original version in superclass only expects one (since Matcher only has one..)
* largely copied from super class
* @author InfanteN
*
*/
public class FactoryMethodReader extends ReflectiveFactoryReader {
private final Class<?> parentClass;
private ClassLoader classLoader;
private final Class<?> observedClass;
public FactoryMethodReader(Class<?> observedClass,Class<?> parentClass) {
super(observedClass);
this.observedClass = observedClass;
this.classLoader = observedClass.getClassLoader();
this.parentClass=parentClass;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected boolean isFactoryMethod(Method javaMethod) {
// TODO Auto-generated method stub
// We dynamically load these classes, to avoid a compile time
// dependency on org.hamcrest.{Factory,Matcher}. This gets around
// a circular bootstrap issue (because generator is required to
// compile core).
Class factoryCls;
try {
factoryCls = classLoader.loadClass("org.hamcrest.Factory");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot load hamcrest core", e);
}
return javaMethod.getDeclaringClass().equals(observedClass)
&& isStatic(javaMethod.getModifiers())
&& isPublic(javaMethod.getModifiers())
&& javaMethod.getAnnotation(factoryCls) != null
&& parentClass.isAssignableFrom(javaMethod.getReturnType());
}
@Override
public Iterator<FactoryMethod> iterator() {
return new Iterator<FactoryMethod>() {
private int currentMethod = -1;
private Method[] allMethods = observedClass.getMethods();
@Override
public boolean hasNext() {
while (true) {
currentMethod++;
if (currentMethod >= allMethods.length) {
return false;
} else if (isFactoryMethod(allMethods[currentMethod])) {
return true;
} // else carry on looping and try the next one.
}
}
@Override
public FactoryMethod next() {
if (outsideArrayBounds()) {
throw new IllegalStateException("next() called without hasNext() check.");
}
return buildFactoryMethod(allMethods[currentMethod]);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private boolean outsideArrayBounds() {
return currentMethod < 0 || allMethods.length <= currentMethod;
}
};
}
private static FactoryMethod buildFactoryMethod(Method javaMethod) {
FactoryMethodPermittingMultipleGenericTypes result = new FactoryMethodPermittingMultipleGenericTypes(
classToString(javaMethod.getDeclaringClass()),
javaMethod.getName(),
classToString(javaMethod.getReturnType()));
for (TypeVariable<Method> typeVariable : javaMethod.getTypeParameters()) {
boolean hasBound = false;
StringBuilder s = new StringBuilder(typeVariable.getName());
for (Type bound : typeVariable.getBounds()) {
if (bound != Object.class) {
if (hasBound) {
s.append(" & ");
} else {
s.append(" extends ");
hasBound = true;
}
s.append(typeToString(bound));
}
}
result.addGenericTypeParameter(s.toString());
}
Type returnType = javaMethod.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) returnType;
List<String> generifiedTypes = new ArrayList<String>();
for(Type generifiedType:parameterizedType.getActualTypeArguments()){
generifiedTypes.add(typeToString(generifiedType));
}
result.setGenerifiedTypes(generifiedTypes);
}
int paramNumber = 0;
for (Type paramType : javaMethod.getGenericParameterTypes()) {
String type = typeToString(paramType);
// Special case for var args methods.... String[] -> String...
if (javaMethod.isVarArgs()
&& paramNumber == javaMethod.getParameterTypes().length - 1) {
type = type.replaceFirst("\\[\\]$", "...");
}
result.addParameter(type, "param" + (++paramNumber));
}
for (Class<?> exception : javaMethod.getExceptionTypes()) {
result.addException(typeToString(exception));
}
return result;
}
/*
* Get String representation of Type (e.g. java.lang.String or Map<Stuff,? extends Cheese>).
* <p/>
* Annoyingly this method wouldn't be needed if java.lang.reflect.Type.toString() behaved consistently
* across implementations. Rock on Liskov.
*/
private static String typeToString(Type type) {
return type instanceof Class<?> ? classToString((Class<?>) type): type.toString();
}
private static String classToString(Class<?> cls) {
final String name = cls.isArray() ? cls.getComponentType().getName() + "[]" : cls.getName();
return name.replace('$', '.');
}
}