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

Last change on this file since 14371 was 14371, checked in by simon04, 5 years ago

fix #15889 - add MapCSS function is_similar

This function tests if two strings are similar. Logic extracted from SimilarNamedWays validation test.

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