teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.artifacts.datacage.templating; sascha@998: teichmann@5608: import java.text.SimpleDateFormat; teichmann@7382: import java.util.ArrayList; sascha@998: import java.util.Collection; teichmann@6933: import java.util.Collections; teichmann@5608: import java.util.Date; teichmann@4949: import java.util.List; sascha@998: import java.util.Map; teichmann@6126: import java.util.HashMap; teichmann@6933: import java.util.Set; teichmann@7382: import java.util.regex.Matcher; teichmann@7382: import java.util.regex.Pattern; sascha@998: aheinecke@5784: import java.lang.reflect.InvocationTargetException; aheinecke@5784: import java.lang.reflect.Method; aheinecke@5784: teichmann@4949: import javax.xml.namespace.QName; teichmann@4949: sascha@998: import javax.xml.xpath.XPathFunction; sascha@998: import javax.xml.xpath.XPathFunctionException; teichmann@4949: import javax.xml.xpath.XPathFunctionResolver; sascha@998: sascha@998: import org.apache.log4j.Logger; teichmann@6933: import org.dive4elements.artifactdatabase.transition.TransitionEngine; teichmann@6933: import org.dive4elements.artifacts.GlobalContext; teichmann@6933: import org.dive4elements.river.artifacts.context.RiverContext; teichmann@6933: import org.dive4elements.river.artifacts.context.RiverContextFactory; sascha@998: felix@4594: felix@4594: /** Resolves functions (e.g. dc:contains) in Datacage/Meta-Data system. */ sascha@998: public class FunctionResolver sascha@998: implements XPathFunctionResolver sascha@998: { felix@4594: /** Home logger. */ sascha@998: private static Logger log = Logger.getLogger(FunctionResolver.class); sascha@998: sascha@998: public static final String FUNCTION_NAMESPACE_URI = "dc"; sascha@998: teichmann@5604: public static final double FAR_AWAY = 99999d; teichmann@5604: teichmann@6126: protected static final class Entry { sascha@998: teichmann@6126: Entry next; sascha@998: XPathFunction function; sascha@998: int arity; sascha@998: teichmann@6126: public Entry(Entry next, XPathFunction function, int arity) { teichmann@6126: this.next = next; teichmann@6126: this.function = function; teichmann@6126: this.arity = arity; sascha@998: } sascha@998: teichmann@6126: XPathFunction find(int arity) { teichmann@6126: Entry current = this; teichmann@6126: while (current != null) { teichmann@6126: if (current.arity == arity) { teichmann@6126: return current.function; teichmann@6126: } teichmann@6126: current = current.next; teichmann@6126: } teichmann@6126: return null; sascha@998: } sascha@998: } // class Entry sascha@998: felix@4594: /** List of functions. */ teichmann@6126: protected Map functions; sascha@998: teichmann@5432: protected Builder.BuildHelper buildHelper; teichmann@5430: teichmann@5430: sascha@998: public FunctionResolver() { teichmann@5432: this(null); teichmann@5432: } teichmann@5432: teichmann@5432: public FunctionResolver(Builder.BuildHelper buildHelper) { teichmann@5432: this.buildHelper = buildHelper; teichmann@5432: teichmann@6126: functions = new HashMap(); teichmann@5430: teichmann@5430: addFunction("contains", 2, new XPathFunction() { teichmann@5430: @Override teichmann@5430: public Object evaluate(List args) throws XPathFunctionException { teichmann@5430: return contains(args); teichmann@5430: } teichmann@5430: }); teichmann@5430: teichmann@5430: addFunction("fromValue", 3, new XPathFunction() { teichmann@5430: @Override teichmann@5430: public Object evaluate(List args) throws XPathFunctionException { teichmann@5430: return fromValue(args); teichmann@5430: } teichmann@5430: }); teichmann@5430: teichmann@5430: addFunction("toValue", 3, new XPathFunction() { teichmann@5430: @Override teichmann@5430: public Object evaluate(List args) throws XPathFunctionException { teichmann@5430: return toValue(args); teichmann@5430: } teichmann@5430: }); teichmann@5433: aheinecke@5561: addFunction("replace", 3, new XPathFunction() { aheinecke@5561: @Override aheinecke@5561: public Object evaluate(List args) throws XPathFunctionException { aheinecke@5561: return replace(args); aheinecke@5561: } aheinecke@5561: }); aheinecke@5561: teichmann@6126: addFunction("replace-all", 3, new XPathFunction() { teichmann@6126: @Override teichmann@6126: public Object evaluate(List args) throws XPathFunctionException { teichmann@6126: return replaceAll(args); teichmann@6126: } teichmann@6126: }); teichmann@6126: teichmann@5433: addFunction("has-result", 0, new XPathFunction() { teichmann@5433: @Override teichmann@5433: public Object evaluate(List args) throws XPathFunctionException { teichmann@5433: return FunctionResolver.this.buildHelper.hasResult(); teichmann@5433: } teichmann@5433: }); teichmann@5608: teichmann@5890: addFunction("group-key", 0, new XPathFunction() { teichmann@5890: @Override teichmann@5890: public Object evaluate(List args) throws XPathFunctionException { teichmann@5890: return FunctionResolver.this.buildHelper.getGroupKey(); teichmann@5890: } teichmann@5890: }); teichmann@5890: teichmann@5608: addFunction("date-format", 2, new XPathFunction() { teichmann@5608: @Override teichmann@5608: public Object evaluate(List args) throws XPathFunctionException { teichmann@5608: return dateFormat(args); teichmann@5608: } teichmann@5608: }); teichmann@6080: teichmann@6080: addFunction("dump-variables", 0, new XPathFunction() { teichmann@6080: @Override teichmann@6080: public Object evaluate(List args) throws XPathFunctionException { teichmann@6080: return FunctionResolver.this.buildHelper.frames.dump(); teichmann@6080: } teichmann@6080: }); teichmann@6180: teichmann@6180: addFunction("get", 1, new XPathFunction() { teichmann@6180: @Override teichmann@6180: public Object evaluate(List args) throws XPathFunctionException { teichmann@6180: Object o = args.get(0); teichmann@6180: if (o instanceof String) { aheinecke@6181: return FunctionResolver.this.buildHelper.frames.getNull( teichmann@6180: (String)o, StackFrames.NULL); teichmann@6180: } teichmann@6180: return StackFrames.NULL; teichmann@6180: } teichmann@6180: }); teichmann@6933: teichmann@6933: addFunction("all-state-successors", 2, new XPathFunction() { teichmann@6933: @Override teichmann@6933: public Object evaluate(List args) throws XPathFunctionException { teichmann@6933: Object artifactName = args.get(0); teichmann@6933: Object stateId = args.get(1); teichmann@6933: teichmann@6933: return artifactName instanceof String teichmann@7248: && stateId instanceof String teichmann@6933: ? allStateSuccessors((String)artifactName, (String)stateId) teichmann@6933: : Collections.emptySet(); teichmann@6933: } teichmann@6933: }); teichmann@7382: teichmann@7382: addFunction("find-all", 2, new XPathFunction() { teichmann@7382: @Override teichmann@7382: public Object evaluate(List args) throws XPathFunctionException { teichmann@7382: Object needle = args.get(0); teichmann@7382: Object haystack = args.get(1); teichmann@7382: return haystack instanceof String teichmann@7382: && needle instanceof String teichmann@7382: ? findAll((String)needle, (String)haystack) teichmann@7382: : Collections.emptyList(); teichmann@7382: } teichmann@7382: }); teichmann@7383: teichmann@7383: addFunction("max-number", 1, new XPathFunction() { teichmann@7383: @Override teichmann@7383: public Object evaluate(List args) throws XPathFunctionException { teichmann@7383: return maxNumber(args.get(0)); teichmann@7383: } teichmann@7383: }); teichmann@7383: teichmann@7383: addFunction("min-number", 1, new XPathFunction() { teichmann@7383: @Override teichmann@7383: public Object evaluate(List args) throws XPathFunctionException { teichmann@7383: return minNumber(args.get(0)); teichmann@7383: } teichmann@7383: }); teichmann@7384: sascha@998: } sascha@998: felix@4594: /** felix@4594: * Create a new function. felix@4594: * @param name Name of the function. felix@4594: * @param arity Number of arguments for function. felix@4594: * @param function the function itself. felix@4594: */ sascha@998: public void addFunction(String name, int arity, XPathFunction function) { teichmann@6126: Entry entry = functions.get(name); teichmann@6126: if (entry == null) { teichmann@6126: entry = new Entry(null, function, arity); teichmann@6126: functions.put(name, entry); teichmann@6126: } teichmann@6126: else { teichmann@6126: Entry newEntry = new Entry(entry.next, function, arity); teichmann@6126: entry.next = newEntry; teichmann@6126: } sascha@998: } sascha@998: sascha@998: @Override sascha@998: public XPathFunction resolveFunction(QName functionName, int arity) { sascha@998: sascha@998: if (!functionName.getNamespaceURI().equals(FUNCTION_NAMESPACE_URI)) { sascha@998: return null; sascha@998: } sascha@998: teichmann@6126: Entry entry = functions.get(functionName.getLocalPart()); teichmann@6126: return entry != null teichmann@6126: ? entry.find(arity) teichmann@6126: : null; sascha@998: } teichmann@5430: teichmann@5430: /** Implementation of case-ignoring dc:contains. */ teichmann@5430: public Object contains(List args) throws XPathFunctionException { teichmann@5430: Object haystack = args.get(0); teichmann@5430: Object needle = args.get(1); teichmann@5430: teichmann@5630: if (needle instanceof String && !(haystack instanceof String)) { teichmann@5430: needle = ((String)needle).toUpperCase(); teichmann@5430: } teichmann@5430: teichmann@5430: try { teichmann@5430: if (haystack instanceof Collection) { teichmann@5430: return Boolean.valueOf( teichmann@5430: ((Collection)haystack).contains(needle)); teichmann@5430: } teichmann@5430: teichmann@5430: if (haystack instanceof Map) { teichmann@5430: return Boolean.valueOf( teichmann@5430: ((Map)haystack).containsKey(needle)); teichmann@5430: } teichmann@5430: teichmann@5430: if (haystack instanceof Object []) { teichmann@5430: for (Object straw: (Object [])haystack) { teichmann@5430: if (straw.equals(needle)) { teichmann@5430: return Boolean.TRUE; teichmann@5430: } teichmann@5430: } teichmann@5430: } teichmann@5430: teichmann@5629: if (haystack instanceof String && needle instanceof String) { teichmann@5629: String h = (String)haystack; teichmann@5629: String n = (String)needle; teichmann@5629: return h.contains(n); teichmann@5629: } teichmann@5629: teichmann@5430: return Boolean.FALSE; teichmann@5430: } teichmann@5430: catch (Exception e) { teichmann@5430: log.error(e); teichmann@5430: throw new XPathFunctionException(e); teichmann@5430: } teichmann@5430: } teichmann@5430: teichmann@5430: /** Implementation for getting the minimum value of location or distance teichmann@5430: * dc:fromValue. teichmann@5430: */ teichmann@5430: public Object fromValue(List args) throws XPathFunctionException { teichmann@5430: Object mode = args.get(0); teichmann@5430: Object locations = args.get(1); teichmann@5430: Object from = args.get(2); teichmann@5430: teichmann@7248: if ((mode instanceof String && mode.equals("location")) || teichmann@6448: (locations instanceof String && !((String)locations).isEmpty())) { teichmann@5430: if (!(locations instanceof String)) { teichmann@5604: return -FAR_AWAY; teichmann@5430: } teichmann@5430: String loc = ((String)locations).replace(" ", ""); teichmann@5430: String[] split = loc.split(","); teichmann@5430: if (split.length < 1) { teichmann@5604: return -FAR_AWAY; teichmann@5430: } teichmann@5430: try { teichmann@5430: double min = Double.parseDouble(split[0]); teichmann@5430: for (int i = 1; i < split.length; ++i) { teichmann@5430: double v = Double.parseDouble(split[i]); teichmann@5430: if (v < min) { teichmann@5430: min = v; teichmann@5430: } teichmann@5430: } teichmann@5430: return min; teichmann@5430: } teichmann@5430: catch (NumberFormatException nfe) { teichmann@5604: return -FAR_AWAY; teichmann@5430: } teichmann@5430: } aheinecke@6176: else { teichmann@5430: if (!(from instanceof String)) { teichmann@5604: return -FAR_AWAY; teichmann@5430: } teichmann@5430: String f = (String)from; teichmann@5430: try { teichmann@5430: return Double.parseDouble(f); teichmann@5430: } teichmann@5430: catch(NumberFormatException nfe) { teichmann@5604: return -FAR_AWAY; teichmann@5430: } teichmann@5430: } teichmann@5430: } teichmann@5430: teichmann@5430: /** Implementation for getting the maximum value of location or distance teichmann@5430: * dc:toValue. teichmann@5430: */ teichmann@5430: public Object toValue(List args) throws XPathFunctionException { teichmann@5430: Object mode = args.get(0); teichmann@5430: Object locations = args.get(1); teichmann@5430: Object to = args.get(2); teichmann@5430: teichmann@6448: if ((mode instanceof String && mode.equals("location")) || teichmann@6448: (locations instanceof String && !((String)locations).isEmpty())) { teichmann@5430: if (!(locations instanceof String)) { teichmann@5604: return FAR_AWAY; teichmann@5430: } teichmann@5430: try { teichmann@5430: String loc = ((String)locations).replace(" ", ""); teichmann@5430: String[] split = loc.split(","); teichmann@5430: if (split.length < 1) { teichmann@5604: return FAR_AWAY; teichmann@5430: } teichmann@5430: double max = Double.parseDouble(split[0]); teichmann@5430: for (int i = 1; i < split.length; ++i) { teichmann@5430: double v = Double.parseDouble(split[i]); teichmann@5430: if (v > max) { teichmann@5430: max = v; teichmann@5430: } teichmann@5430: } teichmann@5430: return max; teichmann@5430: } teichmann@5430: catch (NumberFormatException nfe) { teichmann@5604: return FAR_AWAY; teichmann@5430: } teichmann@5430: } aheinecke@6176: else { teichmann@5430: if (!(to instanceof String)) { teichmann@5604: return FAR_AWAY; teichmann@5430: } teichmann@5430: else { teichmann@5430: String t = (String)to; teichmann@5430: try { teichmann@5430: return Double.parseDouble(t); teichmann@5430: } teichmann@6126: catch (NumberFormatException nfe) { teichmann@5604: return FAR_AWAY; teichmann@5430: } teichmann@5430: } teichmann@5430: } teichmann@5430: } aheinecke@5561: aheinecke@5561: /** Implementation for doing a string replace felix@7508: * dc:replace . aheinecke@5561: */ aheinecke@5561: public Object replace(List args) throws XPathFunctionException { aheinecke@5561: Object haystack = args.get(0); aheinecke@5561: Object needle = args.get(1); aheinecke@5561: Object replacement = args.get(2); aheinecke@5561: teichmann@6126: if (needle instanceof String teichmann@6126: && haystack instanceof String teichmann@6126: && replacement instanceof String) { aheinecke@5561: return ((String)haystack).replace( aheinecke@5561: (String)needle, (String)replacement); aheinecke@5561: } teichmann@6126: return haystack; teichmann@6126: } teichmann@6126: teichmann@6126: /** Implementation for doing a string replace teichmann@6126: * dc:replace-all teichmann@6126: */ teichmann@6126: public Object replaceAll(List args) throws XPathFunctionException { teichmann@6126: Object haystack = args.get(0); teichmann@6126: Object needle = args.get(1); teichmann@6126: Object replacement = args.get(2); teichmann@6126: teichmann@6126: if (needle instanceof String teichmann@6126: && haystack instanceof String teichmann@6126: && replacement instanceof String) { teichmann@6126: return ((String)haystack).replaceAll( teichmann@6126: (String)needle, (String)replacement); teichmann@6126: } teichmann@6126: return haystack; aheinecke@5561: } teichmann@5608: teichmann@5608: public Object dateFormat(List args) throws XPathFunctionException { teichmann@5608: Object pattern = args.get(0); teichmann@5608: Object date = args.get(1); aheinecke@5784: aheinecke@5784: try { aheinecke@5784: // Oracle does not return a date object but an oracle.sql.TIMESTAMP aheinecke@5784: Method meth = date.getClass().getMethod("dateValue", new Class[] {}); aheinecke@5784: date = meth.invoke(date, new Object [] {}); aheinecke@5784: } catch (IllegalArgumentException e) { aheinecke@5784: } catch (IllegalAccessException e) { aheinecke@5784: } catch (InvocationTargetException e) { aheinecke@5784: } catch (NoSuchMethodException e) { aheinecke@5784: } teichmann@5608: if (pattern instanceof String && date instanceof Date) { teichmann@5608: try { teichmann@5608: // TODO: Take locale into account. teichmann@5608: return new SimpleDateFormat((String)pattern).format((Date)date); teichmann@5608: } teichmann@5608: catch (IllegalArgumentException iae) { teichmann@5608: throw new XPathFunctionException(iae); teichmann@5608: } teichmann@5608: } teichmann@5608: return ""; teichmann@5608: } teichmann@6933: teichmann@6933: public Set allStateSuccessors(String artifactName, String stateId) { teichmann@6933: GlobalContext gc = RiverContextFactory.getGlobalContext(); teichmann@6933: if (gc == null) { teichmann@6933: return Collections.emptySet(); teichmann@6933: } teichmann@6933: Object o = gc.get(RiverContext.TRANSITION_ENGINE_KEY); teichmann@6933: if (o instanceof TransitionEngine) { teichmann@6933: TransitionEngine te = (TransitionEngine)o; teichmann@6933: return te.allRecursiveSuccessorStateIds(artifactName, stateId); teichmann@6933: } teichmann@6933: return Collections.emptySet(); teichmann@6933: } teichmann@7382: teichmann@7382: public Collection findAll(String needle, String haystack) { teichmann@7382: teichmann@7382: ArrayList result = new ArrayList(); teichmann@7382: teichmann@7382: Pattern pattern = Pattern.compile(needle); teichmann@7382: Matcher matcher = pattern.matcher(haystack); teichmann@7382: while (matcher.find()) { teichmann@7382: result.add(matcher.group()); teichmann@7382: } teichmann@7382: return result; teichmann@7382: } teichmann@7383: teichmann@7383: public Number maxNumber(Object list) { teichmann@7383: if (list instanceof Collection) { teichmann@7383: Collection collection = (Collection)list; teichmann@7383: double max = -Double.MAX_VALUE; teichmann@7383: for (Object x: collection) { teichmann@7383: Number n; teichmann@7383: if (x instanceof Number) { teichmann@7383: n = (Number)x; teichmann@7383: } teichmann@7383: else if (x instanceof String) { teichmann@7383: try { teichmann@7383: n = Double.valueOf((String)x); teichmann@7383: } teichmann@7383: catch (NumberFormatException nfe) { teichmann@7383: log.warn("'" + x + "' is not a number."); teichmann@7383: continue; teichmann@7383: } teichmann@7383: } teichmann@7383: else { teichmann@7383: log.warn("'" + x + "' is not a number."); teichmann@7383: continue; teichmann@7383: } teichmann@7383: teichmann@7383: double v = n.doubleValue(); teichmann@7383: teichmann@7383: if (v > max) { teichmann@7383: max = v; teichmann@7383: } teichmann@7383: } teichmann@7383: teichmann@7383: return Double.valueOf(max == -Double.MAX_VALUE teichmann@7383: ? Double.MAX_VALUE teichmann@7383: : max); teichmann@7383: } teichmann@7383: teichmann@7383: return list instanceof Number teichmann@7383: ? (Number)list teichmann@7383: : Double.valueOf(Double.MAX_VALUE); teichmann@7383: } teichmann@7383: teichmann@7383: public Number minNumber(Object list) { teichmann@7383: if (list instanceof Collection) { teichmann@7383: Collection collection = (Collection)list; teichmann@7383: double min = Double.MAX_VALUE; teichmann@7383: for (Object x: collection) { teichmann@7383: Number n; teichmann@7383: if (x instanceof Number) { teichmann@7383: n = (Number)x; teichmann@7383: } teichmann@7383: else if (x instanceof String) { teichmann@7383: try { teichmann@7383: n = Double.valueOf((String)x); teichmann@7383: } teichmann@7383: catch (NumberFormatException nfe) { teichmann@7383: log.warn("'" + x + "' is not a number."); teichmann@7383: continue; teichmann@7383: } teichmann@7383: } teichmann@7383: else { teichmann@7383: log.warn("'" + x + "' is not a number."); teichmann@7383: continue; teichmann@7383: } teichmann@7383: teichmann@7383: double v = n.doubleValue(); teichmann@7383: teichmann@7383: if (v < min) { teichmann@7383: min = v; teichmann@7383: } teichmann@7383: } teichmann@7383: teichmann@7383: return Double.valueOf(min == Double.MAX_VALUE teichmann@7383: ? -Double.MAX_VALUE teichmann@7383: : min); teichmann@7383: } teichmann@7383: teichmann@7383: return list instanceof Number teichmann@7383: ? (Number)list teichmann@7383: : Double.valueOf(-Double.MAX_VALUE); teichmann@7383: } sascha@998: } sascha@998: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :