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

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

see #11447 - partial revert of r8384

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