/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.filter; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.capability.FunctionNameImpl; import org.geotools.filter.expression.ExpressionAbstract; import org.opengis.filter.FilterFactory2; import org.opengis.filter.capability.FunctionName; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.ExpressionVisitor; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; import org.opengis.parameter.Parameter; import com.vividsolutions.jts.geom.Geometry; /** * Default implementation of a Function; you may extend this class to * implement specific functionality. * <p> * * @author Cory Horner, Refractions Research * * * * @source $URL$ */ public class FunctionImpl extends ExpressionAbstract implements Function { /** function name **/ String name; /** function params **/ List<Expression> params = Collections.emptyList(); /** * Fall back value to use when implementation is not available. */ Literal fallbackValue; /** * FunctionName description for FilterCapabilities, may be provided by * subclass in constructor, or will be lazily created based on name and * number of arguments. */ protected FunctionName functionName; /** * Gets the name of this function. * * @return the name of the function. * */ public String getName() { return name; } public synchronized FunctionName getFunctionName() { if( functionName == null ){ functionName = new FunctionNameImpl(name,getParameters().size()); } return functionName; } /** * Sets the name of the function. */ public void setName(String name) { this.name = name; } /** * Returns the function parameters. */ public List<Expression> getParameters() { return new ArrayList<Expression>(params); } /** * Default implementation simply returns the fallbackValue. * <p> * Please override this method to produce a value based on the * provided arguments. * @param object Object being evaluated; often a Feature * @return value for the provided object */ public Object evaluate(Object object) { return fallbackValue.evaluate( object ); } /** * Sets the function parameters. */ @SuppressWarnings("unchecked") public void setParameters(List<Expression> params) { this.params = params == null? Collections.EMPTY_LIST : new ArrayList<Expression>(params); } public void setFallbackValue(Literal fallbackValue) { this.fallbackValue = fallbackValue; } public Literal getFallbackValue() { return fallbackValue; } public Object accept(ExpressionVisitor visitor, Object extraData) { return visitor.visit( this, extraData ); } /** * Validates the structure of arguments, basically enforcing java conventions for variable * level arguments. */ private void validateArguments() throws IllegalArgumentException { List<Parameter<?>> args = getFunctionName().getArguments(); for (int i = 0; i < args.size(); i++) { Parameter<?> arg = args.get(i); if (arg.getMaxOccurs() == 0) { throw new IllegalArgumentException(String.format("Argument %s has zero max")); } if (arg.getMinOccurs() != 1 || arg.getMaxOccurs() != 1) { //this can only happen for the last argument if (i != args.size()-1) { throw new IllegalArgumentException(String.format("Argument %s(%d,%d) invalid." + " Variable arguments must be the last argument of function.", arg.getName(), arg.getMinOccurs(), arg.getMaxOccurs())); } } } } /** * Gathers up and groups the parameters to the function based on the declared parameters. * <p> * This method calls {@link #validateArguments()} which enforces java style argument conventions * for multi valued parameters. Basically enforcing that only teh last argument in a function * can be variable. * </p> * */ protected LinkedHashMap<String, Object> dispatchArguments(Object obj) { LinkedHashMap<String, Object> prepped = new LinkedHashMap<String, Object>(); List<Parameter<?>> args = getFunctionName().getArguments(); List<Expression> expr = getParameters(); if (expr.size() < args.size()) { Parameter<?> last = args.get(args.size()-1); //check the last argument if (args.get(0).getMinOccurs() != 0) { throw new IllegalArgumentException(String.format("No arguments specified for arg " + "%s, minOccurs = %d", last.getName().toString(), last.getMinOccurs())); } } for (int i = 0; i < expr.size(); i++) { Parameter<?> arg = args.get(Math.min(i, args.size()-1)); String argName = arg.getName().toString(); Object o = expr.get(i).evaluate(obj, arg.getType()); if (o == null) { if (expr.get(i).evaluate(obj) != null) { //conversion error throw new IllegalArgumentException(String.format("Failure converting value for "+ "argument %s. %s could not be converted to %s", arg.getName(), obj.toString(), arg.getType().getName())); } } if (prepped.containsKey(argName)) { if (arg.getMaxOccurs() == 1) { throw new IllegalArgumentException(String.format("Multiple values specified for " + "argument %s but maxOccurs = 1", argName)); } //if there is already a value for this argument it is a multi argument which // means a list List l = (List) prepped.get(argName); l.add(o); } else { //check for variable argument, use a list if maxOccurs > 1 if (arg.getMaxOccurs() < 0 || arg.getMaxOccurs() > 1) { List l = new ArrayList(); l.add(o); prepped.put(argName, l); } else { prepped.put(argName, o); } } } return prepped; } /** filter factory */ static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); /** * regex for parameter specification */ static Pattern PARAM = Pattern.compile("(\\w+)(?::([\\.\\w]*)(?::(\\d*),(\\d*))?+)?+"); /** * Convenience method for creating a function name from a set of strings describing the return * and argument parameters of the function. * <p> * The value of <tt>ret</tt> and each value of <tt>args</tt> is a string of the following * structure: * <pre> * name[:type[:min,max]] * </pre> * Where: * <ul> * <li><tt>name</tt> is the name of the parameter * <li><tt>type</tt> is type (class name) of the parameter * <li><tt>min</tt> is the minimum number of occurrences * <li><tt>max</tt> is the maximum number of occurrences * </ul> * Examples: * <ul> * <li><tt>foo</tt> * <li><tt>foo:String</tt> * <li><tt>foo:java.lang.Integer</tt> * <li><tt>foo:Polygon:1,1</tt> * <li><tt>foo:Polygon:1,</tt> * <li><tt>foo:Polygon:,</tt> * </ul> * The <tt>type</tt> parameter may be specified relative to the following well known packages: * <ul> * <li><tt>java.lang</tt> * <li><tt>com.vividsolutions.jts.geom</tt> * </ul> * Otherwise it must be specified as a full qualified class name. * </p> * @param name The name of the function * @param ret The parameter specification of the return of the function * @param args The argument specifications of the function arguments */ protected static FunctionName functionName(String name, String ret, String... args) { List<Parameter<?>> list = new ArrayList<Parameter<?>>(); for (String arg : args) { list.add(toParameter(arg)); } return ff.functionName(name, list, toParameter(ret)); } static Parameter toParameter(String param) throws IllegalArgumentException { Matcher m = PARAM.matcher(param); if (!m.matches()) { throw new IllegalArgumentException("Illegal parameter syntax: " + param); } String name = m.group(1); Class type = null; int min = 1; int max = 1; String grp = m.group(2); if ("".equals(grp)) { grp = null; } if (grp != null) { try { type = Class.forName(grp); } catch (ClassNotFoundException e) { //try prefixing with java.lang try { type = Class.forName("java.lang."+grp); } catch (ClassNotFoundException e1) { //try prefixing with jts.geom try { type = Class.forName("com.vividsolutions.jts.geom."+grp); } catch (ClassNotFoundException e2) { //throw back the original throw (IllegalArgumentException) new IllegalArgumentException("Unknown type: " + grp).initCause(e); } } } } //recognize some well known names if (type == null) { if ("geom".equals(name)) { type = Geometry.class; } } if (type == null) { type = Object.class; } grp = m.group(3); if (grp != null) { min = !"".equals(grp) ? Integer.parseInt(grp) : -1; } grp = m.group(4); if (grp != null) { max = !"".equals(grp) ? Integer.parseInt(grp) : -1; } return new org.geotools.data.Parameter(name, type, min, max); } }