001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004import java.awt.Color;
005import java.lang.annotation.ElementType;
006import java.lang.annotation.Retention;
007import java.lang.annotation.RetentionPolicy;
008import java.lang.annotation.Target;
009import java.lang.reflect.Array;
010import java.lang.reflect.InvocationTargetException;
011import java.lang.reflect.Method;
012import java.nio.charset.StandardCharsets;
013import java.util.ArrayList;
014import java.util.Arrays;
015import java.util.Collection;
016import java.util.Collections;
017import java.util.List;
018import java.util.Locale;
019import java.util.Objects;
020import java.util.TreeSet;
021import java.util.function.Function;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024import java.util.zip.CRC32;
025
026import org.openstreetmap.josm.data.coor.LatLon;
027import org.openstreetmap.josm.data.gpx.GpxDistance;
028import org.openstreetmap.josm.data.osm.IPrimitive;
029import org.openstreetmap.josm.data.osm.Node;
030import org.openstreetmap.josm.data.osm.OsmPrimitive;
031import org.openstreetmap.josm.data.osm.Relation;
032import org.openstreetmap.josm.data.osm.RelationMember;
033import org.openstreetmap.josm.data.osm.Way;
034import org.openstreetmap.josm.data.osm.search.SearchCompiler;
035import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
036import org.openstreetmap.josm.data.osm.search.SearchParseError;
037import org.openstreetmap.josm.gui.MainApplication;
038import org.openstreetmap.josm.gui.mappaint.Cascade;
039import org.openstreetmap.josm.gui.mappaint.Environment;
040import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
041import org.openstreetmap.josm.io.XmlWriter;
042import org.openstreetmap.josm.tools.AlphanumComparator;
043import org.openstreetmap.josm.tools.ColorHelper;
044import org.openstreetmap.josm.tools.Geometry;
045import org.openstreetmap.josm.tools.JosmRuntimeException;
046import org.openstreetmap.josm.tools.Logging;
047import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
048import org.openstreetmap.josm.tools.RotationAngle;
049import org.openstreetmap.josm.tools.SubclassFilteredCollection;
050import org.openstreetmap.josm.tools.Territories;
051import org.openstreetmap.josm.tools.Utils;
052
053/**
054 * Factory to generate {@link Expression}s.
055 * <p>
056 * See {@link #createFunctionExpression}.
057 */
058public final class ExpressionFactory {
059
060    /**
061     * Marks functions which should be executed also when one or more arguments are null.
062     */
063    @Target(ElementType.METHOD)
064    @Retention(RetentionPolicy.RUNTIME)
065    @interface NullableArguments {}
066
067    private static final List<Method> arrayFunctions = new ArrayList<>();
068    private static final List<Method> parameterFunctions = new ArrayList<>();
069    private static final List<Method> parameterFunctionsEnv = new ArrayList<>();
070
071    static {
072        for (Method m : Functions.class.getDeclaredMethods()) {
073            Class<?>[] paramTypes = m.getParameterTypes();
074            if (paramTypes.length == 1 && paramTypes[0].isArray()) {
075                arrayFunctions.add(m);
076            } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) {
077                parameterFunctionsEnv.add(m);
078            } else {
079                parameterFunctions.add(m);
080            }
081        }
082        try {
083            parameterFunctions.add(Math.class.getMethod("abs", float.class));
084            parameterFunctions.add(Math.class.getMethod("acos", double.class));
085            parameterFunctions.add(Math.class.getMethod("asin", double.class));
086            parameterFunctions.add(Math.class.getMethod("atan", double.class));
087            parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class));
088            parameterFunctions.add(Math.class.getMethod("ceil", double.class));
089            parameterFunctions.add(Math.class.getMethod("cos", double.class));
090            parameterFunctions.add(Math.class.getMethod("cosh", double.class));
091            parameterFunctions.add(Math.class.getMethod("exp", double.class));
092            parameterFunctions.add(Math.class.getMethod("floor", double.class));
093            parameterFunctions.add(Math.class.getMethod("log", double.class));
094            parameterFunctions.add(Math.class.getMethod("max", float.class, float.class));
095            parameterFunctions.add(Math.class.getMethod("min", float.class, float.class));
096            parameterFunctions.add(Math.class.getMethod("random"));
097            parameterFunctions.add(Math.class.getMethod("round", float.class));
098            parameterFunctions.add(Math.class.getMethod("signum", double.class));
099            parameterFunctions.add(Math.class.getMethod("sin", double.class));
100            parameterFunctions.add(Math.class.getMethod("sinh", double.class));
101            parameterFunctions.add(Math.class.getMethod("sqrt", double.class));
102            parameterFunctions.add(Math.class.getMethod("tan", double.class));
103            parameterFunctions.add(Math.class.getMethod("tanh", double.class));
104        } catch (NoSuchMethodException | SecurityException ex) {
105            throw new JosmRuntimeException(ex);
106        }
107    }
108
109    private ExpressionFactory() {
110        // Hide default constructor for utils classes
111    }
112
113    /**
114     * List of functions that can be used in MapCSS expressions.
115     *
116     * First parameter can be of type {@link Environment} (if needed). This is
117     * automatically filled in by JOSM and the user only sees the remaining arguments.
118     * When one of the user supplied arguments cannot be converted the
119     * expected type or is null, the function is not called and it returns null
120     * immediately. Add the annotation {@link NullableArguments} to allow null arguments.
121     * Every method must be static.
122     */
123    @SuppressWarnings("UnusedDeclaration")
124    public static final class Functions {
125
126        private Functions() {
127            // Hide implicit public constructor for utility classes
128        }
129
130        /**
131         * Identity function for compatibility with MapCSS specification.
132         * @param o any object
133         * @return {@code o} unchanged
134         */
135        public static Object eval(Object o) { // NO_UCD (unused code)
136            return o;
137        }
138
139        /**
140         * Function associated to the numeric "+" operator.
141         * @param args arguments
142         * @return Sum of arguments
143         */
144        public static float plus(float... args) { // NO_UCD (unused code)
145            float res = 0;
146            for (float f : args) {
147                res += f;
148            }
149            return res;
150        }
151
152        /**
153         * Function associated to the numeric "-" operator.
154         * @param args arguments
155         * @return Substraction of arguments
156         */
157        public static Float minus(float... args) { // NO_UCD (unused code)
158            if (args.length == 0) {
159                return 0.0F;
160            }
161            if (args.length == 1) {
162                return -args[0];
163            }
164            float res = args[0];
165            for (int i = 1; i < args.length; ++i) {
166                res -= args[i];
167            }
168            return res;
169        }
170
171        /**
172         * Function associated to the numeric "*" operator.
173         * @param args arguments
174         * @return Multiplication of arguments
175         */
176        public static float times(float... args) { // NO_UCD (unused code)
177            float res = 1;
178            for (float f : args) {
179                res *= f;
180            }
181            return res;
182        }
183
184        /**
185         * Function associated to the numeric "/" operator.
186         * @param args arguments
187         * @return Division of arguments
188         */
189        public static Float divided_by(float... args) { // NO_UCD (unused code)
190            if (args.length == 0) {
191                return 1.0F;
192            }
193            float res = args[0];
194            for (int i = 1; i < args.length; ++i) {
195                if (args[i] == 0) {
196                    return null;
197                }
198                res /= args[i];
199            }
200            return res;
201        }
202
203        /**
204         * Creates a list of values, e.g., for the {@code dashes} property.
205         * @param args The values to put in a list
206         * @return list of values
207         * @see Arrays#asList(Object[])
208         */
209        public static List<Object> list(Object... args) { // NO_UCD (unused code)
210            return Arrays.asList(args);
211        }
212
213        /**
214         * Returns the number of elements in a list.
215         * @param lst the list
216         * @return length of the list
217         */
218        public static Integer count(List<?> lst) { // NO_UCD (unused code)
219            return lst.size();
220        }
221
222        /**
223         * Returns the first non-null object.
224         * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>.
225         * @param args arguments
226         * @return the first non-null object
227         * @see Utils#firstNonNull(Object[])
228         */
229        @NullableArguments
230        public static Object any(Object... args) { // NO_UCD (unused code)
231            return Utils.firstNonNull(args);
232        }
233
234        /**
235         * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
236         * @param lst list
237         * @param n index
238         * @return {@code n}th element of the list, or {@code null} if index out of range
239         * @since 5699
240         */
241        public static Object get(List<?> lst, float n) { // NO_UCD (unused code)
242            int idx = Math.round(n);
243            if (idx >= 0 && idx < lst.size()) {
244                return lst.get(idx);
245            }
246            return null;
247        }
248
249        /**
250         * Splits string {@code toSplit} at occurrences of the separator string {@code sep} and returns a list of matches.
251         * @param sep separator string
252         * @param toSplit string to split
253         * @return list of matches
254         * @see String#split(String)
255         * @since 5699
256         */
257        public static List<String> split(String sep, String toSplit) { // NO_UCD (unused code)
258            return Arrays.asList(toSplit.split(Pattern.quote(sep), -1));
259        }
260
261        /**
262         * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue (arguments from 0.0 to 1.0)
263         * @param r the red component
264         * @param g the green component
265         * @param b the blue component
266         * @return color matching the given components
267         * @see Color#Color(float, float, float)
268         */
269        public static Color rgb(float r, float g, float b) { // NO_UCD (unused code)
270            try {
271                return new Color(r, g, b);
272            } catch (IllegalArgumentException e) {
273                Logging.trace(e);
274                return null;
275            }
276        }
277
278        /**
279         * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue, {@code alpha}
280         * (arguments from 0.0 to 1.0)
281         * @param r the red component
282         * @param g the green component
283         * @param b the blue component
284         * @param alpha the alpha component
285         * @return color matching the given components
286         * @see Color#Color(float, float, float, float)
287         */
288        public static Color rgba(float r, float g, float b, float alpha) { // NO_UCD (unused code)
289            try {
290                return new Color(r, g, b, alpha);
291            } catch (IllegalArgumentException e) {
292                Logging.trace(e);
293                return null;
294            }
295        }
296
297        /**
298         * Create color from hsb color model. (arguments form 0.0 to 1.0)
299         * @param h hue
300         * @param s saturation
301         * @param b brightness
302         * @return the corresponding color
303         */
304        public static Color hsb_color(float h, float s, float b) { // NO_UCD (unused code)
305            try {
306                return Color.getHSBColor(h, s, b);
307            } catch (IllegalArgumentException e) {
308                Logging.trace(e);
309                return null;
310            }
311        }
312
313        /**
314         * Creates a color value from an HTML notation, i.e., {@code #rrggbb}.
315         * @param html HTML notation
316         * @return color matching the given notation
317         */
318        public static Color html2color(String html) { // NO_UCD (unused code)
319            return ColorHelper.html2color(html);
320        }
321
322        /**
323         * Computes the HTML notation ({@code #rrggbb}) for a color value).
324         * @param c color
325         * @return HTML notation matching the given color
326         */
327        public static String color2html(Color c) { // NO_UCD (unused code)
328            return ColorHelper.color2html(c);
329        }
330
331        /**
332         * Get the value of the red color channel in the rgb color model
333         * @param c color
334         * @return the red color channel in the range [0;1]
335         * @see java.awt.Color#getRed()
336         */
337        public static float red(Color c) { // NO_UCD (unused code)
338            return Utils.colorInt2float(c.getRed());
339        }
340
341        /**
342         * Get the value of the green color channel in the rgb color model
343         * @param c color
344         * @return the green color channel in the range [0;1]
345         * @see java.awt.Color#getGreen()
346         */
347        public static float green(Color c) { // NO_UCD (unused code)
348            return Utils.colorInt2float(c.getGreen());
349        }
350
351        /**
352         * Get the value of the blue color channel in the rgb color model
353         * @param c color
354         * @return the blue color channel in the range [0;1]
355         * @see java.awt.Color#getBlue()
356         */
357        public static float blue(Color c) { // NO_UCD (unused code)
358            return Utils.colorInt2float(c.getBlue());
359        }
360
361        /**
362         * Get the value of the alpha channel in the rgba color model
363         * @param c color
364         * @return the alpha channel in the range [0;1]
365         * @see java.awt.Color#getAlpha()
366         */
367        public static float alpha(Color c) { // NO_UCD (unused code)
368            return Utils.colorInt2float(c.getAlpha());
369        }
370
371        /**
372         * Assembles the strings to one.
373         * @param args arguments
374         * @return assembled string
375         * @see Utils#join
376         */
377        @NullableArguments
378        public static String concat(Object... args) { // NO_UCD (unused code)
379            return Utils.join("", Arrays.asList(args));
380        }
381
382        /**
383         * Assembles the strings to one, where the first entry is used as separator.
384         * @param args arguments. First one is used as separator
385         * @return assembled string
386         * @see Utils#join
387         */
388        @NullableArguments
389        public static String join(String... args) { // NO_UCD (unused code)
390            return Utils.join(args[0], Arrays.asList(args).subList(1, args.length));
391        }
392
393        /**
394         * Joins a list of {@code values} into a single string with fields separated by {@code separator}.
395         * @param separator the separator
396         * @param values collection of objects
397         * @return assembled string
398         * @see Utils#join
399         */
400        public static String join_list(final String separator, final List<String> values) { // NO_UCD (unused code)
401            return Utils.join(separator, values);
402        }
403
404        /**
405         * Returns the value of the property {@code key}, e.g., {@code prop("width")}.
406         * @param env the environment
407         * @param key the property key
408         * @return the property value
409         */
410        public static Object prop(final Environment env, String key) { // NO_UCD (unused code)
411            return prop(env, key, null);
412        }
413
414        /**
415         * Returns the value of the property {@code key} from layer {@code layer}.
416         * @param env the environment
417         * @param key the property key
418         * @param layer layer
419         * @return the property value
420         */
421        public static Object prop(final Environment env, String key, String layer) {
422            return env.getCascade(layer).get(key);
423        }
424
425        /**
426         * Determines whether property {@code key} is set.
427         * @param env the environment
428         * @param key the property key
429         * @return {@code true} if the property is set, {@code false} otherwise
430         */
431        public static Boolean is_prop_set(final Environment env, String key) { // NO_UCD (unused code)
432            return is_prop_set(env, key, null);
433        }
434
435        /**
436         * Determines whether property {@code key} is set on layer {@code layer}.
437         * @param env the environment
438         * @param key the property key
439         * @param layer layer
440         * @return {@code true} if the property is set, {@code false} otherwise
441         */
442        public static Boolean is_prop_set(final Environment env, String key, String layer) {
443            return env.getCascade(layer).containsKey(key);
444        }
445
446        /**
447         * Gets the value of the key {@code key} from the object in question.
448         * @param env the environment
449         * @param key the OSM key
450         * @return the value for given key
451         */
452        public static String tag(final Environment env, String key) { // NO_UCD (unused code)
453            return env.osm == null ? null : env.osm.get(key);
454        }
455
456        /**
457         * Gets the first non-null value of the key {@code key} from the object's parent(s).
458         * @param env the environment
459         * @param key the OSM key
460         * @return first non-null value of the key {@code key} from the object's parent(s)
461         */
462        public static String parent_tag(final Environment env, String key) { // NO_UCD (unused code)
463            if (env.parent == null) {
464                if (env.osm != null) {
465                    // we don't have a matched parent, so just search all referrers
466                    for (IPrimitive parent : env.osm.getReferrers()) {
467                        String value = parent.get(key);
468                        if (value != null) {
469                            return value;
470                        }
471                    }
472                }
473                return null;
474            }
475            return env.parent.get(key);
476        }
477
478        /**
479         * Gets a list of all non-null values of the key {@code key} from the object's parent(s).
480         *
481         * The values are sorted according to {@link AlphanumComparator}.
482         * @param env the environment
483         * @param key the OSM key
484         * @return a list of non-null values of the key {@code key} from the object's parent(s)
485         */
486        public static List<String> parent_tags(final Environment env, String key) { // NO_UCD (unused code)
487            if (env.parent == null) {
488                if (env.osm != null) {
489                    final Collection<String> tags = new TreeSet<>(AlphanumComparator.getInstance());
490                    // we don't have a matched parent, so just search all referrers
491                    for (IPrimitive parent : env.osm.getReferrers()) {
492                        String value = parent.get(key);
493                        if (value != null) {
494                            tags.add(value);
495                        }
496                    }
497                    return new ArrayList<>(tags);
498                }
499                return Collections.emptyList();
500            }
501            return Collections.singletonList(env.parent.get(key));
502        }
503
504        /**
505         * Gets the value of the key {@code key} from the object's child.
506         * @param env the environment
507         * @param key the OSM key
508         * @return the value of the key {@code key} from the object's child, or {@code null} if there is no child
509         */
510        public static String child_tag(final Environment env, String key) { // NO_UCD (unused code)
511            return env.child == null ? null : env.child.get(key);
512        }
513
514        /**
515         * Returns the OSM id of the object's parent.
516         * <p>
517         * Parent must be matched by child selector.
518         * @param env the environment
519         * @return the OSM id of the object's parent, if available, or {@code null}
520         * @see IPrimitive#getUniqueId()
521         */
522        public static Long parent_osm_id(final Environment env) { // NO_UCD (unused code)
523            return env.parent == null ? null : env.parent.getUniqueId();
524        }
525
526        /**
527         * Returns the lowest distance between the OSM object and a GPX point
528         * <p>
529         * @param env the environment
530         * @return the distance between the object and the closest gpx point or {@code Double.MAX_VALUE}
531         * @since 14802
532         */
533        public static double gpx_distance(final Environment env) { // NO_UCD (unused code)
534            if (env.osm instanceof OsmPrimitive) {
535                return MainApplication.getLayerManager().getAllGpxData().stream()
536                        .mapToDouble(gpx -> GpxDistance.getLowestDistance((OsmPrimitive) env.osm, gpx))
537                        .min().orElse(Double.MAX_VALUE);
538            }
539            return Double.MAX_VALUE;
540        }
541
542        /**
543         * Determines whether the object has a tag with the given key.
544         * @param env the environment
545         * @param key the OSM key
546         * @return {@code true} if the object has a tag with the given key, {@code false} otherwise
547         */
548        public static boolean has_tag_key(final Environment env, String key) { // NO_UCD (unused code)
549            return env.osm.hasKey(key);
550        }
551
552        /**
553         * Returns the index of node in parent way or member in parent relation.
554         * @param env the environment
555         * @return the index as float. Starts at 1
556         */
557        public static Float index(final Environment env) { // NO_UCD (unused code)
558            if (env.index == null) {
559                return null;
560            }
561            return Float.valueOf(env.index + 1f);
562        }
563
564        /**
565         * Returns the role of current object in parent relation, or role of child if current object is a relation.
566         * @param env the environment
567         * @return role of current object in parent relation, or role of child if current object is a relation
568         * @see Environment#getRole()
569         */
570        public static String role(final Environment env) { // NO_UCD (unused code)
571            return env.getRole();
572        }
573
574        /**
575         * Returns true if role is in relation. Returns false if not a relation or it does not have the role.
576         * @param env the environment
577         * @param roles The roles to count in the relation
578         * @return The number of relation members with the specified role
579         * @since 15196
580         */
581        public static int count_roles(final Environment env, String... roles) { // NO_UCD (unused code)
582            int rValue = 0;
583            if (env.osm instanceof Relation) {
584                List<String> roleList = Arrays.asList(roles);
585                Relation rel = (Relation) env.osm;
586                for (RelationMember member : rel.getMembers()) {
587                    if (roleList.contains(member.getRole())) rValue++;
588                }
589            }
590            return rValue;
591        }
592
593        /**
594         * Returns the area of a closed way or multipolygon in square meters or {@code null}.
595         * @param env the environment
596         * @return the area of a closed way or multipolygon in square meters or {@code null}
597         * @see Geometry#computeArea(IPrimitive)
598         */
599        public static Float areasize(final Environment env) { // NO_UCD (unused code)
600            final Double area = Geometry.computeArea(env.osm);
601            return area == null ? null : area.floatValue();
602        }
603
604        /**
605         * Returns the length of the way in metres or {@code null}.
606         * @param env the environment
607         * @return the length of the way in metres or {@code null}.
608         * @see Way#getLength()
609         */
610        public static Float waylength(final Environment env) { // NO_UCD (unused code)
611            if (env.osm instanceof Way) {
612                return (float) ((Way) env.osm).getLength();
613            } else {
614                return null;
615            }
616        }
617
618        /**
619         * Function associated to the logical "!" operator.
620         * @param b boolean value
621         * @return {@code true} if {@code !b}
622         */
623        public static boolean not(boolean b) { // NO_UCD (unused code)
624            return !b;
625        }
626
627        /**
628         * Function associated to the logical "&gt;=" operator.
629         * @param a first value
630         * @param b second value
631         * @return {@code true} if {@code a &gt;= b}
632         */
633        public static boolean greater_equal(float a, float b) { // NO_UCD (unused code)
634            return a >= b;
635        }
636
637        /**
638         * Function associated to the logical "&lt;=" operator.
639         * @param a first value
640         * @param b second value
641         * @return {@code true} if {@code a &lt;= b}
642         */
643        public static boolean less_equal(float a, float b) { // NO_UCD (unused code)
644            return a <= b;
645        }
646
647        /**
648         * Function associated to the logical "&gt;" operator.
649         * @param a first value
650         * @param b second value
651         * @return {@code true} if {@code a &gt; b}
652         */
653        public static boolean greater(float a, float b) { // NO_UCD (unused code)
654            return a > b;
655        }
656
657        /**
658         * Function associated to the logical "&lt;" operator.
659         * @param a first value
660         * @param b second value
661         * @return {@code true} if {@code a &lt; b}
662         */
663        public static boolean less(float a, float b) { // NO_UCD (unused code)
664            return a < b;
665        }
666
667        /**
668         * Converts an angle in degrees to radians.
669         * @param degree the angle in degrees
670         * @return the angle in radians
671         * @see Math#toRadians(double)
672         */
673        public static double degree_to_radians(double degree) { // NO_UCD (unused code)
674            return Utils.toRadians(degree);
675        }
676
677        /**
678         * Converts an angle diven in cardinal directions to radians.
679         * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast},
680         * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south},
681         * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}.
682         * @param cardinal the angle in cardinal directions.
683         * @return the angle in radians
684         * @see RotationAngle#parseCardinalRotation(String)
685         */
686        public static Double cardinal_to_radians(String cardinal) { // NO_UCD (unused code)
687            try {
688                return RotationAngle.parseCardinalRotation(cardinal);
689            } catch (IllegalArgumentException ignore) {
690                Logging.trace(ignore);
691                return null;
692            }
693        }
694
695        /**
696         * Determines if the objects {@code a} and {@code b} are equal.
697         * @param a First object
698         * @param b Second object
699         * @return {@code true} if objects are equal, {@code false} otherwise
700         * @see Object#equals(Object)
701         */
702        public static boolean equal(Object a, Object b) {
703            if (a.getClass() == b.getClass()) return a.equals(b);
704            if (a.equals(Cascade.convertTo(b, a.getClass()))) return true;
705            return b.equals(Cascade.convertTo(a, b.getClass()));
706        }
707
708        /**
709         * Determines if the objects {@code a} and {@code b} are not equal.
710         * @param a First object
711         * @param b Second object
712         * @return {@code false} if objects are equal, {@code true} otherwise
713         * @see Object#equals(Object)
714         */
715        public static boolean not_equal(Object a, Object b) { // NO_UCD (unused code)
716            return !equal(a, b);
717        }
718
719        /**
720         * Determines whether the JOSM search with {@code searchStr} applies to the object.
721         * @param env the environment
722         * @param searchStr the search string
723         * @return {@code true} if the JOSM search with {@code searchStr} applies to the object
724         * @see SearchCompiler
725         */
726        public static Boolean JOSM_search(final Environment env, String searchStr) { // NO_UCD (unused code)
727            Match m;
728            try {
729                m = SearchCompiler.compile(searchStr);
730            } catch (SearchParseError ex) {
731                Logging.trace(ex);
732                return null;
733            }
734            return m.match(env.osm);
735        }
736
737        /**
738         * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
739         * and defaults to {@code def} if that is null.
740         * @param env the environment
741         * @param key Key in JOSM preference
742         * @param def Default value
743         * @return value for key, or default value if not found
744         */
745        public static String JOSM_pref(Environment env, String key, String def) { // NO_UCD (unused code)
746            return MapPaintStyles.getStyles().getPreferenceCached(key, def);
747        }
748
749        /**
750         * Tests if string {@code target} matches pattern {@code pattern}
751         * @param pattern The regex expression
752         * @param target The character sequence to be matched
753         * @return {@code true} if, and only if, the entire region sequence matches the pattern
754         * @see Pattern#matches(String, CharSequence)
755         * @since 5699
756         */
757        public static boolean regexp_test(String pattern, String target) { // NO_UCD (unused code)
758            return Pattern.matches(pattern, target);
759        }
760
761        /**
762         * Tests if string {@code target} matches pattern {@code pattern}
763         * @param pattern The regex expression
764         * @param target The character sequence to be matched
765         * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
766         * @return {@code true} if, and only if, the entire region sequence matches the pattern
767         * @see Pattern#CASE_INSENSITIVE
768         * @see Pattern#DOTALL
769         * @see Pattern#MULTILINE
770         * @since 5699
771         */
772        public static boolean regexp_test(String pattern, String target, String flags) { // NO_UCD (unused code)
773            int f = 0;
774            if (flags.contains("i")) {
775                f |= Pattern.CASE_INSENSITIVE;
776            }
777            if (flags.contains("s")) {
778                f |= Pattern.DOTALL;
779            }
780            if (flags.contains("m")) {
781                f |= Pattern.MULTILINE;
782            }
783            return Pattern.compile(pattern, f).matcher(target).matches();
784        }
785
786        /**
787         * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
788         * The first element (index 0) is the complete match (i.e. string).
789         * Further elements correspond to the bracketed parts of the regular expression.
790         * @param pattern The regex expression
791         * @param target The character sequence to be matched
792         * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
793         * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
794         * @see Pattern#CASE_INSENSITIVE
795         * @see Pattern#DOTALL
796         * @see Pattern#MULTILINE
797         * @since 5701
798         */
799        public static List<String> regexp_match(String pattern, String target, String flags) { // NO_UCD (unused code)
800            int f = 0;
801            if (flags.contains("i")) {
802                f |= Pattern.CASE_INSENSITIVE;
803            }
804            if (flags.contains("s")) {
805                f |= Pattern.DOTALL;
806            }
807            if (flags.contains("m")) {
808                f |= Pattern.MULTILINE;
809            }
810            return Utils.getMatches(Pattern.compile(pattern, f).matcher(target));
811        }
812
813        /**
814         * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
815         * The first element (index 0) is the complete match (i.e. string).
816         * Further elements correspond to the bracketed parts of the regular expression.
817         * @param pattern The regex expression
818         * @param target The character sequence to be matched
819         * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
820         * @since 5701
821         */
822        public static List<String> regexp_match(String pattern, String target) { // NO_UCD (unused code)
823            return Utils.getMatches(Pattern.compile(pattern).matcher(target));
824        }
825
826        /**
827         * Returns the OSM id of the current object.
828         * @param env the environment
829         * @return the OSM id of the current object
830         * @see IPrimitive#getUniqueId()
831         */
832        public static long osm_id(final Environment env) { // NO_UCD (unused code)
833            return env.osm.getUniqueId();
834        }
835
836        /**
837         * Translates some text for the current locale. The first argument is the text to translate,
838         * and the subsequent arguments are parameters for the string indicated by <code>{0}</code>, <code>{1}</code>, …
839         * @param args arguments
840         * @return the translated string
841         */
842        @NullableArguments
843        public static String tr(String... args) { // NO_UCD (unused code)
844            final String text = args[0];
845            System.arraycopy(args, 1, args, 0, args.length - 1);
846            return org.openstreetmap.josm.tools.I18n.tr(text, (Object[]) args);
847        }
848
849        /**
850         * Returns the substring of {@code s} starting at index {@code begin} (inclusive, 0-indexed).
851         * @param s The base string
852         * @param begin The start index
853         * @return the substring
854         * @see String#substring(int)
855         */
856        public static String substring(String s, /* due to missing Cascade.convertTo for int*/ float begin) { // NO_UCD (unused code)
857            return s == null ? null : s.substring((int) begin);
858        }
859
860        /**
861         * Returns the substring of {@code s} starting at index {@code begin} (inclusive)
862         * and ending at index {@code end}, (exclusive, 0-indexed).
863         * @param s The base string
864         * @param begin The start index
865         * @param end The end index
866         * @return the substring
867         * @see String#substring(int, int)
868         */
869        public static String substring(String s, float begin, float end) { // NO_UCD (unused code)
870            return s == null ? null : s.substring((int) begin, (int) end);
871        }
872
873        /**
874         * Replaces in {@code s} every {@code} target} substring by {@code replacement}.
875         * @param s The source string
876         * @param target The sequence of char values to be replaced
877         * @param replacement The replacement sequence of char values
878         * @return The resulting string
879         * @see String#replace(CharSequence, CharSequence)
880         */
881        public static String replace(String s, String target, String replacement) { // NO_UCD (unused code)
882            return s == null ? null : s.replace(target, replacement);
883        }
884
885        /**
886         * Converts string {@code s} to uppercase.
887         * @param s The source string
888         * @return The resulting string
889         * @see String#toUpperCase(Locale)
890         * @since 11756
891         */
892        public static String upper(String s) {
893            return s == null ? null : s.toUpperCase(Locale.ENGLISH);
894        }
895
896        /**
897         * Converts string {@code s} to lowercase.
898         * @param s The source string
899         * @return The resulting string
900         * @see String#toLowerCase(Locale)
901         * @since 11756
902         */
903        public static String lower(String s) {
904            return s == null ? null : s.toLowerCase(Locale.ENGLISH);
905        }
906
907        /**
908         * Trim whitespaces from the string {@code s}.
909         * @param s The source string
910         * @return The resulting string
911         * @see Utils#strip
912         * @since 11756
913         */
914        public static String trim(String s) {
915            return Utils.strip(s);
916        }
917
918        /**
919         * Check if two strings are similar, but not identical, i.e., have a Levenshtein distance of 1 or 2.
920         * @param string1 first string to compare
921         * @param string2 second string to compare
922         * @return true if the normalized strings are different but only a "little bit"
923         * @see Utils#isSimilar
924         * @since 14371
925         */
926        public static boolean is_similar(String string1, String string2) {
927            return Utils.isSimilar(string1, string2);
928        }
929
930        /**
931         * Percent-decode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
932         * This is especially useful for wikipedia titles
933         * @param s url-encoded string
934         * @return the decoded string, or original in case of an error
935         * @since 11756
936         */
937        public static String URL_decode(String s) {
938            if (s == null) return null;
939            try {
940                return Utils.decodeUrl(s);
941            } catch (IllegalStateException e) {
942                Logging.debug(e);
943                return s;
944            }
945        }
946
947        /**
948         * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
949         * This is especially useful for data urls, e.g.
950         * <code>concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
951         * @param s arbitrary string
952         * @return the encoded string
953         */
954        public static String URL_encode(String s) { // NO_UCD (unused code)
955            return s == null ? null : Utils.encodeUrl(s);
956        }
957
958        /**
959         * XML-encode a string.
960         *
961         * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
962         * @param s arbitrary string
963         * @return the encoded string
964         */
965        public static String XML_encode(String s) { // NO_UCD (unused code)
966            return s == null ? null : XmlWriter.encode(s);
967        }
968
969        /**
970         * Calculates the CRC32 checksum from a string (based on RFC 1952).
971         * @param s the string
972         * @return long value from 0 to 2^32-1
973         */
974        public static long CRC32_checksum(String s) { // NO_UCD (unused code)
975            CRC32 cs = new CRC32();
976            cs.update(s.getBytes(StandardCharsets.UTF_8));
977            return cs.getValue();
978        }
979
980        /**
981         * check if there is right-hand traffic at the current location
982         * @param env the environment
983         * @return true if there is right-hand traffic
984         * @since 7193
985         */
986        public static boolean is_right_hand_traffic(Environment env) {
987            return RightAndLefthandTraffic.isRightHandTraffic(center(env));
988        }
989
990        /**
991         * Determines whether the way is {@link Geometry#isClockwise closed and oriented clockwise},
992         * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in clockwise order}.
993         *
994         * @param env the environment
995         * @return true if the way is closed and oriented clockwise
996         */
997        public static boolean is_clockwise(Environment env) {
998            if (!(env.osm instanceof Way)) {
999                return false;
1000            }
1001            final Way way = (Way) env.osm;
1002            return (way.isClosed() && Geometry.isClockwise(way))
1003                || (!way.isClosed() && way.getNodesCount() > 2 && Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1004        }
1005
1006        /**
1007         * Determines whether the way is {@link Geometry#isClockwise closed and oriented anticlockwise},
1008         * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in anticlockwise order}.
1009         *
1010         * @param env the environment
1011         * @return true if the way is closed and oriented clockwise
1012         */
1013        public static boolean is_anticlockwise(Environment env) {
1014            if (!(env.osm instanceof Way)) {
1015                return false;
1016            }
1017            final Way way = (Way) env.osm;
1018            return (way.isClosed() && !Geometry.isClockwise(way))
1019                || (!way.isClosed() && way.getNodesCount() > 2 && !Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1020        }
1021
1022        /**
1023         * Prints the object to the command line (for debugging purpose).
1024         * @param o the object
1025         * @return the same object, unchanged
1026         */
1027        @NullableArguments
1028        public static Object print(Object o) { // NO_UCD (unused code)
1029            System.out.print(o == null ? "none" : o.toString());
1030            return o;
1031        }
1032
1033        /**
1034         * Prints the object to the command line, with new line at the end
1035         * (for debugging purpose).
1036         * @param o the object
1037         * @return the same object, unchanged
1038         */
1039        @NullableArguments
1040        public static Object println(Object o) { // NO_UCD (unused code)
1041            System.out.println(o == null ? "none" : o.toString());
1042            return o;
1043        }
1044
1045        /**
1046         * Get the number of tags for the current primitive.
1047         * @param env the environment
1048         * @return number of tags
1049         */
1050        public static int number_of_tags(Environment env) { // NO_UCD (unused code)
1051            return env.osm.getNumKeys();
1052        }
1053
1054        /**
1055         * Get value of a setting.
1056         * @param env the environment
1057         * @param key setting key (given as layer identifier, e.g. setting::mykey {...})
1058         * @return the value of the setting (calculated when the style is loaded)
1059         */
1060        public static Object setting(Environment env, String key) { // NO_UCD (unused code)
1061            return env.source.settingValues.get(key);
1062        }
1063
1064        /**
1065         * Returns the center of the environment OSM primitive.
1066         * @param env the environment
1067         * @return the center of the environment OSM primitive
1068         * @since 11247
1069         */
1070        public static LatLon center(Environment env) { // NO_UCD (unused code)
1071            return env.osm instanceof Node ? ((Node) env.osm).getCoor() : env.osm.getBBox().getCenter();
1072        }
1073
1074        /**
1075         * Determines if the object is inside territories matching given ISO3166 codes.
1076         * @param env the environment
1077         * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1078         * @return {@code true} if the object is inside territory matching given ISO3166 codes
1079         * @since 11247
1080         */
1081        public static boolean inside(Environment env, String codes) { // NO_UCD (unused code)
1082            for (String code : codes.toUpperCase(Locale.ENGLISH).split(",")) {
1083                if (Territories.isIso3166Code(code.trim(), center(env))) {
1084                    return true;
1085                }
1086            }
1087            return false;
1088        }
1089
1090        /**
1091         * Determines if the object is outside territories matching given ISO3166 codes.
1092         * @param env the environment
1093         * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1094         * @return {@code true} if the object is outside territory matching given ISO3166 codes
1095         * @since 11247
1096         */
1097        public static boolean outside(Environment env, String codes) { // NO_UCD (unused code)
1098            return !inside(env, codes);
1099        }
1100
1101        /**
1102         * Determines if the object centroid lies at given lat/lon coordinates.
1103         * @param env the environment
1104         * @param lat latitude, i.e., the north-south position in degrees
1105         * @param lon longitude, i.e., the east-west position in degrees
1106         * @return {@code true} if the object centroid lies at given lat/lon coordinates
1107         * @since 12514
1108         */
1109        public static boolean at(Environment env, double lat, double lon) { // NO_UCD (unused code)
1110            return new LatLon(lat, lon).equalsEpsilon(center(env));
1111        }
1112    }
1113
1114    /**
1115     * Main method to create an function-like expression.
1116     *
1117     * @param name the name of the function or operator
1118     * @param args the list of arguments (as expressions)
1119     * @return the generated Expression. If no suitable function can be found,
1120     * returns {@link NullExpression#INSTANCE}.
1121     */
1122    public static Expression createFunctionExpression(String name, List<Expression> args) {
1123        if ("cond".equals(name) && args.size() == 3)
1124            return new CondOperator(args.get(0), args.get(1), args.get(2));
1125        else if ("and".equals(name))
1126            return new AndOperator(args);
1127        else if ("or".equals(name))
1128            return new OrOperator(args);
1129        else if ("length".equals(name) && args.size() == 1)
1130            return new LengthFunction(args.get(0));
1131        else if ("max".equals(name) && !args.isEmpty())
1132            return new MinMaxFunction(args, true);
1133        else if ("min".equals(name) && !args.isEmpty())
1134            return new MinMaxFunction(args, false);
1135
1136        for (Method m : arrayFunctions) {
1137            if (m.getName().equals(name))
1138                return new ArrayFunction(m, args);
1139        }
1140        for (Method m : parameterFunctions) {
1141            if (m.getName().equals(name) && args.size() == m.getParameterTypes().length)
1142                return new ParameterFunction(m, args, false);
1143        }
1144        for (Method m : parameterFunctionsEnv) {
1145            if (m.getName().equals(name) && args.size() == m.getParameterTypes().length-1)
1146                return new ParameterFunction(m, args, true);
1147        }
1148        return NullExpression.INSTANCE;
1149    }
1150
1151    /**
1152     * Expression that always evaluates to null.
1153     */
1154    public static class NullExpression implements Expression {
1155
1156        /**
1157         * The unique instance.
1158         */
1159        public static final NullExpression INSTANCE = new NullExpression();
1160
1161        @Override
1162        public Object evaluate(Environment env) {
1163            return null;
1164        }
1165    }
1166
1167    /**
1168     * Conditional operator.
1169     */
1170    public static class CondOperator implements Expression {
1171
1172        private final Expression condition, firstOption, secondOption;
1173
1174        /**
1175         * Constructs a new {@code CondOperator}.
1176         * @param condition condition
1177         * @param firstOption first option
1178         * @param secondOption second option
1179         */
1180        public CondOperator(Expression condition, Expression firstOption, Expression secondOption) {
1181            this.condition = condition;
1182            this.firstOption = firstOption;
1183            this.secondOption = secondOption;
1184        }
1185
1186        @Override
1187        public Object evaluate(Environment env) {
1188            Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class);
1189            if (b != null && b)
1190                return firstOption.evaluate(env);
1191            else
1192                return secondOption.evaluate(env);
1193        }
1194    }
1195
1196    /**
1197     * "And" logical operator.
1198     */
1199    public static class AndOperator implements Expression {
1200
1201        private final List<Expression> args;
1202
1203        /**
1204         * Constructs a new {@code AndOperator}.
1205         * @param args arguments
1206         */
1207        public AndOperator(List<Expression> args) {
1208            this.args = args;
1209        }
1210
1211        @Override
1212        public Object evaluate(Environment env) {
1213            for (Expression arg : args) {
1214                Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
1215                if (b == null || !b) {
1216                    return Boolean.FALSE;
1217                }
1218            }
1219            return Boolean.TRUE;
1220        }
1221    }
1222
1223    /**
1224     * "Or" logical operator.
1225     */
1226    public static class OrOperator implements Expression {
1227
1228        private final List<Expression> args;
1229
1230        /**
1231         * Constructs a new {@code OrOperator}.
1232         * @param args arguments
1233         */
1234        public OrOperator(List<Expression> args) {
1235            this.args = args;
1236        }
1237
1238        @Override
1239        public Object evaluate(Environment env) {
1240            for (Expression arg : args) {
1241                Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
1242                if (b != null && b) {
1243                    return Boolean.TRUE;
1244                }
1245            }
1246            return Boolean.FALSE;
1247        }
1248    }
1249
1250    /**
1251     * Function to calculate the length of a string or list in a MapCSS eval expression.
1252     *
1253     * Separate implementation to support overloading for different argument types.
1254     *
1255     * The use for calculating the length of a list is deprecated, use
1256     * {@link Functions#count(java.util.List)} instead (see #10061).
1257     */
1258    public static class LengthFunction implements Expression {
1259
1260        private final Expression arg;
1261
1262        /**
1263         * Constructs a new {@code LengthFunction}.
1264         * @param args arguments
1265         */
1266        public LengthFunction(Expression args) {
1267            this.arg = args;
1268        }
1269
1270        @Override
1271        public Object evaluate(Environment env) {
1272            List<?> l = Cascade.convertTo(arg.evaluate(env), List.class);
1273            if (l != null)
1274                return l.size();
1275            String s = Cascade.convertTo(arg.evaluate(env), String.class);
1276            if (s != null)
1277                return s.length();
1278            return null;
1279        }
1280    }
1281
1282    /**
1283     * Computes the maximum/minimum value an arbitrary number of floats, or a list of floats.
1284     */
1285    public static class MinMaxFunction implements Expression {
1286
1287        private final List<Expression> args;
1288        private final boolean computeMax;
1289
1290        /**
1291         * Constructs a new {@code MinMaxFunction}.
1292         * @param args arguments
1293         * @param computeMax if {@code true}, compute max. If {@code false}, compute min
1294         */
1295        public MinMaxFunction(final List<Expression> args, final boolean computeMax) {
1296            this.args = args;
1297            this.computeMax = computeMax;
1298        }
1299
1300        /**
1301         * Compute the minimum / maximum over the list
1302         * @param lst The list
1303         * @return The minimum or maximum depending on {@link #computeMax}
1304         */
1305        public Float aggregateList(List<?> lst) {
1306            final List<Float> floats = Utils.transform(lst, (Function<Object, Float>) x -> Cascade.convertTo(x, float.class));
1307            final Collection<Float> nonNullList = SubclassFilteredCollection.filter(floats, Objects::nonNull);
1308            return nonNullList.isEmpty() ? (Float) Float.NaN : computeMax ? Collections.max(nonNullList) : Collections.min(nonNullList);
1309        }
1310
1311        @Override
1312        public Object evaluate(final Environment env) {
1313            List<?> l = Cascade.convertTo(args.get(0).evaluate(env), List.class);
1314            if (args.size() != 1 || l == null)
1315                l = Utils.transform(args, (Function<Expression, Object>) x -> x.evaluate(env));
1316            return aggregateList(l);
1317        }
1318    }
1319
1320    /**
1321     * Function that takes a certain number of argument with specific type.
1322     *
1323     * Implementation is based on a Method object.
1324     * If any of the arguments evaluate to null, the result will also be null.
1325     */
1326    public static class ParameterFunction implements Expression {
1327
1328        private final Method m;
1329        private final boolean nullable;
1330        private final List<Expression> args;
1331        private final Class<?>[] expectedParameterTypes;
1332        private final boolean needsEnvironment;
1333
1334        /**
1335         * Constructs a new {@code ParameterFunction}.
1336         * @param m method
1337         * @param args arguments
1338         * @param needsEnvironment whether function needs environment
1339         */
1340        public ParameterFunction(Method m, List<Expression> args, boolean needsEnvironment) {
1341            this.m = m;
1342            this.nullable = m.getAnnotation(NullableArguments.class) != null;
1343            this.args = args;
1344            this.expectedParameterTypes = m.getParameterTypes();
1345            this.needsEnvironment = needsEnvironment;
1346        }
1347
1348        /**
1349         * Returns the method.
1350         * @return the method
1351         * @since 14484
1352         */
1353        public final Method getMethod() {
1354            return m;
1355        }
1356
1357        /**
1358         * Returns the arguments.
1359         * @return the arguments
1360         * @since 14484
1361         */
1362        public final List<Expression> getArgs() {
1363            return args;
1364        }
1365
1366        @Override
1367        public Object evaluate(Environment env) {
1368            Object[] convertedArgs;
1369
1370            if (needsEnvironment) {
1371                convertedArgs = new Object[args.size()+1];
1372                convertedArgs[0] = env;
1373                for (int i = 1; i < convertedArgs.length; ++i) {
1374                    convertedArgs[i] = Cascade.convertTo(args.get(i-1).evaluate(env), expectedParameterTypes[i]);
1375                    if (convertedArgs[i] == null && !nullable) {
1376                        return null;
1377                    }
1378                }
1379            } else {
1380                convertedArgs = new Object[args.size()];
1381                for (int i = 0; i < convertedArgs.length; ++i) {
1382                    convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
1383                    if (convertedArgs[i] == null && !nullable) {
1384                        return null;
1385                    }
1386                }
1387            }
1388            Object result = null;
1389            try {
1390                result = m.invoke(null, convertedArgs);
1391            } catch (IllegalAccessException | IllegalArgumentException ex) {
1392                throw new JosmRuntimeException(ex);
1393            } catch (InvocationTargetException ex) {
1394                Logging.error(ex);
1395                return null;
1396            }
1397            return result;
1398        }
1399
1400        @Override
1401        public String toString() {
1402            StringBuilder b = new StringBuilder("ParameterFunction~");
1403            b.append(m.getName()).append('(');
1404            for (int i = 0; i < expectedParameterTypes.length; ++i) {
1405                if (i > 0) b.append(',');
1406                b.append(expectedParameterTypes[i]);
1407                if (!needsEnvironment) {
1408                    b.append(' ').append(args.get(i));
1409                } else if (i > 0) {
1410                    b.append(' ').append(args.get(i-1));
1411                }
1412            }
1413            b.append(')');
1414            return b.toString();
1415        }
1416    }
1417
1418    /**
1419     * Function that takes an arbitrary number of arguments.
1420     *
1421     * Currently, all array functions are static, so there is no need to
1422     * provide the environment, like it is done in {@link ParameterFunction}.
1423     * If any of the arguments evaluate to null, the result will also be null.
1424     */
1425    public static class ArrayFunction implements Expression {
1426
1427        private final Method m;
1428        private final boolean nullable;
1429        private final List<Expression> args;
1430        private final Class<?>[] expectedParameterTypes;
1431        private final Class<?> arrayComponentType;
1432
1433        /**
1434         * Constructs a new {@code ArrayFunction}.
1435         * @param m method
1436         * @param args arguments
1437         */
1438        public ArrayFunction(Method m, List<Expression> args) {
1439            this.m = m;
1440            this.nullable = m.getAnnotation(NullableArguments.class) != null;
1441            this.args = args;
1442            this.expectedParameterTypes = m.getParameterTypes();
1443            this.arrayComponentType = expectedParameterTypes[0].getComponentType();
1444        }
1445
1446        @Override
1447        public Object evaluate(Environment env) {
1448            Object[] convertedArgs = new Object[expectedParameterTypes.length];
1449            Object arrayArg = Array.newInstance(arrayComponentType, args.size());
1450            for (int i = 0; i < args.size(); ++i) {
1451                Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
1452                if (o == null && !nullable) {
1453                    return null;
1454                }
1455                Array.set(arrayArg, i, o);
1456            }
1457            convertedArgs[0] = arrayArg;
1458
1459            Object result = null;
1460            try {
1461                result = m.invoke(null, convertedArgs);
1462            } catch (IllegalAccessException | IllegalArgumentException ex) {
1463                throw new JosmRuntimeException(ex);
1464            } catch (InvocationTargetException ex) {
1465                Logging.error(ex);
1466                return null;
1467            }
1468            return result;
1469        }
1470
1471        @Override
1472        public String toString() {
1473            StringBuilder b = new StringBuilder("ArrayFunction~");
1474            b.append(m.getName()).append('(');
1475            for (int i = 0; i < args.size(); ++i) {
1476                if (i > 0) b.append(',');
1477                b.append(arrayComponentType).append(' ').append(args.get(i));
1478            }
1479            b.append(')');
1480            return b.toString();
1481        }
1482    }
1483}