DelegateClassAdapterTestpublic class DelegateClassAdapterTest extends Object
Fields Summary |
---|
private MockLog | mLog | private static final String | NATIVE_CLASS_NAME | private static final String | OUTER_CLASS_NAME | private static final String | INNER_CLASS_NAME |
Methods Summary |
---|
private java.lang.Throwable | dumpGeneratedClass(java.lang.Throwable t, com.android.tools.layoutlib.create.DelegateClassAdapterTest$ClassLoader2 cl2)For debugging, it's useful to dump the content of the generated classes
along with the exception that was generated.
However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor
class and associated utilities which are found in the ASM source jar. Since we don't
want that dependency in the source code, we only put it manually for development and
access the TraceClassVisitor via reflection if present.
try {
// For debugging, dump the bytecode of the class in case of unexpected error
// if we can find the TraceClassVisitor class.
Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
StringBuilder sb = new StringBuilder();
sb.append('\n").append(t.getClass().getCanonicalName());
if (t.getMessage() != null) {
sb.append(": ").append(t.getMessage());
}
for (Entry<String, byte[]> entry : cl2.getByteCode()) {
String className = entry.getKey();
byte[] bytes = entry.getValue();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
// next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
Constructor<?> cons = tcvClass.getConstructor(pw.getClass());
Object tcv = cons.newInstance(pw);
ClassReader cr2 = new ClassReader(bytes);
cr2.accept((ClassVisitor) tcv, 0 /* flags */);
sb.append("\nBytecode dump: <").append(className).append(">:\n")
.append(sw.toString());
}
// Re-throw exception with new message
return new RuntimeException(sb.toString(), t);
} catch (Throwable ignore) {
// In case of problem, just throw the original exception as-is.
return t;
}
| public void | setUp()
mLog = new MockLog();
mLog.setVerbose(true); // capture debug error too
| public void | testConstructorsNotSupported(){@link DelegateMethodAdapter} does not support overriding constructors yet,
so this should fail with an {@link UnsupportedOperationException}.
Although not tested here, the message of the exception should contain the
constructor signature.
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("<init>");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
| public void | testDelegateInner()
// We'll delegate the "get" method of both the inner and outer class.
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("get");
delegateMethods.add("privateMethod");
// Generate the delegate for the outer class.
ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
String outerClassName = OUTER_CLASS_NAME.replace('.", '/");
DelegateClassAdapter cvOuter = new DelegateClassAdapter(
mLog, cwOuter, outerClassName, delegateMethods);
ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
cr.accept(cvOuter, 0 /* flags */);
// Generate the delegate for the inner class.
ClassWriter cwInner = new ClassWriter(0 /*flags*/);
String innerClassName = INNER_CLASS_NAME.replace('.", '/");
DelegateClassAdapter cvInner = new DelegateClassAdapter(
mLog, cwInner, innerClassName, delegateMethods);
cr = new ClassReader(INNER_CLASS_NAME);
cr.accept(cvInner, 0 /* flags */);
// Load the generated classes in a different class loader and try them
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
// Check the outer class
Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
Object o2 = outerClazz2.newInstance();
assertNotNull(o2);
// The original Outer.get returns 1+10+20,
// but the delegate makes it return 4+10+20
assertEquals(4+10+20, callGet(o2, 10, 20));
assertEquals(1+10+20, callGet_Original(o2, 10, 20));
// The original Outer has a private method,
// so by default we can't access it.
boolean gotIllegalAccessException = false;
try {
callMethod(o2, "privateMethod", false /*makePublic*/);
} catch(IllegalAccessException e) {
gotIllegalAccessException = true;
}
assertTrue(gotIllegalAccessException);
// The private method from original Outer has been
// delegated. The delegate generated should have the
// same access.
gotIllegalAccessException = false;
try {
assertEquals("outerPrivateMethod",
callMethod(o2, "privateMethod_Original", false /*makePublic*/));
} catch (IllegalAccessException e) {
gotIllegalAccessException = true;
}
assertTrue(gotIllegalAccessException);
// Check the inner class. Since it's not a static inner class, we need
// to use the hidden constructor that takes the outer class as first parameter.
Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2);
Object i2 = innerCons.newInstance(o2);
assertNotNull(i2);
// The original Inner.get returns 3+10+20,
// but the delegate makes it return 6+10+20
assertEquals(6+10+20, callGet(i2, 10, 20));
assertEquals(3+10+20, callGet_Original(i2, 10, 20));
}
};
cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
| public void | testDelegateNative()
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
Object i2 = clazz2.newInstance();
assertNotNull(i2);
// Use reflection to access inner methods
assertEquals(42, callAdd(i2, 20, 22));
Object[] objResult = new Object[] { null };
int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
assertEquals((int)(10 + 3.1415), result);
assertSame(i2, objResult[0]);
// Check that the native method now has the new annotation and is not native
Method[] m = clazz2.getDeclaredMethods();
Method nativeInstanceMethod = null;
for (Method method : m) {
if ("native_instance".equals(method.getName())) {
nativeInstanceMethod = method;
break;
}
}
assertNotNull(nativeInstanceMethod);
assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers()));
Annotation[] a = nativeInstanceMethod.getAnnotations();
assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
}
};
cl2.add(NATIVE_CLASS_NAME, cw);
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
| public void | testNoOp()Tests that a class not being modified still works.
// create an instance of the class that will be modified
// (load the class in a distinct class loader so that we can trash its definition later)
ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
ClassWithNative instance1 = clazz1.newInstance();
assertEquals(42, instance1.add(20, 22));
try {
instance1.callNativeInstance(10, 3.1415, new Object[0] );
fail("Test should have failed to invoke callTheNativeMethod [1]");
} catch (UnsatisfiedLinkError e) {
// This is expected to fail since the native method is not implemented.
}
// Now process it but tell the delegate to not modify any method
ClassWriter cw = new ClassWriter(0 /*flags*/);
HashSet<String> delegateMethods = new HashSet<String>();
String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it again
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
Object i2 = clazz2.newInstance();
assertNotNull(i2);
assertEquals(42, callAdd(i2, 20, 22));
try {
callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
fail("Test should have failed to invoke callTheNativeMethod [2]");
} catch (InvocationTargetException e) {
// This is expected to fail since the native method has NOT been
// overridden here.
assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
}
// Check that the native method does NOT have the new annotation
Method[] m = clazz2.getDeclaredMethods();
Method nativeInstanceMethod = null;
for (Method method : m) {
if ("native_instance".equals(method.getName())) {
nativeInstanceMethod = method;
break;
}
}
assertNotNull(nativeInstanceMethod);
assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers()));
Annotation[] a = nativeInstanceMethod.getAnnotations();
assertEquals(0, a.length);
}
};
cl2.add(NATIVE_CLASS_NAME, cw);
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
|
|