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