From 973ca937f6cce86f93b5836163d78199b70f2939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 20 May 2020 17:32:13 +0800 Subject: [PATCH] refactor e class, add pool for member method substitution --- .../TestableClassDevRoleTranslator.java | 2 +- .../TestableClassTestRoleTranslator.java | 44 ++-- .../com/alibaba/testable/util/ConstPool.java | 5 +- src/main/resources/e.java | 193 ++++++++++++++---- 4 files changed, 183 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java index b8b9d9c..432a41f 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/TestableClassDevRoleTranslator.java @@ -154,7 +154,7 @@ public class TestableClassDevRoleTranslator extends TreeTranslator { TestableFieldAccess snMethod = new TestableFieldAccess(snClass, cx.names.fromString(ConstPool.SN_METHOD), null); JCTree.JCExpression classType = new TestableFieldAccess(cx.treeMaker.Ident(className), - cx.names.fromString("class"), null); + cx.names.fromString(ConstPool.TYPE_TO_CLASS), null); ListBuffer args = ListBuffer.of(classType); args.addAll(newClassExpr.args); return new TestableMethodInvocation(null, snMethod, args.toList()); diff --git a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java index 1fde31c..e0b678b 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/TestableClassTestRoleTranslator.java @@ -11,7 +11,10 @@ import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.*; import com.sun.tools.javac.tree.JCTree.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; /** * Travel AST @@ -23,6 +26,8 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { private static final String ANNOTATION_TESTABLE_INJECT = "com.alibaba.testable.annotation.TestableInject"; 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 static final String TYPE_CLASS = "Class"; + private static final String REF_THIS = "this"; private final TestableContext cx; private String sourceClassName = ""; private ListBuffer sourceClassIns = new ListBuffer(); @@ -96,9 +101,9 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { case ANNOTATION_TESTABLE_INJECT: ListBuffer args = new ListBuffer<>(); for (JCVariableDecl p : jcMethodDecl.params) { - args.add(cx.treeMaker.Select(p.vartype, cx.names.fromString("class"))); + args.add(cx.treeMaker.Select(p.vartype, cx.names.fromString(ConstPool.TYPE_TO_CLASS))); } - JCExpression retType = cx.treeMaker.Select(jcMethodDecl.restype, cx.names.fromString("class")); + JCExpression retType = cx.treeMaker.Select(jcMethodDecl.restype, cx.names.fromString(ConstPool.TYPE_TO_CLASS)); injectMethods.add(Pair.of(jcMethodDecl.name, Pair.of(retType, args.toList()))); break; case ANNOTATION_JUNIT5_SETUP: @@ -159,21 +164,7 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { 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)); + statements.append(toGlobalNewStatement(m)); } if (!testSetupMethodName.isEmpty()) { statements.append(cx.treeMaker.Exec(cx.treeMaker.Apply(List.nil(), @@ -182,6 +173,25 @@ public class TestableClassTestRoleTranslator extends TreeTranslator { return cx.treeMaker.Block(0, statements.toList()); } + private JCStatement toGlobalNewStatement( + Pair>> m) { + JCExpression key = nameToExpression(ConstPool.NS_W_KEY); + JCExpression classType = m.snd.fst; + JCExpression parameterTypes = cx.treeMaker.NewArray(cx.treeMaker.Ident(cx.names.fromString(TYPE_CLASS)), + List.nil(), m.snd.snd); + JCNewClass keyClass = cx.treeMaker.NewClass(null, List.nil(), key, + List.of(classType, parameterTypes), null); + JCExpression value = nameToExpression(ConstPool.NS_VAL); + JCExpression thisIns = cx.treeMaker.Ident(cx.names.fromString(REF_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(ConstPool.NS_W_ADD); + JCMethodInvocation apply = cx.treeMaker.Apply(List.nil(), addInjectMethod, + List.from(new JCExpression[] {keyClass, valClass})); + return cx.treeMaker.Exec(apply); + } + private List removeAnnotation(List annotations, String target) { ListBuffer nb = new ListBuffer<>(); for (JCAnnotation i : annotations) { diff --git a/src/main/java/com/alibaba/testable/util/ConstPool.java b/src/main/java/com/alibaba/testable/util/ConstPool.java index ea53bb2..bdcc779 100644 --- a/src/main/java/com/alibaba/testable/util/ConstPool.java +++ b/src/main/java/com/alibaba/testable/util/ConstPool.java @@ -12,9 +12,12 @@ public final class ConstPool { public static final String SN_CLS = "e"; public static final String SN_METHOD = "w"; public static final String SN_PKG_CLS = "n.e"; + public static final String NS_W_KEY = "n.e.wk"; + public static final String NS_VAL = "n.e.v"; + public static final String NS_W_ADD = "n.e.wa"; public static final String STUBBORN_FIELD_METHOD = "stubbornField"; public static final String TESTABLE_GET_METHOD_PREFIX = "TestableGet"; public static final String TESTABLE_SET_METHOD_PREFIX = "TestableSet"; - + public static final String TYPE_TO_CLASS = "class"; } diff --git a/src/main/resources/e.java b/src/main/resources/e.java index fc673fb..4e5d4b6 100644 --- a/src/main/resources/e.java +++ b/src/main/resources/e.java @@ -10,18 +10,21 @@ import java.util.*; public final class e { - public static class k { - public Class c; // target instance type + /** + * key for contructor pool + */ + public static class wk { + public Class c; // target instance type to new public Class[] a; // constructor parameter types - public k(Class c, Class[] a) { + public wk(Class c, Class[] a) { this.c = c; this.a = a; } @Override public boolean equals(Object o) { - return o.getClass().equals(e.k.class) && c.equals(((e.k)o).c) && Arrays.equals(a, ((e.k)o).a); + return o.getClass().equals(e.wk.class) && c.equals(((e.wk)o).c) && Arrays.equals(a, ((e.wk)o).a); } @Override @@ -30,9 +33,35 @@ public final class e { } } + /** + * key for method pool + */ + public static class xk { + public String m; // original member method name + public Class[] a; // member method parameter types + + public xk(String m, Class[] a) { + this.m = m; + this.a = a; + } + + @Override + public boolean equals(Object o) { + return o.getClass().equals(e.xk.class) && m.equals(((e.xk)o).m) && Arrays.equals(a, ((e.xk)o).a); + } + + @Override + public int hashCode() { + return 31 * m.hashCode() + Arrays.hashCode(a); + } + } + + /** + * value for contructor and method pool + */ public static class v { - public Object o; // testable object - public String m; // method to create instance + public Object o; // object which provides substitution + public String m; // substitutional method name public v(Object o, String m) { this.o = o; @@ -40,20 +69,34 @@ public final class e { } } - private static Map pool = new HashMap<>(); + private static Map wp = new HashMap<>(); + private static Map xp = new HashMap<>(); - public static void a(k ki, v vi) { - pool.put(ki, vi); + /** + * add item to contructor pool + */ + public static void wa(wk k, v vv) { + wp.put(k, vv); } + /** + * add item to method pool + */ + public static void xa(xk k, v vv) { + xp.put(k, vv); + } + + /** + * subsitituion entry for new + */ public static T w(Class ct, Object... as) { Class[] cs = new Class[as.length]; for (int i = 0; i < cs.length; i++) { cs[i] = as[i].getClass(); } - if (!pool.isEmpty()) { + if (!wp.isEmpty()) { try { - Pair p = getFromPool(new k(ct, cs)); + Pair p = gwp(new wk(ct, cs)); if (p != null) { Method m = p.snd.o.getClass().getDeclaredMethod(p.snd.m, p.fst.a); m.setAccessible(true); @@ -64,7 +107,7 @@ public final class e { } } try { - Constructor c = getFromConstructors(ct.getConstructors(), cs); + Constructor c = gc(ct.getConstructors(), cs); if (c != null) { return (T)c.newInstance(as); } @@ -74,56 +117,122 @@ public final class e { return null; } - private static Constructor getFromConstructors(Constructor[] constructors, Class[] cs) { - for (Constructor c : constructors) { - if (typeEquals(c.getParameterTypes(), cs)) { - return c; + /** + * subsitituion entry for member call + */ + public static Object x(Object obj, String method, Object... as) { + Class[] cs = gcs(as); + if (!xp.isEmpty()) { + try { + Pair p = gxp(new xk(method, cs)); + if (p != null) { + Method m = p.snd.o.getClass().getDeclaredMethod(p.snd.m, p.fst.a); + m.setAccessible(true); + return m.invoke(p.snd.o, as); + } + } catch (Exception e) { + return null; } } + try { + Method m = gm(obj.getClass().getMethods(), cs); + if (m != null) { + return m.invoke(obj, as); + } + } catch (Exception e) { + return null; + } return null; } - private static Pair getFromPool(k k1) { - for (k k2 : pool.keySet()) { - if (k1.c.equals(k2.c) && typeEquals(k2.a, k1.a)) { - return Pair.of(k2, pool.get(k2)); + /** + * get classes of parameter objects + */ + private static Class[] gcs(Object[] as) { + Class[] cs = new Class[as.length]; + for (int i = 0; i < cs.length; i++) { + cs[i] = as[i].getClass(); + } + return cs; + } + + /** + * get method by parameter matching + */ + private static Method gm(Method[] methods, Class[] cs) { + for (Method m : methods) { + if (te(m.getParameterTypes(), cs)) { + return m; } } return null; } /** - * @param c1 fact types (can be primary type) - * @param c2 user types + * get from method pool by key */ - private static boolean typeEquals(Class[] c1, Class[] c2) { + private static Pair gxp(xk k1) { + for (xk k2 : xp.keySet()) { + if (k1.m.equals(k2.m) && te(k2.a, k1.a)) { + return Pair.of(k2, xp.get(k2)); + } + } + return null; + } + + /** + * get contructor by parameter matching + */ + private static Constructor gc(Constructor[] constructors, Class[] cs) { + for (Constructor c : constructors) { + if (te(c.getParameterTypes(), cs)) { + return c; + } + } + return null; + } + + /** + * get from contructor pool by key + */ + private static Pair gwp(wk k1) { + for (wk k2 : wp.keySet()) { + if (k1.c.equals(k2.c) && te(k2.a, k1.a)) { + return Pair.of(k2, wp.get(k2)); + } + } + return null; + } + + /** + * type equeals + */ + private static boolean te(Class[] c1, Class[] c2) { if (c1.length != c2.length) { return false; } for (int i = 0; i < c1.length; i++) { - if (c1[i].equals(c2[i])) { - continue; - } else if (c1[i].equals(int.class) && c2[i].equals(Integer.class)) { - continue; - } else if (c1[i].equals(long.class) && c2[i].equals(Long.class)) { - continue; - } else if (c1[i].equals(short.class) && c2[i].equals(Short.class)) { - continue; - } else if (c1[i].equals(boolean.class) && c2[i].equals(Boolean.class)) { - continue; - } else if (c1[i].equals(char.class) && c2[i].equals(Character.class)) { - continue; - } else if (c1[i].equals(byte.class) && c2[i].equals(Byte.class)) { - continue; - } else if (c1[i].equals(float.class) && c2[i].equals(Float.class)) { - continue; - } else if (c1[i].equals(double.class) && c2[i].equals(Double.class)) { - continue; - } else { + if (!c1[i].equals(c2[i]) && !fe(c1[i], c2[i])) { return false; } } return true; } + /** + * fuzzy equal + * @param c1 fact types (can be primary type) + * @param c2 user types + */ + private static boolean fe(Class c1, Class c2) { + return (c1.equals(int.class) && c2.equals(Integer.class)) || + (c1.equals(long.class) && c2.equals(Long.class)) || + (c1.equals(short.class) && c2.equals(Short.class)) || + (c1.equals(boolean.class) && c2.equals(Boolean.class)) || + (c1.equals(char.class) && c2.equals(Character.class)) || + (c1.equals(byte.class) && c2.equals(Byte.class)) || + (c1.equals(float.class) && c2.equals(Float.class)) || + (c1.equals(double.class) && c2.equals(Double.class)); + } + }