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

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

fix javadoc warnings

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