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