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

Last change on this file since 13691 was 13640, checked in by Don-vip, 6 years ago

javadoc/codestyle

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