package ognl.helperfunction;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.foundation.NSKeyValueCoding._KeyBinding;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation._NSUtilities;
/**
* HelperFunctionRegistry provides a central point for registering and resolving helper functions.
*
* @author mschrag
*/
public class WOHelperFunctionRegistry {
private static final Logger log = LoggerFactory.getLogger(WOHelperFunctionRegistry.class);
public static final String APP_FRAMEWORK_NAME = "app";
private static WOHelperFunctionRegistry _instance;
private Map _applicationHelperInstanceCache;
private WOHelperFunctionRegistry() {
_applicationHelperInstanceCache = new HashMap();
}
public static synchronized WOHelperFunctionRegistry registry() {
if (_instance == null) {
_instance = new WOHelperFunctionRegistry();
}
return _instance;
}
/**
* Sets the helper object to use for the given class.
*
* @param helperInstance
* an instance of the helper class (i.e. PersonHelper)
* @param targetObjectClass
* the class that maps to this helper instance (i.e. Person.class)
* @param frameworkName
* the scoping of the helper instance (null, or "app" = global)
*/
public synchronized void setHelperInstanceForClassInFrameworkNamed(Object helperInstance, Class targetObjectClass, String frameworkName) {
setHelperInstanceForClassInFrameworkNamed(helperInstance, null, targetObjectClass, frameworkName);
}
/**
* Sets the helper object to use for the given class.
*
* @param helperInstance
* an instance of the helper class (i.e. PersonHelper)
* @param helperFunction
* the helper function being requested (i.e. formattedName)
* @param targetObjectClass
* the class that maps to this helper instance (i.e. Person.class)
* @param frameworkName
* the scoping of the helper instance (null, or "app" = global)
*/
protected synchronized void setHelperInstanceForClassInFrameworkNamed(Object helperInstance, String helperFunction, Class targetObjectClass, String frameworkName) {
if (frameworkName == null) {
frameworkName = WOHelperFunctionRegistry.APP_FRAMEWORK_NAME;
}
Map frameworkHelperInstanceCache = (Map) _applicationHelperInstanceCache.get(frameworkName);
if (frameworkHelperInstanceCache == null) {
frameworkHelperInstanceCache = new HashMap();
_applicationHelperInstanceCache.put(frameworkName, frameworkHelperInstanceCache);
}
frameworkHelperInstanceCache.put(targetObjectClass, helperInstance);
if (helperFunction != null) {
frameworkHelperInstanceCache.put(targetObjectClass.getName() + helperFunction, helperInstance);
}
}
protected synchronized Object _cachedHelperInstanceForFrameworkNamed(Class targetClass, String frameworkName) {
return __cachedHelperInstanceForFrameworkNamed(targetClass, frameworkName);
}
protected synchronized Object _cachedHelperInstanceForFrameworkNamed(Class targetClass, String helperFunction, String frameworkName) {
return __cachedHelperInstanceForFrameworkNamed(targetClass.getName() + "." + helperFunction, frameworkName);
}
protected synchronized Object __cachedHelperInstanceForFrameworkNamed(Object key, String frameworkName) {
Object helperInstance = null;
Map frameworkHelperInstanceCache = (Map) _applicationHelperInstanceCache.get(frameworkName);
if (frameworkHelperInstanceCache != null) {
helperInstance = frameworkHelperInstanceCache.get(key);
}
return helperInstance;
}
public synchronized Object _helperInstanceForFrameworkNamed(Object targetObject, String helperFunction, String keyPath, String frameworkName) throws SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException {
if (frameworkName == null) {
frameworkName = WOHelperFunctionRegistry.APP_FRAMEWORK_NAME;
}
if (targetObject == null) {
throw new IllegalArgumentException("The target of a helper keypath must not be null.");
}
if (keyPath == null) {
throw new NullPointerException("You must specify a keypath to use helper functions.");
}
Object helpedObject = NSKeyValueCodingAdditions.Utility.valueForKeyPath(targetObject, keyPath);
Class helpedClass;
if (helpedObject != null) {
helpedClass = helpedObject.getClass();
}
else {
_KeyBinding keyBinding = WOHelperFunctionClassKeyValueCoding.DefaultImplementation.keyGetBindingForKeyPath(targetObject.getClass(), keyPath);
if (keyBinding != null) {
helpedClass = keyBinding.valueType();
}
else {
log.warn("Unable to determine the value class of the keypath '{}' for the object {}", keyPath, targetObject);
helpedClass = Object.class;
}
}
Object helperInstance = null;
helperInstance = _cachedHelperInstanceForFrameworkNamed(helpedClass, helperFunction, frameworkName);
if (helperInstance == null && !WOHelperFunctionRegistry.APP_FRAMEWORK_NAME.equals(frameworkName)) {
helperInstance = _cachedHelperInstanceForFrameworkNamed(helpedClass, helperFunction, WOHelperFunctionRegistry.APP_FRAMEWORK_NAME);
}
if (helperInstance == null) {
//see if we have a cached helper, but we haven't cached it for the helperFunction
helperInstance = _cachedHelperInstanceForFrameworkNamed(helpedClass, frameworkName);
if (helperInstance == null && !WOHelperFunctionRegistry.APP_FRAMEWORK_NAME.equals(frameworkName)) {
helperInstance = _cachedHelperInstanceForFrameworkNamed(helpedClass, WOHelperFunctionRegistry.APP_FRAMEWORK_NAME);
}
if (helperInstance != null) {
if (classImplementsMethod(helperInstance.getClass(), helperFunction)) {
setHelperInstanceForClassInFrameworkNamed(helperInstance, helperFunction, helpedClass, frameworkName);
} else {
helperInstance = null;
}
}
}
if (helperInstance == null) {
Class targetHelperClass = helperClassForClass(helpedClass, helperFunction);
if (targetHelperClass == null) {
throw new NoSuchElementException("Could not find a helper class for '" + helpedClass.getName() + " implementing " + helperFunction + "'.");
}
helperInstance = targetHelperClass.newInstance();
setHelperInstanceForClassInFrameworkNamed(helperInstance, helperFunction, helpedClass, frameworkName);
}
return helperInstance;
}
/**
* Attempts to locate a helper class for helpedClass that implements helperFunction.
* If it cannot find a class called <className>Helper implementing helperFunction,
* it looks for a helper for each of the interfaces implemented by the class, and starts the
* process over with the superclass if that fails.
* @param helpedClass
* @param helperFunction
* @return
*/
protected Class helperClassForClass(Class helpedClass, String helperFunction) {
String targetClassName = helpedClass.getName();
int lastDotIndex = targetClassName.lastIndexOf('.');
if (lastDotIndex != -1) {
targetClassName = targetClassName.substring(lastDotIndex + 1);
}
String targetHelperName = targetClassName + "Helper";
Class helperClass = _NSUtilities.classWithName(targetHelperName);
if (helperClass != null && classImplementsMethod(helperClass, helperFunction)) {
return helperClass;
}
//check for a helper for the interfaces
Class[] interfaces = helpedClass.getInterfaces();
for(int i = 0; i < interfaces.length; i++) {
helperClass = helperClassForClass(interfaces[i], helperFunction);
if ( helperClass != null && classImplementsMethod(helperClass, helperFunction)) {
return helperClass;
}
}
//if that fails, try the super class
Class superClass = helpedClass.getSuperclass();
if (superClass != null) {
return helperClassForClass(superClass, helperFunction);
}
return null;
}
protected boolean classImplementsMethod(Class theClass, String methodName) {
Method[] methods = theClass.getMethods();
for(int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName)) {
return true;
}
}
return false;
}
}