/*
* 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.Iterator;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.BasicType;
import org.apache.bcel.generic.CodeExceptionGen;
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.TargetLostException;
import org.apache.bcel.generic.Type;
import de.petris.dynamicaspects.util.InstructionSearcher;
import de.petris.dynamicaspects.util.LVTTRemover;
import de.petris.dynamicaspects.util.Logger;
import de.petris.dynamicaspects.util.Reflection;
/**
* This class installs the
* {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper} and the
* {@link de.petris.dynamicaspects.wrapper.ExecutionMapper ExecutionMapper} to a target method.
* The mapper loads the wrapper which belongs to the target method. The wrapper
* then calls the before()-method of all installed
* {@link de.petris.dynamicaspects.BeforeAfterAdvice BeforeAfterAdvices}
* prior to target method execution and it calls the after()-methods of all isntalled aspects
* after the target method execution.
*
* @author Marco Petris
* @see de.petris.dynamicaspects.BeforeAfterAdvice
* @see de.petris.dynamicaspects.WeaveType
*/
public class ExecutionWrapperMethodPatcher implements Constants {
// the type of the ExecutionWrapper
private final static Type EXECUTIONWRAPPERTYPE =
new ObjectType( ExecutionWrapper.class.getName() );
private MethodGen mGen; // a method generator
private InstructionFactory factory;
private InstructionList iList;
private ConstantPoolGen constPoolGen; // a ConstantPool generator
/**
* Constructs an instance of this class.
*
* @param m the method which shall be patched.
* @param javaClassName the name of the class of this method
* @param constPoolGen the ConstantPool generator for this method
*/
public ExecutionWrapperMethodPatcher(
Method m, String javaClassName,
ConstantPoolGen constPoolGen ) {
// construct the method generator
this.constPoolGen = constPoolGen;
mGen = new MethodGen( m, javaClassName, constPoolGen );
this.factory = new InstructionFactory( constPoolGen );
this.iList = mGen.getInstructionList();
}
/**
* Installs the {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}.
*
* @return the patched method.
*/
public Method install() {
patchMethod();
mGen.setMaxStack();
mGen.setMaxLocals();
Method newMethod = mGen.getMethod();
LVTTRemover.removeLVTTs( newMethod, constPoolGen );
iList.dispose();
return newMethod;
}
/**
* Deinstalls the {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}.
*
* @return the unpatched method.
*/
public Method deinstall() {
unpatchMethod();
mGen.setMaxStack();
mGen.setMaxLocals();
Method newMethod = mGen.getMethod();
LVTTRemover.removeLVTTs( newMethod, constPoolGen );
iList.dispose();
return newMethod;
}
/**
* Patches the method. Installs a wrapper around the execution of the target method.
*/
private void patchMethod() {
// patch before starts here
InstructionSearcher aFinder =
new InstructionSearcher( constPoolGen, iList );
InstructionHandle first = null;
// respect the super() and this(...)-call at the
// beginning of constructors
if( mGen.getName().equals( CONSTRUCTOR_NAME ) ) {
first = aFinder.lookUpSuperConstructorCall().getNext();
}
else {
first = iList.getStart();
}
// save return statement
InstructionHandle last = iList.getEnd();
ExecutionWrapperVariableHandler vHandler
= new ExecutionWrapperVariableHandler(
mGen, iList, factory, constPoolGen );
// create variable for argumentList
int argListVarIdx =
vHandler.createArgumentList( first );
// create variable for executionwrapper
int executionWrapperIdx =
vHandler.createExecutionWrapper( first );
// BEFORE CALL
// load the executionwrapper
iList.insert( first,
InstructionFactory.createLoad(
EXECUTIONWRAPPERTYPE, executionWrapperIdx ) );
// load parameter for before()-call
iList.insert(
first,
InstructionFactory.createLoad(
ExecutionWrapperVariableHandler.ARGLIST_TYPE,
argListVarIdx ) );
// call before()
iList.insert(
first,
factory.createInvoke(
ExecutionWrapper.class.getName(),
Wrapper.getBeforeName(),
Type.VOID,
new Type[] {
ExecutionWrapperVariableHandler.ARGLIST_TYPE },
INVOKEVIRTUAL ) );
// look for all return-statements
List<InstructionHandle> returnStatements =
aFinder.lookUpReturnStatements();
Iterator<InstructionHandle> iter = returnStatements.iterator();
while( iter.hasNext() ) {
InstructionHandle curReturnStatement = iter.next();
// AFTER CALLs
// load executionwrapper for after()-call
iList.insert( curReturnStatement,
InstructionFactory.createLoad(
EXECUTIONWRAPPERTYPE, executionWrapperIdx ) );
// load parameter for after()-call
// using the return value on the operand stack
if( !mGen.getReturnType().equals( Type.VOID ) ) {
if( Reflection.isCat2Type(
mGen.getReturnType() ) ) {
iList.insert( curReturnStatement,
InstructionConstants.DUP_X2 );
iList.insert( curReturnStatement,
InstructionConstants.POP );
}
else {
iList.insert( curReturnStatement,
InstructionConstants.SWAP );
}
// call after() within try-block
iList.insert( curReturnStatement,
factory.createInvoke(
ExecutionWrapper.class.getName(),
Wrapper.getAfterName(),
Wrapper.getAfterParamType(
mGen.getReturnType() ),
new Type[] {
Wrapper.getAfterParamType(
mGen.getReturnType() ) },
INVOKEVIRTUAL )
);
if( !(mGen.getReturnType() instanceof BasicType) ) {
iList.insert( curReturnStatement,
factory.createCast(
Type.OBJECT,
mGen.getReturnType() ) );
}
}
else {
// call afterVoid() within try-block
iList.insert( curReturnStatement,
factory.createInvoke(
ExecutionWrapper.class.getName(),
Wrapper.getAfterVoidName(),
Type.VOID,
Type.NO_ARGS,
INVOKEVIRTUAL )
);
}
}
// add afterException()-call within catch-block
// load executionwrapper
InstructionHandle handler =
iList.append(
InstructionFactory.createLoad(
EXECUTIONWRAPPERTYPE, executionWrapperIdx ) );
// load exception
iList.append( InstructionConstants.SWAP );
iList.append(
new PUSH( constPoolGen,
mGen.getReturnType().equals( Type.VOID ) ) );
// call after() within catch-block
iList.append(
factory.createInvoke(
ExecutionWrapper.class.getName(),
Wrapper.getAfterExceptionName(),
Type.THROWABLE,
new Type[] {
Type.THROWABLE, Type.BOOLEAN },
INVOKEVIRTUAL )
);
// rethrow the exception
iList.append( InstructionConstants.ATHROW );
iList.append( InstructionConstants.RETURN );
// register excpeptionhandler
mGen.addExceptionHandler(
first, last, handler,
Type.THROWABLE );
}
/**
* Unpatches the method.
*/
private void unpatchMethod() {
// a finder for instructions
InstructionSearcher aFinder =
new InstructionSearcher( constPoolGen, iList );
// lookup before() call
List<InstructionHandle> beforeList =
aFinder.lookUpMethodCall(
ExecutionWrapper.class.getName(),
Wrapper.getBeforeName(),
Wrapper.getBeforeSignature() );
// save before() call
InstructionHandle beforeCall = beforeList.get(0);
// look for all return-statements
List<InstructionHandle> returnStatements =
aFinder.lookUpReturnStatementsSkipLast();
Iterator<InstructionHandle> iter = returnStatements.iterator();
InstructionHandle exceptionHandler = null;
while( iter.hasNext() ) {
InstructionHandle curReturnStatement = iter.next();
InstructionHandle last = curReturnStatement.getPrev();
// save the exceptionhandler for the removeExceptionHandler-call,
// ( we want the statement before the last return )
exceptionHandler = curReturnStatement.getNext();
InstructionHandle first = last;
// after()-call may have been complex or simple
// depending on the return type of the target method
if( mGen.getReturnType().equals( Type.VOID ) ) {
first = first.getPrev();
}
else {
// two instructions backward for cat2type-operation
if( Reflection.isCat2Type(
mGen.getReturnType() ) ) {
first = first.getPrev().getPrev();
}
else {
first = first.getPrev();
}
first = first.getPrev();
if( !(mGen.getReturnType() instanceof BasicType) ) {
first = first.getPrev();
}
}
// remove the after()-calls
try {
iList.delete( first, last );
}
catch( TargetLostException tle ) {
Logger.debug( "removing after()-calls: %s", tle.toString() );
}
}
removeExceptionHandler( exceptionHandler );
// remove catch() block
try {
iList.delete(
exceptionHandler,
iList.getEnd() );
}
catch( TargetLostException tle ) {
Logger.debug( "removing catch-block: %s", tle.toString() );
}
// remove variable creation and before call
InstructionHandle first = null;
// respect the super() and this(...)-call at the
// beginning of constructors
if( mGen.getName().equals( CONSTRUCTOR_NAME ) ) {
first = aFinder.lookUpSuperConstructorCall().getNext();
}
else {
first = iList.getStart();
}
try {
iList.delete(
first,
beforeCall );
}
catch( TargetLostException tle ) {
Logger.debug(
"removing variable creation and before()-call %s",
tle.toString() );
}
ExecutionWrapperVariableHandler vHandler
= new ExecutionWrapperVariableHandler(
mGen, iList, factory, constPoolGen );
vHandler.removeVariables();
updateLocalVariableTable();
}
/**
* Updates the local variable table.
*/
private void updateLocalVariableTable() {
// save all other local variables
LocalVariableGen[] localVars = mGen.getLocalVariables();
// clear the local variables table
mGen.removeLocalVariables();
// add all other local variables with corrected end position
for( int localVarIdx=localVars.length-1;
localVarIdx>=0; localVarIdx-- ) {
// correct the end position
InstructionHandle end = localVars[localVarIdx].getEnd();
if( ( end == null )
|| ( end.getPosition() > iList.getEnd().getPosition() ) ) {
end = iList.getEnd();
}
// add the variable
mGen.addLocalVariable(
localVars[localVarIdx].getName(),
localVars[localVarIdx].getType(),
localVarIdx, localVars[localVarIdx].getStart(), end );
}
}
/**
* Removes the exception handler.
*
* @param handler the handler to be removed.
*/
private void removeExceptionHandler( InstructionHandle handler ) {
for( CodeExceptionGen ceg : mGen.getExceptionHandlers() ) {
if( ceg.getHandlerPC().equals(
handler ) ) {
mGen.removeExceptionHandler( ceg );
break;
}
}
}
}