/*
* Copyright 2005 by Oracle USA
* 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A.
* All rights reserved.
*/
package javax.ide.extension;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;
import javax.ide.extension.spi.Stack;
/**
* An implementation of ExtensionHook that automatically populates
* model objects using reflection.
*/
public class DynamicHook extends ExtensionHook
{
public final String sApplicationObjectKey =
DynamicHook.class.getName() + ".appObjectKey";
private static final String ATTRIBUTE_CLASS = "class";
private static final String SET_METHOD_PREFIX = "set";
private static final String ADD_METHOD_PREFIX = "add";
private static final Class[] sObjectParamTypes = { Object.class };
private static final Class[] sStringParamTypes = { String.class };
/**
* This stack references the application objects currently in scope. After
* creation, an application object is pushed onto the stack. When that
* complex element goes out of scope, the object is popped from the stack.
*/
private final Stack _applicationObjectStack = new Stack();
/**
* This stack references a verdict on whether the element in scope is complex
* or simple. When the element goes out of scope, the verdict on the parent
* element is available at the top of this stack.
*/
private final Stack _complexTypeIndicatorStack = new Stack();
/**
* If non-null, this classloader is passed to all calls to Class.newInstance()
* , otherwise the current thread context classloader is used.
*/
private ClassLoader _classLoader;
/**
* A list of registered {@link ElementTypeResolver}s which may be queried for
* the runtime type corresponding to an xml element.
*/
private final List _resolvers = new ArrayList(5);
public DynamicHook(Object rootObject)
{
// Push the root object into first place on the stack.
_applicationObjectStack.push(rootObject);
}
public DynamicHook(Object rootObject, ClassLoader classLoader)
{
this(rootObject);
_classLoader = classLoader;
}
public DynamicHook(
Object rootObject,
ClassLoader classLoader,
ElementTypeResolver resolver)
{
this(rootObject, classLoader);
_resolvers.add(resolver);
}
public void registerElementTypeResolver(ElementTypeResolver resolver)
{
_resolvers.add(resolver);
}
public void start(ElementStartContext context)
{
Class runtimeType = getRuntimeType(context);
_complexTypeIndicatorStack.push(new boolean[]
{
runtimeType != null ? true : false
});
if (runtimeType != null)
{
// Retrieve the application object for the complex element.
Object o = getApplicationObject(runtimeType, context);
// Pre initialization hook.
invokePreInitialize(_applicationObjectStack.peek(), o);
// Push the application object onto the stack so that it becomes the
// object in scope for child elements.
_applicationObjectStack.push(o);
// Push the application object onto the context map in scope. This makes
// it available globally under the sApplicationObjectKey key. Note:
// keeping a separate stack of app objects maintains the integrity of the
// dynamic handler instance, since child handlers are at liberty to remove
// an object from the context map.
context.getScopeData().put(sApplicationObjectKey, o);
// Additional complex element start behaviour, e.g. specialized attribute
// handling.
handleComplexElementStart(o, context);
}
else
{
// Additional simple element start behaviour, e.g. specialized attribute
// handling.
handleSimpleElementStart(context);
}
}
public void end(ElementEndContext context)
{
boolean isComplex = ((boolean[])_complexTypeIndicatorStack.pop())[0];
if (isComplex)
{
// Reference the child object.
Object child = _applicationObjectStack.pop();
// Post initialization hook.
invokePostInitialize(child);
// Attach the child and parent objects.
attachObject(_applicationObjectStack.peek(), child, context);
// Hook for additional complex element end behaviour (none by default).
handleComplexElementEnd(context);
}
else
{
// Attach any simple element data to the application object in scope.
attachData(_applicationObjectStack.peek(), context.getText(), context);
// Hook for additional simple element end behaviour (none by default).
handleSimpleElementEnd(context);
}
}
protected void handleComplexElementStart(
Object applicationObject,
ElementStartContext context)
{
// This basic implementation only knows about the 'class' attribute for
// complex elements. However, specializations may override at this point
// to plug in additional behaviour.
}
protected void handleComplexElementEnd(ElementEndContext context)
{
// This implementation takes no more action on ending of a complex element
// after the child object has been attached to the parent object.
}
protected void handleSimpleElementStart(ElementStartContext context)
{
// This basic implementation ignores all attributes for simple elements.
// However, specializations may override at this point to plug in additional
// behaviour.
}
protected void handleSimpleElementEnd(ElementEndContext context)
{
// This implementation takes no more action on ending of a simple element
// after the child data has been attached to the object in scope.
}
protected Class getRuntimeType(ElementStartContext context)
{
// First, interrogate the 'class' attribute. Failing that, interrogate the
// registered resolvers in reverse order.
Class type = null;
String classAttribute = context.getAttributeValue(ATTRIBUTE_CLASS);
if (classAttribute != null)
{
try
{
type = Class.forName(classAttribute, true, _classLoader != null ?
_classLoader : Thread.currentThread().getContextClassLoader());
}
catch (ClassNotFoundException cnfe)
{
log( context, Level.SEVERE, "Unable to load class: " + classAttribute );
}
}
else
{
ListIterator it = _resolvers.listIterator(_resolvers.size());
while (it.hasPrevious() && type == null)
{
type = ((ElementTypeResolver)it.previous()).resolveType(
context.getElementName() );
}
}
return type;
}
protected Object getApplicationObject(
Class runtimeType,
ElementStartContext context)
{
// In this simple implementation, the only information needed to retrieve
// the application object is its type, however extra context is provided
// in this method for the benefit of specializations.
try
{
return runtimeType.newInstance();
}
catch (Exception e)
{
log( context, Level.SEVERE, "Unable to instantiate class: " +
runtimeType.getName() );
e.printStackTrace();
return null;
}
}
protected void attachObject(
Object parent,
Object child,
ElementEndContext context)
{
// Find the relevant setXXX(Object) or addXXX(Object) method on the
// parent application object.
Method m = findMethod(context, parent, context.getElementName().getLocalName(),
sObjectParamTypes);
// Dynamically invoke the target method, passing the child object as the
// parameter to the target method.
try
{
m.invoke(parent, new Object[] {child});
}
catch (InvocationTargetException ite)
{
StringBuffer b = new StringBuffer(200);
b.append("Could not attach child object: ").append(child.toString());
b.append(" to parent: ").append(parent.toString());
b.append(". Root cause: ");
b.append(ite.getTargetException().getClass().getName()).append(": ");
b.append(ite.getTargetException().getMessage());
log( context, Level.SEVERE, b.toString() );
}
catch (Exception e)
{
StringBuffer b = new StringBuffer(200);
b.append("Could not attach child object: ").append(child.toString());
b.append(" to parent: ").append(parent.toString());
b.append(". Root cause: ");
b.append(e.getClass().getName()).append(": ").append(e.getMessage());
log( context, Level.SEVERE, b.toString() );
}
}
protected void attachData(
Object parent,
String data,
ElementEndContext context)
{
// Find the appropriate setXXX(String) method.
Method m = findMethod(context, parent, context.getElementName().getLocalName(),
sStringParamTypes);
// Invoke the method.
try
{
m.invoke(parent, new Object[] { data });
}
catch (InvocationTargetException ite)
{
log( context, Level.SEVERE,
"Unable to attach data '" + data + "' for simple element " +
context.getElementName().getLocalName() );
ite.getTargetException().printStackTrace();
}
catch (Exception e)
{
log( context, Level.SEVERE, "Unable to attach data '" + data +
"' for simple element " + context.getElementName().getLocalName());
e.printStackTrace();
}
}
protected void invokePreInitialize(Object child, Object parent)
{
// TODO: Define a preinit hook?
// E.g: ((InitializableXMLObject)child).preInit(parent);
}
protected void invokePostInitialize(Object o)
{
// TODO: Define a postInit hook.
// E.g: ((InitializableXMLObject)o).postInit();
}
protected Method findMethod(ElementContext context,
Object o, String elementName, Class[] paramTypes)
{
String methodName;
try
{
methodName = getMethodName(SET_METHOD_PREFIX, elementName);
return o.getClass().getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException nsme)
{
try
{
methodName = getMethodName(ADD_METHOD_PREFIX, elementName);
return o.getClass().getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException nme)
{
log( context, Level.SEVERE, "Class " + o.getClass().getName() +
" has no set or add method for element named " + elementName );
nme.printStackTrace();
}
}
return null;
}
protected String getMethodName(String prefix, String elementName)
{
char[] name = new char[prefix.length() + elementName.length()];
for (int i = 0; i < prefix.length(); i++)
{
name[i] = prefix.charAt(i);
}
boolean nextUpper = true;
int nameIndex = prefix.length();
for (int i = 0; i < elementName.length(); i++)
{
if (Character.isJavaIdentifierPart(elementName.charAt(i)))
{
if (nextUpper)
{
name[nameIndex] =
Character.toUpperCase(elementName.charAt(i));
nextUpper = false;
}
else
{
name[nameIndex] = elementName.charAt(i);
}
nameIndex++;
}
else
{
nextUpper = true;
}
}
return new String(name).trim();
}
/**
* An object which can resolve an ElementName into a Class type for that
* element.
*/
public static interface ElementTypeResolver
{
/**
* Resolves a fully-qualified element name to a runtime type which is capable
* of consuming information in child elements as laid out by the rules
* defined by {@link DynamicHook}.
*
* @param elementName the fully qualified name of the element to be
* resolved.
* @return a {@link java.lang.Class} representing the runtime type capable
* of consuming the xml, or null
if no matching type is found.
*/
public Class resolveType( ElementName elementName );
}
}