Package de.petris.dynamicaspects.util

Source Code of de.petris.dynamicaspects.util.ClassPatcher

/*
* Copyright (c) 2005, Marco Petris
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     - Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     - Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     - Neither the name of Marco Petris nor the names of its
*       contributors may be used to endorse or promote products derived from
*       this software without specific prior written permission.
*      
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

package de.petris.dynamicaspects.util;

import java.lang.reflect.Method;
import java.util.List;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;

import de.petris.dynamicaspects.MixIn;
import de.petris.dynamicaspects.MixInImplementationFactory;

/**
* An instance of this class patches a target class,
* i. e. a list of {@link de.petris.dynamicaspects.MixIn MixIns} will be mixed in.
*
* @author Marco Petris
*
* @see de.petris.dynamicaspects.MixIn
*/
public class ClassPatcher {

    // the class generator
    private ClassGen cGen;
    // the list of MixIns to be mixed in
    private List<MixIn> mixInList;
    // the ContantPool generator
    private ConstantPoolGen constPoolGen;
   
    /**
     * Creates an instance of this class.
     *
     * @param classfileBuffer the byte representation of the original version of the target class.
     * @param targetClass the name of the target class
     * @param mixInList the list of mixins.
     */
    public ClassPatcher(
            byte[] classfileBuffer, String targetClass,
            List<MixIn> mixInList ) {
       
        Logger.debug( "patching class %s", targetClass );
       
        this.mixInList = mixInList;
        JavaClass jc  =
            Reflection.classForByteArray( classfileBuffer, targetClass );
       
        cGen  = new ClassGen( jc );
        constPoolGen = cGen.getConstantPool();
   }
  
  
    /**
     * Patches the target class and returns a byte representation of the new version
     * of the target class.
     *
     * @return a byte representation of the new version of the target class
     */
    public byte[] patchClass() {
   
       // add all mixins
       for( MixIn m : mixInList ) {
           addMixIn( m );
       }

       // todo:only a debug yet must be a feature in the future
//       try {
//           cGen.getJavaClass().dump( cGen.getClassName() + "_new.class");
//       }
//       catch( Exception exc ) {
//           exc.printStackTrace();
//       }

       return cGen.getJavaClass().getBytes();
   }
  
    /**
     * Adds a MixIn to the class.
     *
     * @param mixIn the mixIn to be added
     */
    private void addMixIn( MixIn mixIn ) {
      
       // the class of the interface of the mixin
       Class interfaceClass = mixIn.getInterface();
      
       // the fieldname of the delegate member
       String fieldName =
           "de_petris_dynamicaspects_DELEGATE_"
               + mixIn.getInterface().
                   getName().replace('.','_');          
      
       cGen.addInterface( interfaceClass.getName() );
      
       // create the private member for delegation
       FieldGen fGen =
           new FieldGen(
               Constants.ACC_PRIVATE,
               Type.getType( interfaceClass ) ,
               fieldName,
               constPoolGen );
      
      
       cGen.addField( fGen.getField() );
      
       // create a getter method
       String getterMethodName = addGetter(
           mixIn.getImplementation(), interfaceClass, fieldName )
   
       // add the functionality of the added interface
       for( Method m : interfaceClass.getMethods() ) {
          
           addMethodDelegation( m, interfaceClass, getterMethodName );
          
       }
   }
  
   /**
    * Adds a getter method for the member variable which is used for delegation.
    * The getter method  uses lazy initialization of the member variable. 
    *
    * @param implClass the class of the implementation of the delgate
    * @param interfaceClass the added interface
    * @param fieldName the name of the delegate
    * @return the name of the getter method
    */
   private String addGetter(
           Class implClass, Class interfaceClass,
           String fieldName ) {
      
       Type interfaceType = Type.getType( interfaceClass );

       // create getter-method-name
       StringBuffer fieldNameBuffer = new StringBuffer( fieldName );
       fieldNameBuffer.replace(
               0, 1, fieldNameBuffer.substring(0,1).toUpperCase() );
       fieldNameBuffer.insert(0,"get");
      
       // build the getters instructions
       InstructionList iList = new InstructionList();
       InstructionFactory factory =
           new InstructionFactory(cGen);
      
       MethodGen mGen =
           new MethodGen(
               Constants.ACC_PRIVATE,
               interfaceType,
               Type.NO_ARGS,
               new String[] {},
               fieldNameBuffer.toString(),
               cGen.getClassName(),
               iList,
               constPoolGen );
      
       // a local variable
       iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
       // load the member variable
       iList.append(
           factory.createFieldAccess(
               cGen.getClassName(),
               fieldName,
               interfaceType,
               Constants.GETFIELD  ) );
      
       // test for non null of the member variable          
       BranchInstruction bi =
           InstructionFactory.createBranchInstruction(
                   Constants.IFNONNULL, null );
      
       iList.append( bi );
      
       // create the delegate
       iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
      
       iList.append( factory.createNew( implClass.getName() ) );
       iList.append( InstructionConstants.DUP );
       iList.append(
           factory.createInvoke(
               implClass.getName(),
               Constants.CONSTRUCTOR_NAME,
               Type.VOID, Type.NO_ARGS,
               Constants.INVOKESPECIAL ) );
      
       // use a factory if the implementation class is a factory
       if( MixInImplementationFactory.class.isAssignableFrom( implClass ) ) {

           iList.append(
               factory.createInvoke(
                       implClass.getName(),
                       "getInstance",
                       Type.OBJECT,
                       Type.NO_ARGS, Constants.INVOKEVIRTUAL ) );
          
           iList.append(
               factory.createCheckCast
                   new ObjectType( interfaceClass.getName() ) ) );
          
       }
       // store the created delgate object
       iList.append(
           factory.createFieldAccess(
               cGen.getClassName(),
               fieldName,
               interfaceType,
               Constants.PUTFIELD) );
      
       // load the delegate and return it
       InstructionHandle loadInstruction =
           iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );
      
       bi.setTarget( loadInstruction );
      
       iList.append(
               factory.createFieldAccess(
                   cGen.getClassName(),
                   fieldName,
                   interfaceType,
                   Constants.GETFIELD) );
      
       iList.append( InstructionFactory.createReturn( Type.OBJECT ) );

       mGen.setMaxStack();
       mGen.setMaxLocals();
       cGen.addMethod( mGen.getMethod() );
       iList.dispose();
      
       return fieldNameBuffer.toString();
   }
  
   /**
    * Adds a method of the added interface to this class.
    *
    * @param m the method to be added
    * @param interfaceClass the class of the interface
    * @param getterMethodName the name of the getter-method of the delegate
    */
   private void addMethodDelegation( Method m, Class interfaceClass,
           String getterMethodName ) {
      
       String sig = Type.getSignature( m );
      
       // create parameter names for the method
      
       String[] argNames = new String[m.getParameterTypes().length];
      
       for( int i=argNames.length-1; i>=0; i-- ) {
           argNames[i] = "arg"+Integer.toString(i);
       }
      
       // create instructions for delgation
       InstructionList iList = new InstructionList();
      
       InstructionFactory factory =
           new InstructionFactory(cGen);
      
       MethodGen mGen =
           new MethodGen(
               Constants.ACC_PUBLIC,
               Type.getReturnType( sig ),
               Type.getArgumentTypes( sig ),
               argNames,m.getName(),
               cGen.getClassName(),
               iList,
               constPoolGen );
      
       // get the delegate
       iList.append( InstructionFactory.createLoad( Type.OBJECT, 0 ) );

       iList.append(
           factory.createInvoke(
               cGen.getClassName(),
               getterMethodName,
               Type.getType( interfaceClass ),
               Type.NO_ARGS,
               Constants.INVOKESPECIAL ) );
      
       // load the argument on to the stack
       int argIdx =1;
       for( Type t : Type.getArgumentTypes( sig ) ) {
          
           iList.append( InstructionFactory.createLoad( t, argIdx++ ) );
          
           if( Reflection.isCat2Type( t ) ) {
               argIdx++;
           }
          
       }

       // do the delegation
       iList.append(
           factory.createInvoke(
               interfaceClass.getName(),
               m.getName(),
               Type.getReturnType( sig ),
               Type.getArgumentTypes( sig ),
               Constants.INVOKEINTERFACE ) );
      
       // return the result from the delegation
       iList.append( InstructionFactory.createReturn( Type.getReturnType( sig ) ) );
      
       mGen.setMaxStack();
       mGen.setMaxLocals();
       cGen.addMethod( mGen.getMethod() );
       iList.dispose();
   }
  
   
}
TOP

Related Classes of de.petris.dynamicaspects.util.ClassPatcher

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.