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