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

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

sonar - squid:S00100 - Method names should comply with a naming convention

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