/*
* 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.classhandler;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.Method;
import de.petris.dynamicaspects.Advice;
import de.petris.dynamicaspects.AspectException;
import de.petris.dynamicaspects.AdviceFactory;
import de.petris.dynamicaspects.BeforeAfterAdvice;
import de.petris.dynamicaspects.util.Logger;
import de.petris.dynamicaspects.util.Reflection;
import de.petris.dynamicaspects.wrapper.ExecutionWrapper;
import de.petris.dynamicaspects.wrapper.ExecutionWrapperMethodPatcher;
/**
* This classhandler installs {@link de.petris.dynamicaspects.BeforeAfterAdvice BeforeAfterAdvices}
* which wrap a method or constructor execution.
* A {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}
* is wrapped around every matching method execution.
* Each time a method executes the wrapper handles the installed advices.
* A {@link de.petris.dynamicaspects.wrapper.ExecutionMapper ExecutionMapper} maps an
* {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}
* to its corresponding method during runtime.
*
* @author Marco Petris
* @see de.petris.dynamicaspects.WeaveType
* @see de.petris.dynamicaspects.BeforeAfterAdvice
* @see de.petris.dynamicaspects.wrapper.ExecutionWrapper
* @see de.petris.dynamicaspects.wrapper.ExecutionMapper
*/
public class ExecutionClassHandler extends DefaultClassHandler {
// a mapping: methodsignature -> executionwrapper
private Map<String, ExecutionWrapper> executionWrappers;
/**
* Constructs an instance of this class.
*/
public ExecutionClassHandler() {
executionWrappers = new HashMap<String, ExecutionWrapper>();
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#install(de.petris.dynamicaspects.AdviceFactory, java.util.regex.Pattern)
*/
public void install(
AdviceFactory factory, Pattern joinpointPattern ) {
handleInstall( null, factory, joinpointPattern );
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#install(java.lang.Class, java.util.regex.Pattern)
*/
public void install(
Class<? extends Advice> adviceClass, Pattern joinpointPattern ) {
try {
install(
adviceClass.newInstance(),
joinpointPattern );
}
catch( Exception e ) {
throw new AspectException( e );
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#install(de.petris.dynamicaspects.Advice, java.util.regex.Pattern)
*/
public void install( Advice advice, Pattern joinpointPattern ) {
handleInstall( advice, null, joinpointPattern );
}
/**
* Handles the installation of the given advice or factory according to
* the given joinpointPattern. Either advice or factory must not be null.
*
* @param advice the advice to be installed, can be null
* @param factory the factory to be used for advice creation, can be null
* @param joinpointPattern the pointcut
*/
private void handleInstall( Advice advice, AdviceFactory factory, Pattern joinpointPattern ) {
Method[] methods = targetClass.getMethods();
Logger.info( "checking methods for pattern: %s", joinpointPattern.pattern() );
for( int methodIdx=methods.length-1; methodIdx>=0; methodIdx-- ) {
String completeSig =
methods[methodIdx].getName() + methods[methodIdx].getSignature();
String methodName =
Reflection.getMethodDeclaration(
targetClass.getClassName(),
methods[methodIdx].getName(),
methods[methodIdx].getSignature() );
if( joinpointPattern.matcher( methodName ).matches() ) {
if( advice == null ) {
advice = factory.getAdvice();
}
if( !( advice instanceof BeforeAfterAdvice ) ) {
throw new AspectException(
"all aspects installed via the "
+ ExecutionClassHandler.class.getName()
+ " are expected to implement "
+ BeforeAfterAdvice.class.getName()
+ ", " + advice.getClass().getName()
+ " does not respect this restriction!" );
}
Logger.info( "signature %s matched ", methodName );
// get the wrapper for this method
ExecutionWrapper eWrapper =
loadWrapper( methodIdx, completeSig );
eWrapper.install( (BeforeAfterAdvice)advice );
}
else {
Logger.info( "no match for signature %s ", methodName );
}
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#deinstall(de.petris.dynamicaspects.Advice)
*/
public void deinstall( Advice advice ) {
Logger.debug( "deinstalling %s ", advice );
Method[] methods = targetClass.getMethods();
for( int methodIdx=methods.length-1; methodIdx>=0; methodIdx-- ) {
String completeSig =
methods[methodIdx].getName()
+ methods[methodIdx].getSignature();
String methodName =
Reflection.getMethodDeclaration(
targetClass.getClassName(),
methods[methodIdx].getName(),
methods[methodIdx].getSignature() );
// do we have a wrapper for this method?
if( executionWrappers.containsKey( completeSig ) ) {
Logger.info( "signature %s matched ", methodName );
// get the wrapper
ExecutionWrapper curWrapper =
executionWrappers.get( completeSig );
// deinstall aspect
curWrapper.deinstall( advice );
// if this has been the last aspect, remove the wrapper
if( !curWrapper.hasAdvices() ) { // todo: configurable option
removeWrapper( methodIdx, completeSig );
executionWrappers.remove( completeSig );
}
}
else {
Logger.info( "no match for signature %s ", methodName );
}
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#deinstall(java.lang.Class)
*/
public void deinstall( Class<? extends Advice> adviceClass ) {
Logger.debug( "deinstalling %s ", adviceClass );
Method[] methods = targetClass.getMethods();
for( int methodIdx=methods.length-1; methodIdx>=0; methodIdx-- ) {
String completeSig =
methods[methodIdx].getName()
+ methods[methodIdx].getSignature();
String methodName =
Reflection.getMethodDeclaration(
targetClass.getClassName(),
methods[methodIdx].getName(),
methods[methodIdx].getSignature() );
if( executionWrappers.containsKey( completeSig ) ) {
Logger.info( "signature %s matched ", methodName );
ExecutionWrapper curWrapper =
executionWrappers.get( completeSig );
curWrapper.deinstall( adviceClass );
if( !curWrapper.hasAdvices() ) { // todo: configurable option
removeWrapper( methodIdx, completeSig );
executionWrappers.remove( completeSig );
}
}
else {
Logger.info( "no match for signature %s ", methodName );
}
}
}
/**
* Returns the {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}
* for the given method signature. This method is called by the
* {@link de.petris.dynamicaspects.wrapper.ExecutionMapper ExecutionMapper}
* during runtime to obtain
* the appropriate {@link de.petris.dynamicaspects.wrapper.ExecutionWrapper ExecutionWrapper}.
*
* @param fullMethodName the signature of the method we want the wrapper for
* @return the wrapper which corresponds to the method with the given name
*/
public ExecutionWrapper getExecutionWrapper( String fullMethodName ) {
return executionWrappers.get( fullMethodName );
}
/**
* Removes the wrapper from a method.
*
* @param methodIdx the index of this method in the class' method-array
* @param completeSig the name+signature of this method
*/
private void removeWrapper( int methodIdx, String completeSig ) {
Logger.info( "deinstalling method wrapper for %s", completeSig );
ExecutionWrapperMethodPatcher mp =
new ExecutionWrapperMethodPatcher(
targetClass.getMethods()[methodIdx],
targetClass.getClassName(),
constPoolGen );
targetClass.getMethods()[methodIdx] = mp.deinstall();
targetClass.setConstantPool(
constPoolGen.getFinalConstantPool() );
}
/**
* Loads the wrapper for a method. If the wrapper does not exist yet, the wrapper
* is installed into the method.
*
* @param methodIdx the index of this method in the class' method-array
* @param completeSig the name+signature of this method
*/
private ExecutionWrapper loadWrapper(
int methodIdx, String completeSig ) {
if( !executionWrappers.containsKey( completeSig ) ) {
// no wrapper yet, so we install a wrapper first
ExecutionWrapper eWrapper =
new ExecutionWrapper(
targetClass.getClassName(),
targetClass.getMethods()[methodIdx].getName(),
targetClass.getMethods()[methodIdx].getSignature(),
targetClass.getMethods()[methodIdx].isStatic() );
executionWrappers.put( completeSig, eWrapper );
Logger.info( "installing method wrapper for %s", completeSig );
ExecutionWrapperMethodPatcher mp =
new ExecutionWrapperMethodPatcher(
targetClass.getMethods()[methodIdx], targetClass.getClassName(),
constPoolGen );
// get the wrapped method
targetClass.getMethods()[methodIdx] = mp.install();
// and update the class because of this change
targetClass.setConstantPool(
constPoolGen.getFinalConstantPool() );
return eWrapper;
}
else {
// get the already installed wrapper
return executionWrappers.get( completeSig );
}
}
}