/*
* Copyright (C) 2001 Mika Riekkinen, Joni Suominen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package alt.jiapi.instrumentor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
//import java.util.logging.Logger;
import org.apache.log4j.Category;
import alt.jiapi.Runtime;
import alt.jiapi.reflect.InstructionFactory;
import alt.jiapi.reflect.InstructionList;
import alt.jiapi.reflect.JiapiClass;
import alt.jiapi.reflect.JiapiField;
import alt.jiapi.reflect.JiapiMethod;
import alt.jiapi.reflect.JiapiRuntimeException;
import alt.jiapi.reflect.Loader;
import alt.jiapi.reflect.SignatureUtil;
/**
* This instrumentor creates method calls.
* It will create calls to either static or virtual calls.<p>
* If a virtual call is made, a class is added an extra field
* to hold a reference to Object specified by hook-method.<p>
*
*
* NOTE : MethodCallInstrumentor is still under construction on some parts
* of it.
* At the moment, it is assumed that hookmethod has exactly two
* arguments, first of which will be 'this' if available, or String
* if not available,
* and second the name of the method being processed.
* This is likely to change to a more dynamic thing.
*
* @author Mika Riekkinen
* @author Joni Suominen
* @version $Revision: 1.26 $ $Date: 2004/03/21 12:50:08 $
*/
public class MethodCallInstrumentor extends AbstractInstrumentor {
private static Category log = Category.getInstance(MethodCallInstrumentor.class);
protected Hook hook;
protected Class hookClass;
protected Method hookMethod;
protected boolean isDynamic;
private String jiapiFieldName;
// Dynamic initializer
{
// Later on, we could ask Runtime to provide other
// field names.
jiapiFieldName = "__jiapi_field";
}
/**
* Chain which is used to pre-instrument a field to the target
* class when making dynamic calls.
*/
protected InstrumentorChain preChain;
/**
* Chain which is used to post-instrument an initialization
* for the field which will hold a reference to an target instance
* which will be called when making dynamic calls.
*/
protected InstrumentorChain postChain;
/**
* Empty constructor needed when the patch is dynamically instantiated.
* The hook has to be set afterwards with setHook method.
* @see #setHook
*/
public MethodCallInstrumentor() {
}
/**
* Constructor. Hook defines a method, that will be called
* by the bytecode instrumented.
*
* @param hook A Hook to be called by this Instrumentor.
* @see Hook
*/
public MethodCallInstrumentor(Hook hook) {
setHook(hook);
}
/**
* Constructor. This constructor creates MethodCallInstrumentor
* with the help of java.lang.reflect.Method.
*
* @param hookClass A class of the hook, that is to be called
* @param hookMethod A method, that is called. The method must
* a member of the class.
* @see java.lang.reflect.Method
*/
public MethodCallInstrumentor(Class hookClass,
java.lang.reflect.Method hookMethod) {
// A bug in here. We don't have Hooks' instance.
// postInstrument will fail. 'Class hookClass' should be changed to
// 'Object instance'
this.hookClass = hookClass;
this.hookMethod = hookMethod;
this.isDynamic = !Modifier.isStatic(hookMethod.getModifiers());
}
/**
* Instruments given instruction list so, that it will make a
* call to specified by Hook given to this class. Instructions will be
* added to the end of the instruction list.<p>
* If a method being instrumented is native or abstract, instrumentation
* is skipped and instruction list is just forwarded to next in chain.
*
* @param il InstructionList to add new instructions to.
*/
public void instrument(InstructionList il) {
log.info("Instrumenting " + getCurrentClass().getName() + "." +
il.getDeclaringMethod().getName());
JiapiMethod jm = il.getDeclaringMethod();
// Native or abstract methods can't be instrumented.
int modifiers = jm.getModifiers();
if (Modifier.isNative(modifiers) || Modifier.isAbstract(modifiers)) {
log.info("skipping abstract or native method: " +
getCurrentClass().getName() + "." + jm.getName());
forward(il);
return;
}
if (Modifier.isInterface(modifiers)) {
log.info("Will not instrument interface " +
getCurrentClass().getName());
forward(il);
return;
}
// if ("<clinit>".equals(jm.getName())) {
// System.out.println("Will not instrument <clinit>");
// log.debug("Will not instrument <clinit>");
// forward(il);
// return;
// }
patchInstructionList(il, getInstrumentation());
forward(il);
}
/**
* Instrument instruction list.
*/
private void patchInstructionList(InstructionList il,
Instrumentation instrumentation) {
//System.out.println(">> " + il.getDeclaringMethod());
InstructionFactory factory = il.getInstructionFactory();
if (factory == null) {
throw new NullPointerException("Got null factory");
}
int currentMethodModifiers = il.getDeclaringMethod().getModifiers();
JiapiClass clazz = getCurrentClass();
Class[] hookParams = hookMethod.getParameterTypes();
if (isDynamic) {
log.debug("Hook is dynamic");
// Obtain a field, that holds a reference to the method
// to be called.
JiapiField field = null;
try {
field = clazz.getDeclaredField(jiapiFieldName);
}
catch (NoSuchFieldException nsfe) {
throw new JiapiRuntimeException("No such field: " + nsfe.getMessage());
}
il.add(factory.getField(field));
if (Modifier.isStatic(currentMethodModifiers)) {
il.add(factory.pushConstant(clazz.getName()));
}
else {
il.add(factory.pushThis()); // First argument is 'this'
}
}
else {
log.debug("Hook is static");
// On static methods, First argument is name of the current class.
// This should be Class object.
// Runtime.forName(clazz.getName())
// factory.forName(clazz.getName())
il.add(factory.pushConstant(clazz.getName()));
}
// Skip first param (source object made above)
for (int i = 1; i < hookParams.length; i++) {
if (hookParams[i].equals(String.class)) {
// --- target name ---
String targetName = instrumentation.getTargetName();
if (targetName == null) {
targetName = "???";
}
il.add(factory.pushConstant(targetName));
}
else if (hookParams[i].equals(Object.class)) {
// --- target Object ---
InstructionList targetCode = instrumentation.getTargetCode();
if (targetCode != null) {
log.debug("Got target code: " + targetCode);
il.add(targetCode);
}
else {
log.debug("No target code");
il.add(factory.pushNull());
}
}
else if (hookParams[i].equals(Object[].class)) {
// --- target Object[] ---
log.warn("target arguments are not supported");
il.add(factory.pushNull());
}
else {
log.error("Invalid Hook method: " + hookMethod);
}
}
Loader l = new Loader();
try {
JiapiClass hClass = l.loadClass(hookClass.getName());
Class[] hpTypes = hookMethod.getParameterTypes();
String[] pTypes = new String[hpTypes.length];
for (int i = 0; i < pTypes.length; i++) {
pTypes[i] = hpTypes[i].getName();
}
JiapiMethod hMethod =hClass.getDeclaredMethod(hookMethod.getName(),
pTypes);
il.add(factory.invoke(hMethod));
}
catch(Exception e) {
log.error("Failed to add invoke instruction: " + e);
}
}
// Removed static below. -- mcr70 -- 02032004
private /*static*/ HashMap preInstrumentations = new HashMap();
private /*static*/ HashMap postInstrumentations = new HashMap();
/**
* Creates a chain which is used to add a new field to a target
* class. This field will hold a reference to an instance for
* which the calls will be made. On static calls there's no
* need for this field.
*
* @return A chain, that will be processed before current Instrumentor.
*/
public InstrumentorChain preInstrument() {
if (!isDynamic) {
log.info("Will not pre instrument for static calls");
// Static calls don't need any helper instrumentors.
return null;
}
JiapiClass c = getCurrentClass();
// Make sure pre instrumentation is done only once per JiapiClass
if (preInstrumentations.containsKey(c)) {
return null;
}
else {
preInstrumentations.put(c, null);
}
int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) {
log.info("Will not pre instrument interface " + c.getName());
return null;
}
preChain = new InstrumentorChain();
// Add an instrumentor which will create a new field to hold
// reference to hook instance in dynamic calls.
preChain.add(new CreateFieldInstrumentor(Modifier.PUBLIC +
Modifier.STATIC,
hookClass.getName(),
jiapiFieldName));
// preChain.add(new CreateMethodInstrumentor(Modifier.STATIC,
// "<clinit>"));
// preChain.add(new HeadInstrumentor());
// // Add an instrumentor which will initialize the field in
// // a static initializer.
// preChain.add(new FieldAssignInstrumentor(jiapiFieldName,
// hook.getInstance()));
return preChain;
}
/**
* Creates a chain which properly initializes the field at
* static initializer.
*/
public InstrumentorChain postInstrument() {
//if (true) return null;
if (!isDynamic) {
// Static calls don't need any helper instrumentors.
return null;
}
JiapiClass c = getCurrentClass();
// Make sure pre instrumentation is done only once per JiapiClass
if (postInstrumentations.containsKey(c)) {
return null;
}
else {
postInstrumentations.put(c, null);
}
int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) {
log.info("Will not post instrument interface " + c.getName());
return null;
}
postChain = new InstrumentorChain();
// Add an instrumentor which will create a static initializer
// if it doesn't exist yet.
// NOTE! Mode must be FORWARD_NEW:
postChain.add(new CreateMethodInstrumentor(Modifier.STATIC,
"<clinit>"));
postChain.add(new HeadInstrumentor());
// Add an instrumentor which will initialize the field in
// a static initializer.
postChain.add(new FieldAssignInstrumentor(jiapiFieldName,
hook.getInstance()));
return postChain;
}
/**
* Sets the for which the method calls are delegated at runtime.
* @param hook A Hook to be called by this patch.
*/
public void setHook(Hook hook) {
this.hook = hook;
this.hookMethod = hook.getHookMethod();
this.hookClass = hookMethod.getDeclaringClass();
this.jiapiFieldName = hookClass.getName().replace('.', '_') +
"_" + hook.getInstance().hashCode();
log.debug("Jiapi field name is '" + jiapiFieldName + "'");
this.isDynamic = !Modifier.isStatic(hookMethod.getModifiers());
}
/**
* Get the associated Hook.
* @return Hook
*/
public Hook getHook() {
return hook;
}
public String toString() {
return getClass().getName() + "#" + hook.getInstance().toString();//getClass().getName();
}
}