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