feat: invoke super constructor explicitly in generated class

This commit is contained in:
金戟 2022-12-11 20:06:46 +08:00
parent a463bcbfd9
commit d5c1f780de
3 changed files with 82 additions and 34 deletions

View File

@ -15,9 +15,15 @@ public enum ConstructionOption {
EXCEPT_INTERFACE,
/**
* 构造的接口成员方法返回非空对象某些JDK内置接口不兼容此选项
* 生成的接口成员方法返回非空对象某些JDK内置接口不兼容此选项
* methods in constructed interface return real object instead of null
*/
RICH_INTERFACE
RICH_INTERFACE_METHOD,
/**
* 生成的虚拟抽象类调用父构造方法时使用非空对象作为参数
* methods in constructed interface return real object instead of null
*/
RICH_INTERFACE_CONSTRUCTOR
}

View File

@ -8,7 +8,8 @@ import java.lang.reflect.*;
import java.util.*;
import static com.alibaba.testable.core.constant.ConstPool.DOT;
import static com.alibaba.testable.core.model.ConstructionOption.RICH_INTERFACE;
import static com.alibaba.testable.core.model.ConstructionOption.RICH_INTERFACE_CONSTRUCTOR;
import static com.alibaba.testable.core.model.ConstructionOption.RICH_INTERFACE_METHOD;
import static com.alibaba.testable.core.util.CollectionUtil.entryOf;
import static com.alibaba.testable.core.util.CollectionUtil.mapOf;
@ -16,22 +17,25 @@ public class ConstructionUtil {
private static final String TESTABLE_IMPL = "$TestableImpl";
private static final Map<String, String> RETURN_VALUES = mapOf(
/**
* Default value of basic types
*/
private static final Map<String, String> DEFAULT_VALUES = mapOf(
entryOf("java.lang.String", "\"mock\""),
entryOf("byte", "'\0'"),
entryOf("java.lang.Byte", "'\0'"),
entryOf("char", "'\0'"),
entryOf("java.lang.Character", "'\0'"),
entryOf("double", "0.0D"),
entryOf("java.lang.Double", "0.0D"),
entryOf("float", "0.0"),
entryOf("java.lang.Float", "0.0"),
entryOf("int", "0"),
entryOf("java.lang.Integer", "0"),
entryOf("short", "0"),
entryOf("java.lang.Short", "0"),
entryOf("long", "0L"),
entryOf("java.lang.Long", "0L"),
entryOf("double", "1.0D"),
entryOf("java.lang.Double", "1.0D"),
entryOf("float", "1.0F"),
entryOf("java.lang.Float", "1.0F"),
entryOf("int", "1"),
entryOf("java.lang.Integer", "1"),
entryOf("short", "1"),
entryOf("java.lang.Short", "1"),
entryOf("long", "1L"),
entryOf("java.lang.Long", "1L"),
entryOf("boolean", "true"),
entryOf("java.lang.Boolean", "true")
);
@ -39,16 +43,19 @@ public class ConstructionUtil {
public static <T> T generateSubClassOf(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
StringBuilder sourceCode = new StringBuilder();
String packageName = adaptName(clazz.getPackage().getName());
String subclassName = getSubclassName(clazz);
Map<String, String> noMapping = new HashMap<String, String>();
sourceCode.append("package ")
.append(packageName)
.append(";\npublic class ")
.append(getSubclassName(clazz))
.append(subclassName)
.append(getTypeParameters(clazz.getTypeParameters(), true, noMapping))
.append(clazz.isInterface() ? " implements " : " extends ")
.append(getClassName(clazz, noMapping))
.append(getTypeParameters(clazz.getTypeParameters(), false, noMapping))
.append(" {\n");
sourceCode.append("\tpublic ").append(subclassName).append("() { ")
.append(invokeConstructorOf(clazz, noMapping, options)).append(" }\n");
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping, options)) {
sourceCode.append(method);
}
@ -59,13 +66,34 @@ public class ConstructionUtil {
.useParentClassLoader(clazz.getClassLoader())
.useOptions("-Xlint:unchecked")
.ignoreWarnings()
.compile(packageName + DOT + getSubclassName(clazz), sourceCode.toString())
.compile(packageName + DOT + subclassName, sourceCode.toString())
.newInstance();
} catch (Exception e) {
} catch (Throwable e) {
throw new InstantiationException(e.toString());
}
}
private static String invokeConstructorOf(Class<?> clazz, Map<String, String> genericTypes,
ConstructionOption[] options) {
if (clazz.isInterface()) {
return "";
}
Constructor<?> constructor = TypeUtil.getBestConstructor(clazz, false);
StringBuilder invocation = new StringBuilder("super(");
Type[] genericParameterTypes = constructor.getGenericParameterTypes();
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (i > 0) {
invocation.append(", ");
}
invocation.append(getDefaultValue(getClassName(genericParameterTypes[i], genericTypes),
getClassName(parameterTypes[i]),
CollectionUtil.contains(options, RICH_INTERFACE_CONSTRUCTOR), genericTypes));
}
invocation.append(");");
return invocation.toString();
}
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool,
Map<String, String> genericTypes, ConstructionOption[] options) {
Set<String> methods = new HashSet<String>();
@ -100,20 +128,10 @@ public class ConstructionUtil {
sourceCode.append(") {\n");
String returnType = getClassName(m.getGenericReturnType(), genericTypes);
if (!"void".equals(returnType)) {
if (RETURN_VALUES.containsKey(returnType)) {
sourceCode.append("\t\treturn ").append(RETURN_VALUES.get(returnType)).append(";\n");
} else if (CollectionUtil.contains(options, RICH_INTERFACE)) {
sourceCode.append("\t\treturn (")
.append(returnType)
.append(") ")
.append(getClassName(OmniConstructor.class, genericTypes))
.append(".")
.append("newInstance(")
.append(getClassName(m.getReturnType(), genericTypes))
.append(".class);\n");
} else {
sourceCode.append("\t\treturn null;\n");
}
sourceCode.append("\t\treturn ")
.append(getDefaultValue(returnType, getClassName(m.getReturnType()),
CollectionUtil.contains(options, RICH_INTERFACE_METHOD), genericTypes))
.append(";\n");
}
sourceCode.append("\t}\n");
methods.add(sourceCode.toString());
@ -135,6 +153,18 @@ public class ConstructionUtil {
return methods;
}
private static String getDefaultValue(String genericTypeName, String simpleTypeName, boolean richValue,
Map<String, String> genericTypes) {
if (DEFAULT_VALUES.containsKey(genericTypeName)) {
return DEFAULT_VALUES.get(genericTypeName);
} else if (richValue) {
return "(" + genericTypeName + ") " + getClassName(OmniConstructor.class, genericTypes) +
".newInstance(" + simpleTypeName + ".class)";
} else {
return "null";
}
}
private static Map<String, String> parseGenericTypes(ParameterizedType type) {
Map<String, String> templateTypeMap = new HashMap<String, String>();
Type[] actualTypeArguments = type.getActualTypeArguments();
@ -227,7 +257,7 @@ public class ConstructionUtil {
if (clazz instanceof Class) {
return ((Class<?>)clazz).getCanonicalName();
} else if (clazz instanceof GenericArrayType) {
return getClassName(((GenericArrayType) clazz).getGenericComponentType()) + "[]";
return getClassName(((GenericArrayType) clazz).getGenericComponentType(), genericTypes) + "[]";
} else if (clazz instanceof TypeVariable) {
String name = ((TypeVariable<?>)clazz).getName();
return genericTypes.containsKey(name) ? genericTypes.get(name) : name;

View File

@ -116,7 +116,7 @@ public class TypeUtil {
* @param clazz any class
* @return best constructor
*/
public static Constructor<?> getBestConstructor(Class<?> clazz) {
public static Constructor<?> getBestConstructor(Class<?> clazz, boolean useGeneratedConstructor) {
Constructor<?> bestConstructor = null;
int minimalExceptionCount = 999;
int minimalParameterCount = 999;
@ -124,7 +124,10 @@ public class TypeUtil {
Class<?>[] parameterTypes = constructor.getParameterTypes();
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
if (parameterTypes.length == 1 && parameterTypes[0].equals(Void.class)) {
return constructor;
// if generated constructor allowed then use it, otherwise skip and continue
if (useGeneratedConstructor) {
return constructor;
}
} else if (exceptionTypes.length < minimalExceptionCount
|| (exceptionTypes.length == minimalExceptionCount && parameterTypes.length < minimalParameterCount)) {
minimalExceptionCount = exceptionTypes.length;
@ -135,6 +138,15 @@ public class TypeUtil {
return bestConstructor;
}
/**
* find the simplest constructor including the testable generated one
* @param clazz any class
* @return best constructor
*/
public static Constructor<?> getBestConstructor(Class<?> clazz) {
return getBestConstructor(clazz, true);
}
/**
* type equals
* @param classesLeft class to be compared