package alt.jiapi.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.StringTokenizer;
import org.apache.log4j.Category;
import alt.jiapi.instrumentor.ChainInstrumentor;
import alt.jiapi.instrumentor.InstrumentorChain;
import alt.jiapi.JiapiException;
import alt.jiapi.Runtime;
import alt.jiapi.instrumentor.Strategy;
import alt.jiapi.instrumentor.Hook;
/**
* This utility class may be used to create InstrumentorChain from String.
* It uses a special String format for specifying how the chain is to be
* built. <p>
*
* <blockquote>
* <b>NOTE: This code is experimental at the moment, and may not function
* correctly. Furthermore, String specs described below, might change
* without a notice.</b>
* </blockquote>
*
* General format of the chainSpec String is :
* <blockquote>
* <code> instrumentorSpec (| instrumentorSpec)* </code>
* </blockquote>
*
* Each instrumentorSpec represents an Instrumentor in chain that is
* to be created. First instrumentor(Spec) in chainSpec forward its output
* to the next instrumentor(Spec) and so on. '|' character acts as a marker
* between instrumentorSpecs. One may think that marker as a pipe marker
* in some operating system shells. <p>
*
* Format of the instrumentorSpec is :
* <blockquote>
* <code> instrumentorSymbol(#childSymbol(#childHint)?)?</code>
* </blockquote>
*
* instrumentorSymbol is a symbolic name of the Instrumentor to be used.
* It may be a fully qualified class name of the Instrumentor, or it may
* be its symbolic equivalence. SymbolMap is used to map symbolic
* names to class names.<p>
* childSymbol has a similar way of instantiating child; It could be a fully
* qualified class name or its symbolic name from SymbolMap.
* If child cannot be instantiated, it is considered to be an instance of
* String. In that case, child hints make no sense.<p>
* Instrumentor gets its child associated with itself through constructor.
* Reflection API, <code>Constructor</code> in particular, is
* used to instantiate Instrumentor. <p>
*
* childHint is passed to child the same way, as child gets passed to
* Instrumentor. Trough Constructor. This version supports only integer
* hints. Child is looked for a field named 'childHint'. If one is found,
* it is assumed, that there is a Constructor with same type as its
* argument.<p>
*
* Future versions of this class might slightly modify format described
* above. More childHint types might be added if there is need for one.
* and different ways of passing childHints and childs to their parents.
* Like java beans style get/set methods.<p>
*
* Samples:
* <blockquote>
* <code>"GrepInstrumentor#MethodCallStrategy | HeadInstrumentor | MethodCallInstrumentor#DummyHook"</code><br>
* If the default SymbolMap is used, chainSpec above would generate a chain
* with instrumentors added to it in the following order:
*
* <ol>
* <li>Instatiate <code>alt.jiapi.instrumentor.GrepInstrumentor</code> with
* <code>alt.jiapi.instrumentor.MethodCallStrategy</code> as its argument.
* This instrumentor is put first in the chain.
* <li><code>alt.jiapi.instrumentor.HeadInstrumentor</code> is
* instatiated, and added to chain.
* <li><code>alt.jiapi.instrumentor.MethodCallInstrumentor</code> is
* instatiated with and instance of <code>DummyHook</code> as its
* constructor argument. It is then added to chain.
* </ol>
*
* Resulting chain would instrument its input so, that a call is being made
* to DummyHook before a method call is being made.
* </blockquote>
*
* @see SymbolMap
* @author Mika Riekkinen, Joni Suominen
* @version $Revision: 1.5 $ $Date: 2004/03/15 14:47:53 $
*/
public class ChainBuilder {
private static Category log = Runtime.getLogCategory(ChainBuilder.class);
private static final Class instrumentorClass = ChainInstrumentor.class;
private static final Class strategyClass = Strategy.class;
private static final Class hookClass = Hook.class;
private SymbolMap map;
/**
* Used in debugging.
*/
public static void main(String [] args) throws Exception {
SymbolMap map = new SymbolMap("alt.jiapi.instrumentor");
ChainBuilder cb = new ChainBuilder(map);
InstrumentorChain chain = cb.createChain(args[0]);
System.out.println(chain);
}
public ChainBuilder() {
this.map = new SymbolMap("alt.jiapi.instrumentor");
}
public ChainBuilder(SymbolMap map) {
this.map = map;
}
/**
* Creates a chain from chainSpec and SymbolMap given in constructor.
*/
public InstrumentorChain createChain(String chainSpec) throws JiapiException, ClassNotFoundException, InstantiationException {
return createChain(chainSpec, map);
}
/**
* Creates a chain from chainSpec and given SymbolMap
*/
public InstrumentorChain createChain(String chainSpec, SymbolMap symbols) throws JiapiException, ClassNotFoundException, InstantiationException {
InstrumentorChain chain = new InstrumentorChain();
StringTokenizer st = new StringTokenizer(chainSpec, "|");
while (st.hasMoreTokens()) {
String instrumentorSpec = st.nextToken().trim();
ChainInstrumentor i = createInstrumentor(instrumentorSpec, symbols);
if (i == null) {
throw new JiapiException("ChainBuilder.createInstrumentor(...) returned null");
}
chain.add(i);
}
return chain;
}
/**
* Create an Instrumentor from instrumentorSpec.
*
* @param instrumentorSpec String spec as defined above
* @param symbols SymbolMap used to resolve symbolic names
* to Class names.
* @return An instance of Instrumentor if instrumentorSpec is valid
* @exception JiapiException is thrown if instrumentorSpec does not meet
* the basic requirements of its format.
* @exception ClassNotFoundException is thrown, if instrumentor class
* was not found.
* @exception InstantiationException is thrown, if instantiation failes
* in any way. This exception also wraps all the other reflection
* API exception that might be thrown during instantiation.
*/
public ChainInstrumentor createInstrumentor(String instrumentorSpec,
SymbolMap symbols) throws JiapiException, ClassNotFoundException, InstantiationException {
log.debug("Creating Instrumentor from spec: " + instrumentorSpec);
ChainInstrumentor instrumentor = null;
String instrumentorClassName = null;
String childClassName = null;
String childHints = null;
StringTokenizer st = new StringTokenizer(instrumentorSpec, "#");
if (!st.hasMoreTokens()) {
throw new JiapiException("Illegal insrumentorSpec " +
instrumentorSpec);
}
// (Symbolic)Class name of the Instrumentor
String instrumentorSymbol = st.nextToken().trim();
instrumentorClassName = symbols.getSymbol(instrumentorSymbol);
if (instrumentorClassName == null) {
instrumentorClassName = instrumentorSymbol;
}
if (st.hasMoreTokens()) {
String childSymbol = st.nextToken().trim();
childClassName = symbols.getSymbol(childSymbol);
if (childClassName == null) {
childClassName = childSymbol;
}
}
if (st.hasMoreTokens()) {
childHints = st.nextToken().trim();
}
// Load relevant classes
Class instrumentorClass = null;
Class childClass = null;
Object child = null;
instrumentorClass = Class.forName(instrumentorClassName);
if (childClassName != null) {
try {
childClass = Class.forName(childClassName);
child = createChild(childClass, childHints);
}
catch (ClassNotFoundException e) {
// ForwardingInstrumentor wants at least a String
// in constructor.
// Child could be a plain number !!!
child = childClassName; // Try String as a child
}
}
instrumentor = createInstrumentor(instrumentorClass, child);
log.info("Created instrumentor: " + instrumentor);
return instrumentor;
}
private Object createChild(Class child, String childHint) throws InstantiationException {
System.out.println("Creating child : " + child);
Object childObject = null;
log.debug("Creating child: " + child.getName() + ", hints " +
childHint);
if (childHint == null) {
// Use empty constructor
try {
childObject = child.newInstance();
}
catch (IllegalAccessException iae) {
throw new InstantiationException("Illegal access: " +
iae.getMessage());
}
}
else {
// Try to figure out how hint gets passed to child
// Try field:
Field hintField = null;
try {
hintField = child.getField(childHint);
}
catch (Exception e) {
log.debug("Exception occured: " + e.getMessage());
}
if (hintField != null) {
log.debug("hint field: " + hintField);
// We have a hint field
// Try a matching constructor first
Class[] parameterTypes = new Class[1];
parameterTypes[0] = hintField.getType();
try {
Constructor c = child.getConstructor(parameterTypes);
log.debug("Child constructor: " + c);
Object[] params = new Object[1];
// Assume integers here
params[0] = new Integer(hintField.getInt(null));
childObject = c.newInstance(params);
}
catch (NoSuchMethodException nsme) {
throw new InstantiationException("No such method: " +
nsme.getMessage());
}
catch (IllegalAccessException iae) {
throw new InstantiationException("Illegal access: " +
iae.getMessage());
}
catch (InvocationTargetException ite) {
throw new InstantiationException("Exception in constructor: "+
ite.getMessage());
}
}
}
return childObject;
}
private ChainInstrumentor createInstrumentor(Class instrumentorClass, Object child) throws InstantiationException {
log.debug("Creating Instrumentor: " + instrumentorClass.getName() +
", child: " + child);
ChainInstrumentor instrumentor = null;
if (child == null) {
// Use empty constructor
try {
instrumentor = (ChainInstrumentor)instrumentorClass.newInstance();
}
catch (IllegalAccessException iae) {
throw new InstantiationException("Illegal access: " +
iae.getMessage());
}
}
else {
// Try primitive types in constructor.
// if(child instanceof java.lang.Number) {
// ...
// } else {
// Use constructor with child as argument
Class[] parameterTypes = new Class[1];
if (instrumentorClass.isAssignableFrom(child.getClass())) {
parameterTypes[0] = instrumentorClass;
}
else if (strategyClass.isAssignableFrom(child.getClass())) {
parameterTypes[0] = strategyClass;
}
else if (hookClass.isAssignableFrom(child.getClass())){
parameterTypes[0] = hookClass;
}
else {
parameterTypes[0] = child.getClass();
}
// System.out.println("child " + child);
// System.out.println(parameterTypes[0]);
try {
Constructor c =
instrumentorClass.getConstructor(parameterTypes);
log.debug("Using constructor: " + c);
Object [] params = new Object[] {child};
instrumentor = (ChainInstrumentor)c.newInstance(params);
}
catch (NoSuchMethodException nsme) {
// Instead of rethrowing, we could try JavaBean stuff...
throw new InstantiationException("No such method: " +
nsme.getMessage());
}
catch (IllegalAccessException iae) {
throw new InstantiationException("Illegal access: " +
iae.getMessage());
}
catch (InvocationTargetException ite) {
throw new InstantiationException("Exception in constructor: "+
ite.getMessage());
}
}
return instrumentor;
}
}