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