//
// This file is part of the prose package.
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Original Code is prose.
//
// The Initial Developer of the Original Code is Angela Nicoara. Portions
// created by Angela Nicoara are Copyright (C) 2004 Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: HotSwapAdvancedClassWeaver.java,v 1.2 2008/11/18 11:09:31 anicoara Exp $
// =====================================================================
//
package ch.ethz.inf.iks.jvmai.jvmdi;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Collection;
import java.util.ListIterator;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantValue;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.*;
import org.apache.bcel.verifier.Verifier;
import org.apache.bcel.verifier.VerifierFactory;
import org.apache.bcel.verifier.VerificationResult;
import ch.ethz.inf.iks.jvmai.jvmdi.HotSwapSimpleClassWeaver.MethodWeaver.RedefineAdviceListEntry;
import ch.ethz.jvmai.*;
/*
* Modifies a given class to support advice execution.
* Container for MethodWeavers, which does the code
* manipulations. Use the static method {@link
* #getWeaver(Method)} to get an instance of an
* {@link ch.ethz.jvmai.MethodWeaver}.
* <P>
* Optimized class weaver implementation. Holds a list with all
* ClassWeavers that contains changes which are not yet committed.
* On commit it iterates only over the ClassWeavers in this list.
*
* The SimpleClassWeaver iterates over classweaver list.
* The AdvancedClassWeaver iterates only over the last modified classweavers.
* The advantage is that doesn't have to check for each class weaver if has been modified.
*
* @author Angela Nicoara
* @author Gerald Linhofer
* @version $Revision: 1.2 $
*/
public class HotSwapAdvancedClassWeaver {
/**
* String representation of the JVMAI class.
*/
public static final String JVMAI_CLASS = "ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAspectInterfaceImpl";
/**
* Set holding all class weavers, which hold uncommitted modifications.
*/
private static Collection modifiedClassWeavers = new LinkedList(); // for JDK < 1.5 (compatible with newer JVMs)
// private static Collection<HotSwapClassWeaver> modifiedClassWeavers = new LinkedList<HotSwapClassWeaver>(); // only JDK 1.5 or above
/**
* Map with all class weaver. For each Class there
* exists exactly one class weaver in the system.
*/
private static Map classWeavers = new LinkedHashMap(1024); // for JDK < 1.5 (compatible with newer JVMs)
// private static Map<Class,HotSwapClassWeaver> classWeavers = new LinkedHashMap<Class,HotSwapClassWeaver>(1024); // only JDK 1.5 or above
/**
* Map with all method weavers. For each Method there
* exists exactly one method weaver in the system.
*/
private static Map methodWeavers = new LinkedHashMap(1024); // for JDK < 1.5 (compatible with newer JVMs)
// private static Map<Member,MethodWeaver> methodWeavers = new LinkedHashMap<Member,MethodWeaver>(1024); // only JDK 1.5 or above
/**
* Maps unique method ids to the method objects.
*/
private static Member methodMap[] = new Member[1024];
/**
* Manages unique method ids.
*/
private static UniqueIdArrayManager idMgr = new UniqueIdArrayManager();
/**
* The JVMAspectInterface.
*/
private static HotSwapAspectInterfaceImpl aspectInterface = null;
/**
* Is there anything to do for {@link #resetAll}
*/
private static boolean anyWoven = false;
//----------------------------------------------------------------------
/**
* Get a unique method weaver for 'target'.
*
* @param target method for which a weaver will be returned.
* @return MethodWeaver associated to <CODE>target</CODE>.
*/
static public MethodWeaver getWeaver(Member target) /*throws ClassNotFoundException*/ {
if( null == target )
throw new NullPointerException("Parameter 'target' must not be null");
MethodWeaver result;
synchronized(methodWeavers) {
result = (MethodWeaver) methodWeavers.get( target );
if( null == result ) {
try {
// get or create the ClassWeaver
HotSwapAdvancedClassWeaver cw = getClassWeaver( target.getDeclaringClass() );
// create a new MethodWeaver
result = cw.getNewMethodWeaver( target );
} catch(ClassNotFoundException e) { throw new JVMAIRuntimeException("Class weaver could not get the definition for " + target.getDeclaringClass()); }
}
}
return result;
}
/**
* Get a unique class weaver for 'target'.
*/
static protected HotSwapAdvancedClassWeaver getClassWeaver(Class target) throws ClassNotFoundException {
if( null == target )
throw new NullPointerException("Parameter 'target' must not be null");
HotSwapAdvancedClassWeaver result;
synchronized(classWeavers) {
result = (HotSwapAdvancedClassWeaver) classWeavers.get( target );
if( null == result ) {
// create a new ClassWeaver
result = new HotSwapAdvancedClassWeaver( target );
classWeavers.put( target, result );
}
}
return result;
}
/**
* Re-Weaves all modified classes and activates them.
*/
static public void commit() {
if( modifiedClassWeavers.isEmpty() )
return;
// synchronize commit and reset
synchronized( classWeavers ) {
Collection clazzes = new ArrayList();
Collection definitions = new ArrayList();
Iterator iter = modifiedClassWeavers.iterator();
while( iter.hasNext() ) {
HotSwapAdvancedClassWeaver weaver = (HotSwapAdvancedClassWeaver) iter.next();
try {
weaver.prepareClassDefinition();
definitions.add( weaver.getClassDefinition() );
clazzes.add( weaver.targetClass );
// remove weaver if its not associated with an modified method.
if( weaver.hasNoModifiedMethods() )
classWeavers.remove( weaver.targetClass );
else {
weaver.setWoven();
}
} catch(ClassNotFoundException e) {System.err.println("ERROR HotSwapClassWeaver: Class redefinition failed: " + e);}
catch(IOException e) {System.err.println("ERROR HotSwapClassWeaver: Class redefinition failed: " + e);}
}
modifiedClassWeavers.clear();
if( ! clazzes.isEmpty() ) {
anyWoven = true;
// redefine classes
Class cls[] = new Class[ clazzes.size() ];
clazzes.toArray( cls );
byte defs[][] = new byte[definitions.size()][];
definitions.toArray( defs );
redefineClasses( cls, defs );
}
}
}
/**
* Resets all woven classes.
*/
static public void resetAll() {
// synchronize commit() and resetAll()
synchronized( classWeavers ) {
if( anyWoven ) {
Collection clazzes = new ArrayList();
Collection definitions = new ArrayList();
Iterator it = classWeavers.values().iterator();
// get classes and old class definitions
while( it.hasNext() ) {
HotSwapAdvancedClassWeaver cw = (HotSwapAdvancedClassWeaver) it.next();
if( cw.isWoven() && null != cw.originalCode ) {
clazzes.add( cw.targetClass );
definitions.add( cw.originalCode );
}
}
if( ! clazzes.isEmpty() ) {
// redefine classes
Class cls[] = new Class[ clazzes.size() ];
clazzes.toArray( cls );
byte defs[][] = new byte[ definitions.size() ][];
definitions.toArray( defs );
redefineClasses( cls, defs );
}
}
classWeavers.clear(); // = new LinkedHashMap();
methodWeavers.clear(); // = new LinkedHashMap();
modifiedClassWeavers.clear(); // = new LinkedList();
methodMap = new Member[1024];
idMgr.reset();
anyWoven = false;
}
}
/**
* Returns an identifier for <CODE>method</CODE> and
* stores the association, so that {@link #idToMethod(int) idToMethod}
* may be used to get the <CODE>method</CODE> using the id.
*
* Note: only the id is unique, if <CODE>createNewMethodId(Method)
* </CODE> is called more than one time with the same argument,
* each call will create a new id and a new map entry.
*
* @param method that will be associated with a new identifier.
* @return new identifier for <CODE>method</CODE>.
*/
private static int createNewMethodId( Member method ) {
if( null == method )
throw new NullPointerException();
int result;
synchronized( methodMap ) {
result = idMgr.newId();
// check if map must be expanded
int mapLength = methodMap.length;
if( mapLength <= result ) {
Member newMap[] = new Member[ 2 * mapLength ];
System.arraycopy( methodMap, 0, newMap, 0, mapLength );
methodMap = newMap;
}
// add method to the map
methodMap[result] = method;
}
return result;
}
/**
* Returns the Member object associated to <CODE>methodId</CODE>
* or <CODE>null</CODE>, if <CODE>methodID</CODE> is not a valid
* id.
*
* @param methodId id for the Method that will be returned.
* @return Method associated to <CODE>methodID<CODE> or <CODE>null</CODE>.
* @throws ArrayOutOfBoundException
*/
public static Member idToMethod( int methodId ) {
return methodMap[methodId];
}
/**
* Sets the aspect interface, must be called before any call to
* {@link #commit commit}.
*
* @param ai
*/
public synchronized static void setAspectInterface( HotSwapAspectInterfaceImpl ai ) {
aspectInterface = ai;
}
//---------------------------------------------------------------------
private boolean initialized;
private boolean modified;
private boolean woven;
/**
* Class bound to this weaver
*/
public final Class targetClass;
/**
* Cached original (/unmodified) class file of {@link #targetClass}.
*/
public final byte[] originalCode;
/**
* BCEL class generator used during weaving processs.
* holds the actual class definition during weaving process.
*/
private ClassGen clGen;
/**
* BCEL constant pool generator used during weaving process.
*/
private ConstantPoolGen cpGen;
/**
* BCEL instruction factory used during the weaving process.
*/
private InstructionFactory instructionFactory;
/**
* Collection of member MethodWeavers (of {@link #targetClass})
* that will be redefined.
*/
private Map methods; // for JDK < 1.5 (compatible with newer JVMs)
// private Map<Member,MethodWeaver> methods; // for JDK >= 1.5 (not compatible with older JVMs)
// Used during weaving (introduced for verfyClassSchema)
private JavaClass bcelClass;
//-----------------------------------------------------------------------
/**
* Creates a new class weaver. Use the static method
* {@link #getClassWeaver(java.lang.Class) getClassWeaver} to obtain a weaver.
*/
protected HotSwapAdvancedClassWeaver(Class target) throws ClassNotFoundException {
targetClass = target;
methods = new HashMap();
bcelClass = HotSwapAspectInterfaceImpl.getBCELClassDefinition( targetClass );
originalCode = bcelClass.getBytes();
initialized = false;
woven = false;
modified = false;
clGen = null;
cpGen = null;
}
/**
* Indicates if this class weaver holds any
* method weavers.
*
* @return <CODE>true</CODE> if no method weavers are associated.
*/
public boolean hasNoModifiedMethods() {
return methods.isEmpty();
}
/**
* Returns the class bound to this weaver.
* @return Class that is bound to this weaver
*/
public Class getTargetClass() {
return targetClass;
}
public Collection getMethods() {
return methods.keySet();
}
/**
* Returns a collection of all method weavers for
* this class weaver.
*
* @return Collection of <CODE>MethodWeaver</CODE>s associated to this
* <CODE>ClassWeaver</CODE>
*/
public Collection getMethodWeavers() {
return methods.values();
}
/**
* Sets some internal variables to indicate
* that the target class was woven.
*/
protected void setWoven() {
modified = false;
woven = true;
clGen = null;
}
/**
* Indicates if not yet woven modifications are registered
* for the target class.
*
* @return <CODE>true</CODE> if weaver holds unwoven
* modifications for its target class.
*/
public boolean isModified() {
return modified;
}
/**
* Indicates if the target class was woven (redefined).
*
* @return <CODE>true</CODE> if the target class is redefined.
*/
public boolean isWoven() {
return woven;
}
/**
* Creates a new <CODE>MethodWeaver</CODE> and adds it to
* this weaver.
* This adds the new weaver also to the global methodWeavers
* table.
* Use {@link HotSwapAdvancedClassWeaver#getWeaver(java.lang.reflect.Member) getWeaver} to create a new MethodWeaver.
*/
protected MethodWeaver getNewMethodWeaver( Member target ) {
MethodWeaver result = new MethodWeaver( target );
methodWeavers.put( target, result );
methods.put( target, result );
return result;
}
/**
* Prepares a class for weaving. This mean mainly
* instrumenting the class file representation,
* so that the new definition may be fetched with
* {@link #getClassDefinition()}.
*/
protected void prepareClassDefinition() throws java.io.IOException, ClassNotFoundException {
initPreparation();
weaveMethods();
// verify();
// verifyClassSchema();
finishWeaving();
}
/**
* Prepare for weaving. Initializes some member field.
* This method must only be called once (for each instance
* of this class).
* called by {@link #prepareClassDefinition()}.
*/
private void initPreparation() throws java.io.IOException, ClassNotFoundException {
if( initialized ) {
// weaver was already used, so reset bcelClass to its original state
ClassParser classParser = new ClassParser( new ByteArrayInputStream(originalCode), bcelClass.getFileName());
bcelClass = classParser.parse();
}
else
initialized = true;
// create bcel generator objects
clGen = new ClassGen( bcelClass );
cpGen = clGen.getConstantPool();
instructionFactory = new InstructionFactory( clGen, cpGen );
}
/**
* Resets some member fields to the values they should have
* after weaving.
* Called by {@link #prepareClassDefinition()}-
*/
private void finishWeaving() {
woven = true;
cpGen = null;
instructionFactory = null;
}
/**
* Generate instrumented versions of the member methods
* of {@link #targetClass}.
* called by {@link #prepareClassDefinition()}.
*/
private void weaveMethods() {
// Copy the methods to another collection to allow removal
// from the entire collection.
int numOfMethods = methods.size();
MethodWeaver[] mws = new MethodWeaver[numOfMethods];
methods.values().toArray(mws);
for(int i = 0; i < numOfMethods; i++ ) {
mws[i].weaveMethod();
}
}
/**
* Returns the instrumented class file.
* {@link #prepareClassDefinition()} must be called first.
*/
protected byte[] getClassDefinition() {
if( ! initialized )
throw new JVMAIRuntimeException("ClassWeaver for <" + targetClass + "> not initialized ");
return clGen.getJavaClass().getBytes();
}
private void reset() {
woven = false;
modified = false;
Iterator it = methods.values().iterator();
while( it.hasNext() ) {
MethodWeaver mw = (MethodWeaver) it.next();
methodWeavers.remove(mw.target);
}
methods.clear();
}
/**
* Verifies if the new class definition doesn't change anything,
* which RedefineClasses can not handle (anything except
* changing method bodies).
*
* @return <CODE>false</CODE> if any incompabilities betwen
* the new and the old class definitions where found,
* otherwise <CODE>true</CODE>.
*/
private boolean verifyClassSchema() {
boolean retval = true;
if( null == clGen || null == bcelClass )
throw new RuntimeException( "nothing to verify" );
//System.out.println( "Verifying " + oldClass.getClassName() );
// Get classes
ClassParser cp = new ClassParser( new ByteArrayInputStream(originalCode), targetClass.getName().replace( '.', '/') );
try {
bcelClass = cp.parse();
}
catch (java.io.IOException e) {
throw new RuntimeException( e.getMessage() );
}
JavaClass newClass = clGen.getJavaClass();
// Check class name
if( ! bcelClass.getClassName().equals( newClass.getClassName() ) ) {
System.out.println( "Class name changed ( old: " + bcelClass.getClassName() + ", new: " + newClass.getClassName() + ")" );
retval = false;
}
// Check class modifiers (access flags)
if( bcelClass.getAccessFlags() != newClass.getAccessFlags() ) {
System.out.println( "Class modifiers changed (old: " + bcelClass.getAccessFlags() + ", new: " + newClass.getAccessFlags() + ")" );
retval = false;
}
// Methods
org.apache.bcel.classfile.Method oldMethods[] = bcelClass.getMethods();
org.apache.bcel.classfile.Method newMethods[] = newClass.getMethods();
int oldMethodsLength = oldMethods.length;
// number of methods
if( oldMethodsLength != newMethods.length ) {
System.out.println( "Number of methods changed (old: " + oldMethodsLength + ", new: " + newMethods.length + ")" );
retval = false;
}
else {
for( int i = 0; i < oldMethodsLength; i++ ) {
org.apache.bcel.classfile.Method oldMeth = oldMethods[i];
org.apache.bcel.classfile.Method newMeth = newMethods[i];
// method name
String oldMName = oldMeth.getName();
if( ! oldMName.equals( newMeth.getName() ) ) {
System.out.println( "Method name changed (old: " + oldMName + ", new: " + newMeth.getName() + ")" );
retval = false;
}
// method modifiers
if( oldMeth.getAccessFlags() != newMeth.getAccessFlags() ) {
System.out.println( "Method modifiers changed for" + oldMName );
retval = false;
}
// signature
if( ! oldMeth.getSignature().equals( newMeth.getSignature() ) ) {
System.out.println( "Method signature changed for " + oldMName );
retval = false;
}
}
}
// Check for JVMDI_ERROR_SCHEMA_CHANGE... (Fields)
org.apache.bcel.classfile.Field newFields[] = newClass.getFields();
org.apache.bcel.classfile.Field oldFields[] = bcelClass.getFields();
int newFieldsLength = newFields.length;
if( newFieldsLength != oldFields.length ) {
System.out.println( "Number of fields changed (old " + oldFields.length + ", new " + newFieldsLength + ")" );
retval = false;
}
else {
for( int i = 0; i < newFieldsLength; i++ ) {
org.apache.bcel.classfile.Field nf = newFields[i];
org.apache.bcel.classfile.Field of = oldFields[i];
String oldName = of.getName();
// Name
if( ! nf.getName().equals( oldName ) ) {
System.out.println( "Field name changed ( new: " + nf.getName() + ", old: " + oldName + ")" );
retval = false;
}
// Access flags
if( nf.getAccessFlags() != of.getAccessFlags() ) {
System.out.println( "Field access flags changed for " + oldName + "( old: " + of.getAccessFlags() + ", new: " + nf.getAccessFlags() + ")" );
retval = false;
}
// Signature
if( ! nf.getSignature().equals( of.getSignature() ) ) {
System.out.println( "Field signature changed for " + oldName + "( old: " + of.getSignature() + ", new: " + nf.getSignature() + ")" );
retval = false;
}
// Constant value (may be 'null')
ConstantValue oldConst = of.getConstantValue();
ConstantValue newConst = nf.getConstantValue();
if( oldConst != newConst ) {
if( null == oldConst ) {
System.out.println( "Changed constant field to modifiable field " + oldName );
retval = false;
}
else if( null == newConst ) {
System.out.println( "Changed modifiable field to constant field " + oldName );
retval = false;
}
else if( ! oldConst.toString().equals( newConst.toString() ) ) {
System.out.println( "Changed " + oldConst.toString() + " to " + newConst.toString() );
retval = false;
}
}
// Attributes
Attribute newAttributes[] = nf.getAttributes();
Attribute oldAttributes[] = of.getAttributes();
int oldAttributesLength = oldAttributes.length;
if( oldAttributesLength != newAttributes.length ) {
System.out.println( "Number of field attributes changend (old: " + oldAttributesLength + " new: " + newAttributes.length + ")" );
retval = false;
}
else {
for( int j = 0; j < oldAttributesLength; j++ ) {
Attribute oldAttr = oldAttributes[j];
Attribute newAttr = newAttributes[j];
}
}
}
}
return retval;
}
/**
* Verify bytecodes with the BCEL verifier.
* <p>
* NOTE: Can't be used since even `normal' classes are rejected with Jikes
* RVM 2.3.0.1.
*
*/
private void verify() {
if( null == clGen )
throw new RuntimeException( "nothing to verify" );
JavaClass jc = clGen.getJavaClass();
Repository.addClass( jc );
Verifier v = VerifierFactory.getVerifier( jc.getClassName() );
checkVerificationResult(v.doPass1(), "1");
checkVerificationResult(v.doPass2(), "2");
MethodWeaver methodWeavers[] = new MethodWeaver[methods.size()];
methods.values().toArray( methodWeavers );
for( int i = 0; i < methods.size(); i++ ) {
int method_index = methodWeavers[i].getTargetIndex();
checkVerificationResult(v.doPass3a(method_index), "3a");
checkVerificationResult(v.doPass3b(method_index), "3b");
}
try {
String[] warnings = v.getMessages();
if (warnings.length != 0)
System.err.println("Messages:");
for (int j = 0; j < warnings.length; j++)
System.err.println(warnings[j]);
} catch(Exception e) {System.err.println("could not verify " + targetClass);}
}
/**
* Check bytecode verification result.
*
* @param vr verification result which must be checked
* @param pass verification pass
*/
private void checkVerificationResult(VerificationResult vr, String pass) {
if (vr != VerificationResult.VR_OK) {
System.err.println("Verification failed in pass " + pass + " for " + targetClass + ".");
System.err.println(vr);
}
}
/**
* Redefines existing classes, actually only method bodies may be redefined,
* but the JVMDI and JVMTI functions requieres the whole class code. The parameters
* are two arrays of the same size, each entry of one array corresponds to the
* entry with the same index in the other array.
* The class that should be redefined must exist in the JVM (they must be loaded
* in advance).
*
* Required for JVMDI/JVMTI HotSwap advice weaving.
*
* @param cls Array of Class objects that should get redefined
* @param definitions Array of class definitions, which are arrays of bytes,
* the definition is in the same form like in a class file.
* @exception UnmodifiableClassException <CODE>cls</CODE> contains a not modifiable
* class (for example an interface or a class that's not in the class path).
* @exception IlligalClassFormatException <CODE>definitions</CODE> contains a invalide
* class definition
* @exception RuntimeException could not apply operation.
* @exception NullPointerException if <CODE>cls</CODE> is <CODE>null</CODE>.
* @exception IlligalArgumentExceptin <CODE>definitions</CODE> is <CODE>null</CODE>.
*/
public static void redefineClasses( Class[] cls, byte[][] definitions ) {
HotSwapClassWeaver.redefineClasses( cls, definitions );
}
/**
* Redefines an existing class, actually only method bodies may be redefined,
* but the JVMDI and JVMTI functions requieres the whole class code.
* The class that should be redefined must exist in the JVM (they must be loaded
* in advance).
*
* Required for JVMDI/JVMTI HotSwap advice weaving.
*
* @param cl Class objects that should get redefined
* @param definition class definition,
* the definition is in the same form like in a class file.
* @exception UnmodifiableClassException <CODE>cl</CODE> is a not modifiable
* class (for example an interface or a class that's not in the class path).
* @exception IlligalClassFormatException <CODE>definition</CODE> is a invalide
* class definition
* @exception RuntimeException could not apply operation.
* @exception NullPointerException if <CODE>cl</CODE> is <CODE>null</CODE>.
* @exception IlligalArgumentExceptin <CODE>definition</CODE> is <CODE>null</CODE>.
*/
public static void redefineClass( Class cl, byte[] definition ) {
HotSwapClassWeaver.redefineClass( cl, definition );
}
//----------------------------------------------------------------------
/**
* Implementation of MethodWeaver. Collects redefinition
* requests and weaves them into the methods bytecode.
*
* Based on MethodWeaver implementation by Johann Gyger.
*
* @see ch.ethz.jvmai.MethodWeaver
*/
protected class MethodWeaver implements ch.ethz.jvmai.MethodWeaver {
/**
* Method bound to this weaver.
*/
public final Member target;
/**
* Method identifier for {@link #target}.
* Generated with {@link HotSwapAdvancedClassWeaver#createNewMethodId ClassWeaver.createNewMethodId}.
*/
public final int targetId;
/**
* Method index (in the array of declared methods of the declaring
* class {@link Class#getDeclaredMethods()}) or -1 if not initialized.
*/
private int targetIndex;
/**
* Indicates how the method should be modified.
* see {@link ch.ethz.jvmai.MethodWeaver}
*/
private int status;
/**
* Ordered list holding redefine advice methods, if any.
* New redefinine advices are put at the end of the list.
* <P>
* The list will hold entries of type <CODE>RedefineAdviceListEntry</CODE>
*/
private LinkedList redefineAdvices; // for JDK < 1.5 (compatible with newer JVMs)
// private LinkedList<RedefineAdviceListEntry> redefineAdvices; // for JDK >= 1.5 (not compatible with older JVMs)
/*
* RedefineAdviceListEntry is a container to store
* a redefine advice together with its crosscut object
* in a list entry.
*/
protected class RedefineAdviceListEntry implements Comparable {
final Method advice;
final Object cut;
final int priority;
RedefineAdviceListEntry(Method advice, Object cut, int priority){
this.advice = advice;
this.cut = cut;
this.priority = priority;
}
public int compareTo(Object o) {
return ((RedefineAdviceListEntry)o).priority - priority;
}
public boolean equals(Object obj) {
return cut.equals(obj) ||
(obj instanceof RedefineAdviceListEntry) &&
cut.equals(((RedefineAdviceListEntry)obj).cut);
}
public String toString() {
return "RedefineAdviceListEntry for " + cut.toString() + " @" + target;
}
};
// /**
// * Watched method calls (not implemented).
// */
// private Map methodCalls;
//
// /**
// * Watched method returns (not implemented).
// */
// private Map methodReturns;
/**
* Watched field accesses
*/
private Map fieldAccessors; // for JDK < 1.5 (compatible with newer JVMs)
// private Map<String,HotSwapFieldWeaver> fieldAccessors; // for JDK >= 1.5 (not compatible with older JVMs)
/**
* Watched field modifications
*/
private Map fieldModifiers; // for JDK < 1.5 (compatible with newer JVMs)
// private Map<String,HotSwapFieldWeaver> fieldModifiers; // for JDK >= 1.5 (not compatible with older JVMs)
/**
* BCELs method generator
*/
private MethodGen methodGen;
/**
* BCEL instructions, used during weaving
* process (normalwise set to 'null').
*/
private InstructionList instructions;
/**
* Create a new method weaver. Use the static method
* {@link HotSwapAdvancedClassWeaver#getWeaver(java.lang.reflect.Member) ClassWeaver.getWeaver}to obtain a weaver.
*
* @param target method that will be woven
*/
public MethodWeaver( Member target ) {
this.target = target;
targetId = createNewMethodId( target );
targetIndex = -1;
methodGen = null;
instructions = null;
//methodCalls = Collections.synchronizedMap(new LinkedHashMap());
//methodReturns = Collections.synchronizedMap(new LinkedHashMap());
fieldAccessors = Collections.synchronizedMap(new LinkedHashMap()); // for JDK < 1.5 (compatible with newer JVMs)
//fieldAccessors = Collections.synchronizedMap(new LinkedHashMap<String,HotSwapFieldWeaver>()); // for JDK >= 1.5 (not compatible with older JVMs)
fieldModifiers = Collections.synchronizedMap(new LinkedHashMap()); // for JDK < 1.5 (compatible with newer JVMs)
//fieldModifiers = Collections.synchronizedMap(new LinkedHashMap<String,HotSwapFieldWeaver>()); // for JDK >= 1.5 (not compatible with older JVMs)
redefineAdvices = new LinkedList(); // for JDK < 1.5 (compatible with newer JVMs)
//redefineAdvices = new LinkedList<RedefineAdviceListEntry>(); // for JDK >= 1.5 (not compatible with older JVMs)
status = MWEAVER_STATUS_UNCHANGED;
}
/**
* Changes the status field to reflect that an watch
* of type <code>'type'</code> is set.
*
* @param type type of the watch (see ch.ethz.jvmai.MethodWeaver).
*/
private void addWatch(int type) {
status |= type;
setModified();
}
/**
* Changes the status field to reflect that an watch
* of type <code>'type'</code> has to be removed.
*
* @param type type of the watch (see ch.ethz.jvmai.MethodWeaver).
*/
private void removeWatch(int type) {
status &= ~type;
setModified();
}
/**
* Changes the status field to either add or remove a watch.
*
* @param type type of the watch (see ch.ethz.jvmai.MethodWeaver).
* @param flag <code>true</code> to add the watch, false to remove it.
*/
private void setWatch(int type, boolean flag) {
status = flag ? (status | type)
: (status & ~type);
setModified();
}
/**
* Indicates if a watch is set or not.
*
* @param type type of the watch (see ch.ethz.jvmai.MethodWeaver).
* @return <CODE>'true'</CODE> if the watch is set.
*/
public boolean hasWatch(int type) {
return (status & type) > 0;
}
/**
* Returns the method bound to this weaver.
* @return Method bound to this weaver.
*/
public Member getTarget() {
return target;
}
/**
* Returns the identifier of the method bound to this weaver.
* @return identifier of the method bound to this weaver
*/
public int getTargetId() {
return targetId;
}
/**
* Returns the index of the target method in the array, which
* will be returned by its declaring classes <CODE>getDeclaredMethods()
* </CODE> method.
* @return index in the array of declared methods
*/
public int getTargetIndex() {
return targetIndex;
}
/**
* Returns the status of this weaver. The codes are defined
* in {@link ch.ethz.jvmai.MethodWeaver MethodWeaver}.
*
* @return int status of the weaver
*/
public int getStatus() {
return status;
}
/**
* Redefine the method in this weaver.
*
* @param advice method that will be called instead
* or null to reset a redefined method.
* @param cut crosscut object owning <CODE>advice</CODE>
* (or a unique identifier for this cut)
* @param priority priority of the advice holding cut
*/
public void setRedefineAdvice(Method advice, Object cut, int priority) {
if(null != advice) {
RedefineAdviceListEntry newEntry = new RedefineAdviceListEntry(advice, cut, priority);
addRedefineAdvice(newEntry);
}
else if(!redefineAdvices.isEmpty()) {
removeRedefineAdvice(cut);
}
setWatch(MWEAVER_STATUS_REDEFINE, true);
}
/**
* Adds a new method redefinition advice to <CODE>redefineAdvices</CODE>.
*
* @param newEntry new entry to insert into <CODE>redefineAdvices</CODE>
*/
private void addRedefineAdvice(RedefineAdviceListEntry newEntry) {
// find the right position for insertion
ListIterator iter = redefineAdvices.listIterator();
while(iter.hasNext()) {
if(newEntry.priority <= ((RedefineAdviceListEntry)iter.next()).priority) {
// a. found the position where newEntry will be inserted
redefineAdvices.add(iter.previousIndex(), newEntry);
return;
}
}
// b. the list is empty or all entries have a lower priority number:
// append the new entry to the list.
redefineAdvices.addLast(newEntry);
}
/**
* Removes an entry from <CODE>redefineAdvices</CODE>.
*
* @param cut crosscut object that will be removed
*/
private void removeRedefineAdvice(Object cut) {
// find the entry for 'cut'
for(
ListIterator iter = redefineAdvices.listIterator();
iter.hasNext();
) {
RedefineAdviceListEntry e = (RedefineAdviceListEntry) iter.next();
if(cut == e.cut) {
// remove the entry for cut
iter.remove();
break; // just remove the first occurence (assume that there's only one).
}
}
}
/**
* Enable method entry join point.
*
* @param flag enable/disable
*/
public void setMethodEntryEnabled(boolean flag) {
setWatch(MWEAVER_STATUS_ENTRY_ENABLED, flag);
}
/**
* Enable method entry join point.
*
* @param flag enable/disable
*/
public void setMethodExitEnabled(boolean flag) {
setWatch(MWEAVER_STATUS_EXIT_ENABLED, flag);
}
/**
* Add a method for which a callback should be woven (before each call).
*
* @param m watched method
*/
public void addMethodCall(Method m) {
throw new JVMAIRuntimeException("Method HotSwapAdvancedClassWeaver.addMethodCall() not implemented");
// String key = m.getDeclaringClass().getName() + "#" + m.getName();
// if( ! methodCalls.containsKey( key ) ) {
// methodCalls.put( key, m );
// addWatch(MWEAVER_STATUS_HAS_METHOD_CALL_WATCH);
// }
}
/**
* Remove a method for which a callback was woven (before each call).
*
* @param m watched method
*/
public void removeMethodCall(Method m) {
throw new JVMAIRuntimeException("Method HotSwapAdvancedClassWeaver.removeMethodCall() not implemented");
// String key = m.getDeclaringClass().getName() + "#" + m.getName();
// if( methodCalls.containsKey( key ) ) {
// methodCalls.remove( key );
// if( methodCalls.isEmpty() )
// removeWatch(MWEAVER_STATUS_HAS_METHOD_CALL_WATCH);
// else
// setModified();
// }
}
/**
* Add a method for which a callback should be woven (after each call).
*
* @param m watched method
*/
public void addMethodReturn(Method m) {
throw new JVMAIRuntimeException("Method HotSwapAdvancedClassWeaver.addMethodReturn() not implemented");
// String key = m.getDeclaringClass().getName() + "#" + m.getName();
// if( ! methodReturns.containsKey( key ) ) {
// methodReturns.put( key, m );
// addWatch(MWEAVER_STATUS_HAS_METHOD_RETURN_WATCH);
// }
}
/**
* Remove a method for which a callback was woven (after each call).
*
* @param m watched method
*/
public void removeMethodReturn(Method m) {
throw new JVMAIRuntimeException("Method HotSwapAdvancedClassWeaver.removeMethodReturn() not implemented");
// String key = m.getDeclaringClass().getName() + "#" + m.getName();
// if( methodReturns.containsKey( key ) ) {
// methodReturns.remove( key );
// if( methodReturns.isEmpty() )
// removeWatch(MWEAVER_STATUS_HAS_METHOD_RETURN_WATCH);
// else
// setModified();
// }
}
/**
* Add a field for which a callback should be woven (for each access).
*
* @param fw weaver for the watched field
*/
public void addFieldAccessor(HotSwapFieldWeaver fw) {
String f = fw.getKey();
//System.out.println("HotSwapClassWeaver.addFieldAccessor(" + f + ") to " + target.getName() );
if( ! fieldAccessors.containsKey( f ) ) {
fieldAccessors.put( f, fw );
addWatch(MWEAVER_STATUS_HAS_FIELD_ACCESS_WATCH);
}
}
/**
* Remove a field for which a callback was woven (for each access).
*
* @param fw weaver for the watched field
*/
public void removeFieldAccessor(HotSwapFieldWeaver fw) {
String f = fw.getKey();
if( fieldAccessors.containsKey( f ) ) {
fieldAccessors.remove( f );
if( fieldAccessors.isEmpty() )
removeWatch(MWEAVER_STATUS_HAS_FIELD_ACCESS_WATCH);
else
setModified();
}
}
/**
* Add a field for which a callback should be woven (for each modification).
*
* @param fw weaver for the watched field
*/
public void addFieldModifier(HotSwapFieldWeaver fw) {
String f = fw.getKey();
//System.out.println("HotSwapClassWeaver.addFieldModifier(" + f + ") to " + target.getName() );
if( ! fieldModifiers.containsKey( f ) ) {
fieldModifiers.put( f, fw );
addWatch(MWEAVER_STATUS_HAS_FIELD_MODIFICATION_WATCH);
}
}
/**
* Remove a field for which a callback was woven (for each modification).
*
* @param fw weaver for the watched field
*/
public void removeFieldModifier(HotSwapFieldWeaver fw) {
String f = fw.getKey();
if( fieldModifiers.containsKey( f ) ) {
fieldModifiers.remove( f );
if( fieldModifiers.isEmpty() )
removeWatch(MWEAVER_STATUS_HAS_FIELD_MODIFICATION_WATCH);
else
setModified();
}
}
/**
* Weave all registered field accesses/modifications.
*/
private void weaveFieldAdvice() {
for (InstructionHandle h = instructions.getStart(); h != null; h = h.getNext()) {
Instruction instr = h.getInstruction();
if (instr instanceof FieldInstruction) {
FieldInstruction fi = (FieldInstruction) instr;
String key = fi.getReferenceType(cpGen).toString() + "#" + fi.getName(cpGen) + "#" + fi.getSignature(cpGen);
if ((instr instanceof GETSTATIC || instr instanceof GETFIELD) && fieldAccessors.containsKey(key)) {
// Field Access
HotSwapFieldWeaver fieldWeaver = (HotSwapFieldWeaver) fieldAccessors.get(key);
// check if this is a watched field access
if(null != fieldWeaver) {
//System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " GET " + key);
// weave the callback function call
instructions.insert(h, createFieldAccessAdviceCallback(fieldWeaver));
}
} else if ((instr instanceof PUTSTATIC || instr instanceof PUTFIELD) && fieldModifiers.containsKey(key)) {
// Field modification
HotSwapFieldWeaver fieldWeaver = (HotSwapFieldWeaver) fieldModifiers.get(key);
// check if this is a watched field modification
if( null != fieldWeaver ) {
// Store new value to a local variable
Type field_type = fi.getFieldType(cpGen);
// The use of the LocalVariableGen is faster
// than just using max local + 1 (Dont no why, but meassurements showed it.
int local_index = methodGen.getMaxLocals() + 1;
instructions.insert(h, InstructionFactory.createStore(field_type, local_index));
//System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " PUT " + key);
// weaver the callback function call
instructions.insert(h, createFieldModificationAdviceCallback("doOnFieldModification", fieldWeaver, local_index));
// Load new value
instructions.insert(h, InstructionFactory.createLoad(field_type, local_index));
methodGen.setMaxLocals(local_index + field_type.getSize());
}
}
}
}
}
/**
* Initiates weaving of method redefinition advices.
*
*/
private void weaveRedefineAdvice() {
//System.out.println("MethodWeaver.weaveRedefineAdvice(): " + target);
if(!redefineAdvices.isEmpty()) {
Iterator nextAdvices = redefineAdvices.iterator();
weaveRedefineAdvice((RedefineAdviceListEntry)nextAdvices.next(), nextAdvices);
}
}
/**
* Weaves a method redefinition advice.
*
* @param entry advice to be woven
* @param nextAdvices other advices with lower precedence, which will be executed
* for 'proceed' statements.
*/
private void weaveRedefineAdvice(RedefineAdviceListEntry entry, Iterator nextAdvices) {
//System.out.println("MethodWeaver.weaveRedefineAdvice(" + entry.advice + "): " + target);
// 1. create BCEL generators
// 1.1. generators for actual advice
// 1.1.1. class generator
Class actualAdviceClass = entry.advice.getDeclaringClass();
ClassGen actualAdviceClassGen;
try { actualAdviceClassGen = new ClassGen(HotSwapAspectInterfaceImpl.getBCELClassDefinition(actualAdviceClass)); }
catch(ClassNotFoundException e) { throw new JVMAIRuntimeException("MethodWeaver.weaveRedefineAdvice(): can not read class file " + actualAdviceClass.getName()); }
// 1.1.2. constant pool generator
ConstantPoolGen actualAdviceCPGen = actualAdviceClassGen.getConstantPool();
// 1.1.3. method generator
org.apache.bcel.classfile.Method[] aMethods = actualAdviceClassGen.getMethods();
MethodGen actualAdviceMethodGen = null;
for( int i = 0; i < aMethods.length; i++ )
if( "METHOD_ARGS".equals( aMethods[i].getName() ) ) {
actualAdviceMethodGen = new MethodGen( aMethods[i], actualAdviceClass.getName(), actualAdviceCPGen );
break;
}
if( null == actualAdviceMethodGen )
throw new JVMAIRuntimeException( "can not find method " + actualAdviceClass.getName() + ".METHOD_ARGS()");
// 2. method redefinition
HotSwapProceedWeaver pweaver = new HotSwapProceedWeaver(actualAdviceMethodGen, actualAdviceCPGen, methodGen);
if(pweaver.prepareAdvice()) {
// 2.a. 'proceed' statement in actual advice
// proceed-weaving required
// nextAdvice might be either the next advice in the list or it's the target
if(nextAdvices.hasNext()) {
// 2.a.a. nextAdvice is an advice method, which need to be prepared first
RedefineAdviceListEntry nextAdvice = (RedefineAdviceListEntry)nextAdvices.next();
// prepare nextAdvice (RECURSION!)
weaveRedefineAdvice(nextAdvice, nextAdvices);
}
// 2.a.1. save the original code of 'target' before it is redefined.
pweaver.setTarget(methodGen, cpGen);
// 2.a.2. replace the code of target (methodGen) by the code of actualAdvice
redefineMethod(entry.advice, actualAdviceMethodGen, actualAdviceCPGen);
// 2.a.3. weave 'proceed' statements, target method is now already
// redefined, so methodGen holds now the code of actualAdviceMethodGen
pweaver.weaveRedefinedMethod();
}
else
// 2.b. no 'proceed' invokation -> no proceed-weaving required
// replace the code of target (methodGen) by the code of actualAdvice
redefineMethod(entry.advice, actualAdviceMethodGen, actualAdviceCPGen);
}
/**
* Weave method redefine advice callback into method body and remove original
* code.
* <P>
* @param newMethod advice method
* @param newMethodGen bcel generator for the advice method
* @param newCP bcel constant pool of the class declaring the advice method
*/
private void redefineMethod(Method newMethod, MethodGen newMethodGen, ConstantPoolGen newCP) {
//System.out.println("MethodWeaver.weaveRedefineAdvice(): " + target + " / " + ((RedefineAdviceListEntry)redefineAdvices.getLast()).getAdvice());
// 1. remove the original exception handlers
methodGen.removeExceptionHandlers();
// 2. set targets method generator
instructions = newMethodGen.getInstructionList();
methodGen.setInstructionList(instructions);
// 3. transform bytecode
HotSwapLocalVariableMapper varMapper = new HotSwapLocalVariableMapper(newMethod,(Method) getTarget());
for (InstructionHandle h = instructions.getEnd(); h != null; h = h.getPrev()) {
Instruction instr = h.getInstruction();
// 3.1. Transform local variable (and parameter) access.
if (instr instanceof LocalVariableInstruction) {
LocalVariableInstruction lv_instr = (LocalVariableInstruction) instr;
lv_instr.setIndex(varMapper.getNewSlot(lv_instr.getIndex()));
}
// 3.2. Transform constant pool references to constant pool of target class.
// Add missing constant pool entries in target class.
else if (instr instanceof CPInstruction) {
CPInstruction cp_instr = (CPInstruction) instr;
org.apache.bcel.classfile.Constant c = newCP.getConstant(cp_instr.getIndex());
int new_index = cpGen.addConstant(c, newCP);
cp_instr.setIndex(new_index);
}
}
// 3.3. set the number of local variable slots used for the redefined method
methodGen.setMaxLocals(varMapper.getNewMaxLocals(newMethodGen.getMaxLocals()));
//methodGen.setMaxStack(advice_mg.getMaxStack()); // until now stack size is not used or verified by Sun JVM
// 4. Copy exception handlers
CodeExceptionGen[] cegs = newMethodGen.getExceptionHandlers();
for (int i = 0; i < cegs.length; i++) {
CodeExceptionGen ceg = cegs[i];
methodGen.addExceptionHandler(ceg.getStartPC(), ceg.getEndPC(), ceg.getHandlerPC(), ceg.getCatchType());
}
}
/**
* Weave method entry advice callback into method body.
*/
private void weaveMethodEntryAdvice() {
//System.out.println("MethodWeaver.weaveMethodEntryAdvice(): " + target);
InstructionHandle start = instructions.getStart();
if( target instanceof Constructor ) {
// JVM requires call to super constructors, to be done first,
// but this works also when the callback is called first.
// check if the constructor calls another constructor
InstructionHandle h = usesSuper();
if( null != h ) {
// call must be woven after the other constructor was called.
h = instructions.append( h, new PUSH(cpGen, targetId) );
instructions.append( h,
instructionFactory.createInvoke(
JVMAI_CLASS,
"doOnConstructor",
Type.VOID,
new Type[] { Type.INT },
Constants.INVOKESTATIC));
}
else {
// Push parameter: int methodId
instructions.insert(start, new PUSH(cpGen, targetId));
// Call advice
instructions.insert( start,
instructionFactory.createInvoke(
JVMAI_CLASS,
"doOnConstructor",
Type.VOID,
new Type[] { Type.INT },
Constants.INVOKESTATIC));
}
}
else {
// Push parameter: int methodId
instructions.insert(start, new PUSH(cpGen, targetId));
// Call advice
instructions.insert( start,
instructionFactory.createInvoke(
JVMAI_CLASS,
"doOnMethodEntry",
Type.VOID,
new Type[] { Type.INT },
Constants.INVOKESTATIC));
}
}
/**
* Checks if another constructor is called by a constructor.
* The call is the first thing that a constructor does.
* @return InstructionHandle of the call or null if no call
* to another constructor was found
*/
private InstructionHandle usesSuper() {
//System.out.println("MethodWeaver.usesSuper(): " + target);
InstructionHandle handle = instructions.getStart();
while( handle != null ) {
Instruction inst = handle.getInstruction();
if( inst instanceof INVOKESPECIAL ) {
return handle;
}
if( ! (inst instanceof StackProducer) )
break;
handle = handle.getNext();
}
return null;
}
/**
* Weave method exit advice callback into method body.
*/
private void weaveMethodExitAdvice() {
//System.out.println("MethodWeaver.weaveMethodExitAdvice(): " + target);
InstructionHandle try_start = instructions.getStart();
InstructionHandle try_end = instructions.getEnd();
InstructionHandle real_end;
// Weave method exit advice (in a finally block)
int local_index = methodGen.getMaxLocals();
InstructionHandle finally_start = instructions.append(new ASTORE(local_index));
// Push parameter: int methodId
instructions.append(new PUSH(cpGen, targetId));
// Push parameter: int local value index of the return value
// actual local index + 3 because two additional local variables
// will be created below.
instructions.append(new PUSH(cpGen, local_index + 3));
// Call advice
instructions.append(
instructionFactory.createInvoke(
JVMAI_CLASS,
"doOnMethodExit",
Type.VOID,
new Type[] { Type.INT, Type.INT },
Constants.INVOKESTATIC));
real_end = instructions.append(new RET(local_index++));
// Insert exception handler before finally block
InstructionHandle catch_start = instructions.insert(finally_start, new ASTORE(local_index));
instructions.insert(finally_start, new JSR(finally_start));
instructions.insert(finally_start, new ALOAD(local_index++));
instructions.insert(finally_start, new ATHROW());
// Jump to finally block before each return
JumpFinallyVisitor visitor = new JumpFinallyVisitor(instructions, try_start, catch_start, finally_start, local_index);
visitor.go();
local_index += visitor.getLocalSize();
methodGen.setMaxLocals(local_index);
methodGen.addExceptionHandler(try_start, catch_start.getPrev(), catch_start, null);
}
/**
* Create a field access advice callback that can be woven.
*
* @param fieldWeaver weaver for the field.
* @return List of instructions that make up the callback.
*/
private InstructionList createFieldAccessAdviceCallback(HotSwapFieldWeaver fieldWeaver) {
InstructionList il = new InstructionList();
// Push parameter: Object owner
if ( Modifier.isStatic( fieldWeaver.getTarget().getModifiers() ) )
il.append( InstructionConstants.ACONST_NULL );
else
il.append( InstructionConstants.DUP );
// Push parameter: int fieldId
il.append( new PUSH(cpGen, fieldWeaver.getTargetId() ) );
// Call advice
il.append(
instructionFactory.createInvoke(
JVMAI_CLASS,
"doOnFieldAccess",
Type.VOID,
new Type[] { Type.OBJECT, Type.INT },
Constants.INVOKESTATIC));
return il;
}
/**
* Create a field advice callback that can be woven.
*
* @param callbackMethod method that will be called.
* @param fieldWeaver weaver for the field.
* @param slot variable table index of the new value for the field
* @return List of instructions that make up the callback.
*/
private InstructionList createFieldModificationAdviceCallback(String callbackMethod, HotSwapFieldWeaver fieldWeaver, int slot) {
InstructionList il = new InstructionList();
// Push parameter: Object owner
if ( Modifier.isStatic( fieldWeaver.getTarget().getModifiers() ) )
il.append(InstructionConstants.ACONST_NULL);
else
il.append(InstructionConstants.DUP);
// Push parameter: int fieldId
int fieldId = fieldWeaver.getTargetId();
il.append(new PUSH(cpGen, fieldId ));
// push parameter: local variable index of the field
il.append(new PUSH(cpGen, slot));
// Call advice
il.append(
instructionFactory.createInvoke(
JVMAI_CLASS,
callbackMethod,
Type.VOID,
new Type[] { Type.OBJECT, Type.INT, Type.INT },
Constants.INVOKESTATIC));
return il;
}
/**
* Weave advice that are associated with the method in this weaver.
*
* Called by {@link HotSwapAdvancedClassWeaver#weaveMethods()}.
*/
protected void weaveMethod() {
if( MWEAVER_STATUS_UNCHANGED == status ) {
// Remove this weaver from all maps
//System.out.println("MethodWeaverImpl.weaveMethod(): remove " + this.target.getName());
HotSwapAdvancedClassWeaver.methodWeavers.remove(this.target);
methods.remove(this.target);
}
else {
// 1. Prepare
initMethodWeaving();
// 2. Redefine method
if( !redefineAdvices.isEmpty() )
weaveRedefineAdvice();
// 3. Weave field advices
if( hasWatch(MWEAVER_STATUS_HAS_FIELD_ACCESS_WATCH
|MWEAVER_STATUS_HAS_FIELD_MODIFICATION_WATCH)
)
weaveFieldAdvice();
// 4. Weave method entry advice
if( hasWatch(MWEAVER_STATUS_ENTRY_ENABLED) )
weaveMethodEntryAdvice();
// 5. Weaver method exit advice
if( hasWatch(MWEAVER_STATUS_EXIT_ENABLED) )
weaveMethodExitAdvice();
// 6. Finish method weaving
finishMethodWeaving();
}
}
/**
* Prepare for weaving by initializing the corresponding fields.
*
* Called by {@link #weaveMethod()}.
*/
private void initMethodWeaving() {
// 1. Get the method name
String methodName;
String signature;
if( target instanceof Constructor ) {
// the name of constructors is alway <init>
methodName = "<init>";
signature = JNIUtil.jniSignature((Constructor) target);
}
else { // target is a method
methodName = target.getName();
signature = JNIUtil.jniSignature((Method) target);
}
// 2. Create the BCEL MethodGen object
if( 0 > targetIndex ) {
// 2.a Try to get the BCEL method object
methodGen = null;
org.apache.bcel.classfile.Method[] meths = clGen.getMethods();
// Iterate over all member methods of the class
for( int i = 0; i < meths.length; i++ ) {
// Check for
if( methodName.equalsIgnoreCase( meths[i].getName() ) && signature.equals( meths[i].getSignature() ) ) {
targetIndex = i;
methodGen = new MethodGen( meths[i], methodName, cpGen );
break;
}
}
}
else {
methodGen = new MethodGen( clGen.getMethodAt(targetIndex), methodName, cpGen );
}
if( null == methodGen )
throw new RuntimeException("Method " + methodName + " not found");
// create BCEL instructions list
instructions = methodGen.getInstructionList();
}
/**
* Clean up weaving process and replaces the method definition in ClassWeavers
* class definition.
*
* Called by {@link #weaveMethod()}
*/
private void finishMethodWeaving() {
methodGen.setMaxStack();
methodGen.setMaxLocals();
methodGen.removeLocalVariables();
methodGen.removeLineNumbers();
methodGen.removeCodeAttributes();
clGen.setMethodAt( methodGen.getMethod(), targetIndex );
// release instructions
instructions.dispose();
instructions = null;
methodGen = null;
}
/**
* Sets some variables to indicate that the target class
* must be woven at the next call of
* {@link HotSwapAdvancedClassWeaver#commit()}.
* <P>
* This does not set the {@link #status} variable, which must
* be managed too.
*/
private void setModified() {
if( ! modified ) {
// synchronize with commit and resetAll
synchronized (classWeavers) {
modifiedClassWeavers.add(HotSwapAdvancedClassWeaver.this);
modified = true;
}
}
}
/**
* Removes all modifications of the target method, so that it
* will be restored to its original state after the next commitment
* ({@link HotSwapAdvancedClassWeaver#commit()}).
*/
private void resetMethod() {
setModified();
status = MWEAVER_STATUS_UNCHANGED;
redefineAdvices.clear();
//methodCalls.clear();
//methodReturns.clear();
fieldAccessors.clear();
fieldModifiers.clear();
}
}
}