feat: get generic type mapping recursively

This commit is contained in:
金戟 2022-12-03 17:15:18 +08:00
parent 65b58d0861
commit ada70f88c0
2 changed files with 82 additions and 55 deletions
testable-core/src
main/java/com/alibaba/testable/core/util
test/java/com/alibaba/testable/core/util

View File

@ -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) {

View File

@ -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 {}