package jeql.engine;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import jeql.api.function.AggregateFunction;
import jeql.api.function.SplittingFunction;
import jeql.std.function.AggFunction;
import jeql.std.function.AggregateFunctions;
import jeql.std.function.ColorFunction;
import jeql.std.function.ConsoleFunction;
import jeql.std.function.DateFunction;
import jeql.std.function.DebugFunction;
import jeql.std.function.FileFunction;
import jeql.std.function.FileSysFunction;
import jeql.std.function.GenerateFunction;
import jeql.std.function.HtmlFunction;
import jeql.std.function.IOFunction;
import jeql.std.function.MathFunction;
import jeql.std.function.NetFunction;
import jeql.std.function.RegExFunction;
import jeql.std.function.ScriptFunction;
import jeql.std.function.SplitByFunction;
import jeql.std.function.StrMatchFunction;
import jeql.std.function.StringFunction;
import jeql.std.function.SystemFunction;
import jeql.std.function.ValFunction;
import jeql.std.function.XmlStrFunction;
import jeql.std.geom.CRSFunction;
import jeql.std.geom.GeodeticFunction;
import jeql.std.geom.GeomFunction;
import jeql.std.geom.GeomPrepAllFunction;
import jeql.std.geom.GeomPrepFunction;
import jeql.std.geom.GridFunction;
import jeql.std.geom.LinearRefFunction;
import jeql.util.ClassUtil;
import jeql.util.MultiMap;
/**
* A registry of the functions which are available
* in the JQL execution context.
*
* @author Martin Davis
*
*/
public class FunctionRegistry
{
public static final String FUNCTION_SUFFIX = "Function";
public static final String FUNCTION_CONNECTOR = ".";
public static String moduleName(String funcName)
{
int dotIndex = funcName.indexOf(".");
if (dotIndex < 0)
return "";
return funcName.substring(0, dotIndex);
}
public static String functionName(String funcName)
{
int lastDotIndex = funcName.lastIndexOf(".");
if (lastDotIndex < 0)
return funcName;
return funcName.substring(lastDotIndex + 1, funcName.length());
}
public static String functionClassName(Class functionClass)
{
String name = ClassUtil.classname(functionClass);
// strip Function suffix, if any
String jqlClassName = stripSuffix(name, FUNCTION_SUFFIX);
return jqlClassName;
}
public static String functionClassName(String functionClassName)
{
String name = ClassUtil.classname(functionClassName);
// strip Function suffix, if any
String jqlClassName = stripSuffix(name, FUNCTION_SUFFIX);
return jqlClassName;
}
/**
* Compute registration name of function.
* Classname may be null, in which case function name
* is simply value of <tt>name</tt>.
*
* @param className
* @param name
* @return
*/
public static String functionName(String className, String funcName)
{
if (className != null)
return functionClassName(className) + FUNCTION_CONNECTOR + funcName;
return funcName;
}
public static String resultType(Method meth)
{
Class retType = meth.getReturnType();
return ClassUtil.classname(retType);
}
/*
public static String basicClassName(Class cl)
{
String fullName = cl.getName();
int dotPos = fullName.lastIndexOf('.');
String name = fullName;
if (dotPos > 0) {
name = fullName.substring(dotPos + 1);
}
return name;
}
*/
public static String stripSuffix(String str, String suffix)
{
if (str.endsWith(suffix)) {
String stripStr = str.substring(0, str.length() - suffix.length());
return stripStr;
}
return str;
}
/**
* Gets the number of arguments actual visible to the user.
*
* @param method
* @return
*/
public static int userArgCount(Method method)
{
int nArgs = method.getParameterTypes().length;
if (nArgs == 0) return 0;
if (method.getParameterTypes()[0] == EngineContext.class)
nArgs--;
return nArgs;
}
private Map<String, Class> functionClasses = new HashMap<String, Class>();
private Map functionMap = new MultiMap(new TreeMap());
public FunctionRegistry() {
init();
}
private void init()
{
register(null, AggregateFunctions.class, false);
register(AggFunction.class);
register(ColorFunction.class);
register(ConsoleFunction.class);
register(CRSFunction.class);
// register(ProjFunction.class);
register(DateFunction.class);
register(DebugFunction.class);
register(FileFunction.class);
register(FileSysFunction.class);
register(GeodeticFunction.class);
register(HtmlFunction.class);
register(IOFunction.class);
register(MathFunction.class);
register(NetFunction.class);
register(RegExFunction.class);
register(StringFunction.class);
register(StrMatchFunction.class);
register(GenerateFunction.class);
register(ScriptFunction.class);
register(SplitByFunction.class);
register(SystemFunction.class);
register(ValFunction.class);
register(XmlStrFunction.class);
register(GeomFunction.class);
register(GridFunction.class);
register(LinearRefFunction.class);
register(GeomPrepFunction.class);
register(GeomPrepAllFunction.class);
register(jeql.std.function.geommatch.GeomMatchFunction.class);
}
public void register(Class functionClass)
{
register(functionClass, false);
}
public void register(Class functionClass, boolean allowReplacement)
{
String name = ClassUtil.classname(functionClass);
// strip Function suffix, if any
String jqlClassName = stripSuffix(name, FUNCTION_SUFFIX);
register(jqlClassName, functionClass, allowReplacement);
}
private void register(String modName,
Class functionClass,
boolean allowReplacement)
{
// only register once
if (! allowReplacement && functionClasses.containsKey(modName))
throw new ConfigurationException("Function class " + modName
+ " is already registered");
functionClasses.put(modName, functionClass);
Method[] method = functionClass.getMethods();
for (int i = 0; i < method.length; i++) {
if (Modifier.isStatic(method[i].getModifiers()) ) {
String funcName = functionName(modName, method[i].getName());
int nArgs = userArgCount(method[i]);
if (! allowReplacement && getMethod(funcName, nArgs) != null)
throw new ConfigurationException("Function " + funcName
+ "(nargs = " + nArgs + ") already registered");
functionMap.put(funcName, method[i]);
}
}
}
/**
*
* @return collection of values 'module.function'
*/
public Collection<String> getFunctionNames()
{
return functionMap.keySet();
}
public Collection<Method> getFunctionMethods(String functionName)
{
Object item = functionMap.get(functionName);
if (item == null)
return new ArrayList();
if (item instanceof Method) {
List l = new ArrayList();
l.add(item);
return l;
}
return (Collection<Method>) item;
}
/**
* Gets the {@link Method} object corresponding to the given
* name and argument count.
*
* @param name the name of the function to resolve
* @return the method for this function
* @return null if no matching function exists
*/
public Method getMethod(String name, int nArgs)
{
Object item = functionMap.get(name);
if (item == null) return null;
// CASE: uniquely-defined method
if (item instanceof Method) {
Method method = (Method) item;
/**
* Check for special (function object) methods.
*
* These are always matched without checking if arg count matches,
* since they actually return an function evaluator object
* (so they are called with no args)
*/
if (method.getReturnType() == SplittingFunction.class
|| method.getReturnType() == AggregateFunction.class)
return method;
// regular value method
return match(name, nArgs, (Method) item);
}
// CASE: method name registered to multiple methods
Collection c = (Collection) item;
for (Iterator i = c.iterator(); i.hasNext(); ) {
Method meth = match(name, nArgs, (Method) i.next());
if (meth != null) return meth;
}
return null;
}
public Class getReturnType(String name, int nArgs)
{
Method meth = getMethod(name, nArgs);
if (meth == null)
return null;
return meth.getReturnType();
}
/**
* Check if the number of user-supplied args matches the
* arg count for the given method, and if so return it.
*
* @param name
* @param nArgs
* @param method
* @return null if method does not match
*/
public Method match(String name, int nArgs, Method method)
{
if (nArgs != userArgCount(method))
return null;
return method;
}
}