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