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

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

sonar - Immutable Field

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