lithium.classloadertest.impl.UnfinalizingClassTransformer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multiverse-test Show documentation
Show all versions of multiverse-test Show documentation
The Multiverse Test Framework for Java
The newest version!
package lithium.classloadertest.impl;
import lithium.classloadertest.common.TransformingURLClassLoader.ClassTransformer;
import net.sf.cglib.asm.AnnotationVisitor;
import net.sf.cglib.asm.Attribute;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.ClassVisitor;
import net.sf.cglib.asm.ClassWriter;
import net.sf.cglib.asm.FieldVisitor;
import net.sf.cglib.asm.Label;
import net.sf.cglib.asm.MethodVisitor;
import net.sf.cglib.asm.Opcodes;
/**
* Transforms the bytes of a class to remove all final designation on Classes, Methods and Fields as
* well as making private and protected members and the class itself public. Useful when creating
* a fresh classloader as it will make the specified classes more testable with mocking and proxying frameworks.
*
* @author jeff.collins
*/
public class UnfinalizingClassTransformer implements ClassTransformer {
@Override
public byte[] transformClass(byte[] inputClass) {
ClassReader classReader = new ClassReader(inputClass);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
UnfinalizingClassVisitor ufcv = new UnfinalizingClassVisitor(classWriter);
classReader.accept(ufcv, 0);
return classWriter.toByteArray();
}
/**
*
* @author jeff.collins
*
*/
private class UnfinalizingClassVisitor implements ClassVisitor {
private final ClassVisitor cv;
private String className;
public UnfinalizingClassVisitor(ClassVisitor cv) {
this.cv = cv;
}
private int removeFinalAndMakePublic(int access) {
// change modifier.
if ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE)
access -= Opcodes.ACC_PRIVATE;
if ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED)
access -= Opcodes.ACC_PROTECTED;
access |= Opcodes.ACC_PUBLIC;
if ((access & Opcodes.ACC_FINAL) != 0) {
access -= Opcodes.ACC_FINAL;
}
return access;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
className = name;
cv.visit(version, removeFinalAndMakePublic(access), name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return cv.visitAnnotation(desc, visible);
}
@Override
public void visitAttribute(Attribute attr) {
cv.visitAttribute(attr);
}
@Override
public void visitEnd() {
cv.visitEnd();
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return cv.visitField(removeFinalAndMakePublic(access), name, desc, signature, value);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
cv.visitInnerClass(name, outerName, innerName, removeFinalAndMakePublic(access));
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(removeFinalAndMakePublic(access), name, desc, signature, exceptions);
return new UnprivatizingMethodVisitor(mv, className);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
cv.visitOuterClass(owner, name, desc);
}
@Override
public void visitSource(String source, String debug) {
cv.visitSource(source, debug);
}
}
/*
* Modifies calls to private methods to use InvokeVirtual instead of InvokeSpecial. Without this,
* private helper methods in a test class will not by overridden by dynamically created proxies, and instead
* the private method will be called directly.
*/
class UnprivatizingMethodVisitor implements MethodVisitor {
private final MethodVisitor mv;
private final String className;
public UnprivatizingMethodVisitor(MethodVisitor mv, String className) {
this.mv = mv;
this.className = className;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return mv.visitAnnotation(desc, visible);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return mv.visitAnnotationDefault();
}
@Override
public void visitAttribute(Attribute attr) {
mv.visitAttribute(attr);
}
@Override
public void visitCode() {
mv.visitCode();
}
@Override
public void visitEnd() {
mv.visitEnd();
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
mv.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
mv.visitFrame(type, nLocal, local, nStack, stack);
}
@Override
public void visitIincInsn(int var, int increment) {
mv.visitIincInsn(var, increment);
}
@Override
public void visitInsn(int opcode) {
mv.visitInsn(opcode);
}
@Override
public void visitIntInsn(int opcode, int operand) {
mv.visitIntInsn(opcode, operand);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
mv.visitJumpInsn(opcode, label);
}
@Override
public void visitLabel(Label label) {
mv.visitLabel(label);
}
@Override
public void visitLdcInsn(Object cst) {
mv.visitLdcInsn(cst);
}
@Override
public void visitLineNumber(int line, Label start) {
mv.visitLineNumber(line, start);
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
mv.visitLocalVariable(name, desc, signature, start, end, index);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
mv.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitMaxs(maxStack, maxLocals);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.INVOKESPECIAL && !name.equals("") && className.equals(owner)) {
// if the method we're calling is private (because of the invokespecial designation, and
// is not a constructor, and not from another class, change to calling with InvokeVirtual
// which will allow use of the virtual tables, and thus overriding by a proxy subclass...
opcode = Opcodes.INVOKEVIRTUAL;
}
mv.visitMethodInsn(opcode, owner, name, desc);
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
mv.visitMultiANewArrayInsn(desc, dims);
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
return mv.visitParameterAnnotation(parameter, desc, visible);
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
mv.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
mv.visitTryCatchBlock(start, end, handler, type);
}
@Override
public void visitTypeInsn(int opcode, String type) {
mv.visitTypeInsn(opcode, type);
}
@Override
public void visitVarInsn(int opcode, int var) {
mv.visitVarInsn(opcode, var);
}
}
}