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

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

fix remaining checkstyle issues

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