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

Last change on this file since 9540 was 9540, checked in by simon04, 8 years ago

see #7099 - Validate correct traffic flow at roundabouts

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