From 14659f332df504599c8c34cb655aeeb0b066f7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Tue, 19 May 2020 21:18:08 +0800 Subject: [PATCH] add substitutions to global inject method pool --- .../generator/StaticNewClassGenerator.java | 76 +++------- .../TestableClassTestRoleTranslator.java | 140 ++++++++++++------ src/main/resources/e.java | 71 +++++++++ 3 files changed, 183 insertions(+), 104 deletions(-) create mode 100644 src/main/resources/e.java diff --git a/src/main/java/com/alibaba/testable/generator/StaticNewClassGenerator.java b/src/main/java/com/alibaba/testable/generator/StaticNewClassGenerator.java index bfb62a1..acb76aa 100644 --- a/src/main/java/com/alibaba/testable/generator/StaticNewClassGenerator.java +++ b/src/main/java/com/alibaba/testable/generator/StaticNewClassGenerator.java @@ -1,13 +1,10 @@ package com.alibaba.testable.generator; -import com.alibaba.testable.util.ConstPool; -import com.squareup.javapoet.*; +import com.alibaba.testable.model.TestableContext; -import javax.lang.model.element.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; /** * Generate global n.e class code @@ -16,55 +13,24 @@ import java.util.Map; */ public class StaticNewClassGenerator { + private final TestableContext cx; + + public StaticNewClassGenerator(TestableContext cx) { + this.cx = cx; + } + public String fetch() { - return JavaFile.builder(ConstPool.SN_PKG, - TypeSpec.classBuilder(ConstPool.SN_CLS) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addField(buildStaticPoolField()) - .addMethod(buildStaticNewMethod()) - .build()) - .build().toString(); - } - - private FieldSpec buildStaticPoolField() { - return FieldSpec.builder(ParameterizedTypeName.get(Map.class, Class.class, Object.class), "pool", Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .initializer("new $T<>()", HashMap.class) - .build(); - } - - private MethodSpec buildStaticNewMethod() { - TypeVariableName typeVariable = TypeVariableName.get("T"); - MethodSpec.Builder builder = MethodSpec.methodBuilder(ConstPool.SN_METHOD) - .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC) - .addTypeVariable(typeVariable) - .varargs(true) - .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), typeVariable), "type") - .addParameter(ArrayTypeName.of(Object.class), "args") - .returns(typeVariable); - addStaticNewMethodStatement(builder); - return builder.build(); - } - - private void addStaticNewMethodStatement(MethodSpec.Builder builder) { - builder.addStatement("$T<$T> pts = new $T<>()", List.class, Class.class, ArrayList.class) - .beginControlFlow("for (Object o : args)") - .addStatement("pts.add(o.getClass())") - .endControlFlow() - .beginControlFlow("if (!pool.isEmpty())") - .beginControlFlow("try") - .addStatement("T obj = (T)pool.get(type)") - .beginControlFlow("if (obj != null)") - .addStatement("return obj") - .endControlFlow() - .nextControlFlow("catch (Exception e)") - .endControlFlow() - .endControlFlow() - .beginControlFlow("try") - .addStatement("return type.getConstructor(pts.toArray(new Class[0])).newInstance(args)") - .nextControlFlow("catch (Exception e)") - .addStatement("return null") - .endControlFlow(); + ClassLoader classLoader = this.getClass().getClassLoader(); + File file = new File(classLoader.getResource("e.java").getFile()); + if (!file.exists()) { + cx.logger.error("Failed to fetch testable new stand-in."); + } + try { + return new String(Files.readAllBytes(file.toPath())); + } catch (IOException e) { + cx.logger.error("Failed to generate testable new stand-in."); + return ""; + } } } diff --git a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java index 5a34b24..1fde31c 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java @@ -9,6 +9,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.*; +import com.sun.tools.javac.tree.JCTree.*; import java.lang.reflect.Modifier; @@ -23,11 +24,15 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { private static final String ANNOTATION_JUNIT5_SETUP = "org.junit.jupiter.api.BeforeEach"; private static final String ANNOTATION_JUNIT5_TEST = "org.junit.jupiter.api.Test"; private final TestableContext cx; - private String sourceClassName; + private String sourceClassName = ""; private ListBuffer sourceClassIns = new ListBuffer(); private List stubbornFields = List.nil(); - private ListBuffer>>> injectMethods = new ListBuffer<>(); - private String testSetupMethodName; + + /** + * MethodName -> (ResultType -> ParameterTypes) + */ + private ListBuffer>>> injectMethods = new ListBuffer<>(); + private String testSetupMethodName = ""; private TestLibType testLibType = TestLibType.JUnit4; public TestableClassTestRoleTranslator(String pkgName, String className, TestableContext cx) { @@ -44,29 +49,39 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { } @Override - public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) { + public void visitVarDef(JCVariableDecl jcVariableDecl) { super.visitVarDef(jcVariableDecl); - if (((JCTree.JCIdent)jcVariableDecl.vartype).name.toString().equals(sourceClassName)) { + if (((JCIdent)jcVariableDecl.vartype).name.toString().equals(sourceClassName)) { jcVariableDecl.vartype = getTestableClassIdent(jcVariableDecl.vartype); sourceClassIns.add(jcVariableDecl.name); } } @Override - public void visitNewClass(JCTree.JCNewClass jcNewClass) { + public void visitNewClass(JCNewClass jcNewClass) { super.visitNewClass(jcNewClass); - if (((JCTree.JCIdent)jcNewClass.clazz).name.toString().equals(sourceClassName)) { + if (getSimpleClassName(jcNewClass).equals(sourceClassName)) { jcNewClass.clazz = getTestableClassIdent(jcNewClass.clazz); } } + private String getSimpleClassName(JCNewClass jcNewClass) { + if (jcNewClass.clazz.getClass().equals(JCIdent.class)) { + return ((JCIdent)jcNewClass.clazz).name.toString(); + } else if (jcNewClass.clazz.getClass().equals(JCFieldAccess.class)) { + return ((JCFieldAccess)jcNewClass.clazz).name.toString(); + } else { + return ""; + } + } + @Override - public void visitExec(JCTree.JCExpressionStatement jcExpressionStatement) { - if (jcExpressionStatement.expr.getClass().equals(JCTree.JCAssign.class) && - isAssignStubbornField((JCTree.JCAssign)jcExpressionStatement.expr)) { - JCTree.JCAssign assign = (JCTree.JCAssign)jcExpressionStatement.expr; + public void visitExec(JCExpressionStatement jcExpressionStatement) { + if (jcExpressionStatement.expr.getClass().equals(JCAssign.class) && + isAssignStubbornField((JCAssign)jcExpressionStatement.expr)) { + JCAssign assign = (JCAssign)jcExpressionStatement.expr; // TODO: Use treeMaker.Apply() and treeMaker.Select() - TestableFieldAccess stubbornSetter = new TestableFieldAccess(((JCTree.JCFieldAccess)assign.lhs).selected, + TestableFieldAccess stubbornSetter = new TestableFieldAccess(((JCFieldAccess)assign.lhs).selected, getStubbornSetterMethodName(assign), null); jcExpressionStatement.expr = new TestableMethodInvocation(null, stubbornSetter, com.sun.tools.javac.util.List.of(assign.rhs)); @@ -75,15 +90,16 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { } @Override - public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) { - for (JCTree.JCAnnotation a : jcMethodDecl.mods.annotations) { + public void visitMethodDef(JCMethodDecl jcMethodDecl) { + for (JCAnnotation a : jcMethodDecl.mods.annotations) { switch (a.type.tsym.toString()) { case ANNOTATION_TESTABLE_INJECT: - ListBuffer args = new ListBuffer<>(); - for (JCTree.JCVariableDecl p : jcMethodDecl.params) { - args.add(p.vartype.type); + ListBuffer args = new ListBuffer<>(); + for (JCVariableDecl p : jcMethodDecl.params) { + args.add(cx.treeMaker.Select(p.vartype, cx.names.fromString("class"))); } - injectMethods.add(Pair.of(jcMethodDecl.restype.type, Pair.of(jcMethodDecl.name, args.toList()))); + JCExpression retType = cx.treeMaker.Select(jcMethodDecl.restype, cx.names.fromString("class")); + injectMethods.add(Pair.of(jcMethodDecl.name, Pair.of(retType, args.toList()))); break; case ANNOTATION_JUNIT5_SETUP: testSetupMethodName = jcMethodDecl.name.toString(); @@ -99,35 +115,22 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { } @Override - public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { + public void visitClassDef(JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); ListBuffer ndefs = new ListBuffer<>(); ndefs.addAll(jcClassDecl.defs); - JCTree.JCModifiers mods = cx.treeMaker.Modifiers(Modifier.PUBLIC, makeAnnotations()); + JCModifiers mods = cx.treeMaker.Modifiers(Modifier.PUBLIC, makeAnnotations(ANNOTATION_JUNIT5_SETUP)); ndefs.add(cx.treeMaker.MethodDef(mods, cx.names.fromString("testableSetup"), - cx.treeMaker.Type(new Type.JCVoidType()), List.nil(), - List.nil(), List.nil(), testableSetupBlock(), null)); + cx.treeMaker.Type(new Type.JCVoidType()), List.nil(), + List.nil(), List.nil(), testableSetupBlock(), null)); jcClassDecl.defs = ndefs.toList(); } - private List makeAnnotations() { - String[] elems = ANNOTATION_JUNIT5_TEST.split("\\."); - JCTree.JCExpression e = cx.treeMaker.Ident(cx.names.fromString(elems[0])); - for (int i = 1 ; i < elems.length ; i++) { - e = cx.treeMaker.Select(e, cx.names.fromString(elems[i])); - } - return List.of(cx.treeMaker.Annotation(e, List.nil())); - } - - private JCTree.JCBlock testableSetupBlock() { - return cx.treeMaker.Block(0, List.nil()); - } - /** * For break point */ @Override - public void visitAssign(JCTree.JCAssign jcAssign) { + public void visitAssign(JCAssign jcAssign) { super.visitAssign(jcAssign); } @@ -135,14 +138,53 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { * For break point */ @Override - public void visitSelect(JCTree.JCFieldAccess jcFieldAccess) { + public void visitSelect(JCFieldAccess jcFieldAccess) { super.visitSelect(jcFieldAccess); } - private List removeAnnotation( - List annotations, String target) { - ListBuffer nb = new ListBuffer<>(); - for (JCTree.JCAnnotation i : annotations) { + private List makeAnnotations(String fullAnnotationName) { + JCExpression setupAnnotation = nameToExpression(fullAnnotationName); + return List.of(cx.treeMaker.Annotation(setupAnnotation, List.nil())); + } + + private JCExpression nameToExpression(String dotName) { + String[] nameParts = dotName.split("\\."); + JCExpression e = cx.treeMaker.Ident(cx.names.fromString(nameParts[0])); + for (int i = 1 ; i < nameParts.length ; i++) { + e = cx.treeMaker.Select(e, cx.names.fromString(nameParts[i])); + } + return e; + } + + private JCBlock testableSetupBlock() { + ListBuffer statements = new ListBuffer<>(); + for (Pair>> m : injectMethods.toList()) { + JCExpression key = nameToExpression("n.e.k"); + JCExpression classType = m.snd.fst; + JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString("Class")), + List.nil(), m.snd.snd); + JCNewClass keyClass = cx.treeMaker.NewClass(null, List.nil(), key, + List.of(classType, parameterTypes), null); + JCExpression value = nameToExpression("n.e.v"); + JCExpression thisIns = cx.treeMaker.Ident(cx.names.fromString("this")); + JCExpression methodName = cx.treeMaker.Literal(m.fst.toString()); + JCNewClass valClass = cx.treeMaker.NewClass(null, List.nil(), value, + List.of(thisIns, methodName), null); + JCExpression addInjectMethod = nameToExpression("n.e.a"); + JCMethodInvocation apply = cx.treeMaker.Apply(List.nil(), addInjectMethod, + List.from(new JCExpression[] {keyClass, valClass})); + statements.append(cx.treeMaker.Exec(apply)); + } + if (!testSetupMethodName.isEmpty()) { + statements.append(cx.treeMaker.Exec(cx.treeMaker.Apply(List.nil(), + nameToExpression(testSetupMethodName), List.nil()))); + } + return cx.treeMaker.Block(0, statements.toList()); + } + + private List removeAnnotation(List annotations, String target) { + ListBuffer nb = new ListBuffer<>(); + for (JCAnnotation i : annotations) { if (!i.type.tsym.toString().equals(target)) { nb.add(i); } @@ -150,19 +192,19 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { return nb.toList(); } - private Name getStubbornSetterMethodName(JCTree.JCAssign assign) { - String name = ((JCTree.JCFieldAccess)assign.lhs).name.toString() + ConstPool.TESTABLE_SET_METHOD_PREFIX; + private Name getStubbornSetterMethodName(JCAssign assign) { + String name = ((JCFieldAccess)assign.lhs).name.toString() + ConstPool.TESTABLE_SET_METHOD_PREFIX; return cx.names.fromString(name); } - private boolean isAssignStubbornField(JCTree.JCAssign expr) { - return expr.lhs.getClass().equals(JCTree.JCFieldAccess.class) && - sourceClassIns.contains(((JCTree.JCIdent)((JCTree.JCFieldAccess)(expr).lhs).selected).name) && - stubbornFields.contains(((JCTree.JCFieldAccess)(expr).lhs).name.toString()); + private boolean isAssignStubbornField(JCAssign expr) { + return expr.lhs.getClass().equals(JCFieldAccess.class) && + sourceClassIns.contains(((JCIdent)((JCFieldAccess)(expr).lhs).selected).name) && + stubbornFields.contains(((JCFieldAccess)(expr).lhs).name.toString()); } - private JCTree.JCIdent getTestableClassIdent(JCTree.JCExpression clazz) { - Name className = ((JCTree.JCIdent)clazz).name; + private JCIdent getTestableClassIdent(JCExpression clazz) { + Name className = ((JCIdent)clazz).name; return cx.treeMaker.Ident(cx.names.fromString(className + ConstPool.TESTABLE)); } diff --git a/src/main/resources/e.java b/src/main/resources/e.java new file mode 100644 index 0000000..e5c1b04 --- /dev/null +++ b/src/main/resources/e.java @@ -0,0 +1,71 @@ +package n; + +import java.lang.Class; +import java.lang.Object; +import java.lang.reflect.Method; +import java.util.*; + +public final class e { + + public static class k { + public Class c; // target instance type + public Class[] a; // constructor parameter types + + public k(Class c, Class[] a) { + this.c = c; + this.a = a; + } + + @Override + public boolean equals(Object o) { + return o.getClass().equals(k.class) && c.equals(((k)o).c) && Arrays.equals(a, ((k)o).a); + } + + @Override + public int hashCode() { + return 31 * c.hashCode() + Arrays.hashCode(a); + } + } + + public static class v { + public Object o; // testable object + public String m; // method to create instance + + public v(Object o, String m) { + this.o = o; + this.m = m; + } + } + + private static Map pool = new HashMap<>(); + + public static void a(k ki, v vi) { + pool.put(ki, vi); + } + + public static T w(Class ct, Object... as) { + Class[] cs = new Class[as.length / 2]; + Object[] ps = new Object[as.length / 2]; + for (int i = 0; i < cs.length; i++) { + cs[i] = (Class)as[i]; + } + System.arraycopy(as, ps.length, ps, 0, ps.length); + if (!pool.isEmpty()) { + try { + v p = pool.get(new k(ct, cs)); + if (p != null) { + Method m = p.o.getClass().getDeclaredMethod(p.m, cs); + m.setAccessible(true); + return (T)m.invoke(p.o, ps); + } + } catch (Exception e) { + return null; + } + } + try { + return ct.getConstructor(cs).newInstance(ps); + } catch (Exception e) { + return null; + } + } +}