diff --git a/core/src/main/java/com/alibaba/testable/accessor/PrivateAccessor.java b/core/src/main/java/com/alibaba/testable/accessor/PrivateAccessor.java index 2f6506e..1dd4282 100644 --- a/core/src/main/java/com/alibaba/testable/accessor/PrivateAccessor.java +++ b/core/src/main/java/com/alibaba/testable/accessor/PrivateAccessor.java @@ -1,5 +1,7 @@ package com.alibaba.testable.accessor; +import com.alibaba.testable.util.TypeUtil; + import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -31,11 +33,8 @@ public class PrivateAccessor { public static T invoke(Object ref, String method, Object... args) { try { - Class[] cls = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - cls[i] = args[i].getClass(); - } - Method declaredMethod = ref.getClass().getDeclaredMethod(method, cls); + Class[] cls = TypeUtil.gcs(args); + Method declaredMethod = TypeUtil.gm(ref.getClass().getDeclaredMethods(), method, cls); declaredMethod.setAccessible(true); return (T)declaredMethod.invoke(ref, args); } catch (Exception e) { diff --git a/core/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java b/core/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java index 0f11e96..0856d42 100644 --- a/core/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java +++ b/core/src/main/java/com/alibaba/testable/processor/EnableTestableInjectProcessor.java @@ -3,7 +3,6 @@ package com.alibaba.testable.processor; import com.alibaba.testable.annotation.EnableTestableInject; import com.alibaba.testable.translator.EnableTestableInjectTranslator; import com.alibaba.testable.translator.MethodRecordTranslator; -import com.alibaba.testable.util.ConstPool; import com.alibaba.testable.util.ResourceUtil; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree; @@ -15,11 +14,9 @@ import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.FileObject; -import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.IOException; import java.io.OutputStream; -import java.io.Writer; import java.util.Set; /** @@ -29,11 +26,9 @@ import java.util.Set; @SupportedSourceVersion(SourceVersion.RELEASE_7) public class EnableTestableInjectProcessor extends BaseProcessor { - private static final String JAVA_POSTFIX = ".java"; - private static final String AGENT_TARGET_FOLDER = "generated_testable"; - private static final String AGENT_TARGET_FILE = "agent.jar"; + private static final String AGENT_TARGET_FOLDER = ""; + private static final String AGENT_TARGET_FILE = "testable.jar"; private static final String AGENT_SOURCE_FILE = "testable-agent.jar"; - private static final String NE_SOURCE_FILE = ConstPool.NE_CLS + JAVA_POSTFIX; private static boolean hasFirstClassCompiled = false; @Override @@ -50,12 +45,11 @@ public class EnableTestableInjectProcessor extends BaseProcessor { private void createStaticNewClass() { if (!checkFirstClassCompiled()) { - try { - writeBinaryFile(AGENT_TARGET_FOLDER, AGENT_TARGET_FILE, ResourceUtil.fetchBinary(AGENT_SOURCE_FILE)); - writeSourceFile(ConstPool.NE_PKG_CLS, ResourceUtil.fetchText(NE_SOURCE_FILE)); - } catch (IOException e) { - e.printStackTrace(); + byte[] bytes = ResourceUtil.fetchBinary(AGENT_SOURCE_FILE); + if (bytes.length == 0) { + cx.logger.error("Failed to generate testable agent jar"); } + writeBinaryFile(AGENT_TARGET_FOLDER, AGENT_TARGET_FILE, bytes); } } @@ -74,20 +68,17 @@ public class EnableTestableInjectProcessor extends BaseProcessor { tree.accept(new EnableTestableInjectTranslator(cx, methodRecordTranslator.getMethods())); } - private void writeSourceFile(String fullQualityTypeName, String content) throws IOException { - JavaFileObject jfo = cx.filter.createSourceFile(fullQualityTypeName); - Writer writer = jfo.openWriter(); - writer.write(content); - writer.flush(); - writer.close(); - } - - private void writeBinaryFile(String path, String fileName, byte[] content) throws IOException { - FileObject resource = cx.filter.createResource(StandardLocation.SOURCE_OUTPUT, path, fileName); - OutputStream out = resource.openOutputStream(); - out.write(content); - out.flush(); - out.close(); + private void writeBinaryFile(String path, String fileName, byte[] content) { + try { + FileObject resource = cx.filter.createResource(StandardLocation.SOURCE_OUTPUT, path, fileName); + OutputStream out = resource.openOutputStream(); + out.write(content); + out.flush(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + cx.logger.error("Failed to write " + fileName); + } } } diff --git a/core/src/main/java/com/alibaba/testable/util/ConstPool.java b/core/src/main/java/com/alibaba/testable/util/ConstPool.java index 7c59673..2f1b7f3 100644 --- a/core/src/main/java/com/alibaba/testable/util/ConstPool.java +++ b/core/src/main/java/com/alibaba/testable/util/ConstPool.java @@ -5,7 +5,7 @@ package com.alibaba.testable.util; */ public final class ConstPool { - public static final String NE_PKG = "generated_testable.n"; + public static final String NE_PKG = "testable_internal.n"; public static final String NE_CLS = "e"; public static final String NE_NEW = "w"; public static final String NE_FUN = "f"; diff --git a/core/src/main/java/com/alibaba/testable/util/ResourceUtil.java b/core/src/main/java/com/alibaba/testable/util/ResourceUtil.java index 9bc832d..6b78951 100644 --- a/core/src/main/java/com/alibaba/testable/util/ResourceUtil.java +++ b/core/src/main/java/com/alibaba/testable/util/ResourceUtil.java @@ -29,6 +29,10 @@ public class ResourceUtil { public static byte[] fetchBinary(String fileName) { InputStream in = ResourceUtil.class.getResourceAsStream("/" + fileName); + if (in == null) { + System.err.println("Resource " + fileName + " not exist"); + return new byte[] {}; + } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final int bufLen = 4 * 1024; byte[] buf = new byte[bufLen]; @@ -40,7 +44,7 @@ public class ResourceUtil { buffer.close(); return buffer.toByteArray(); } catch (IOException e) { - System.err.println("Failed to fetch text file: " + fileName); + System.err.println("Failed to fetch file: " + fileName); return new byte[] {}; } } diff --git a/core/src/main/java/com/alibaba/testable/util/TypeUtil.java b/core/src/main/java/com/alibaba/testable/util/TypeUtil.java new file mode 100644 index 0000000..2ffb089 --- /dev/null +++ b/core/src/main/java/com/alibaba/testable/util/TypeUtil.java @@ -0,0 +1,74 @@ +package com.alibaba.testable.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class TypeUtil { + + /** + * get classes of parameter objects + */ + public 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 name and parameter matching + */ + public static Method gm(Method[] mds, String mn, Class[] cs) { + for (Method m : mds) { + if (m.getName().equals(mn) && te(m.getParameterTypes(), cs)) { + return m; + } + } + return null; + } + + /** + * get constructor by parameter matching + */ + public static Constructor gc(Constructor[] cons, Class[] cs) { + for (Constructor c : cons) { + if (te(c.getParameterTypes(), cs)) { + return c; + } + } + return null; + } + + /** + * type equeals + */ + public 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]) && !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)); + } + +} diff --git a/core/src/main/java/testable_internal/n/e.java b/core/src/main/java/testable_internal/n/e.java new file mode 100644 index 0000000..96c1f13 --- /dev/null +++ b/core/src/main/java/testable_internal/n/e.java @@ -0,0 +1,125 @@ +package testable_internal.n; + +import com.alibaba.testable.util.TypeUtil; + +import java.lang.Class; +import java.lang.Object; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.*; + +public final class e { + + public static class p { + public Class c; // target instance type to new / method return type + public Class[] a; // constructor parameter types / member method parameter types + public Object o; // object which provides substitution / object which provides substitution + public String m; // substitutional method name / original member method name + + public p(Class c, String m, Class[] a, Object o) { + this.c = c; + this.m = m; + this.a = a; + this.o = o; + } + } + + private static List

pw = new ArrayList<>(); + private static List

pf = new ArrayList<>(); + + /** + * add item to constructor pool + */ + public static void aw(p np) { + pw.add(np); + } + + /** + * add item to method pool + */ + public static void af(p np) { + pf.add(np); + } + + /** + * substitution entry for new + */ + public static T w(Class ct, Object... as) { + Class[] cs = TypeUtil.gcs(as); + if (!pw.isEmpty()) { + try { + p pi = gpw(ct, cs); + if (pi != null) { + Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); + m.setAccessible(true); + return (T)m.invoke(pi.o, as); + } + } catch (Exception e) { + return null; + } + } + try { + Constructor c = TypeUtil.gc(ct.getConstructors(), cs); + if (c != null) { + return (T)c.newInstance(as); + } + } catch (Exception e) { + return null; + } + return null; + } + + /** + * substitution entry for member call + */ + public static T f(Object obj, String mn, Object... as) { + Class[] cs = TypeUtil.gcs(as); + if (!pf.isEmpty()) { + try { + p pi = gpf(mn, cs); + if (pi != null) { + Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); + m.setAccessible(true); + return (T)m.invoke(pi.o, as); + } + } catch (Exception e) { + return null; + } + } + try { + Method m = TypeUtil.gm(obj.getClass().getDeclaredMethods(), mn, cs); + if (m != null) { + m.setAccessible(true); + return (T)m.invoke(obj, as); + } + } catch (Exception e) { + return null; + } + return null; + } + + /** + * get from method pool by key + */ + private static p gpf(String mn, Class[] cs) { + for (p f : pf) { + if (f.m.equals(mn) && TypeUtil.te(f.a, cs)) { + return f; + } + } + return null; + } + + /** + * get from constructor pool by key + */ + private static p gpw(Class ct, Class[] cs) { + for (p w : pw) { + if (w.c.equals(ct) && TypeUtil.te(w.a, cs)) { + return w; + } + } + return null; + } + +} diff --git a/core/src/main/resources/e.java b/core/src/main/resources/e.java deleted file mode 100644 index ff4f92e..0000000 --- a/core/src/main/resources/e.java +++ /dev/null @@ -1,194 +0,0 @@ -package generated_testable.n; - -import com.sun.tools.javac.util.Pair; - -import java.lang.Class; -import java.lang.Object; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.*; - -public final class e { - - public static class p { - public Class c; // target instance type to new / method return type - public Class[] a; // constructor parameter types / member method parameter types - public Object o; // object which provides substitution / object which provides substitution - public String m; // substitutional method name / original member method name - - public p(Class c, String m, Class[] a, Object o) { - this.c = c; - this.m = m; - this.a = a; - this.o = o; - } - } - - private static List

pw = new ArrayList<>(); - private static List

pf = new ArrayList<>(); - - /** - * add item to contructor pool - */ - public static void aw(p np) { - pw.add(np); - } - - /** - * add item to method pool - */ - public static void af(p np) { - pf.add(np); - } - - /** - * 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 (!pw.isEmpty()) { - try { - p pi = gpw(ct, cs); - if (pi != null) { - Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); - m.setAccessible(true); - return (T)m.invoke(pi.o, as); - } - } catch (Exception e) { - return null; - } - } - try { - Constructor c = gc(ct.getConstructors(), cs); - if (c != null) { - return (T)c.newInstance(as); - } - } catch (Exception e) { - return null; - } - return null; - } - - /** - * subsitituion entry for member call - */ - public static T f(Object obj, String mn, Object... as) { - Class[] cs = gcs(as); - if (!pf.isEmpty()) { - try { - p pi = gpf(mn, cs); - if (pi != null) { - Method m = pi.o.getClass().getDeclaredMethod(pi.m, pi.a); - m.setAccessible(true); - return (T)m.invoke(pi.o, as); - } - } catch (Exception e) { - return null; - } - } - try { - Method m = gm(obj.getClass().getDeclaredMethods(), mn, cs); - if (m != null) { - m.setAccessible(true); - return (T)m.invoke(obj, as); - } - } catch (Exception e) { - return null; - } - return null; - } - - /** - * 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 name and parameter matching - */ - private static Method gm(Method[] mds, String mn, Class[] cs) { - for (Method m : mds) { - if (m.getName().equals(mn) && te(m.getParameterTypes(), cs)) { - return m; - } - } - return null; - } - - /** - * get from method pool by key - */ - private static p gpf(String mn, Class[] cs) { - for (p f : pf) { - if (f.m.equals(mn) && te(f.a, cs)) { - return f; - } - } - return null; - } - - /** - * get contructor by parameter matching - */ - private static Constructor gc(Constructor[] cons, Class[] cs) { - for (Constructor c : cons) { - if (te(c.getParameterTypes(), cs)) { - return c; - } - } - return null; - } - - /** - * get from contructor pool by key - */ - private static p gpw(Class ct, Class[] cs) { - for (p w : pw) { - if (w.c.equals(ct) && te(w.a, cs)) { - return w; - } - } - 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]) && !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)); - } - -}