001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.mapcss; 003 004import java.awt.Color; 005import java.lang.annotation.ElementType; 006import java.lang.annotation.Retention; 007import java.lang.annotation.RetentionPolicy; 008import java.lang.annotation.Target; 009import java.lang.reflect.Array; 010import java.lang.reflect.InvocationTargetException; 011import java.lang.reflect.Method; 012import java.nio.charset.StandardCharsets; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.Collection; 016import java.util.Collections; 017import java.util.List; 018import java.util.Objects; 019import java.util.TreeSet; 020import java.util.function.Function; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023import java.util.zip.CRC32; 024 025import org.openstreetmap.josm.Main; 026import org.openstreetmap.josm.actions.search.SearchCompiler; 027import org.openstreetmap.josm.actions.search.SearchCompiler.Match; 028import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 029import org.openstreetmap.josm.data.osm.Node; 030import org.openstreetmap.josm.data.osm.OsmPrimitive; 031import org.openstreetmap.josm.data.osm.Way; 032import org.openstreetmap.josm.gui.mappaint.Cascade; 033import org.openstreetmap.josm.gui.mappaint.Environment; 034import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 035import org.openstreetmap.josm.gui.util.RotationAngle; 036import org.openstreetmap.josm.io.XmlWriter; 037import org.openstreetmap.josm.tools.AlphanumComparator; 038import org.openstreetmap.josm.tools.ColorHelper; 039import org.openstreetmap.josm.tools.Geometry; 040import org.openstreetmap.josm.tools.RightAndLefthandTraffic; 041import org.openstreetmap.josm.tools.SubclassFilteredCollection; 042import org.openstreetmap.josm.tools.Utils; 043 044/** 045 * Factory to generate {@link Expression}s. 046 * <p> 047 * See {@link #createFunctionExpression}. 048 */ 049public final class ExpressionFactory { 050 051 /** 052 * Marks functions which should be executed also when one or more arguments are null. 053 */ 054 @Target(ElementType.METHOD) 055 @Retention(RetentionPolicy.RUNTIME) 056 static @interface NullableArguments {} 057 058 private static final List<Method> arrayFunctions = new ArrayList<>(); 059 private static final List<Method> parameterFunctions = new ArrayList<>(); 060 private static final List<Method> parameterFunctionsEnv = new ArrayList<>(); 061 062 static { 063 for (Method m : Functions.class.getDeclaredMethods()) { 064 Class<?>[] paramTypes = m.getParameterTypes(); 065 if (paramTypes.length == 1 && paramTypes[0].isArray()) { 066 arrayFunctions.add(m); 067 } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) { 068 parameterFunctionsEnv.add(m); 069 } else { 070 parameterFunctions.add(m); 071 } 072 } 073 try { 074 parameterFunctions.add(Math.class.getMethod("abs", float.class)); 075 parameterFunctions.add(Math.class.getMethod("acos", double.class)); 076 parameterFunctions.add(Math.class.getMethod("asin", double.class)); 077 parameterFunctions.add(Math.class.getMethod("atan", double.class)); 078 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class)); 079 parameterFunctions.add(Math.class.getMethod("ceil", double.class)); 080 parameterFunctions.add(Math.class.getMethod("cos", double.class)); 081 parameterFunctions.add(Math.class.getMethod("cosh", double.class)); 082 parameterFunctions.add(Math.class.getMethod("exp", double.class)); 083 parameterFunctions.add(Math.class.getMethod("floor", double.class)); 084 parameterFunctions.add(Math.class.getMethod("log", double.class)); 085 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class)); 086 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class)); 087 parameterFunctions.add(Math.class.getMethod("random")); 088 parameterFunctions.add(Math.class.getMethod("round", float.class)); 089 parameterFunctions.add(Math.class.getMethod("signum", double.class)); 090 parameterFunctions.add(Math.class.getMethod("sin", double.class)); 091 parameterFunctions.add(Math.class.getMethod("sinh", double.class)); 092 parameterFunctions.add(Math.class.getMethod("sqrt", double.class)); 093 parameterFunctions.add(Math.class.getMethod("tan", double.class)); 094 parameterFunctions.add(Math.class.getMethod("tanh", double.class)); 095 } catch (NoSuchMethodException | SecurityException ex) { 096 throw new RuntimeException(ex); 097 } 098 } 099 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 ">=" operator. 573 * @param a first value 574 * @param b second value 575 * @return {@code true} if {@code a >= 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 "<=" operator. 583 * @param a first value 584 * @param b second value 585 * @return {@code true} if {@code a <= 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 ">" operator. 593 * @param a first value 594 * @param b second value 595 * @return {@code true} if {@code a > 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 "<" operator. 603 * @param a first value 604 * @param b second value 605 * @return {@code true} if {@code a < 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("<svg>...</svg>"));</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 <![CDATA[ ... ]]> 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}