source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java@ 7596

Last change on this file since 7596 was 7596, checked in by Don-vip, 10 years ago

fix various warnings

File size: 42.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.awt.Color;
5import java.io.UnsupportedEncodingException;
6import java.lang.annotation.ElementType;
7import java.lang.annotation.Retention;
8import java.lang.annotation.RetentionPolicy;
9import java.lang.annotation.Target;
10import java.lang.reflect.Array;
11import java.lang.reflect.InvocationTargetException;
12import java.lang.reflect.Method;
13import java.net.URLEncoder;
14import java.nio.charset.StandardCharsets;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.List;
20import java.util.regex.Matcher;
21import java.util.regex.Pattern;
22import java.util.zip.CRC32;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.actions.search.SearchCompiler;
26import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
27import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.gui.mappaint.Cascade;
31import org.openstreetmap.josm.gui.mappaint.Environment;
32import org.openstreetmap.josm.io.XmlWriter;
33import org.openstreetmap.josm.tools.ColorHelper;
34import org.openstreetmap.josm.tools.Predicates;
35import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
36import org.openstreetmap.josm.tools.Utils;
37
38/**
39 * Factory to generate Expressions.
40 *
41 * See {@link #createFunctionExpression}.
42 */
43public final class ExpressionFactory {
44
45 /**
46 * Marks functions which should be executed also when one or more arguments are null.
47 */
48 @Target(ElementType.METHOD)
49 @Retention(RetentionPolicy.RUNTIME)
50 static @interface NullableArguments {}
51
52 private static final List<Method> arrayFunctions = new ArrayList<>();
53 private static final List<Method> parameterFunctions = new ArrayList<>();
54 private static final List<Method> parameterFunctionsEnv = new ArrayList<>();
55
56 static {
57 for (Method m : Functions.class.getDeclaredMethods()) {
58 Class<?>[] paramTypes = m.getParameterTypes();
59 if (paramTypes.length == 1 && paramTypes[0].isArray()) {
60 arrayFunctions.add(m);
61 } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) {
62 parameterFunctionsEnv.add(m);
63 } else {
64 parameterFunctions.add(m);
65 }
66 }
67 try {
68 parameterFunctions.add(Math.class.getMethod("abs", float.class));
69 parameterFunctions.add(Math.class.getMethod("acos", double.class));
70 parameterFunctions.add(Math.class.getMethod("asin", double.class));
71 parameterFunctions.add(Math.class.getMethod("atan", double.class));
72 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class));
73 parameterFunctions.add(Math.class.getMethod("ceil", double.class));
74 parameterFunctions.add(Math.class.getMethod("cos", double.class));
75 parameterFunctions.add(Math.class.getMethod("cosh", double.class));
76 parameterFunctions.add(Math.class.getMethod("exp", double.class));
77 parameterFunctions.add(Math.class.getMethod("floor", double.class));
78 parameterFunctions.add(Math.class.getMethod("log", double.class));
79 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class));
80 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class));
81 parameterFunctions.add(Math.class.getMethod("random"));
82 parameterFunctions.add(Math.class.getMethod("round", float.class));
83 parameterFunctions.add(Math.class.getMethod("signum", double.class));
84 parameterFunctions.add(Math.class.getMethod("sin", double.class));
85 parameterFunctions.add(Math.class.getMethod("sinh", double.class));
86 parameterFunctions.add(Math.class.getMethod("sqrt", double.class));
87 parameterFunctions.add(Math.class.getMethod("tan", double.class));
88 parameterFunctions.add(Math.class.getMethod("tanh", double.class));
89 } catch (NoSuchMethodException | SecurityException ex) {
90 throw new RuntimeException(ex);
91 }
92 }
93
94 private ExpressionFactory() {
95 // Hide default constructor for utils classes
96 }
97
98 /**
99 * 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>icon-image: 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}
Note: See TracBrowser for help on using the repository browser.