/*
* 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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.MethodGen;
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.InstructionSearcher;
import de.petris.dynamicaspects.util.Logger;
import de.petris.dynamicaspects.wrapper.CallMatch;
import de.petris.dynamicaspects.wrapper.CallWrapper;
import de.petris.dynamicaspects.wrapper.CallWrapperMethodPatcher;
/**
* This classhandler installs {@link de.petris.dynamicaspects.BeforeAfterAdvice BeforeAfterAdvices}
* which wrap a method or constructor call.
* A {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}
* is wrapped around every matching method/constructor call.
* The mapping of a CallClassHandler to a class is done by the
* {@link de.petris.dynamicaspects.wrapper.CallMapper CallMapper}
* during runtime. A {@link de.petris.dynamicaspects.WeaveType WeaveType.CALL}
* can be queried for a CallClassHandler which handles a certain class.
* The CallClassHandler has a mapping from method to
* joinPointPattern and a mapping from joinpointPattern to a
* {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}. During runtime the appropriate
* {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper} can be determined.
* The {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper} maintains
* a list of {@link de.petris.dynamicaspects.Advice Advices} which belong to the positions
* that match the joinpointPattern in the method.
*
* @author Marco Petris
* @see de.petris.dynamicaspects.wrapper.CallWrapper
* @see de.petris.dynamicaspects.wrapper.CallMapper
* @see de.petris.dynamicaspects.WeaveType
* @see de.petris.dynamicaspects.BeforeAfterAdvice
*/
public class CallClassHandler extends DefaultClassHandler {
// a mapping: methodIndex -> joinPointPattern -> callwrapper
private Map<Integer, Map<String, CallWrapper>> callWrappers;
/**
* Creates an instance of this class.
*/
public CallClassHandler() {
callWrappers = new HashMap<Integer, Map<String, CallWrapper>>();
}
/* (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 exc ) {
throw new AspectException( exc );
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#install(de.petris.dynamicaspects.Advice, java.util.regex.Pattern)
*/
public void install( Advice advice, Pattern joinpointPattern ) {
Logger.info( "checking methods for pattern: %s", joinpointPattern.pattern() );
Method[] methods = targetClass.getMethods();
for( int methodIdx=methods.length-1; methodIdx>=0; methodIdx-- ) {
handleInstall(
new Integer( methodIdx ), joinpointPattern,
advice, null );
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#install(de.petris.dynamicaspects.AdviceFactory, java.util.regex.Pattern)
*/
public void install( AdviceFactory factory, Pattern joinpointPattern ) {
Logger.info( "checking methods for pattern: %s", joinpointPattern.pattern() );
Method[] methods = targetClass.getMethods();
for( int methodIdx=methods.length-1; methodIdx>=0; methodIdx-- ) {
handleInstall(
new Integer( methodIdx ), joinpointPattern,
null, factory );
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#deinstall(de.petris.dynamicaspects.Advice)
*/
public void deinstall( Advice advice ) {
// get a iterator over all method->joinpointPattern mappings
// of all woven methods of the class
Iterator<Map.Entry<Integer, Map<String, CallWrapper>>> joinPointIter
= callWrappers.entrySet().iterator();
// loop over the method->joinpointPattern mappings
while( joinPointIter.hasNext() ) {
// get the current mapping: method->joinpointPattern
Map.Entry<Integer, Map<String, CallWrapper>> curJoinPointMapping =
joinPointIter.next();
// get a iterator over the joinpointPattern->wrapper mappings
Iterator<Map.Entry<String, CallWrapper>> wrapperIter
= curJoinPointMapping.getValue().entrySet().iterator();
// loop over the joinpointPattern->wrapper mappings
while( wrapperIter.hasNext() ) {
// get the current joinpoint->wrapper mapping
Map.Entry<String, CallWrapper> curWrapperEntry =
wrapperIter.next();
// does the wrapper contain our advice?
if( curWrapperEntry.getValue().contains( advice ) ) {
// yes, so deinstall the advice
curWrapperEntry.getValue().deinstall( advice );
// shall we deinstall the wrapper because
// it contains no more advices?
if( !curWrapperEntry.getValue().hasAdvices() ) { //todo: configurable option
// remove the joinpointPattern->wrapper from the list
wrapperIter.remove();
// remove the wrapper from the method
removeWrapper(
curJoinPointMapping.getKey(),
curWrapperEntry.getKey() );
}
}
}
}
}
/* (non-Javadoc)
* @see de.petris.dynamicaspects.classhandler.ClassHandler#deinstall(java.lang.Class)
*/
public void deinstall( Class<? extends Advice> adviceClass ) {
// get a iterator over all method->joinpointPattern mappings
// of all woven methods of the class
Iterator<Map.Entry<Integer, Map<String, CallWrapper>>> joinPointIter
= callWrappers.entrySet().iterator();
// loop over the method->joinpointPattern mappings
while( joinPointIter.hasNext() ) {
// get the current mapping: method->joinpointPattern
Map.Entry<Integer, Map<String, CallWrapper>> curJoinPointMapping =
joinPointIter.next();
// get a iterator over the joinpointPattern->wrapper mappings
Iterator<Map.Entry<String, CallWrapper>> wrapperIter
= curJoinPointMapping.getValue().entrySet().iterator();
// loop over the joinpointPattern->wrapper mappings
while( wrapperIter.hasNext() ) {
Map.Entry<String, CallWrapper> curWrapperEntry =
wrapperIter.next();
// get the current joinpoint->wrapper mapping
curWrapperEntry.getValue().deinstall( adviceClass );
// shall we deinstall the wrapper because
// it contains no more advices?
if( !curWrapperEntry.getValue().hasAdvices() ) { //todo: configurable option
// remove the joinpointPattern->wrapper from the list
wrapperIter.remove();
// remove the wrapper from the method
removeWrapper(
curJoinPointMapping.getKey(),
curWrapperEntry.getKey() );
}
}
}
}
/**
* Returns the wrapper for the given method and the given joinpointPattern.
* This method is called by the {@link de.petris.dynamicaspects.wrapper.CallMapper CallMapper}.
* The returned {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper} is then used
* during runtime to execute the {@link de.petris.dynamicaspects.Advice Advice}s.
*
* @param methodIdx the index of the method
* @param joinpointPattern the joinpointPattern
* @return the wrapper which belongs to the arguments.
*/
public CallWrapper getCallWrapper( int methodIdx, String joinpointPattern ) {
return (CallWrapper)((Map)callWrappers.get(
new Integer( methodIdx ))).get( joinpointPattern );
}
/**
* Removes the wrapper from the given method at the given joinpoints.
*
* @param methodIdx index of the method.
* @param joinpointPattern pattern of the joinpoints.
*/
private void removeWrapper( int methodIdx, String joinpointPattern ) {
// a method generator for the method
MethodGen mGen =
new MethodGen(
targetClass.getMethods()[methodIdx],
targetClass.getClassName(),
constPoolGen );
// a searcher to look for the joinpoints
InstructionSearcher searcher =
new InstructionSearcher( constPoolGen, mGen.getInstructionList() );
// look for joinpoints
List<CallMatch> targets =
searcher.lookUpMethodCall(
Pattern.compile( joinpointPattern ) );
// a patcher for the given method
CallWrapperMethodPatcher mp =
new CallWrapperMethodPatcher(
methodIdx, mGen, constPoolGen,
joinpointPattern, targets );
// get the new method without the wrapper
targetClass.getMethods()[methodIdx] =
mp.deinstall();
// and update the class because of this change
targetClass.setConstantPool(
constPoolGen.getFinalConstantPool() );
}
/**
* Installs an advice to a wrapper.
* It the wrapper does not exist yet, the wrapper is installed.
* Either the advice argument or the factory argument must not be null.
* If the advice is not null it is installed, else the factory is used to
* create the advice an the created advice will be installed.
*
* @param methodIdx Index of the method.
* @param joinpointPattern Pattern of the joinpoints
* @param factory the factory to create an advice, can be null.
*/
private void handleInstall(
Integer methodIdx, Pattern joinpointPattern,
Advice advice, AdviceFactory factory ) {
if( !( advice instanceof BeforeAfterAdvice ) ) {
throw new AspectException(
"all advices installed via the "
+ CallClassHandler.class.getName()
+ " are expected to implement "
+ BeforeAfterAdvice.class.getName()
+ ", " + advice.getClass().getName()
+ " does not respect this restriction!" );
}
// do we have wrapper for the method/joinpointPattern combination?
if( ( callWrappers.containsKey( methodIdx ) )
&& ( ((Map)callWrappers.get( methodIdx )).containsKey(
joinpointPattern.pattern() ) ) ) {
// yes, so we use this wrapper
// do we need to create the advice?
if( advice == null ) {
advice = (BeforeAfterAdvice)factory.getAdvice();
}
// get the already installed wrapper and install the advice
((CallWrapper)((Map)callWrappers.get( methodIdx )).get(
joinpointPattern.pattern() )).install( (BeforeAfterAdvice)advice );
}
else {
// no, so we install the wrapper first
// get a method generator
MethodGen mGen =
new MethodGen(
targetClass.getMethods()[methodIdx.intValue()],
targetClass.getClassName(),
constPoolGen );
// get a searcher for the joinpoints
InstructionSearcher searcher =
new InstructionSearcher( constPoolGen, mGen.getInstructionList() );
// look for joinpoints
List<CallMatch> targets =
searcher.lookUpMethodCall( joinpointPattern );
// are there any joinpoints matching the pattern in this methdod?
if( !targets.isEmpty() ) {
// yes, so we install a wrapper here
// do we need to create the advice?
if( advice == null ) {
advice = (BeforeAfterAdvice)factory.getAdvice();
}
// do we have a joinpointPattern-map for this method yet?
if( !callWrappers.containsKey( methodIdx ) ) {
// create a map for all the joinpoinPattern->wrapper mappings
// for this method
callWrappers.put(
methodIdx, new HashMap<String, CallWrapper>());
}
// create a wrapper
CallWrapper cWrapper = new CallWrapper();
Logger.info(
"installing call wrapper for method %s " +
"with index %s and pattern %s",
mGen.getName(), methodIdx,
joinpointPattern.pattern() );
// install the wrapper to the method
CallWrapperMethodPatcher mp =
new CallWrapperMethodPatcher(
methodIdx.intValue(), mGen, constPoolGen,
joinpointPattern.pattern(), targets );
// install the advice to the wrapper
cWrapper.install( (BeforeAfterAdvice)advice );
// create mapping: method->joinpointPattern->wrapper
callWrappers.get( methodIdx ).put(
joinpointPattern.pattern(), cWrapper );
// get the new method and replace the old version
targetClass.getMethods()[methodIdx.intValue()] =
mp.install();
// and update the class because of this change
targetClass.setConstantPool(
constPoolGen.getFinalConstantPool() );
}
}
}
}