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