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