view artifacts/src/main/java/org/dive4elements/river/artifacts/datacage/templating/FunctionResolver.java @ 9299:4a6cc7c6716a

uinfo.inundation_duration veg'zone select
author gernotbelger
date Wed, 25 Jul 2018 14:42:44 +0200
parents 5030c46d8cb4
children e511eb935ccd
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.datacage.templating;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathFunctionException;
import javax.xml.xpath.XPathFunctionResolver;

import org.apache.log4j.Logger;
import org.dive4elements.artifactdatabase.transition.TransitionEngine;
import org.dive4elements.artifacts.GlobalContext;
import org.dive4elements.river.artifacts.context.RiverContext;
import org.dive4elements.river.artifacts.context.RiverContextFactory;

/** Resolves functions (e.g. dc:contains) in Datacage/Meta-Data system. */
public class FunctionResolver implements XPathFunctionResolver {
    /** Home log. */
    private static Logger log = Logger.getLogger(FunctionResolver.class);

    public static final String FUNCTION_NAMESPACE_URI = "dc";

    public static final double FAR_AWAY = 99999d;

    protected static final class Entry {

        Entry next;
        XPathFunction function;
        int arity;

        public Entry(final Entry next, final XPathFunction function, final int arity) {
            this.next = next;
            this.function = function;
            this.arity = arity;
        }

        XPathFunction find(final int arity) {
            Entry current = this;
            while (current != null) {
                if (current.arity == arity) {
                    return current.function;
                }
                current = current.next;
            }
            return null;
        }
    } // class Entry

    /** List of functions. */
    protected Map<String, Entry> functions;

    protected Builder.BuildHelper buildHelper;

    public FunctionResolver() {
        this(null);
    }

    public FunctionResolver(final Builder.BuildHelper buildHelper) {
        this.buildHelper = buildHelper;

        this.functions = new HashMap<>();

        addFunction("coalesce", 2, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return coalesce(args);
            }
        });

        addFunction("toString", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                final Object arg = args.get(0);
                return arg == null ? null : arg.toString();
            }
        });

        addFunction("lowercase", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return args.get(0).toString().toLowerCase();
            }
        });

        addFunction("uppercase", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return args.get(0).toString().toUpperCase();
            }
        });

        addFunction("contains", 2, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return contains(args);
            }
        });

        addFunction("fromValue", 3, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return fromValue(args);
            }
        });

        addFunction("toValue", 3, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return toValue(args);
            }
        });

        addFunction("replace", 3, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return replace(args);
            }
        });

        addFunction("replace-all", 3, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return replaceAll(args);
            }
        });

        addFunction("has-result", 0, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return FunctionResolver.this.buildHelper.hasResult();
            }
        });

        addFunction("group-key", 0, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return FunctionResolver.this.buildHelper.getGroupKey();
            }
        });

        addFunction("date-format", 2, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return dateFormat(args);
            }
        });

        addFunction("dump-variables", 0, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return FunctionResolver.this.buildHelper.frames.dump();
            }
        });

        addFunction("get", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                final Object o = args.get(0);
                if (o instanceof String) {
                    return FunctionResolver.this.buildHelper.frames.getNull((String) o, StackFrames.NULL);
                }
                return StackFrames.NULL;
            }
        });

        addFunction("all-state-successors", 2, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                final Object artifactName = args.get(0);
                final Object stateId = args.get(1);

                return artifactName instanceof String && stateId instanceof String ? allStateSuccessors((String) artifactName, (String) stateId)
                        : Collections.<String>emptySet();
            }
        });

        addFunction("find-all", 2, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                final Object needle = args.get(0);
                final Object haystack = args.get(1);
                return haystack instanceof String && needle instanceof String ? findAll((String) needle, (String) haystack) : Collections.<String>emptyList();
            }
        });

        addFunction("max-number", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return maxNumber(args.get(0));
            }
        });

        addFunction("min-number", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return minNumber(args.get(0));
            }
        });

        addFunction("column", 1, new XPathFunction() {
            @Override
            public Object evaluate(final List args) throws XPathFunctionException {
                return column(args.get(0));
            }
        });

        addFunction(FixAnalysisYearXPathFunction.ID, FixAnalysisYearXPathFunction.ARITY, new FixAnalysisYearXPathFunction(buildHelper.getContext()));

        addFunction(DefaultVegetationZoneXPathFunction.ID, DefaultVegetationZoneXPathFunction.ARITY, new DefaultVegetationZoneXPathFunction());

        addFunction(DataFromArtifactXPathFunction.ID, DataFromArtifactXPathFunction.ARITY, new DataFromArtifactXPathFunction(buildHelper.getContext()));
    }

    /**
     * Create a new function.
     *
     * @param name
     *            Name of the function.
     * @param arity
     *            Number of arguments for function.
     * @param function
     *            the function itself.
     */
    public void addFunction(final String name, final int arity, final XPathFunction function) {
        Entry entry = this.functions.get(name);
        if (entry == null) {
            entry = new Entry(null, function, arity);
            this.functions.put(name, entry);
        } else {
            final Entry newEntry = new Entry(entry.next, function, arity);
            entry.next = newEntry;
        }
    }

    @Override
    public XPathFunction resolveFunction(final QName functionName, final int arity) {

        if (!functionName.getNamespaceURI().equals(FUNCTION_NAMESPACE_URI)) {
            return null;
        }

        final Entry entry = this.functions.get(functionName.getLocalPart());
        return entry != null ? entry.find(arity) : null;
    }

    /** Implementation of case-ignoring dc:contains. */
    public static Object contains(final List args) throws XPathFunctionException {
        final Object haystack = args.get(0);
        Object needle = args.get(1);

        if (needle instanceof String && !(haystack instanceof String)) {
            needle = ((String) needle).toUpperCase();
        }

        try {
            if (haystack instanceof Collection) {
                return Boolean.valueOf(((Collection) haystack).contains(needle));
            }

            if (haystack instanceof Map) {
                return Boolean.valueOf(((Map) haystack).containsKey(needle));
            }

            if (haystack instanceof Object[]) {
                for (final Object straw : (Object[]) haystack) {
                    if (straw.equals(needle)) {
                        return Boolean.TRUE;
                    }
                }
            }

            if (haystack instanceof String && needle instanceof String) {
                final String h = (String) haystack;
                final String n = (String) needle;
                return h.contains(n);
            }

            return Boolean.FALSE;
        }
        catch (final Exception e) {
            log.error(e);
            throw new XPathFunctionException(e);
        }
    }

    /**
     * Implementation for getting the minimum value of location or distance
     * dc:fromValue.
     */
    public static Object fromValue(final List args) throws XPathFunctionException {
        final Object mode = args.get(0);
        final Object locations = args.get(1);
        final Object from = args.get(2);

        if ((mode instanceof String && mode.equals("location")) || (locations instanceof String && !((String) locations).isEmpty())) {
            if (!(locations instanceof String)) {
                return -FAR_AWAY;
            }
            final String loc = ((String) locations).replace(" ", "");
            final String[] split = loc.split(",");
            if (split.length < 1) {
                return -FAR_AWAY;
            }
            try {
                double min = Double.parseDouble(split[0]);
                for (int i = 1; i < split.length; ++i) {
                    final double v = Double.parseDouble(split[i]);
                    if (v < min) {
                        min = v;
                    }
                }
                return min;
            }
            catch (final NumberFormatException nfe) {
                return -FAR_AWAY;
            }
        } else {
            if (!(from instanceof String)) {
                return -FAR_AWAY;
            }
            final String f = (String) from;
            try {
                return Double.parseDouble(f);
            }
            catch (final NumberFormatException nfe) {
                return -FAR_AWAY;
            }
        }
    }

    /**
     * Implementation for getting the maximum value of location or distance
     * dc:toValue.
     */
    public static Object toValue(final List args) throws XPathFunctionException {
        final Object mode = args.get(0);
        final Object locations = args.get(1);
        final Object to = args.get(2);

        if ((mode instanceof String && mode.equals("location")) || (locations instanceof String && !((String) locations).isEmpty())) {
            if (!(locations instanceof String)) {
                return FAR_AWAY;
            }
            try {
                final String loc = ((String) locations).replace(" ", "");
                final String[] split = loc.split(",");
                if (split.length < 1) {
                    return FAR_AWAY;
                }
                double max = Double.parseDouble(split[0]);
                for (int i = 1; i < split.length; ++i) {
                    final double v = Double.parseDouble(split[i]);
                    if (v > max) {
                        max = v;
                    }
                }
                return max;
            }
            catch (final NumberFormatException nfe) {
                return FAR_AWAY;
            }
        } else {
            if (!(to instanceof String)) {
                return FAR_AWAY;
            } else {
                final String t = (String) to;
                try {
                    return Double.parseDouble(t);
                }
                catch (final NumberFormatException nfe) {
                    return FAR_AWAY;
                }
            }
        }
    }

    /**
     * Implementation for doing a string replace
     * dc:replace .
     */
    public static Object replace(final List args) throws XPathFunctionException {
        final Object haystack = args.get(0);
        final Object needle = args.get(1);
        final Object replacement = args.get(2);

        if (needle instanceof String && haystack instanceof String && replacement instanceof String) {
            return ((String) haystack).replace((String) needle, (String) replacement);
        }
        return haystack;
    }

    /**
     * Implementation for doing a string replace
     * dc:replace-all
     */
    public static Object replaceAll(final List args) throws XPathFunctionException {
        final Object haystack = args.get(0);
        final Object needle = args.get(1);
        final Object replacement = args.get(2);

        if (needle instanceof String && haystack instanceof String && replacement instanceof String) {
            return ((String) haystack).replaceAll((String) needle, (String) replacement);
        }
        return haystack;
    }

    public static Object dateFormat(final List args) throws XPathFunctionException {
        final Object pattern = args.get(0);
        Object date = args.get(1);

        try {
            // TODO: Take locale into account.
            final SimpleDateFormat format = new SimpleDateFormat((String) pattern);

            if (date instanceof Number) {
                return format.format(new Date(((Number) date).longValue()));
            }

            try {
                /*
                 * Oracle does not return a date object but
                 * an oracle.sql.TIMESTAMP
                 */
                final Method meth = date.getClass().getMethod("dateValue", new Class[] {});
                date = meth.invoke(date, new Object[] {});
            }
            catch (final IllegalArgumentException e) {
            }
            catch (final IllegalAccessException e) {
            }
            catch (final InvocationTargetException e) {
            }
            catch (final NoSuchMethodException e) {
            }

            if (date instanceof Date) {
                return format.format((Date) date);
            }
        }
        catch (final IllegalArgumentException iae) {
            log.error(iae.getMessage());
        }

        return "";
    }

    public static Set<String> allStateSuccessors(final String artifactName, final String stateId) {
        final GlobalContext gc = RiverContextFactory.getGlobalContext();
        if (gc == null) {
            return Collections.<String>emptySet();
        }
        final Object o = gc.get(RiverContext.TRANSITION_ENGINE_KEY);
        if (o instanceof TransitionEngine) {
            final TransitionEngine te = (TransitionEngine) o;
            return te.allRecursiveSuccessorStateIds(artifactName, stateId);
        }
        return Collections.<String>emptySet();
    }

    public static Collection<String> findAll(final String needle, final String haystack) {

        final ArrayList<String> result = new ArrayList<>();

        final Pattern pattern = Pattern.compile(needle);
        final Matcher matcher = pattern.matcher(haystack);
        while (matcher.find()) {
            result.add(matcher.group());
        }
        return result;
    }

    public static Number maxNumber(final Object list) {
        if (list instanceof Collection) {
            final Collection collection = (Collection) list;
            double max = -Double.MAX_VALUE;
            for (final Object x : collection) {
                Number n;
                if (x instanceof Number) {
                    n = (Number) x;
                } else if (x instanceof String) {
                    try {
                        n = Double.valueOf((String) x);
                    }
                    catch (final NumberFormatException nfe) {
                        log.warn("'" + x + "' is not a number.");
                        continue;
                    }
                } else {
                    log.warn("'" + x + "' is not a number.");
                    continue;
                }

                final double v = n.doubleValue();

                if (v > max) {
                    max = v;
                }
            }

            return Double.valueOf(max == -Double.MAX_VALUE ? Double.MAX_VALUE : max);
        }

        return list instanceof Number ? (Number) list : Double.valueOf(Double.MAX_VALUE);
    }

    public static Number minNumber(final Object list) {
        if (list instanceof Collection) {
            final Collection collection = (Collection) list;
            double min = Double.MAX_VALUE;
            for (final Object x : collection) {
                Number n;
                if (x instanceof Number) {
                    n = (Number) x;
                } else if (x instanceof String) {
                    try {
                        n = Double.valueOf((String) x);
                    }
                    catch (final NumberFormatException nfe) {
                        log.warn("'" + x + "' is not a number.");
                        continue;
                    }
                } else {
                    log.warn("'" + x + "' is not a number.");
                    continue;
                }

                final double v = n.doubleValue();

                if (v < min) {
                    min = v;
                }
            }

            return Double.valueOf(min == Double.MAX_VALUE ? -Double.MAX_VALUE : min);
        }

        return list instanceof Number ? (Number) list : Double.valueOf(-Double.MAX_VALUE);
    }

    public static Object coalesce(final List list) {
        for (final Object x : list) {
            if (x instanceof String && ((String) x).length() != 0) {
                return x;
            }
            if (x instanceof Number && ((Number) x).doubleValue() != 0.0) {
                return x;
            }
        }
        return StackFrames.NULL;
    }

    private Object column(final Object argument) {

        if (!(argument instanceof String))
            throw new IllegalArgumentException("Argument of 'column' function must be a string");

        final String columnName = (String) argument;

        final StackFrames frms = this.buildHelper.frames;

        return frms.getNull(columnName);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org