Changeset 17758 in josm


Ignore:
Timestamp:
2021-04-12T21:20:31+02:00 (6 months ago)
Author:
simon04
Message:

fix #20744 - Evaluate MapCSS expression without array creation

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java

    r17619 r17758  
    132132                .filter(c -> c instanceof ConditionFactory.ExpressionCondition)
    133133                .map(c -> ((ConditionFactory.ExpressionCondition) c).getExpression())
    134                 .filter(c -> c instanceof ExpressionFactory.ParameterFunction)
    135                 .map(c -> (ExpressionFactory.ParameterFunction) c)
    136                 .filter(c -> c.getMethod().equals(insideMethod))
    137                 .flatMap(c -> c.getArgs().stream())
     134                .filter(c -> c instanceof ExpressionFactory.IsInsideFunction)
     135                .map(c -> (ExpressionFactory.IsInsideFunction) c)
     136                .map(ExpressionFactory.IsInsideFunction::getArg)
    138137                .filter(e -> e instanceof LiteralExpression)
    139138                .map(e -> ((LiteralExpression) e).getLiteral())
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java

    r16438 r17758  
    22package org.openstreetmap.josm.gui.mappaint.mapcss;
    33
     4import java.awt.Color;
    45import java.lang.annotation.ElementType;
    56import java.lang.annotation.Retention;
    67import java.lang.annotation.RetentionPolicy;
    78import java.lang.annotation.Target;
    8 import java.lang.reflect.Array;
    9 import java.lang.reflect.InvocationTargetException;
    10 import java.lang.reflect.Method;
    11 import java.util.ArrayList;
    129import java.util.Collection;
    1310import java.util.Collections;
     11import java.util.HashMap;
    1412import java.util.List;
     13import java.util.Map;
    1514import java.util.Objects;
     15import java.util.function.BiFunction;
     16import java.util.function.DoubleBinaryOperator;
     17import java.util.function.DoubleUnaryOperator;
    1618import java.util.function.Function;
    17 import java.util.stream.Collectors;
    1819
    1920import org.openstreetmap.josm.gui.mappaint.Cascade;
    2021import org.openstreetmap.josm.gui.mappaint.Environment;
    21 import org.openstreetmap.josm.tools.JosmRuntimeException;
    22 import org.openstreetmap.josm.tools.Logging;
    2322import org.openstreetmap.josm.tools.SubclassFilteredCollection;
    2423import org.openstreetmap.josm.tools.Utils;
     
    3837    @interface NullableArguments {}
    3938
    40     private static final List<Method> arrayFunctions = new ArrayList<>();
    41     private static final List<Method> parameterFunctions = new ArrayList<>();
    42     private static final List<Method> parameterFunctionsEnv = new ArrayList<>();
     39    @FunctionalInterface
     40    public interface TriFunction<T, U, V, R> {
     41        R apply(T t, U u, V v);
     42    }
     43
     44    @FunctionalInterface
     45    public interface QuadFunction<T, U, V, W, R> {
     46        R apply(T t, U u, V v, W w);
     47    }
     48
     49    @FunctionalInterface
     50    interface Factory {
     51        Expression createExpression(List<Expression> args);
     52
     53        static Factory of(DoubleUnaryOperator operator) {
     54            return of(Double.class, operator::applyAsDouble);
     55        }
     56
     57        static Factory ofNumberVarArgs(DoubleBinaryOperator operator) {
     58            return args -> env -> args.stream()
     59                    .map(arg -> Cascade.convertTo(arg.evaluate(env), Double.class))
     60                    .filter(Objects::nonNull)
     61                    .reduce(operator::applyAsDouble)
     62                    .orElse(null);
     63        }
     64
     65        static Factory ofStringVarargs(BiFunction<Environment, String[], ?> function) {
     66            return args -> env -> function.apply(env, args.stream()
     67                    .map(arg -> Cascade.convertTo(arg.evaluate(env), String.class))
     68                    .toArray(String[]::new));
     69        }
     70
     71        static Factory ofObjectVarargs(BiFunction<Environment, Object[], ?> function) {
     72            return args -> env -> function.apply(env, args.stream()
     73                    .map(arg -> arg.evaluate(env))
     74                    .toArray(Object[]::new));
     75        }
     76
     77        static <T> Factory of(Class<T> type, Function<T, ?> function) {
     78            return args -> env -> {
     79                T v = Cascade.convertTo(args.get(0).evaluate(env), type);
     80                return v == null ? null : function.apply(v);
     81            };
     82        }
     83
     84        static <T, U> Factory of(Class<T> type1, Class<U> type2, BiFunction<T, U, ?> function) {
     85            return args -> env -> {
     86                T v1 = Cascade.convertTo(args.get(0).evaluate(env), type1);
     87                U v2 = Cascade.convertTo(args.get(1).evaluate(env), type2);
     88                return v1 == null || v2 == null ? null : function.apply(v1, v2);
     89            };
     90        }
     91
     92        static <T, U, V> Factory of(Class<T> type1, Class<U> type2, Class<V> type3,
     93                                    BiFunction<T, U, ?> biFunction, TriFunction<T, U, V, ?> triFunction) {
     94            return args -> env -> {
     95                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     96                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
     97                V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
     98                return v1 == null || v2 == null ? null : v3 == null ? biFunction.apply(v1, v2) : triFunction.apply(v1, v2, v3);
     99            };
     100        }
     101
     102        static <T, U, V, W> Factory of(Class<T> type1, Class<U> type2, Class<V> type3, Class<W> type4,
     103                                       QuadFunction<T, U, V, W, ?> function) {
     104            return args -> env -> {
     105                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     106                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
     107                V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
     108                W v4 = args.size() >= 4 ? Cascade.convertTo(args.get(3).evaluate(env), type4) : null;
     109                return v1 == null || v2 == null || v3 == null || v4 == null ? null : function.apply(v1, v2, v3, v4);
     110            };
     111        }
     112
     113        static <T> Factory ofEnv(Function<Environment, ?> function) {
     114            return args -> function::apply;
     115        }
     116
     117        static <T> Factory ofEnv(Class<T> type, BiFunction<Environment, T, ?> function) {
     118            return args -> env -> {
     119                T v = Cascade.convertTo(args.get(0).evaluate(env), type);
     120                return v == null ? null : function.apply(env, v);
     121            };
     122        }
     123
     124        static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2,
     125                                    BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
     126            return args -> env -> {
     127                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     128                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
     129                return v1 == null ? null : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
     130            };
     131        }
     132    }
     133
     134    static final Map<String, Factory> FACTORY_MAP = new HashMap<>();
    43135
    44136    static {
    45         for (Method m : Functions.class.getDeclaredMethods()) {
    46             Class<?>[] paramTypes = m.getParameterTypes();
    47             if (paramTypes.length == 1 && paramTypes[0].isArray()) {
    48                 arrayFunctions.add(m);
    49             } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) {
    50                 parameterFunctionsEnv.add(m);
    51             } else {
    52                 parameterFunctions.add(m);
    53             }
    54         }
    55         try {
    56             parameterFunctions.add(Math.class.getMethod("abs", float.class));
    57             parameterFunctions.add(Math.class.getMethod("acos", double.class));
    58             parameterFunctions.add(Math.class.getMethod("asin", double.class));
    59             parameterFunctions.add(Math.class.getMethod("atan", double.class));
    60             parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class));
    61             parameterFunctions.add(Math.class.getMethod("ceil", double.class));
    62             parameterFunctions.add(Math.class.getMethod("cos", double.class));
    63             parameterFunctions.add(Math.class.getMethod("cosh", double.class));
    64             parameterFunctions.add(Math.class.getMethod("exp", double.class));
    65             parameterFunctions.add(Math.class.getMethod("floor", double.class));
    66             parameterFunctions.add(Math.class.getMethod("log", double.class));
    67             parameterFunctions.add(Math.class.getMethod("max", float.class, float.class));
    68             parameterFunctions.add(Math.class.getMethod("min", float.class, float.class));
    69             parameterFunctions.add(Math.class.getMethod("random"));
    70             parameterFunctions.add(Math.class.getMethod("round", float.class));
    71             parameterFunctions.add(Math.class.getMethod("signum", double.class));
    72             parameterFunctions.add(Math.class.getMethod("sin", double.class));
    73             parameterFunctions.add(Math.class.getMethod("sinh", double.class));
    74             parameterFunctions.add(Math.class.getMethod("sqrt", double.class));
    75             parameterFunctions.add(Math.class.getMethod("tan", double.class));
    76             parameterFunctions.add(Math.class.getMethod("tanh", double.class));
    77         } catch (NoSuchMethodException | SecurityException ex) {
    78             throw new JosmRuntimeException(ex);
    79         }
     137        FACTORY_MAP.put("CRC32_checksum", Factory.of(String.class, Functions::CRC32_checksum));
     138        FACTORY_MAP.put("JOSM_pref", Factory.ofEnv(String.class, String.class, null, Functions::JOSM_pref));
     139        FACTORY_MAP.put("JOSM_search", Factory.ofEnv(String.class, Functions::JOSM_search));
     140        FACTORY_MAP.put("URL_decode", Factory.of(String.class, Functions::URL_decode));
     141        FACTORY_MAP.put("URL_encode", Factory.of(String.class, Functions::URL_encode));
     142        FACTORY_MAP.put("XML_encode", Factory.of(String.class, Functions::XML_encode));
     143        FACTORY_MAP.put("abs", Factory.of(Math::acos));
     144        FACTORY_MAP.put("acos", Factory.of(Math::acos));
     145        FACTORY_MAP.put("alpha", Factory.of(Color.class, Functions::alpha));
     146        FACTORY_MAP.put("any", Factory.ofObjectVarargs(Functions::any));
     147        FACTORY_MAP.put("areasize", Factory.ofEnv(Functions::areasize));
     148        FACTORY_MAP.put("asin", Factory.of(Math::asin));
     149        FACTORY_MAP.put("at", Factory.ofEnv(double.class, double.class, null, Functions::at));
     150        FACTORY_MAP.put("atan", Factory.of(Math::atan));
     151        FACTORY_MAP.put("atan2", Factory.of(Double.class, Double.class, Math::atan2));
     152        FACTORY_MAP.put("blue", Factory.of(Color.class, Functions::blue));
     153        FACTORY_MAP.put("cardinal_to_radians", Factory.of(String.class, Functions::cardinal_to_radians));
     154        FACTORY_MAP.put("ceil", Factory.of(Math::ceil));
     155        FACTORY_MAP.put("center", Factory.ofEnv(Functions::center));
     156        FACTORY_MAP.put("child_tag", Factory.ofEnv(String.class, Functions::child_tag));
     157        FACTORY_MAP.put("color2html", Factory.of(Color.class, Functions::color2html));
     158        FACTORY_MAP.put("concat", Factory.ofObjectVarargs(Functions::concat));
     159        FACTORY_MAP.put("cos", Factory.of(Math::cos));
     160        FACTORY_MAP.put("cosh", Factory.of(Math::cosh));
     161        FACTORY_MAP.put("count", Factory.of(List.class, Functions::count));
     162        FACTORY_MAP.put("count_roles", Factory.ofStringVarargs(Functions::count_roles));
     163        FACTORY_MAP.put("degree_to_radians", Factory.of(Functions::degree_to_radians));
     164        FACTORY_MAP.put("divided_by", Factory.ofNumberVarArgs(Functions::divided_by));
     165        FACTORY_MAP.put("equal", Factory.of(Object.class, Object.class, Functions::equal));
     166        FACTORY_MAP.put("eval", Factory.of(Object.class, Functions::eval));
     167        FACTORY_MAP.put("exp", Factory.of(Math::exp));
     168        FACTORY_MAP.put("floor", Factory.of(Math::floor));
     169        FACTORY_MAP.put("get", Factory.of(List.class, float.class, Functions::get));
     170        FACTORY_MAP.put("gpx_distance", Factory.ofEnv(Functions::gpx_distance));
     171        FACTORY_MAP.put("greater", Factory.of(float.class, float.class, Functions::greater));
     172        FACTORY_MAP.put("greater_equal", Factory.of(float.class, float.class, Functions::greater_equal));
     173        FACTORY_MAP.put("green", Factory.of(Color.class, Functions::green));
     174        FACTORY_MAP.put("has_tag_key", Factory.ofEnv(String.class, Functions::has_tag_key));
     175        FACTORY_MAP.put("hsb_color", Factory.of(float.class, float.class, float.class, null, Functions::hsb_color));
     176        FACTORY_MAP.put("html2color", Factory.of(String.class, Functions::html2color));
     177        FACTORY_MAP.put("index", Factory.ofEnv(Functions::index));
     178        FACTORY_MAP.put("inside", Factory.ofEnv(String.class, Functions::inside));
     179        FACTORY_MAP.put("is_anticlockwise", Factory.ofEnv(Functions::is_anticlockwise));
     180        FACTORY_MAP.put("is_clockwise", Factory.ofEnv(Functions::is_clockwise));
     181        FACTORY_MAP.put("is_prop_set", Factory.ofEnv(String.class, Functions::is_prop_set));
     182        FACTORY_MAP.put("is_right_hand_traffic", Factory.ofEnv(Functions::is_right_hand_traffic));
     183        FACTORY_MAP.put("is_similar", Factory.of(String.class, String.class, Functions::is_similar));
     184        FACTORY_MAP.put("join", Factory.ofStringVarargs(Functions::join));
     185        FACTORY_MAP.put("join_list", Factory.of(String.class, List.class, Functions::join_list));
     186        FACTORY_MAP.put("less", Factory.of(float.class, float.class, Functions::less));
     187        FACTORY_MAP.put("less_equal", Factory.of(float.class, float.class, Functions::less_equal));
     188        FACTORY_MAP.put("list", Factory.ofObjectVarargs(Functions::list));
     189        FACTORY_MAP.put("log", Factory.of(Math::log));
     190        FACTORY_MAP.put("lower", Factory.of(String.class, Functions::lower));
     191        FACTORY_MAP.put("minus", Factory.ofNumberVarArgs(Functions::minus));
     192        FACTORY_MAP.put("not", Factory.of(boolean.class, Functions::not));
     193        FACTORY_MAP.put("not_equal", Factory.of(Object.class, Object.class, Functions::not_equal));
     194        FACTORY_MAP.put("number_of_tags", Factory.ofEnv(Functions::number_of_tags));
     195        FACTORY_MAP.put("osm_changeset_id", Factory.ofEnv(Functions::osm_changeset_id));
     196        FACTORY_MAP.put("osm_id", Factory.ofEnv(Functions::osm_id));
     197        FACTORY_MAP.put("osm_timestamp", Factory.ofEnv(Functions::osm_timestamp));
     198        FACTORY_MAP.put("osm_user_id", Factory.ofEnv(Functions::osm_user_id));
     199        FACTORY_MAP.put("osm_user_name", Factory.ofEnv(Functions::osm_user_name));
     200        FACTORY_MAP.put("osm_version", Factory.ofEnv(Functions::osm_version));
     201        FACTORY_MAP.put("outside", Factory.ofEnv(String.class, Functions::outside));
     202        FACTORY_MAP.put("parent_osm_id", Factory.ofEnv(Functions::parent_osm_id));
     203        FACTORY_MAP.put("parent_tag", Factory.ofEnv(String.class, Functions::parent_tag));
     204        FACTORY_MAP.put("parent_tags", Factory.ofEnv(String.class, Functions::parent_tags));
     205        FACTORY_MAP.put("plus", Factory.ofNumberVarArgs(Functions::plus));
     206        FACTORY_MAP.put("print", Factory.of(Object.class, Functions::print));
     207        FACTORY_MAP.put("println", Factory.of(Object.class, Functions::println));
     208        FACTORY_MAP.put("prop", Factory.ofEnv(String.class, Functions::prop));
     209        FACTORY_MAP.put("red", Factory.of(Color.class, Functions::red));
     210        FACTORY_MAP.put("regexp_match", Factory.of(String.class, String.class, String.class, Functions::regexp_match, Functions::regexp_match));
     211        FACTORY_MAP.put("regexp_test", Factory.of(String.class, String.class, String.class, Functions::regexp_test, Functions::regexp_test));
     212        FACTORY_MAP.put("replace", Factory.of(String.class, String.class, String.class, null, Functions::replace));
     213        FACTORY_MAP.put("rgb", Factory.of(float.class, float.class, float.class, null, Functions::rgb));
     214        FACTORY_MAP.put("rgba", Factory.of(float.class, float.class, float.class, float.class, Functions::rgba));
     215        FACTORY_MAP.put("role", Factory.ofEnv(Functions::role));
     216        FACTORY_MAP.put("round", Factory.of(Math::acos));
     217        FACTORY_MAP.put("setting", Factory.ofEnv(String.class, Functions::setting));
     218        FACTORY_MAP.put("signum", Factory.of(Math::signum));
     219        FACTORY_MAP.put("sin", Factory.of(Math::sin));
     220        FACTORY_MAP.put("sinh", Factory.of(Math::sinh));
     221        FACTORY_MAP.put("sort", Factory.ofStringVarargs(Functions::sort));
     222        FACTORY_MAP.put("sort_list", Factory.of(List.class, Functions::sort_list));
     223        FACTORY_MAP.put("split", Factory.of(String.class, String.class, Functions::split));
     224        FACTORY_MAP.put("sqrt", Factory.of(Math::sqrt));
     225        FACTORY_MAP.put("substring", Factory.of(String.class, float.class, float.class, Functions::substring, Functions::substring));
     226        FACTORY_MAP.put("tag", Factory.ofEnv(String.class, Functions::tag));
     227        FACTORY_MAP.put("tag_regex", Factory.ofEnv(String.class, String.class, Functions::tag_regex, Functions::tag_regex));
     228        FACTORY_MAP.put("tan", Factory.of(Math::tan));
     229        FACTORY_MAP.put("tanh", Factory.of(Math::tanh));
     230        FACTORY_MAP.put("times", Factory.ofNumberVarArgs(Functions::times));
     231        FACTORY_MAP.put("title", Factory.of(String.class, Functions::title));
     232        FACTORY_MAP.put("to_boolean", Factory.of(String.class, Functions::to_boolean));
     233        FACTORY_MAP.put("to_byte", Factory.of(String.class, Functions::to_byte));
     234        FACTORY_MAP.put("to_double", Factory.of(String.class, Functions::to_double));
     235        FACTORY_MAP.put("to_float", Factory.of(String.class, Functions::to_float));
     236        FACTORY_MAP.put("to_int", Factory.of(String.class, Functions::to_int));
     237        FACTORY_MAP.put("to_long", Factory.of(String.class, Functions::to_long));
     238        FACTORY_MAP.put("to_short", Factory.of(String.class, Functions::to_short));
     239        FACTORY_MAP.put("tr", Factory.ofStringVarargs(Functions::tr));
     240        FACTORY_MAP.put("trim", Factory.of(String.class, Functions::trim));
     241        FACTORY_MAP.put("trim_list", Factory.of(List.class, Functions::trim_list));
     242        FACTORY_MAP.put("uniq", Factory.ofStringVarargs(Functions::uniq));
     243        FACTORY_MAP.put("uniq_list", Factory.of(List.class, Functions::uniq_list));
     244        FACTORY_MAP.put("upper", Factory.of(String.class, Functions::upper));
     245        FACTORY_MAP.put("waylength", Factory.ofEnv(Functions::waylength));
    80246    }
    81247
     
    105271        else if ("min".equals(name) && !args.isEmpty())
    106272            return new MinMaxFunction(args, false);
    107 
    108         for (Method m : arrayFunctions) {
    109             if (m.getName().equals(name))
    110                 return new ArrayFunction(m, args);
    111         }
    112         for (Method m : parameterFunctions) {
    113             if (m.getName().equals(name) && args.size() == m.getParameterTypes().length)
    114                 return new ParameterFunction(m, args, false);
    115         }
    116         for (Method m : parameterFunctionsEnv) {
    117             if (m.getName().equals(name) && args.size() == m.getParameterTypes().length-1)
    118                 return new ParameterFunction(m, args, true);
     273        else if ("inside".equals(name) && args.size() == 1)
     274            return new IsInsideFunction(args.get(0));
     275        else if ("random".equals(name))
     276            return env -> Math.random();
     277
     278        Factory factory = FACTORY_MAP.get(name);
     279        if (factory != null) {
     280            return factory.createExpression(args);
    119281        }
    120282        return NullExpression.INSTANCE;
     
    283445
    284446    /**
    285      * Function that takes a certain number of argument with specific type.
     447     * {@code Functions#inside} implementation for use in {@link org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker}
    286448     *
    287      * Implementation is based on a Method object.
    288      * If any of the arguments evaluate to null, the result will also be null.
    289      */
    290     public static class ParameterFunction implements Expression {
    291 
    292         private final Method m;
    293         private final boolean nullable;
    294         private final List<Expression> args;
    295         private final Class<?>[] expectedParameterTypes;
    296         private final boolean needsEnvironment;
    297 
    298         /**
    299          * Constructs a new {@code ParameterFunction}.
    300          * @param m method
    301          * @param args arguments
    302          * @param needsEnvironment whether function needs environment
    303          */
    304         public ParameterFunction(Method m, List<Expression> args, boolean needsEnvironment) {
    305             this.m = m;
    306             this.nullable = m.getAnnotation(NullableArguments.class) != null;
    307             this.args = args;
    308             this.expectedParameterTypes = m.getParameterTypes();
    309             this.needsEnvironment = needsEnvironment;
    310         }
    311 
    312         /**
    313          * Returns the method.
    314          * @return the method
    315          * @since 14484
    316          */
    317         public final Method getMethod() {
    318             return m;
    319         }
    320 
    321         /**
    322          * Returns the arguments.
    323          * @return the arguments
    324          * @since 14484
    325          */
    326         public final List<Expression> getArgs() {
    327             return args;
    328         }
    329 
    330         @Override
    331         public Object evaluate(Environment env) {
    332             Object[] convertedArgs;
    333 
    334             int start = 0;
    335             int offset = 0;
    336             if (needsEnvironment) {
    337                 start = 1;
    338                 offset = 1;
    339                 convertedArgs = new Object[args.size() + 1];
    340                 convertedArgs[0] = env;
    341             } else {
    342                 convertedArgs = new Object[args.size()];
    343             }
    344 
    345             for (int i = start; i < convertedArgs.length; ++i) {
    346                 if (!expectedParameterTypes[i].isArray()) {
    347                     convertedArgs[i] = Cascade.convertTo(args.get(i - offset).evaluate(env), expectedParameterTypes[i]);
    348                 } else {
    349                     Class<?> clazz = expectedParameterTypes[i].getComponentType();
    350                     Object[] varargs = (Object[]) Array.newInstance(clazz, args.size() - i + 1);
    351                     for (int j = 0; j < args.size() - i + 1; ++j) {
    352                         varargs[j] = Cascade.convertTo(args.get(j + i - 1).evaluate(env), clazz);
    353                     }
    354                     convertedArgs[i] = expectedParameterTypes[i].cast(varargs);
    355                     break;
    356                 }
    357                 if (convertedArgs[i] == null && !nullable) {
    358                     return null;
    359                 }
    360             }
    361 
    362             Object result = null;
    363             try {
    364                 result = m.invoke(null, convertedArgs);
    365             } catch (IllegalAccessException | IllegalArgumentException ex) {
    366                 throw new JosmRuntimeException(ex);
    367             } catch (InvocationTargetException ex) {
    368                 Logging.error(ex);
    369                 return null;
    370             }
    371             return result;
    372         }
    373 
    374         @Override
    375         public String toString() {
    376             StringBuilder b = new StringBuilder("ParameterFunction~");
    377             b.append(m.getName()).append('(');
    378             for (int i = 0; i < expectedParameterTypes.length; ++i) {
    379                 if (i > 0) b.append(',');
    380                 b.append(expectedParameterTypes[i]);
    381                 if (!needsEnvironment) {
    382                     b.append(' ').append(args.get(i));
    383                 } else if (i > 0) {
    384                     b.append(' ').append(args.get(i-1));
    385                 }
    386             }
    387             b.append(')');
    388             return b.toString();
    389         }
    390     }
    391 
    392     /**
    393      * Function that takes an arbitrary number of arguments.
    394      *
    395      * Currently, all array functions are static, so there is no need to
    396      * provide the environment, like it is done in {@link ParameterFunction}.
    397      * If any of the arguments evaluate to null, the result will also be null.
    398      */
    399     public static class ArrayFunction implements Expression {
    400 
    401         private final Method m;
    402         private final boolean nullable;
    403         private final List<Expression> args;
    404         private final Class<?>[] expectedParameterTypes;
    405         private final Class<?> arrayComponentType;
    406 
    407         /**
    408          * Constructs a new {@code ArrayFunction}.
    409          * @param m method
    410          * @param args arguments
    411          */
    412         public ArrayFunction(Method m, List<Expression> args) {
    413             this.m = m;
    414             this.nullable = m.getAnnotation(NullableArguments.class) != null;
    415             this.args = args;
    416             this.expectedParameterTypes = m.getParameterTypes();
    417             this.arrayComponentType = expectedParameterTypes[0].getComponentType();
    418         }
    419 
    420         @Override
    421         public Object evaluate(Environment env) {
    422             Object[] convertedArgs = new Object[expectedParameterTypes.length];
    423             Object arrayArg = Array.newInstance(arrayComponentType, args.size());
    424             for (int i = 0; i < args.size(); ++i) {
    425                 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
    426                 if (o == null && !nullable) {
    427                     return null;
    428                 }
    429                 Array.set(arrayArg, i, o);
    430             }
    431             convertedArgs[0] = arrayArg;
    432 
    433             Object result = null;
    434             try {
    435                 result = m.invoke(null, convertedArgs);
    436             } catch (IllegalAccessException | IllegalArgumentException ex) {
    437                 throw new JosmRuntimeException(ex);
    438             } catch (InvocationTargetException ex) {
    439                 Logging.error(ex);
    440                 return null;
    441             }
    442             return result;
    443         }
    444 
    445         @Override
    446         public String toString() {
    447             return args.stream()
    448                     .map(arg -> arrayComponentType + " " + arg)
    449                     .collect(Collectors.joining(",", "ArrayFunction~" + m.getName() + '(', ")"));
     449     * @see Functions#inside
     450     */
     451    public static class IsInsideFunction implements Expression {
     452        private final Expression arg;
     453
     454        /**
     455         * Constructs a new {@code IsInsideFunction}.
     456         * @param arg argument
     457         */
     458        public IsInsideFunction(Expression arg) {
     459            this.arg = arg;
     460        }
     461
     462        /**
     463         * Returns the argument
     464         * @return the argument
     465         */
     466        public Expression getArg() {
     467            return arg;
     468        }
     469
     470        @Override
     471        public Object evaluate(Environment env) {
     472            String codes = Cascade.convertTo(arg.evaluate(env), String.class);
     473            return Functions.inside(env, codes);
    450474        }
    451475    }
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java

    r17614 r17758  
    7272    /**
    7373     * Function associated to the numeric "+" operator.
    74      * @param args arguments
     74     * @param a the first operand
     75     * @param b the second operand
    7576     * @return Sum of arguments
    76      */
    77     public static float plus(float... args) { // NO_UCD (unused code)
    78         float res = 0;
    79         for (float f : args) {
    80             res += f;
    81         }
    82         return res;
     77     * @see Float#sum
     78     */
     79    public static double plus(double a, double b) { // NO_UCD (unused code)
     80        return a + b;
    8381    }
    8482
    8583    /**
    8684     * Function associated to the numeric "-" operator.
    87      * @param args arguments
    88      * @return Substraction of arguments
    89      */
    90     public static Float minus(float... args) { // NO_UCD (unused code)
    91         if (args.length == 0) {
    92             return 0.0F;
    93         }
    94         if (args.length == 1) {
    95             return -args[0];
    96         }
    97         float res = args[0];
    98         for (int i = 1; i < args.length; ++i) {
    99             res -= args[i];
    100         }
    101         return res;
     85     * @param a the first operand
     86     * @param b the second operand
     87     * @return Subtraction of arguments
     88     */
     89    public static double minus(double a, double b) { // NO_UCD (unused code)
     90        return a - b;
    10291    }
    10392
    10493    /**
    10594     * Function associated to the numeric "*" operator.
    106      * @param args arguments
     95     * @param a the first operand
     96     * @param b the second operand
    10797     * @return Multiplication of arguments
    10898     */
    109     public static float times(float... args) { // NO_UCD (unused code)
    110         float res = 1;
    111         for (float f : args) {
    112             res *= f;
    113         }
    114         return res;
     99    public static double times(double a, double b) { // NO_UCD (unused code)
     100        return a * b;
    115101    }
    116102
    117103    /**
    118104     * Function associated to the numeric "/" operator.
    119      * @param args arguments
     105     * @param a the first operand
     106     * @param b the second operand
    120107     * @return Division of arguments
    121108     */
    122     public static Float divided_by(float... args) { // NO_UCD (unused code)
    123         if (args.length == 0) {
    124             return 1.0F;
    125         }
    126         float res = args[0];
    127         for (int i = 1; i < args.length; ++i) {
    128             if (args[i] == 0) {
    129                 return null;
    130             }
    131             res /= args[i];
    132         }
    133         return res;
     109    public static double divided_by(double a, double b) { // NO_UCD (unused code)
     110        return a / b;
    134111    }
    135112
    136113    /**
    137114     * Creates a list of values, e.g., for the {@code dashes} property.
     115     * @param ignored The environment (ignored)
    138116     * @param args The values to put in a list
    139117     * @return list of values
    140118     * @see Arrays#asList(Object[])
    141119     */
    142     public static List<Object> list(Object... args) { // NO_UCD (unused code)
     120    public static List<Object> list(Environment ignored, Object... args) { // NO_UCD (unused code)
    143121        return Arrays.asList(args);
    144122    }
     
    156134     * Returns the first non-null object.
    157135     * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>.
     136     * @param ignored The environment (ignored)
    158137     * @param args arguments
    159138     * @return the first non-null object
     
    161140     */
    162141    @NullableArguments
    163     public static Object any(Object... args) { // NO_UCD (unused code)
     142    public static Object any(Environment ignored, Object... args) { // NO_UCD (unused code)
    164143        return Utils.firstNonNull(args);
    165144    }
     
    304283    /**
    305284     * Assembles the strings to one.
     285     * @param ignored The environment (ignored)
    306286     * @param args arguments
    307287     * @return assembled string
     
    309289     */
    310290    @NullableArguments
    311     public static String concat(Object... args) { // NO_UCD (unused code)
     291    public static String concat(Environment ignored, Object... args) { // NO_UCD (unused code)
    312292        return Arrays.stream(args)
    313293                .filter(Objects::nonNull)
     
    318298    /**
    319299     * Assembles the strings to one, where the first entry is used as separator.
     300     * @param ignored The environment (ignored)
    320301     * @param args arguments. First one is used as separator
    321302     * @return assembled string
     
    323304     */
    324305    @NullableArguments
    325     public static String join(String... args) { // NO_UCD (unused code)
     306    public static String join(Environment ignored, String... args) { // NO_UCD (unused code)
    326307        return String.join(args[0], Arrays.asList(args).subList(1, args.length));
    327308    }
     
    548529    /**
    549530     * Sort an array of strings
     531     * @param ignored The environment (ignored)
    550532     * @param sortables The array to sort
    551533     * @return The sorted list
    552534     * @since 15279
    553535     */
    554     public static List<String> sort(String... sortables) { // NO_UCD (unused code)
     536    public static List<String> sort(Environment ignored, String... sortables) { // NO_UCD (unused code)
    555537        Arrays.parallelSort(sortables);
    556538        return Arrays.asList(sortables);
     
    570552    /**
    571553     * Get unique values
     554     * @param ignored The environment (ignored)
    572555     * @param values A list of values that may have duplicates
    573556     * @return A list with no duplicates
    574557     * @since 15323
    575558     */
    576     public static List<String> uniq(String... values) { // NO_UCD (unused code)
     559    public static List<String> uniq(Environment ignored, String... values) { // NO_UCD (unused code)
    577560        return uniq_list(Arrays.asList(values));
    578561    }
     
    904887     * Translates some text for the current locale. The first argument is the text to translate,
    905888     * and the subsequent arguments are parameters for the string indicated by <code>{0}</code>, <code>{1}</code>, …
     889     * @param ignored The environment (ignored)
    906890     * @param args arguments
    907891     * @return the translated string
    908892     */
    909893    @NullableArguments
    910     public static String tr(String... args) { // NO_UCD (unused code)
     894    public static String tr(Environment ignored, String... args) { // NO_UCD (unused code)
    911895        final String text = args[0];
    912896        System.arraycopy(args, 1, args, 0, args.length - 1);
  • trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactoryTest.java

    r17275 r17758  
    88import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    99import net.trajano.commons.testing.UtilityClassTestUtil;
     10
     11import java.lang.reflect.Method;
     12import java.lang.reflect.Modifier;
    1013
    1114/**
     
    2932        UtilityClassTestUtil.assertUtilityClassWellDefined(Functions.class);
    3033    }
     34
     35    /**
     36     * Tests that all functions have been registered to {@link ExpressionFactory#FACTORY_MAP}
     37     *
     38     * For instance to register {@link Functions#osm_id}, {@code FACTORY_MAP.put("osm_id", Factory.ofEnv(Functions::osm_id))}
     39     */
     40    @Test
     41    void testNoUnregisteredFunctions() {
     42        for (Method m : Functions.class.getDeclaredMethods()) {
     43            if (!Modifier.isPrivate(m.getModifiers()) && !ExpressionFactory.FACTORY_MAP.containsKey(m.getName())) {
     44                throw new AssertionError(m + " has not registered in ExpressionFactory.FACTORY_MAP");
     45            }
     46        }
     47    }
    3148}
  • trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParserTest.java

    r17745 r17758  
    368368        MultiCascade mc = new MultiCascade();
    369369        sheet.apply(mc, OsmUtils.createPrimitive("way foo=bar"), 20, false);
    370         assertEquals(Float.valueOf(5f), mc.getCascade(null).get("width"));
     370        assertEquals(5.0f, mc.getCascade(null).get("width"));
    371371        sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true"), 20, false);
    372         assertEquals(Float.valueOf(15f), mc.getCascade(null).get("width"));
     372        assertEquals(15.0, mc.getCascade(null).get("width"));
    373373        sheet.apply(mc, OsmUtils.createPrimitive("way keyB=true"), 20, false);
    374         assertEquals(Float.valueOf(15f), mc.getCascade(null).get("width"));
     374        assertEquals(15.0, mc.getCascade(null).get("width"));
    375375        sheet.apply(mc, OsmUtils.createPrimitive("way keyA=true keyB=true"), 20, false);
    376         assertEquals(Float.valueOf(15f), mc.getCascade(null).get("width"));
     376        assertEquals(15.0, mc.getCascade(null).get("width"));
    377377    }
    378378
     
    480480    @Test
    481481    void testSort() throws Exception {
    482         assertEquals(Arrays.asList(new String[] {"alpha", "beta"}), Functions.sort("beta", "alpha"));
     482        assertEquals(Arrays.asList(new String[] {"alpha", "beta"}), Functions.sort(null, "beta", "alpha"));
    483483        Way way1 = TestUtils.newWay("highway=residential name=Alpha alt_name=Beta ref=\"A9;A8\"", new Node(new LatLon(0.001, 0.001)),
    484484                new Node(new LatLon(0.002, 0.002)));
     
    490490        assertTrue(source.rules.get(0).matches(e));
    491491        source.rules.get(0).declaration.execute(e);
    492         assertEquals(Functions.join(",", "Alpha", "Beta"), e.getCascade(null).get("sorted", null, String.class));
     492        assertEquals(Functions.join(null, ",", "Alpha", "Beta"), e.getCascade(null).get("sorted", null, String.class));
    493493
    494494        source = new MapCSSStyleSource("way[ref] {sorted: join_list(\",\", sort_list(split(\";\", tag(\"ref\"))));}");
     
    497497        assertTrue(source.rules.get(0).matches(e));
    498498        source.rules.get(0).declaration.execute(e);
    499         assertEquals(Functions.join(",", "A8", "A9"), e.getCascade(null).get("sorted", null, String.class));
     499        assertEquals(Functions.join(null, ",", "A8", "A9"), e.getCascade(null).get("sorted", null, String.class));
    500500    }
    501501
     
    503503    void testUniqueValues() throws Exception {
    504504        assertEquals(Arrays.asList(new String[] {"alpha", "beta"}),
    505                 Functions.uniq("alpha", "alpha", "alpha", "beta"));
     505                Functions.uniq(null, "alpha", "alpha", "alpha", "beta"));
    506506        assertEquals(Arrays.asList(new String[] {"one", "two", "three"}),
    507507                Functions.uniq_list(Arrays.asList(new String[] {"one", "one", "two", "two", "two", "three"})));
     
    597597
    598598    @Test
     599    void testMath() {
     600        MapCSSStyleSource source = new MapCSSStyleSource("node { add: 1 + 2 + 3 + 4; mul: 2 * 3 * 5 * 7; sub: 0 - 1 - 2 - 3; div: 360 / 15; }");
     601        source.loadStyleSource();
     602        MultiCascade mc = new MultiCascade();
     603        source.apply(mc, OsmUtils.createPrimitive("node"), 20, false);
     604        assertEquals(10.0, mc.getCascade(null).get("add"));
     605        assertEquals(210.0, mc.getCascade(null).get("mul"));
     606        assertEquals(-6.0, mc.getCascade(null).get("sub"));
     607        assertEquals(24.0, mc.getCascade(null).get("div"));
     608    }
     609
     610    @Test
    599611    void testMinMaxFunctions() throws Exception {
    600612        MapCSSStyleSource sheet = new MapCSSStyleSource("* {" +
Note: See TracChangeset for help on using the changeset viewer.