feat: support generate class with inherited genericity

This commit is contained in:
金戟 2022-11-17 20:56:21 +08:00
parent 76cff45123
commit fc7e4af2c6
2 changed files with 114 additions and 32 deletions
testable-core/src
main/java/com/alibaba/testable/core/util
test/java/com/alibaba/testable/core/util

View File

@ -3,12 +3,9 @@ package com.alibaba.testable.core.util;
import com.alibaba.testable.core.compile.InMemoryJavaCompiler;
import com.alibaba.testable.core.tool.OmniConstructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.*;
import java.util.*;
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
import static com.alibaba.testable.core.constant.ConstPool.DOT;
public class ConstructionUtil {
@ -17,31 +14,37 @@ public class ConstructionUtil {
public static <T> T generateSubClassOf(Class<T> clazz) throws InstantiationException {
StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package ").append(clazz.getPackage().getName()).append(";\n")
.append("public class ").append(getSubclassName(clazz));
appendTypeParameters(sourceCode, clazz.getTypeParameters(), true);
sourceCode.append(clazz.isInterface() ? " implements " : " extends ")
.append(getClassName(clazz));
appendTypeParameters(sourceCode, clazz.getTypeParameters(), false);
sourceCode.append(" {\n");
Map<String, String> genericNames = getImplicitGenericParameters(clazz);
sourceCode.append("package ")
.append(clazz.getPackage().getName())
.append(";\npublic class ")
.append(getSubclassName(clazz))
.append(getTypeParameters(clazz.getTypeParameters(), true, genericNames))
.append(clazz.isInterface() ? " implements " : " extends ")
.append(getClassName(clazz, genericNames))
.append(getTypeParameters(clazz.getTypeParameters(), false, genericNames))
.append(" {\n");
for (Method m : clazz.getMethods()) {
if (!Modifier.isStatic(m.getModifiers()) && !Modifier.isFinal(m.getModifiers())) {
sourceCode.append("\tpublic ");
appendTypeParameters(sourceCode, m.getTypeParameters(), true);
sourceCode.append(getClassName(m.getGenericReturnType())).append(" ")
.append(m.getName()).append("(");
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])).append(" p").append(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())).append(") ")
.append(getClassName(OmniConstructor.class)).append(".")
.append("newInstance(").append(getClassName(m.getReturnType())).append(".class);\n");
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");
}
@ -60,41 +63,120 @@ public class ConstructionUtil {
}
}
private static void appendTypeParameters(StringBuilder sourceCode, TypeVariable<?>[] typeParameters, boolean withScope) {
private static <T> Map<String, String> getImplicitGenericParameters(Class<T> clazz) {
Map<String, String> templateTypeMap = new HashMap<String, String>();
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]));
}
}
}
return templateTypeMap;
}
private static String getTypeParameters(TypeVariable<?>[] typeParameters, boolean withScope,
Map<String, String> genericNames) {
if (typeParameters.length > 0) {
sourceCode.append("<");
StringBuilder sb = new StringBuilder("<");
for (int i = 0; i < typeParameters.length; i++) {
sourceCode.append(typeParameters[i].getName());
sb.append(typeParameters[i].getName());
if (withScope) {
sourceCode.append(" extends ");
sb.append(" extends ");
Type[] bounds = typeParameters[i].getBounds();
for (int j = 0; j < bounds.length; j++) {
sourceCode.append(getClassName(bounds[j]));
sb.append(getClassName(bounds[j], genericNames));
if (j < bounds.length - 1) {
sourceCode.append(" & ");
sb.append(" & ");
}
}
}
if (i < typeParameters.length - 1) {
sourceCode.append(", ");
sb.append(", ");
}
}
sourceCode.append("> ");
sb.append("> ");
return sb.toString();
}
return "";
}
private static String getTypeParameters(Type[] typeParameters, Map<String, String> genericNames) {
if (typeParameters.length > 0) {
StringBuilder sb = new StringBuilder("<");
for (int i = 0; i < typeParameters.length; i++) {
sb.append(getClassName(typeParameters[i], genericNames));
if (i < typeParameters.length - 1) {
sb.append(", ");
}
}
sb.append("> ");
return sb.toString();
}
return "";
}
private static String getWildcardType(WildcardType wildcardType, Map<String, String> genericNames) {
StringBuilder sb = new StringBuilder();
Type[] lowerBounds = wildcardType.getLowerBounds();
Type[] bounds = lowerBounds;
if (lowerBounds.length > 0) {
sb.append("? super ");
} else {
Type[] upperBounds = wildcardType.getUpperBounds();
if (upperBounds.length == 0 || upperBounds[0].equals(Object.class)) {
return "?";
}
bounds = upperBounds;
sb.append("? extends ");
}
boolean firstItem = true;
for (Type type : bounds) {
if (!firstItem) {
sb.append(" & ");
}
firstItem = false;
sb.append(getClassName(type, genericNames));
}
return sb.toString();
}
private static String getClassName(Type clazz) {
return (clazz instanceof Class) ? ((Class<?>)clazz).getCanonicalName() : clazz.toString();
}
private static String getClassName(Type clazz, Map<String, String> genericNames) {
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;
} else if (clazz instanceof ParameterizedType) {
ParameterizedType ptClazz = (ParameterizedType)clazz;
return getClassName(ptClazz.getRawType()) + getTypeParameters(ptClazz.getActualTypeArguments(), genericNames);
} else if (clazz instanceof WildcardType) {
return getWildcardType((WildcardType)clazz, genericNames);
}
return clazz.toString();
}
private static String getParameterName(Type parameter) {
private static String getParameterName(Type parameter, Map<String, String> genericNames) {
if (parameter instanceof Class && ((Class<?>)parameter).isArray()) {
return getParameterName(((Class<?>)parameter).getComponentType()) + "[]";
return getParameterName(((Class<?>)parameter).getComponentType(), genericNames) + "[]";
}
return getClassName(parameter);
return getClassName(parameter, genericNames);
}
private static String getSubclassName(Class<?> clazz) {

View File

@ -76,7 +76,7 @@ class ConstructionUtilTest {
}
@Test
void should_generate_typed_interface() throws Exception {
void should_generate_implicit_generic_interface() throws Exception {
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class);
assertNotNull(ins);
}