/*
* Copyright (c) 2005, Marco Petris
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Marco Petris nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.petris.dynamicaspects.util;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;
import de.petris.dynamicaspects.MixIn;
import de.petris.dynamicaspects.MixInImplementationFactory;
/**
* An instance of this class patches a target class,
* i. e. a list of {@link de.petris.dynamicaspects.MixIn MixIns} will be mixed in.
*
* @author Marco Petris
*
* @see de.petris.dynamicaspects.MixIn
*/
public class ClassPatcher {
// the class generator
private ClassGen cGen;
// the list of MixIns to be mixed in
private List<MixIn> mixInList;
// the ContantPool generator
private ConstantPoolGen constPoolGen;
/**
* Creates an instance of this class.
*
* @param classfileBuffer the byte representation of the original version of the target class.
* @param targetClass the name of the target class
* @param mixInList the list of mixins.
*/
public ClassPatcher(
byte[] classfileBuffer, String targetClass,
List<MixIn> mixInList ) {
Logger.debug( "patching class %s", targetClass );
this.mixInList = mixInList;
JavaClass jc =
Reflection.classForByteArray( classfileBuffer, targetClass );
cGen = new ClassGen( jc );
constPoolGen = cGen.getConstantPool();
}
/**
* Patches the target class and returns a byte representation of the new version
* of the target class.
*
* @return a byte representation of the new version of the target class
*/
public byte[] patchClass() {
// add all mixins
for( MixIn m : mixInList ) {
addMixIn( m );
}
// todo:only a debug yet must be a feature in the future
// try {
// cGen.getJavaClass().dump( cGen.getClassName() + "_new.class");
// }
// catch( Exception exc ) {
// exc.printStackTrace();
// }
return cGen.getJavaClass().getBytes();
}
/**
* Adds a MixIn to the class.
*
* @param mixIn the mixIn to be added
*/
private void addMixIn( MixIn mixIn ) {
// the class of the interface of the mixin
Class interfaceClass = mixIn.getInterface();
// the fieldname of the delegate member
String fieldName =
"de_petris_dynamicaspects_DELEGATE_"
+ mixIn.getInterface().
getName().replace('.','_');
cGen.addInterface( interfaceClass.getName() );
// create the private member for delegation
FieldGen fGen =
new FieldGen(
Constants.ACC_PRIVATE,
Type.getType( interfaceClass ) ,
fieldName,
constPoolGen );
cGen.addField( fGen.getField() );
// create a getter method
String getterMethodName = addGetter(
mixIn.getImplementation(), interfaceClass, fieldName );
// add the functionality of the added interface
for( Method m : interfaceClass.getMethods() ) {
addMethodDelegation( m, interfaceClass, getterMethodName );
}
}
/**
* Adds a getter method for the member variable which is used for delegation.
* The getter method uses lazy initialization of the member variable.
*
* @param implClass the class of the implementation of the delgate
* @param interfaceClass the added interface
* @param fieldName the name of the delegate
* @return the name of the getter method
*/
private String addGetter(
Class implClass, Class interfaceClass,
String fieldName ) {
Type interfaceType = Type.getType( interfaceClass );
// create getter-method-name
StringBuffer fieldNameBuffer = new StringBuffer( fieldName );
fieldNameBuffer.replace(
0, 1, fieldNameBuffer.substring(0,1).toUpperCase() );
fieldNameBuffer.insert(0,"get");
// build the getters instructions
InstructionList iList = new InstructionList();
InstructionFactory factory =
new InstructionFactory(cGen);
MethodGen mGen =
new MethodGen(
Constants.ACC_PRIVATE,
interfaceType,
Type.NO_ARGS,
new String[] {},
fieldNameBuffer.toString(),
cGen.getClassName(),
iList,
constPoolGen );
// a local variable
iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
// load the member variable
iList.append(
factory.createFieldAccess(
cGen.getClassName(),
fieldName,
interfaceType,
Constants.GETFIELD ) );
// test for non null of the member variable
BranchInstruction bi =
InstructionFactory.createBranchInstruction(
Constants.IFNONNULL, null );
iList.append( bi );
// create the delegate
iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
iList.append( factory.createNew( implClass.getName() ) );
iList.append( InstructionConstants.DUP );
iList.append(
factory.createInvoke(
implClass.getName(),
Constants.CONSTRUCTOR_NAME,
Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL ) );
// use a factory if the implementation class is a factory
if( MixInImplementationFactory.class.isAssignableFrom( implClass ) ) {
iList.append(
factory.createInvoke(
implClass.getName(),
"getInstance",
Type.OBJECT,
Type.NO_ARGS, Constants.INVOKEVIRTUAL ) );
iList.append(
factory.createCheckCast(
new ObjectType( interfaceClass.getName() ) ) );
}
// store the created delgate object
iList.append(
factory.createFieldAccess(
cGen.getClassName(),
fieldName,
interfaceType,
Constants.PUTFIELD) );
// load the delegate and return it
InstructionHandle loadInstruction =
iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
bi.setTarget( loadInstruction );
iList.append(
factory.createFieldAccess(
cGen.getClassName(),
fieldName,
interfaceType,
Constants.GETFIELD) );
iList.append( InstructionFactory.createReturn( Type.OBJECT ) );
mGen.setMaxStack();
mGen.setMaxLocals();
cGen.addMethod( mGen.getMethod() );
iList.dispose();
return fieldNameBuffer.toString();
}
/**
* Adds a method of the added interface to this class.
*
* @param m the method to be added
* @param interfaceClass the class of the interface
* @param getterMethodName the name of the getter-method of the delegate
*/
private void addMethodDelegation( Method m, Class interfaceClass,
String getterMethodName ) {
String sig = Type.getSignature( m );
// create parameter names for the method
String[] argNames = new String[m.getParameterTypes().length];
for( int i=argNames.length-1; i>=0; i-- ) {
argNames[i] = "arg"+Integer.toString(i);
}
// create instructions for delgation
InstructionList iList = new InstructionList();
InstructionFactory factory =
new InstructionFactory(cGen);
MethodGen mGen =
new MethodGen(
Constants.ACC_PUBLIC,
Type.getReturnType( sig ),
Type.getArgumentTypes( sig ),
argNames,m.getName(),
cGen.getClassName(),
iList,
constPoolGen );
// get the delegate
iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
iList.append(
factory.createInvoke(
cGen.getClassName(),
getterMethodName,
Type.getType( interfaceClass ),
Type.NO_ARGS,
Constants.INVOKESPECIAL ) );
// load the argument on to the stack
int argIdx =1;
for( Type t : Type.getArgumentTypes( sig ) ) {
iList.append( InstructionFactory.createLoad( t, argIdx++ ) );
if( Reflection.isCat2Type( t ) ) {
argIdx++;
}
}
// do the delegation
iList.append(
factory.createInvoke(
interfaceClass.getName(),
m.getName(),
Type.getReturnType( sig ),
Type.getArgumentTypes( sig ),
Constants.INVOKEINTERFACE ) );
// return the result from the delegation
iList.append( InstructionFactory.createReturn( Type.getReturnType( sig ) ) );
mGen.setMaxStack();
mGen.setMaxLocals();
cGen.addMethod( mGen.getMethod() );
iList.dispose();
}
}