mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-03-14 20:00:26 +08:00
feat: get generic type mapping recursively
This commit is contained in:
parent
65b58d0861
commit
ada70f88c0
testable-core/src
main/java/com/alibaba/testable/core/util
test/java/com/alibaba/testable/core/util
@ -15,40 +15,18 @@ public class ConstructionUtil {
|
||||
public static <T> T generateSubClassOf(Class<T> clazz) throws InstantiationException {
|
||||
StringBuilder sourceCode = new StringBuilder();
|
||||
String packageName = adaptName(clazz.getPackage().getName());
|
||||
Map<String, String> genericNames = getImplicitGenericParameters(clazz);
|
||||
Map<String, String> noMapping = new HashMap<String, String>();
|
||||
sourceCode.append("package ")
|
||||
.append(packageName)
|
||||
.append(";\npublic class ")
|
||||
.append(getSubclassName(clazz))
|
||||
.append(getTypeParameters(clazz.getTypeParameters(), true, genericNames))
|
||||
.append(getTypeParameters(clazz.getTypeParameters(), true, noMapping))
|
||||
.append(clazz.isInterface() ? " implements " : " extends ")
|
||||
.append(getClassName(clazz, genericNames))
|
||||
.append(getTypeParameters(clazz.getTypeParameters(), false, genericNames))
|
||||
.append(getClassName(clazz, noMapping))
|
||||
.append(getTypeParameters(clazz.getTypeParameters(), false, noMapping))
|
||||
.append(" {\n");
|
||||
for (Method m : clazz.getMethods()) {
|
||||
if (!Modifier.isStatic(m.getModifiers()) && !Modifier.isFinal(m.getModifiers())) {
|
||||
sourceCode.append("\tpublic ")
|
||||
.append(getTypeParameters(m.getTypeParameters(), true, genericNames))
|
||||
.append(getClassName(m.getGenericReturnType(), genericNames))
|
||||
.append(" ").append(m.getName()).append("(");
|
||||
Type[] parameters = m.getGenericParameterTypes();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
sourceCode.append(getParameterName(parameters[i], genericNames)).append(" p").append(i);
|
||||
if (i < parameters.length - 1) {
|
||||
sourceCode.append(", ");
|
||||
}
|
||||
}
|
||||
sourceCode.append(") {\n");
|
||||
if (!m.getReturnType().equals(void.class)) {
|
||||
sourceCode.append("\t\treturn (").append(getClassName(m.getGenericReturnType(), genericNames))
|
||||
.append(") ")
|
||||
.append(getClassName(OmniConstructor.class, genericNames))
|
||||
.append(".")
|
||||
.append("newInstance(").append(getClassName(m.getReturnType(), genericNames))
|
||||
.append(".class);\n");
|
||||
}
|
||||
sourceCode.append("\t}\n");
|
||||
}
|
||||
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping)) {
|
||||
sourceCode.append(method);
|
||||
}
|
||||
sourceCode.append("}");
|
||||
|
||||
@ -64,32 +42,78 @@ public class ConstructionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static String adaptName(String name) {
|
||||
// create class in 'java' package will cause 'prohibited package name' error
|
||||
return name.replaceAll("^java\\.", "testable.");
|
||||
}
|
||||
|
||||
private static <T> Map<String, String> getImplicitGenericParameters(Class<T> clazz) {
|
||||
Map<String, String> templateTypeMap = new HashMap<String, String>();
|
||||
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> finalMethods, Map<String, String> genericTypes) {
|
||||
Set<String> methods = new HashSet<String>();
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
StringBuilder methodSignatureBuilder = new StringBuilder(m.getName());
|
||||
for (Type p : m.getGenericParameterTypes()) {
|
||||
methodSignatureBuilder.append("#").append(getParameterName(p, genericTypes));
|
||||
}
|
||||
String methodSignature = methodSignatureBuilder.toString();
|
||||
if (finalMethods.contains(methodSignature)) {
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isFinal(m.getModifiers())) {
|
||||
finalMethods.add(methodSignature);
|
||||
}
|
||||
if (Modifier.isAbstract(m.getModifiers())) {
|
||||
StringBuilder sourceCode = new StringBuilder();
|
||||
sourceCode.append("\tpublic ")
|
||||
.append(getTypeParameters(m.getTypeParameters(), true, genericTypes))
|
||||
.append(getClassName(m.getGenericReturnType(), genericTypes))
|
||||
.append(" ").append(m.getName()).append("(");
|
||||
Type[] parameters = m.getGenericParameterTypes();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
sourceCode.append(getParameterName(parameters[i], genericTypes)).append(" p").append(i);
|
||||
if (i < parameters.length - 1) {
|
||||
sourceCode.append(", ");
|
||||
}
|
||||
}
|
||||
sourceCode.append(") {\n");
|
||||
if (!m.getReturnType().equals(void.class)) {
|
||||
sourceCode.append("\t\treturn (").append(getClassName(m.getGenericReturnType(), genericTypes))
|
||||
.append(") ")
|
||||
.append(getClassName(OmniConstructor.class, genericTypes))
|
||||
.append(".")
|
||||
.append("newInstance(").append(getClassName(m.getReturnType(), genericTypes))
|
||||
.append(".class);\n");
|
||||
}
|
||||
sourceCode.append("\t}\n");
|
||||
methods.add(sourceCode.toString());
|
||||
}
|
||||
}
|
||||
List<Type> superTypes = new ArrayList<Type>(Arrays.asList(clazz.getGenericInterfaces()));
|
||||
if (clazz.getGenericSuperclass() != null) {
|
||||
superTypes.add(clazz.getGenericSuperclass());
|
||||
}
|
||||
for (Type t : superTypes) {
|
||||
if (t instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType)t;
|
||||
Type[] actualTypeArguments = pt.getActualTypeArguments();
|
||||
TypeVariable<? extends Class<?>>[] rawTypedParameters = ((Class<?>) pt.getRawType()).getTypeParameters();
|
||||
for (int i = 0; i < actualTypeArguments.length; i++) {
|
||||
templateTypeMap.put(getClassName(rawTypedParameters[i]), getClassName(actualTypeArguments[i]));
|
||||
}
|
||||
ParameterizedType pt = (ParameterizedType) t;
|
||||
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), finalMethods, parseGenericTypes(pt)));
|
||||
} else if (t instanceof Class) {
|
||||
methods.addAll(generateMethodsOf((Class<?>) t, finalMethods, Collections.<String, String>emptyMap()));
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private static Map<String, String> parseGenericTypes(ParameterizedType type) {
|
||||
Map<String, String> templateTypeMap = new HashMap<String, String>();
|
||||
Type[] actualTypeArguments = type.getActualTypeArguments();
|
||||
TypeVariable<? extends Class<?>>[] rawTypedParameters = ((Class<?>) type.getRawType()).getTypeParameters();
|
||||
for (int i = 0; i < actualTypeArguments.length; i++) {
|
||||
templateTypeMap.put(getClassName(rawTypedParameters[i]), getClassName(actualTypeArguments[i]));
|
||||
}
|
||||
return templateTypeMap;
|
||||
}
|
||||
|
||||
private static String adaptName(String name) {
|
||||
// create class in 'java' package will cause 'prohibited package name' error
|
||||
return name.replaceAll("^java\\.", "testable.");
|
||||
}
|
||||
|
||||
private static String getTypeParameters(TypeVariable<?>[] typeParameters, boolean withScope,
|
||||
Map<String, String> genericNames) {
|
||||
Map<String, String> genericTypes) {
|
||||
if (typeParameters.length > 0) {
|
||||
StringBuilder sb = new StringBuilder("<");
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
@ -98,7 +122,7 @@ public class ConstructionUtil {
|
||||
sb.append(" extends ");
|
||||
Type[] bounds = typeParameters[i].getBounds();
|
||||
for (int j = 0; j < bounds.length; j++) {
|
||||
sb.append(getClassName(bounds[j], genericNames));
|
||||
sb.append(getClassName(bounds[j], genericTypes));
|
||||
if (j < bounds.length - 1) {
|
||||
sb.append(" & ");
|
||||
}
|
||||
@ -114,11 +138,11 @@ public class ConstructionUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getTypeParameters(Type[] typeParameters, Map<String, String> genericNames) {
|
||||
private static String getTypeParameters(Type[] typeParameters, Map<String, String> genericTypes) {
|
||||
if (typeParameters.length > 0) {
|
||||
StringBuilder sb = new StringBuilder("<");
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
sb.append(getClassName(typeParameters[i], genericNames));
|
||||
sb.append(getClassName(typeParameters[i], genericTypes));
|
||||
if (i < typeParameters.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
@ -129,7 +153,7 @@ public class ConstructionUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getWildcardType(WildcardType wildcardType, Map<String, String> genericNames) {
|
||||
private static String getWildcardType(WildcardType wildcardType, Map<String, String> genericTypes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Type[] lowerBounds = wildcardType.getLowerBounds();
|
||||
Type[] bounds = lowerBounds;
|
||||
@ -151,7 +175,7 @@ public class ConstructionUtil {
|
||||
}
|
||||
|
||||
firstItem = false;
|
||||
sb.append(getClassName(type, genericNames));
|
||||
sb.append(getClassName(type, genericTypes));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@ -161,28 +185,28 @@ public class ConstructionUtil {
|
||||
return (clazz instanceof Class) ? ((Class<?>)clazz).getCanonicalName() : clazz.toString();
|
||||
}
|
||||
|
||||
private static String getClassName(Type clazz, Map<String, String> genericNames) {
|
||||
private static String getClassName(Type clazz, Map<String, String> genericTypes) {
|
||||
if (clazz instanceof Class) {
|
||||
return ((Class<?>)clazz).getCanonicalName();
|
||||
} else if (clazz instanceof GenericArrayType) {
|
||||
return getClassName(((GenericArrayType) clazz).getGenericComponentType()) + "[]";
|
||||
} else if (clazz instanceof TypeVariable) {
|
||||
String name = ((TypeVariable<?>)clazz).getName();
|
||||
return genericNames.containsKey(name) ? genericNames.get(name) : name;
|
||||
return genericTypes.containsKey(name) ? genericTypes.get(name) : name;
|
||||
} else if (clazz instanceof ParameterizedType) {
|
||||
ParameterizedType ptClazz = (ParameterizedType)clazz;
|
||||
return getClassName(ptClazz.getRawType()) + getTypeParameters(ptClazz.getActualTypeArguments(), genericNames);
|
||||
return getClassName(ptClazz.getRawType()) + getTypeParameters(ptClazz.getActualTypeArguments(), genericTypes);
|
||||
} else if (clazz instanceof WildcardType) {
|
||||
return getWildcardType((WildcardType)clazz, genericNames);
|
||||
return getWildcardType((WildcardType)clazz, genericTypes);
|
||||
}
|
||||
return clazz.toString();
|
||||
}
|
||||
|
||||
private static String getParameterName(Type parameter, Map<String, String> genericNames) {
|
||||
private static String getParameterName(Type parameter, Map<String, String> genericTypes) {
|
||||
if (parameter instanceof Class && ((Class<?>)parameter).isArray()) {
|
||||
return getParameterName(((Class<?>)parameter).getComponentType(), genericNames) + "[]";
|
||||
return getParameterName(((Class<?>)parameter).getComponentType(), genericTypes) + "[]";
|
||||
}
|
||||
return getClassName(parameter, genericNames);
|
||||
return getClassName(parameter, genericTypes);
|
||||
}
|
||||
|
||||
private static String getSubclassName(Class<?> clazz) {
|
||||
|
@ -47,7 +47,10 @@ class ConstructionUtilTest {
|
||||
public abstract P getByMap(Map<String, P> m);
|
||||
}
|
||||
|
||||
public interface StringMap extends Map<String, Object> {}
|
||||
public interface StringMap extends Map<String, Object> {
|
||||
<K> K nextKey(K k);
|
||||
<V> V nextValue(V v);
|
||||
}
|
||||
|
||||
public interface Inner$Interface {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user