Package ch.ethz.inf.iks.jvmai.jvmdi

Source Code of ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAdvancedClassWeaver$MethodWeaver$RedefineAdviceListEntry

//
//  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();
    }

  }
}
TOP

Related Classes of ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAdvancedClassWeaver$MethodWeaver$RedefineAdviceListEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.