/*
* Copyright (c) 2004, 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.wrapper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.BasicType;
import org.apache.bcel.generic.ConstantPoolGen;
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.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import de.petris.dynamicaspects.ArgumentInfo;
import de.petris.dynamicaspects.AspectException;
import de.petris.dynamicaspects.util.Reflection;
/**
* Handler for the variable creation used for a
* {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}.
* During installation of the wrapper the wrapper variable and an instance of
* {@link de.petris.dynamicaspects.ArgumentInfo ArgumentInfo} is created.
* During deinstallation the variables are removed.
*
* @author Marco Petris
*/
class CallWrapperVariableHandler implements Constants {
// the type name of the argument list
private final static String ARGLIST_TYPENAME =
ArgumentInfo.class.getName();
// the type of the argument list
final static Type ARGLIST_TYPE = new ObjectType( ARGLIST_TYPENAME );
// the method names called at the argument list
private final static String ARGLIST_REVERT = "revert";
private final static String ARGLIST_ADD = "add";
private final static String ARGLIST_SETTARGET = "setTarget";
private final static String ARGLIST_SETSTATICTARGET = "setStaticTarget";
private final static String ARGLIST_GETTARGET = "getTarget";
private final static String ARGLIST_SETMETHODNAMESIGNATURE =
"setMethodNameSignature";
// static part of variable name of the wrapper:
// !use member wrapperVarName!
private final static
String CALLWRAPPER_VARNAME_STATICPART =
"de_petris_dynamicaspects_CallWrapper";
// static part of variable name of the argument list
private final static
String ARGLIST_VARNAME_STATICPART = "_ARGUMENTLIST";
private final static
char NEGATIVEHASHCODE = '_';
// the type of the call wrapper
private final static Type CALLWRAPPER_TYPE =
new ObjectType( CallWrapper.class.getName() );
private MethodGen mGen; // the method generator for this method
private InstructionList iList;
private InstructionFactory factory;
private ConstantPoolGen constPoolGen; // constant pool generator
// the pattern for the joinpoints matched by the targetcalls
private String joinpointPattern;
// variable name of the wrapper
private String wrapperVarName;
// next index of the argument list
private int argListVarNameIdx;
// base of the name of the argument list variable
private String argListVarNameBase;
/**
* Constructs an instance of this class.
*
* @param mGen the method generator
* @param iList the instruction list
* @param factory the instruction factory
* @param constPoolGen the constant pool generator
* @param joinpointPattern the joinpoint pattern for the target calls
* ( is used for loading the wrapper )
*/
CallWrapperVariableHandler(
MethodGen mGen, InstructionList iList,
InstructionFactory factory, ConstantPoolGen constPoolGen,
String joinpointPattern ) {
this.mGen = mGen;
this.iList = iList;
this.factory = factory;
this.constPoolGen = constPoolGen;
this.joinpointPattern = joinpointPattern;
this.wrapperVarName = createVariableName();
this.argListVarNameBase = wrapperVarName + ARGLIST_VARNAME_STATICPART;
this.argListVarNameIdx = 1;
}
/**
* Creates the variable for the call wrapper
*
* @param insertPoint where to insert the creation instructions
* @return the local variable index of the variable
*/
int createCallWrapper(
InstructionHandle insertPoint, int methodIdx ) {
// the local variable for our callwrapper
LocalVariableGen callWrapperVarGen =
mGen.addLocalVariable(
createVariableName(),
CALLWRAPPER_TYPE, null, null );
int callWrapperIdx = callWrapperVarGen.getIndex();
// set the variable to null first
iList.insert( insertPoint,
InstructionFactory.createNull( CALLWRAPPER_TYPE ) );
// store variable name
callWrapperVarGen.setStart(
iList.insert( insertPoint,
InstructionFactory.createStore(
CALLWRAPPER_TYPE, callWrapperIdx ) ) );
// put the arguments for the mapper on the stack
// classname
iList.insert(
insertPoint,
new PUSH( constPoolGen, mGen.getClassName() ) );
// method index
iList.insert(
insertPoint,
new PUSH( constPoolGen, methodIdx ) );
// join point pattern
iList.insert(
insertPoint,
new PUSH( constPoolGen, joinpointPattern ) );
// query the mapper for the wrapper
iList.insert( insertPoint, factory.createInvoke(
CallMapper.class.getName(),
CallMapper.GETWRAPPER_METHODNAME,
CALLWRAPPER_TYPE,
new Type[] { Type.STRING, Type.INT, Type.STRING },
INVOKESTATIC) );
// store initialized callwrapper
iList.insert( insertPoint,
InstructionFactory.createStore(
CALLWRAPPER_TYPE, callWrapperIdx ) );
return callWrapperIdx;
}
/**
* The name of the wrapper variable is constructed by a static part of the name
* and a string representation of the hashcode of the joinpoint pattern.
*
* @return the name of the wrapper variable.
*/
private String createVariableName() {
int hashCode = joinpointPattern.hashCode();
StringBuffer buf = new StringBuffer(CALLWRAPPER_VARNAME_STATICPART);
if( hashCode < 0 ) {
buf.append( NEGATIVEHASHCODE );
}
buf.append( Math.abs( hashCode ) );
return buf.toString();
}
/**
* Looks for the local variable index of the given variable.
*
* @param variableName the name of the variable
* @return the local variable index of given variable.
*/
private int searchVariableIndex( String variableName ) {
for( LocalVariableGen localVarsGen : mGen.getLocalVariables() ) {
if( localVarsGen.getName().equals( variableName ) ) {
return localVarsGen.getIndex();
}
}
throw new AspectException(
"unexpected situation: could not find variable "
+ variableName + " in local variable table!" );
}
/**
* @return the local variable index of wrapper variable.
*/
int getCallWrapperVariableIndex() {
return searchVariableIndex( this.wrapperVarName );
}
/**
* @return the local variable index of the next argument list variable.
*/
int getNextArgumentListVariableIndex() {
return searchVariableIndex( getNextArgListVarName() );
}
/**
* @return the name of the next argument list variable
*/
private String getNextArgListVarName() {
StringBuffer buf = new StringBuffer( argListVarNameBase );
buf.append( argListVarNameIdx++ );
return buf.toString();
}
/**
* Creates the variable for the next argument list
*
* @param insertPoint where to insert the creation instructions
* @param callMatch information about the target call
* @return the local variable index of the variable
*/
int createArgumentList(
InstructionHandle insertPoint, CallMatch callMatch ) {
// get number of arguments for the method
int paramCount = callMatch.getCallInfo().getArgTypes().length;
String curArgListVarName = getNextArgListVarName();
// create a variable for the argument list
LocalVariableGen argListVarGen =
mGen.addLocalVariable(
curArgListVarName,
ARGLIST_TYPE,
null, null ); // todo: insertPoint
int argListVarIdx = argListVarGen.getIndex();
// init variable
iList.insert( insertPoint,
InstructionFactory.createNull( ARGLIST_TYPE ) );
// set visibility start
argListVarGen.setStart(
iList.insert( insertPoint,
InstructionFactory.createStore(
ARGLIST_TYPE, argListVarIdx ) ) );
//todo: setEnd for localVars
// 'new' call
iList.insert( insertPoint,
factory.createNew( ARGLIST_TYPENAME ) );
iList.insert( insertPoint, InstructionConstants.DUP);
// init call
iList.insert( insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME,
CONSTRUCTOR_NAME,
Type.VOID,
Type.NO_ARGS,
INVOKESPECIAL ) );
// store argument list
iList.insert( insertPoint,
InstructionFactory.createStore(
ARGLIST_TYPE, argListVarIdx ) );
// add parameters to argument list
for( int count = paramCount-1; count >=0; count-- ) {
addParam(
insertPoint, argListVarIdx,
callMatch.getCallInfo().getArgTypes()[count] );
}
// the arguments are in the wrong order
// so we revert the argument list
iList.insert(
insertPoint,
InstructionFactory.createLoad(
Type.OBJECT, argListVarIdx ) );
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_REVERT,
Type.VOID,
Type.NO_ARGS,
INVOKEVIRTUAL ) );
// set the target of the call
setTarget(
insertPoint, argListVarIdx,
callMatch );
// set the name and the signature of the target method
iList.insert(
insertPoint,
InstructionFactory.createLoad(
Type.OBJECT, argListVarIdx ) );
iList.insert(
insertPoint,
new PUSH(
constPoolGen,
callMatch.getCallInfo().getMethodName() ) );
iList.insert(
insertPoint,
new PUSH(
constPoolGen,
callMatch.getCallInfo().getMethodSignature() ) );
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_SETMETHODNAMESIGNATURE,
Type.VOID,
new Type[] { Type.STRING, Type.STRING },
INVOKEVIRTUAL ) );
return argListVarIdx;
}
/**
* Sets the target instance of the target method call.
*
* @param insertPoint the insertion point
* @param argListVarIdx the index of the argument list variable
* @param callMatch the matching target
*/
private void setTarget(
InstructionHandle insertPoint, int argListVarIdx,
CallMatch callMatch ) {
// load argument list
iList.insert(
insertPoint,
InstructionFactory.createLoad( Type.OBJECT, argListVarIdx ) );
// do we have a static target call?
if( callMatch.getCallInfo().isStatic() ) {
// put target class name on stack
iList.insert(
insertPoint,
new PUSH(
constPoolGen,
callMatch.getCallInfo().getClassName() ) );
// set the static target
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_SETSTATICTARGET,
Type.VOID,
new Type[] { Type.STRING },
INVOKEVIRTUAL ) );
}
else {
// use the target instance on the operand stack as a parameter
// for the setTarget()-call
iList.insert( insertPoint, InstructionConstants.SWAP );
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_SETTARGET,
Type.VOID,
new Type[] { Type.OBJECT },
INVOKEVIRTUAL ) );
}
}
/**
* Adds a parameter to the argument list.
*
* @param insertPoint where to insert the add instructions
* @param argListVarIdx the index of the argument list variable
* @param paramType the type of the argument
*/
private void addParam(
InstructionHandle insertPoint, int argListVarIdx,
Type paramType ) {
// load argument list
iList.insert(
insertPoint,
InstructionFactory.createLoad(
Type.OBJECT, argListVarIdx ) );
// use the arguments on the operand stack as arguments for the add()-call
if( Reflection.isCat2Type( paramType ) ) {
iList.insert(
insertPoint,
InstructionConstants.DUP_X2 );
iList.insert(
insertPoint,
InstructionConstants.POP );
}
else {
iList.insert( insertPoint, InstructionConstants.SWAP );
}
if( paramType instanceof ReferenceType ) {
paramType = Type.OBJECT;
}
// add()-call
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_ADD,
Type.VOID,
new Type[] { paramType },
INVOKEVIRTUAL ) );
}
/**
* Reloads the arguments used for the argument list of the before()-call on
* the stack to satisfy the target call, which is using these arguments.
*
* @param insertPoint the insertion point
* @param argListVarIdx the local variable index of the argument list
* @param callInfo the information about the target call
*/
void reloadArguments(
InstructionHandle insertPoint, int argListVarIdx,
CallInfo callInfo ) {
// restore the target first for non.static target calls
if( !callInfo.isStatic() ) {
iList.insert(
insertPoint,
InstructionFactory.createLoad( Type.OBJECT, argListVarIdx ) );
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, ARGLIST_GETTARGET,
Type.OBJECT,
Type.NO_ARGS,
INVOKEVIRTUAL ) );
// cast the target instance to its original type
iList.insert(
insertPoint,
factory.createCast(
Type.OBJECT,
new ObjectType(
callInfo.getClassName() ) ) );
}
// get number of arguments for the method
int paramCount = callInfo.getArgTypes().length;
// restore each argument
for( int count = 0; count <paramCount; count++ ) {
String methodName =
ArgumentInfo.getTypedGetMethodname(
callInfo.getArgTypes()[count] );
iList.insert(
insertPoint,
InstructionFactory.createLoad( Type.OBJECT, argListVarIdx ) );
iList.insert(
insertPoint,
new PUSH( constPoolGen, count ) );
iList.insert(
insertPoint,
factory.createInvoke(
ARGLIST_TYPENAME, methodName,
getParamType(
callInfo.getArgTypes()[count] ),
new Type[] { Type.INT },
INVOKEVIRTUAL ) );
// cast non-primitive types to there original type
if( !(callInfo.
getArgTypes()[count] instanceof BasicType) ) {
iList.insert(
insertPoint,
factory.createCast(
Type.OBJECT,
callInfo.getArgTypes()[count] ) );
}
}
}
/**
* Returns the parameter type in correspondence to
* the given type.
*
* @param type the type which should be passed
* @return the type that is the right paramter type
*/
private Type getParamType( Type type ) {
if( ( type instanceof BasicType ) && !type.equals( Type.VOID ) ) {
return type;
}
else {
return Type.OBJECT;
}
}
/**
* Returns the last instruction of the argument reload.
*
* @param beforeCall the before()-call
* @param callInfo the information about the target call
* @return the last instruction of the argument reload
*/
InstructionHandle getLastReloadInstruction(
InstructionHandle beforeCall, CallInfo callInfo ) {
InstructionHandle lastReloadInstruction = beforeCall;
// get number of arguments for the method
int paramCount = callInfo.getArgTypes().length;
// add parameters to argument list
for( int count = paramCount-1; count>=0; count-- ) {
// argList-load, push param-idx on stack, invoke get
lastReloadInstruction =
lastReloadInstruction.getNext().getNext().getNext();
// did we insert a typecast?
if( !(callInfo.getArgTypes()[count] instanceof BasicType) ) {
lastReloadInstruction =
lastReloadInstruction.getNext();
}
}
// did we push the instance of the targetCall on the stack?
if( !callInfo.isStatic() ) {
// argList-load, invoke get, typeCast
lastReloadInstruction =
lastReloadInstruction.getNext().getNext().getNext();
}
return lastReloadInstruction;
}
/**
* Removes all created variables.
*/
public void removeVariables() {
Iterator<LocalVariableGen> iter = lookUpVariables();
while( iter.hasNext() ) {
mGen.removeLocalVariable( iter.next() );
}
}
/**
* Looks up the variables in the local variables table.
*
* @return an iterator of the list of found variable generators.
*/
private Iterator<LocalVariableGen> lookUpVariables() {
List<LocalVariableGen> vars = new ArrayList<LocalVariableGen>();
for( LocalVariableGen localVarsGen : mGen.getLocalVariables() ) {
if( ( localVarsGen.getName().length() >= wrapperVarName.length() )
&& ( localVarsGen.getName().substring(
0, wrapperVarName.length() ).equals( wrapperVarName ) ) ) {
vars.add( localVarsGen );
}
}
return vars.iterator();
}
}