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

Last change on this file since 11360 was 11360, checked in by bastiK, 7 years ago

fixed #10387 - efficiency for "inside(...)" function in MapCSS

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