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