//
// jbl: 13 april 2006
//
package er.directtoweb.assignments.delayed;
import org.apache.log4j.Logger;
import com.webobjects.directtoweb.Assignment;
import com.webobjects.directtoweb.D2WContext;
import com.webobjects.eocontrol.EOKeyValueUnarchiver;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSSelector;
import er.directtoweb.assignments.ERDComputingAssignmentInterface;
import er.extensions.foundation.ERXSelectorUtilities;
/**
* Similar in nature to a key-value assignment, but allows you to construct arbitrary method invocations to
* resolve rules. As a somewhat contrived example, assume we're inferring on the componentName rule:
* <P>
* <code>entity.name = 'Person' and propertyKey = 'username' -> componentName = (object, componentForKey, propertyKey)</code>
*
* <P>
*
* Resolving the rule for componentName, would end up invoking the <code>componentForKey(Object)</code> method on the current object
* from the rule context, passing the current propertyKey through for the argument. This would boil down to
* <code>object.componentForKey("username")</code>.
*
* <P>
*
* The array in the value for this assignment must have two or more objects. The first object is a key path evaluated
* on the rule context to find the target of the selector. The second object is the selector name, it is a constant
* and is not evaluated on the rule context. All subsequent objects in the array are treated as key paths to resolve
* on the rule context to get the arguments for the selector.
*
* <P>
*
* Assumptions:
* <ul>
* <li>The arguments to the invoked method must all be Objects. This isn't strictly speaking necessary, but the way the
* assignment is currently coded, it's required.</li>
* </ul>
*/
public class ERDDelayedSelectorInvocationAssignment extends ERDDelayedAssignment implements ERDComputingAssignmentInterface {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver eokeyvalueunarchiver) {
return new ERDDelayedSelectorInvocationAssignment(eokeyvalueunarchiver);
}
public ERDDelayedSelectorInvocationAssignment(EOKeyValueUnarchiver u) {
super(u);
}
public ERDDelayedSelectorInvocationAssignment(String key, Object value) {
super(key,value);
}
public NSArray dependentKeys(String keyPath) {
return DefaultImplementation.dependentKeys(this, keyPath);
}
@Override
public Object fireNow(D2WContext c) {
return DefaultImplementation.fire(this, c);
}
public static class DefaultImplementation {
private static final Logger _log = Logger.getLogger(ERDDelayedSelectorInvocationAssignment.class);
// we cache 0 - 5 arguments
private static Class[][] _parameterTypesArrays = new Class[5 + 1][];
static {
for ( int i = 0; i < _parameterTypesArrays.length; i++ ) {
Class[] types = null;
if ( i > 0 ) {
types = new Class[i];
for ( int j = 0; j < types.length; j++ )
types[j] = Object.class;
}
_parameterTypesArrays[i] = types;
}
}
private static Class[] _parameterTypesForNumberOfArguments(int numberOfArguments) {
final Class[] result;
if ( numberOfArguments < _parameterTypesArrays.length ) {
result = _parameterTypesArrays[numberOfArguments];
}
else {
result = new Class[numberOfArguments];
for ( int i = 0; i < numberOfArguments; i++ )
result[i] = Object.class;
}
return result;
}
public static NSArray dependentKeys(Assignment assignment, String keyPath) {
final NSArray value = (NSArray)assignment.value();
NSArray result = value;
if ( result != null && result.count() > 1 ) {
NSMutableArray a = value.mutableClone();
a.removeObjectAtIndex(1); // selector name is constant
result = a;
}
return result != null ? result : NSArray.EmptyArray;
}
public static Object fire(Assignment assignment, D2WContext c) {
final NSArray value = (NSArray)assignment.value();
final int valueCount = value.count();
final Object target;
Object result = null;
if ( valueCount < 2 )
throw new RuntimeException("Must have at least 2 components in value: " + value);
target = c.valueForKeyPath((String)value.objectAtIndex(0));
if ( target != null ) {
final int numberOfArguments = valueCount - 2;
final String selectorName = (String)value.objectAtIndex(1);
final NSSelector selector;
Object[] arguments = null;
if ( numberOfArguments > 0 ) {
arguments = new Object[numberOfArguments];
for ( int i = 2; i < valueCount; i++ )
arguments[i-2] = c.valueForKeyPath((String)value.objectAtIndex(i));
}
if ( _log.isDebugEnabled() ) {
final StringBuilder sb = new StringBuilder("(");
if ( arguments != null ) {
for ( int i = 0; i < arguments.length; i++ ) {
if ( i > 0)
sb.append(", ");
sb.append(arguments[i]);
}
}
sb.append(')');
_log.debug("Going to fire " + selectorName + " on object " + target + " with " + numberOfArguments + " arguments: " + sb);
}
selector = new NSSelector(selectorName, _parameterTypesForNumberOfArguments(numberOfArguments));
result = ERXSelectorUtilities.invoke(selector, target, arguments);
}
return result;
}
}
}