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.Locale; 019import java.util.Objects; 020import java.util.TreeSet; 021import java.util.function.Function; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024import java.util.zip.CRC32; 025 026import org.openstreetmap.josm.data.coor.LatLon; 027import org.openstreetmap.josm.data.osm.IPrimitive; 028import org.openstreetmap.josm.data.osm.Node; 029import org.openstreetmap.josm.data.osm.OsmPrimitive; 030import org.openstreetmap.josm.data.osm.Way; 031import org.openstreetmap.josm.data.osm.search.SearchCompiler; 032import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 033import org.openstreetmap.josm.data.osm.search.SearchParseError; 034import org.openstreetmap.josm.gui.mappaint.Cascade; 035import org.openstreetmap.josm.gui.mappaint.Environment; 036import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 037import org.openstreetmap.josm.io.XmlWriter; 038import org.openstreetmap.josm.tools.AlphanumComparator; 039import org.openstreetmap.josm.tools.ColorHelper; 040import org.openstreetmap.josm.tools.Geometry; 041import org.openstreetmap.josm.tools.JosmRuntimeException; 042import org.openstreetmap.josm.tools.Logging; 043import org.openstreetmap.josm.tools.RightAndLefthandTraffic; 044import org.openstreetmap.josm.tools.RotationAngle; 045import org.openstreetmap.josm.tools.SubclassFilteredCollection; 046import org.openstreetmap.josm.tools.Territories; 047import org.openstreetmap.josm.tools.Utils; 048 049/** 050 * Factory to generate {@link Expression}s. 051 * <p> 052 * See {@link #createFunctionExpression}. 053 */ 054public final class ExpressionFactory { 055 056 /** 057 * Marks functions which should be executed also when one or more arguments are null. 058 */ 059 @Target(ElementType.METHOD) 060 @Retention(RetentionPolicy.RUNTIME) 061 @interface NullableArguments {} 062 063 private static final List<Method> arrayFunctions = new ArrayList<>(); 064 private static final List<Method> parameterFunctions = new ArrayList<>(); 065 private static final List<Method> parameterFunctionsEnv = new ArrayList<>(); 066 067 static { 068 for (Method m : Functions.class.getDeclaredMethods()) { 069 Class<?>[] paramTypes = m.getParameterTypes(); 070 if (paramTypes.length == 1 && paramTypes[0].isArray()) { 071 arrayFunctions.add(m); 072 } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) { 073 parameterFunctionsEnv.add(m); 074 } else { 075 parameterFunctions.add(m); 076 } 077 } 078 try { 079 parameterFunctions.add(Math.class.getMethod("abs", float.class)); 080 parameterFunctions.add(Math.class.getMethod("acos", double.class)); 081 parameterFunctions.add(Math.class.getMethod("asin", double.class)); 082 parameterFunctions.add(Math.class.getMethod("atan", double.class)); 083 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class)); 084 parameterFunctions.add(Math.class.getMethod("ceil", double.class)); 085 parameterFunctions.add(Math.class.getMethod("cos", double.class)); 086 parameterFunctions.add(Math.class.getMethod("cosh", double.class)); 087 parameterFunctions.add(Math.class.getMethod("exp", double.class)); 088 parameterFunctions.add(Math.class.getMethod("floor", double.class)); 089 parameterFunctions.add(Math.class.getMethod("log", double.class)); 090 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class)); 091 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class)); 092 parameterFunctions.add(Math.class.getMethod("random")); 093 parameterFunctions.add(Math.class.getMethod("round", float.class)); 094 parameterFunctions.add(Math.class.getMethod("signum", double.class)); 095 parameterFunctions.add(Math.class.getMethod("sin", double.class)); 096 parameterFunctions.add(Math.class.getMethod("sinh", double.class)); 097 parameterFunctions.add(Math.class.getMethod("sqrt", double.class)); 098 parameterFunctions.add(Math.class.getMethod("tan", double.class)); 099 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 ">=" operator. 590 * @param a first value 591 * @param b second value 592 * @return {@code true} if {@code a >= 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 "<=" operator. 600 * @param a first value 601 * @param b second value 602 * @return {@code true} if {@code a <= 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 ">" operator. 610 * @param a first value 611 * @param b second value 612 * @return {@code true} if {@code a > 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 "<" operator. 620 * @param a first value 621 * @param b second value 622 * @return {@code true} if {@code a < 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("<svg>...</svg>"));</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 <![CDATA[ ... ]]> 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}