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