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

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

improve mapcss javadoc

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