feat: add RICH_INTERFACE option to avoid introduce omni class in system interface implementation

This commit is contained in:
金戟
2022-12-04 17:52:19 +08:00
parent 1446382f53
commit d9f65189f7
5 changed files with 69 additions and 21 deletions

View File

@@ -51,7 +51,8 @@ public class OmniClassHandler extends BaseClassHandler {
entryOf("java/io/BufferedReader", arrayOf("Ljava/io/Reader;")),
entryOf("java/io/BufferedWriter", arrayOf("Ljava/io/Reader;")),
entryOf("java/io/BufferedInputStream", arrayOf("Ljava/io/Reader;")),
entryOf("java/io/BufferedOutputStream", arrayOf("Ljava/io/Reader;"))
entryOf("java/io/BufferedOutputStream", arrayOf("Ljava/io/Reader;")),
entryOf("java/nio/CharBuffer", arrayOf("I", "I", "I", "I"))
);
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {

View File

@@ -12,6 +12,12 @@ public enum ConstructionOption {
* 不初始化接口和抽象类型的成员
* allow members of interface or abstract class type be initialized as null
*/
EXCEPT_INTERFACE
EXCEPT_INTERFACE,
/**
* 构造的接口成员方法返回非空对象某些JDK内置接口不兼容此选项
* methods in constructed interface return real object instead of null
*/
RICH_INTERFACE
}

View File

@@ -121,7 +121,8 @@ public class OmniConstructor {
}
private static <T> T newAbstractClass(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
return CollectionUtil.contains(options, EXCEPT_INTERFACE) ? null : ConstructionUtil.generateSubClassOf(clazz);
return CollectionUtil.contains(options, EXCEPT_INTERFACE) ? null
: ConstructionUtil.generateSubClassOf(clazz, options);
}
private static <T> T newInterface(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
@@ -262,6 +263,9 @@ public class OmniConstructor {
} else if (clazz.getName().equals("java.nio.charset.Charset")) {
// better to use its default instance
return (T) Charset.defaultCharset();
} else if (clazz.getName().equals("java.util.Date")) {
// better to use current time
return (T) new Date();
} else if (clazz.getName().equals("java.nio.ByteBuffer")) {
// has package-private abstract methods
try {

View File

@@ -1,18 +1,42 @@
package com.alibaba.testable.core.util;
import com.alibaba.testable.core.compile.InMemoryJavaCompiler;
import com.alibaba.testable.core.model.ConstructionOption;
import com.alibaba.testable.core.tool.OmniConstructor;
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.util.CollectionUtil.entryOf;
import static com.alibaba.testable.core.util.CollectionUtil.mapOf;
public class ConstructionUtil {
private static final String TESTABLE_IMPL = "$TestableImpl";
public static <T> T generateSubClassOf(Class<T> clazz) throws InstantiationException {
private static final Map<String, String> RETURN_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("boolean", "true"),
entryOf("java.lang.Boolean", "true")
);
public static <T> T generateSubClassOf(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
StringBuilder sourceCode = new StringBuilder();
String packageName = adaptName(clazz.getPackage().getName());
Map<String, String> noMapping = new HashMap<String, String>();
@@ -25,7 +49,7 @@ public class ConstructionUtil {
.append(getClassName(clazz, noMapping))
.append(getTypeParameters(clazz.getTypeParameters(), false, noMapping))
.append(" {\n");
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping)) {
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping, options)) {
sourceCode.append(method);
}
sourceCode.append("}");
@@ -42,7 +66,8 @@ public class ConstructionUtil {
}
}
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool, Map<String, String> genericTypes) {
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool,
Map<String, String> genericTypes, ConstructionOption[] options) {
Set<String> methods = new HashSet<String>();
// in a very special situation, getDeclaredMethods() could fetch method declaration in the parent interface
// as none-abstract, that will cause the corresponding abstract method in current class be skipped.
@@ -73,13 +98,22 @@ public class ConstructionUtil {
}
}
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");
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}\n");
methods.add(sourceCode.toString());
@@ -93,9 +127,9 @@ public class ConstructionUtil {
for (Type t : superTypes) {
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt)));
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt), options));
} else if (t instanceof Class) {
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap()));
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap(), options));
}
}
return methods;

View File

@@ -1,5 +1,6 @@
package com.alibaba.testable.core.util;
import com.alibaba.testable.core.model.ConstructionOption;
import org.junit.jupiter.api.Test;
import sun.reflect.generics.factory.CoreReflectionFactory;
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
@@ -54,39 +55,41 @@ class ConstructionUtilTest {
public interface Inner$Interface {}
private final ConstructionOption[] options = arrayOf();
@Test
void should_generate_empty_interface() throws Exception {
EmptyInterface ins = ConstructionUtil.generateSubClassOf(EmptyInterface.class);
EmptyInterface ins = ConstructionUtil.generateSubClassOf(EmptyInterface.class, options);
assertNotNull(ins);
}
@Test
void should_generate_real_interface() throws Exception {
RealInterface ins = ConstructionUtil.generateSubClassOf(RealInterface.class);
RealInterface ins = ConstructionUtil.generateSubClassOf(RealInterface.class, options);
assertNotNull(ins);
}
@Test
void should_generate_abstract_class() throws Exception {
RealInterface ins = ConstructionUtil.generateSubClassOf(AbstractClazz.class);
RealInterface ins = ConstructionUtil.generateSubClassOf(AbstractClazz.class, options);
assertNotNull(ins);
}
@Test
void should_generate_parameterized_class() throws Exception {
RealInterface ins = ConstructionUtil.generateSubClassOf(ParameterizedClazz.class);
RealInterface ins = ConstructionUtil.generateSubClassOf(ParameterizedClazz.class, options);
assertNotNull(ins);
}
@Test
void should_generate_implicit_generic_interface() throws Exception {
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class);
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class, options);
assertNotNull(ins);
}
@Test
void should_generate_name_with_dollar() throws Exception {
Inner$Interface ins = ConstructionUtil.generateSubClassOf(Inner$Interface.class);
Inner$Interface ins = ConstructionUtil.generateSubClassOf(Inner$Interface.class, options);
assertNotNull(ins);
}