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