diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java index 7869995..65e893c 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java @@ -15,40 +15,18 @@ public class ConstructionUtil { public static T generateSubClassOf(Class clazz) throws InstantiationException { StringBuilder sourceCode = new StringBuilder(); String packageName = adaptName(clazz.getPackage().getName()); - Map genericNames = getImplicitGenericParameters(clazz); + Map noMapping = new HashMap(); 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(), 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 Map getImplicitGenericParameters(Class clazz) { - Map templateTypeMap = new HashMap(); + private static Set generateMethodsOf(Class clazz, Set finalMethods, Map genericTypes) { + Set methods = new HashSet(); + 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 superTypes = new ArrayList(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>[] 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.emptyMap())); } } + return methods; + } + + private static Map parseGenericTypes(ParameterizedType type) { + Map templateTypeMap = new HashMap(); + Type[] actualTypeArguments = type.getActualTypeArguments(); + TypeVariable>[] 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 genericNames) { + Map 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 genericNames) { + private static String getTypeParameters(Type[] typeParameters, Map 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 genericNames) { + private static String getWildcardType(WildcardType wildcardType, Map 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 genericNames) { + private static String getClassName(Type clazz, Map 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 genericNames) { + private static String getParameterName(Type parameter, Map 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) { diff --git a/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java b/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java index a5b2d00..cb0c0d1 100644 --- a/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java +++ b/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java @@ -47,7 +47,10 @@ class ConstructionUtilTest { public abstract P getByMap(Map m); } - public interface StringMap extends Map {} + public interface StringMap extends Map { + K nextKey(K k); + V nextValue(V v); + } public interface Inner$Interface {}