package net.xoetrope.xui.evaluator;
import java.lang.reflect.Method;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xui.PageSupport;
import net.xoetrope.xui.XPage;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.build.BuildProperties;
import net.xoetrope.xui.helper.XuiUtilities;
import java.util.Hashtable;
import net.xoetrope.xui.XMethodReference;
import net.xoetrope.xui.exception.XExceptionHandler;
/**
* An attribute evaluator
* <p>An evaluated attribute's implementing method is by default in the owner
* page such that a reference like</p>
* <code>${myMethod()}</code>
* <p>would evaluate to a method in the current page with a signature like:</p>
* <code>public void myMethod();</code>
* <p>The attributes can also be defined in classes other than the current page
* or classes derived from XPage. The syntax for such expressions is as follows:</p>
* <ul>
* <li>1. <code>${mypackage.MyClass[referenceName].myMethod(args...)}</code> for a named object instance</li>
* <li>2. <code>${mypackage.MyClass[].myMethod(args...)}</code> to create a new instance of the class on each evaluation</li>
* <li>3. <code>${mypackage.MyClass.myMethod(args...)}</code> to invoke a static method</li>
* <li>4. <code>${myMethod[referenceName](args...)}</code> for a method contained with the invoking page</li>
* <li>5. <code>${this[componentName].componentMethod(args...)}</code> for a method contained within named component</li>
* <li>6. <code>${this.method(args...)}</code> for a method contained within the enclosing page, identical to 4 above</li>
* <li>7. <code>${project.method(args...)}</code> for a method contained within the project</li>
* <li>8. <code>${comp.method(args...)}</code> for a method contained within the component</li>
// * <li>9. <code>${this}</code> for a reference to the page</li>
// * <li>10. <code>${comp}</code> for a reference to the component</li>
* </ul>
* <p>where mypackage is the name of the Java package containing the class MyClass.
* The value of referenceName is a user defined value that identifies the instance
* of the class. The application instantiates an instance of the class when
* the expression is first encountered and thereafter maintains the instance with
* each subsequent call retrieving the same instance of the class.</p>
* <p>The method call can contain zero or more arguments</p>
* <p> Copyright (c) Xoetrope Ltd., 2002-2004</p>
* <p> $Revision: 2.8 $</p>
* <p> License: see License.txt</p>
*/
public class XDefaultAttributeEvaluator implements XAttributeEvaluator
{
/**
* The collection of class instances that are known to implement the methods
* of evaluated attributes
*/
protected Hashtable classInstances;
protected Object provider;
protected XProject currentProject;
protected XExceptionHandler exceptionHandler;
protected Object result;
/**
* Create a new evaluator
* @param currentProject the current project
* @param project the current or owning project
*/
public XDefaultAttributeEvaluator( XProject project )
{
currentProject = project;
}
/**
* Set the current project and complete any initialization that depends on the
* project reference/instance.
* @param project the current or owning project
*/
public void setCurrentProject( XProject project )
{
currentProject = project;
classInstances = (Hashtable)currentProject.getObject( "UserClassReferences" );
if ( classInstances == null ) {
classInstances = new Hashtable( 5 );
currentProject.setObject( "UserClassReferences", classInstances );
}
}
/**
* Set an exception handler for processing exceptions
* @param eh the exception handler
*/
public void setExceptionHandler( XExceptionHandler eh )
{
exceptionHandler = eh;
}
/**
* Explicitly set the result of an evaluation. This method may be used
* by an exception handler to override the result and set a sensible
* value in case of an exception
* @param the new result value
*/
public void setResult( Object value )
{
result = value;
}
/**
* Explicitly get the result of an evaluation. This method may be used
* by an exception handler to determine the result and set a sensible
* value in case of an exception
* @return the current/intermediate result value
*/
public Object getResult()
{
return result;
}
/**
* Get the value of an attribute.
* @param instance the page or instance that provides the methods/field for the evaluator
* @param attributeValue the raw value of the attribute
* @return the evaluated value of the attribute
*/
public Object evaluateAttribute( Object instance, String attributeValue )
{
if ( attributeValue == null )
return null;
provider = instance;
String resultStr = attributeValue;
result = resultStr;
try {
int startPos = resultStr.indexOf( "${" );
int len = resultStr.length();
while ( startPos >= 0 ) {
XMethodReference methodRef = getMethodReference( provider, resultStr );
if ( methodRef == null ) {
DebugLogger.logWarning( "Unable to evaluate attribute with the method call(?): " + result );
return result;
}
int endPos = XuiUtilities.indexOfMatchingEnding( resultStr, '{', '}', startPos + 2 );
Object methodValue = methodRef.method.invoke( methodRef.instance, methodRef.args );
if (( startPos > 0 ) || ( endPos < ( len -1 ))) {
String newStr = "";
if ( startPos > 0 )
newStr = resultStr.substring( 0, startPos );
newStr += methodValue.toString();
if ( ( endPos < ( len -1 )))
newStr += resultStr.substring( endPos + 1 );
result = resultStr = newStr;
startPos = resultStr.indexOf( "${" );
}
else
return methodValue;
}
}
catch ( Exception ex ) {
if ( exceptionHandler != null ) {
if ( exceptionHandler.handleException( provider, ex, this ))
return result;
}
if ( BuildProperties.DEBUG ) {
DebugLogger.logWarning( "Unable to evaluate attribute with the method call(?): " + attributeValue );
Throwable t = ex.getCause();
if ( t != null )
t.printStackTrace();
else
ex.printStackTrace();
}
}
return result;
}
/**
* Get the method reference for the methods named in the attribute
* @param attributeValue the method name
* @return the method reference or null if the referenced/named method cannot be found
*/
public XMethodReference getMethodReference( String attributeValue )
{
return getMethodReference( null, attributeValue );
}
/**
* Get the value of an attribute by evaluating a method reference
* @param instnace the page or object from that provides the methods or fields referenced in the evaluation
* @param attributeValue the attribute to be evaluated
*/
public XMethodReference getMethodReference( Object instance, String attributeValue )
{
try {
int expPos = attributeValue.indexOf( "${" );
if ( expPos >= 0 ) {
Class[] params;
int endAttribName = attributeValue.indexOf( '(' );
int endAttribArgs = XuiUtilities.indexOfMatchingEnding( attributeValue, '(', ')', endAttribName + 1 );
String methodName = attributeValue.substring( expPos + 2, endAttribName );
Object args[];
// Check for an argumentless call
if ( ( endAttribName + 1 ) == endAttribArgs ) {
args = new Object[ 0 ];
params = new Class[ 0 ];
}
else {
String argValues = attributeValue.substring( endAttribName + 1, endAttribArgs );
// Count the number of commas
int numArgs = 1 + XuiUtilities.count( argValues, ',', '(', ')' );
args = new Object[ numArgs ];
params = new Class[ numArgs ];
XuiUtilities.getArguments( argValues, params, args, ',', '(', ')' );
}
// Find the class that implements the method
Class clazz = null;
Object classInstance = null;
int pos = methodName.lastIndexOf( '.' );
if ( pos > 0 ) {
String className = methodName.substring( 0, pos );
methodName = methodName.substring( pos + 1 );
// Check for the class instance type
className = className.substring( 0, pos );
if (( pos = className.indexOf( "this[" )) == 0 ) {
String componentName = className.substring( pos + 5, className.length() -1 ).trim();
classInstance = ((PageSupport)instance).findComponent( componentName );
}
else if ( ( pos = className.indexOf( "[" ) ) >= 0 ) {
String referenceName = className.substring( pos + 1, className.length() -1 ).trim();
className = className.substring( 0, pos );
if ( referenceName.length() > 0 ) {
classInstance = classInstances.get( referenceName );
if ( classInstance == null ) {
clazz = XPage.class.forName( className.trim());
classInstance = clazz.newInstance();
classInstances.put( referenceName, classInstance );
}
else
clazz = classInstance.getClass();
}
}
else if ( className.equals( "this" )) // This is provided purely for self consistency
classInstance = instance;
else if ( className.equals( "project" ))
classInstance = currentProject;
else {
clazz = Class.forName( className.trim());
classInstance = clazz.newInstance();
}
if ( clazz == null )
clazz = classInstance.getClass();
}
else if ( instance != null ) {
clazz = instance.getClass();
classInstance = instance;
}
// Find and invoke the method
Method theMethod = clazz.getMethod( methodName, params );
return new XMethodReference ( clazz, classInstance, theMethod, args );
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logWarning( "Unable to evaluate attribute with the method call(?): " + attributeValue );
}
return null;
}
/**
* Get the current provider object. In most cases this should correspond to the active
* page at the time the evaluator was last used, but no guarantees can be provided
* that multiple threads are not updating the value. The value should be retrieved
* and cached as soon as the evaluated method is entered as other method calls
* could have the side effect of reseting the value.
* @return the current object used for evaluation of method and field references
*/
public Object getObject()
{
return provider;
}
}