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