001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.mapcss; 003 004import static org.openstreetmap.josm.tools.Utils.equal; 005 006import java.awt.Color; 007import java.lang.reflect.Array; 008import java.lang.reflect.InvocationTargetException; 009import java.lang.reflect.Method; 010import java.util.ArrayList; 011import java.util.Arrays; 012import java.util.List; 013import java.util.regex.Matcher; 014import java.util.regex.Pattern; 015 016import org.openstreetmap.josm.Main; 017import org.openstreetmap.josm.actions.search.SearchCompiler; 018import org.openstreetmap.josm.actions.search.SearchCompiler.Match; 019import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 020import org.openstreetmap.josm.data.osm.OsmPrimitive; 021import org.openstreetmap.josm.gui.mappaint.Cascade; 022import org.openstreetmap.josm.gui.mappaint.Environment; 023import org.openstreetmap.josm.tools.ColorHelper; 024import org.openstreetmap.josm.tools.Utils; 025 026/** 027 * Factory to generate Expressions. 028 * 029 * See {@link #createFunctionExpression}. 030 */ 031public final class ExpressionFactory { 032 033 private static final List<Method> arrayFunctions; 034 private static final List<Method> parameterFunctions; 035 private static final Functions FUNCTIONS_INSTANCE = new Functions(); 036 037 static { 038 arrayFunctions = new ArrayList<Method>(); 039 parameterFunctions = new ArrayList<Method>(); 040 for (Method m : Functions.class.getDeclaredMethods()) { 041 Class<?>[] paramTypes = m.getParameterTypes(); 042 if (paramTypes.length == 1 && paramTypes[0].isArray()) { 043 arrayFunctions.add(m); 044 } else { 045 parameterFunctions.add(m); 046 } 047 } 048 try { 049 parameterFunctions.add(Math.class.getMethod("abs", float.class)); 050 parameterFunctions.add(Math.class.getMethod("acos", double.class)); 051 parameterFunctions.add(Math.class.getMethod("asin", double.class)); 052 parameterFunctions.add(Math.class.getMethod("atan", double.class)); 053 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class)); 054 parameterFunctions.add(Math.class.getMethod("ceil", double.class)); 055 parameterFunctions.add(Math.class.getMethod("cos", double.class)); 056 parameterFunctions.add(Math.class.getMethod("cosh", double.class)); 057 parameterFunctions.add(Math.class.getMethod("exp", double.class)); 058 parameterFunctions.add(Math.class.getMethod("floor", double.class)); 059 parameterFunctions.add(Math.class.getMethod("log", double.class)); 060 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class)); 061 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class)); 062 parameterFunctions.add(Math.class.getMethod("random")); 063 parameterFunctions.add(Math.class.getMethod("round", float.class)); 064 parameterFunctions.add(Math.class.getMethod("signum", double.class)); 065 parameterFunctions.add(Math.class.getMethod("sin", double.class)); 066 parameterFunctions.add(Math.class.getMethod("sinh", double.class)); 067 parameterFunctions.add(Math.class.getMethod("sqrt", double.class)); 068 parameterFunctions.add(Math.class.getMethod("tan", double.class)); 069 parameterFunctions.add(Math.class.getMethod("tanh", double.class)); 070 } catch (NoSuchMethodException ex) { 071 throw new RuntimeException(ex); 072 } catch (SecurityException ex) { 073 throw new RuntimeException(ex); 074 } 075 } 076 077 private ExpressionFactory() { 078 // Hide default constructor for utils classes 079 } 080 081 public static class Functions { 082 083 Environment env; 084 085 public static Object eval(Object o) { 086 return o; 087 } 088 089 public static float plus(float... args) { 090 float res = 0; 091 for (float f : args) { 092 res += f; 093 } 094 return res; 095 } 096 097 public static Float minus(float... args) { 098 if (args.length == 0) { 099 return 0.0F; 100 } 101 if (args.length == 1) { 102 return -args[0]; 103 } 104 float res = args[0]; 105 for (int i = 1; i < args.length; ++i) { 106 res -= args[i]; 107 } 108 return res; 109 } 110 111 public static float times(float... args) { 112 float res = 1; 113 for (float f : args) { 114 res *= f; 115 } 116 return res; 117 } 118 119 public static Float divided_by(float... args) { 120 if (args.length == 0) { 121 return 1.0F; 122 } 123 float res = args[0]; 124 for (int i = 1; i < args.length; ++i) { 125 if (args[i] == 0.0F) { 126 return null; 127 } 128 res /= args[i]; 129 } 130 return res; 131 } 132 133 public static List list(Object... args) { 134 return Arrays.asList(args); 135 } 136 137 public static Object get(List<? extends Object> objects, float index) { 138 int idx = Math.round(index); 139 if (idx >= 0 && idx < objects.size()) { 140 return objects.get(idx); 141 } 142 return null; 143 } 144 145 public static List<String> split(String sep, String toSplit) { 146 return Arrays.asList(toSplit.split(Pattern.quote(sep), -1)); 147 } 148 149 public static Color rgb(float r, float g, float b) { 150 Color c; 151 try { 152 c = new Color(r, g, b); 153 } catch (IllegalArgumentException e) { 154 return null; 155 } 156 return c; 157 } 158 159 public static Color html2color(String html) { 160 return ColorHelper.html2color(html); 161 } 162 163 public static String color2html(Color c) { 164 return ColorHelper.color2html(c); 165 } 166 167 public static float red(Color c) { 168 return Utils.color_int2float(c.getRed()); 169 } 170 171 public static float green(Color c) { 172 return Utils.color_int2float(c.getGreen()); 173 } 174 175 public static float blue(Color c) { 176 return Utils.color_int2float(c.getBlue()); 177 } 178 179 public static String concat(Object... args) { 180 StringBuilder res = new StringBuilder(); 181 for (Object f : args) { 182 res.append(f.toString()); 183 } 184 return res.toString(); 185 } 186 187 public Object prop(String key) { 188 return prop(key, null); 189 } 190 191 public Object prop(String key, String layer) { 192 Cascade c; 193 if (layer == null) { 194 c = env.mc.getCascade(env.layer); 195 } else { 196 c = env.mc.getCascade(layer); 197 } 198 return c.get(key); 199 } 200 201 public Boolean is_prop_set(String key) { 202 return is_prop_set(key, null); 203 } 204 205 public Boolean is_prop_set(String key, String layer) { 206 Cascade c; 207 if (layer == null) { 208 // env.layer is null if expression is evaluated 209 // in ExpressionCondition, but MultiCascade.getCascade 210 // handles this 211 c = env.mc.getCascade(env.layer); 212 } else { 213 c = env.mc.getCascade(layer); 214 } 215 return c.containsKey(key); 216 } 217 218 public String tag(String key) { 219 return env.osm.get(key); 220 } 221 222 public String parent_tag(String key) { 223 if (env.parent == null) { 224 // we don't have a matched parent, so just search all referrers 225 for (OsmPrimitive parent : env.osm.getReferrers()) { 226 String value = parent.get(key); 227 if (value != null) { 228 return value; 229 } 230 } 231 return null; 232 } 233 return env.parent.get(key); 234 } 235 236 public boolean has_tag_key(String key) { 237 return env.osm.hasKey(key); 238 } 239 240 public Float index() { 241 if (env.index == null) { 242 return null; 243 } 244 return new Float(env.index + 1); 245 } 246 247 public String role() { 248 return env.getRole(); 249 } 250 251 public static boolean not(boolean b) { 252 return !b; 253 } 254 255 public static boolean greater_equal(float a, float b) { 256 return a >= b; 257 } 258 259 public static boolean less_equal(float a, float b) { 260 return a <= b; 261 } 262 263 public static boolean greater(float a, float b) { 264 return a > b; 265 } 266 267 public static boolean less(float a, float b) { 268 return a < b; 269 } 270 271 public static boolean equal(Object a, Object b) { 272 // make sure the casts are done in a meaningful way, so 273 // the 2 objects really can be considered equal 274 for (Class<?> klass : new Class[]{Float.class, Boolean.class, Color.class, float[].class, String.class}) { 275 Object a2 = Cascade.convertTo(a, klass); 276 Object b2 = Cascade.convertTo(b, klass); 277 if (a2 != null && b2 != null && a2.equals(b2)) { 278 return true; 279 } 280 } 281 return false; 282 } 283 284 public Boolean JOSM_search(String s) { 285 Match m; 286 try { 287 m = SearchCompiler.compile(s, false, false); 288 } catch (ParseError ex) { 289 return null; 290 } 291 return m.match(env.osm); 292 } 293 294 public static String JOSM_pref(String s, String def) { 295 String res = Main.pref.get(s, null); 296 return res != null ? res : def; 297 } 298 299 public static Color JOSM_pref_color(String s, Color def) { 300 Color res = Main.pref.getColor(s, null); 301 return res != null ? res : def; 302 } 303 304 public static boolean regexp_test(String pattern, String target) { 305 return Pattern.matches(pattern, target); 306 } 307 308 public static boolean regexp_test(String pattern, String target, String flags) { 309 int f = 0; 310 if (flags.contains("i")) { 311 f |= Pattern.CASE_INSENSITIVE; 312 } 313 if (flags.contains("s")) { 314 f |= Pattern.DOTALL; 315 } 316 if (flags.contains("m")) { 317 f |= Pattern.MULTILINE; 318 } 319 return Pattern.compile(pattern, f).matcher(target).matches(); 320 } 321 322 public static List<String> regexp_match(String pattern, String target, String flags) { 323 int f = 0; 324 if (flags.contains("i")) { 325 f |= Pattern.CASE_INSENSITIVE; 326 } 327 if (flags.contains("s")) { 328 f |= Pattern.DOTALL; 329 } 330 if (flags.contains("m")) { 331 f |= Pattern.MULTILINE; 332 } 333 Matcher m = Pattern.compile(pattern, f).matcher(target); 334 if (m.matches()) { 335 List<String> result = new ArrayList<String>(m.groupCount() + 1); 336 for (int i = 0; i <= m.groupCount(); i++) { 337 result.add(m.group(i)); 338 } 339 return result; 340 } else { 341 return null; 342 } 343 } 344 345 public static List<String> regexp_match(String pattern, String target) { 346 Matcher m = Pattern.compile(pattern).matcher(target); 347 if (m.matches()) { 348 List<String> result = new ArrayList<String>(m.groupCount() + 1); 349 for (int i = 0; i <= m.groupCount(); i++) { 350 result.add(m.group(i)); 351 } 352 return result; 353 } else { 354 return null; 355 } 356 } 357 358 public long osm_id() { 359 return env.osm.getUniqueId(); 360 } 361 } 362 363 /** 364 * Main method to create an function-like expression. 365 * 366 * @param name the name of the function or operator 367 * @param args the list of arguments (as expressions) 368 * @return the generated Expression. If no suitable function can be found, 369 * returns {@link NullExpression#INSTANCE}. 370 */ 371 public static Expression createFunctionExpression(String name, List<Expression> args) { 372 if (equal(name, "cond") && args.size() == 3) 373 return new CondOperator(args.get(0), args.get(1), args.get(2)); 374 else if (equal(name, "and")) 375 return new AndOperator(args); 376 else if (equal(name, "or")) 377 return new OrOperator(args); 378 else if (equal(name, "length") && args.size() == 1) 379 return new LengthFunction(args.get(0)); 380 381 for (Method m : arrayFunctions) { 382 if (m.getName().equals(name)) 383 return new ArrayFunction(m, args); 384 } 385 for (Method m : parameterFunctions) { 386 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length) 387 return new ParameterFunction(m, args); 388 } 389 return NullExpression.INSTANCE; 390 } 391 392 /** 393 * Expression that always evaluates to null. 394 */ 395 public static class NullExpression implements Expression { 396 397 final public static NullExpression INSTANCE = new NullExpression(); 398 399 @Override 400 public Object evaluate(Environment env) { 401 return null; 402 } 403 } 404 405 /** 406 * Conditional operator. 407 */ 408 public static class CondOperator implements Expression { 409 410 private Expression condition, firstOption, secondOption; 411 412 public CondOperator(Expression condition, Expression firstOption, Expression secondOption) { 413 this.condition = condition; 414 this.firstOption = firstOption; 415 this.secondOption = secondOption; 416 } 417 418 @Override 419 public Object evaluate(Environment env) { 420 Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class); 421 if (b != null && b) 422 return firstOption.evaluate(env); 423 else 424 return secondOption.evaluate(env); 425 } 426 } 427 428 public static class AndOperator implements Expression { 429 430 private List<Expression> args; 431 432 public AndOperator(List<Expression> args) { 433 this.args = args; 434 } 435 436 @Override 437 public Object evaluate(Environment env) { 438 for (Expression arg : args) { 439 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class); 440 if (b == null || !b) { 441 return false; 442 } 443 } 444 return true; 445 } 446 } 447 448 public static class OrOperator implements Expression { 449 450 private List<Expression> args; 451 452 public OrOperator(List<Expression> args) { 453 this.args = args; 454 } 455 456 @Override 457 public Object evaluate(Environment env) { 458 for (Expression arg : args) { 459 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class); 460 if (b != null && b) { 461 return true; 462 } 463 } 464 return false; 465 } 466 } 467 468 /** 469 * Function to calculate the length of a string or list in a MapCSS eval 470 * expression. 471 * 472 * Separate implementation to support overloading for different 473 * argument types. 474 */ 475 public static class LengthFunction implements Expression { 476 477 private Expression arg; 478 479 public LengthFunction(Expression args) { 480 this.arg = args; 481 } 482 483 @Override 484 public Object evaluate(Environment env) { 485 List<?> l = Cascade.convertTo(arg.evaluate(env), List.class); 486 if (l != null) 487 return l.size(); 488 String s = Cascade.convertTo(arg.evaluate(env), String.class); 489 if (s != null) 490 return s.length(); 491 return null; 492 } 493 } 494 495 /** 496 * Function that takes a certain number of argument with specific type. 497 * 498 * Implementation is based on a Method object. 499 * If any of the arguments evaluate to null, the result will also be null. 500 */ 501 public static class ParameterFunction implements Expression { 502 503 private final Method m; 504 private final List<Expression> args; 505 private final Class<?>[] expectedParameterTypes; 506 507 public ParameterFunction(Method m, List<Expression> args) { 508 this.m = m; 509 this.args = args; 510 expectedParameterTypes = m.getParameterTypes(); 511 } 512 513 @Override 514 public Object evaluate(Environment env) { 515 FUNCTIONS_INSTANCE.env = env; 516 Object[] convertedArgs = new Object[expectedParameterTypes.length]; 517 for (int i = 0; i < args.size(); ++i) { 518 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]); 519 if (convertedArgs[i] == null) { 520 return null; 521 } 522 } 523 Object result = null; 524 try { 525 result = m.invoke(FUNCTIONS_INSTANCE, convertedArgs); 526 } catch (IllegalAccessException ex) { 527 throw new RuntimeException(ex); 528 } catch (IllegalArgumentException ex) { 529 throw new RuntimeException(ex); 530 } catch (InvocationTargetException ex) { 531 Main.error(ex); 532 return null; 533 } 534 return result; 535 } 536 } 537 538 /** 539 * Function that takes an arbitrary number of arguments. 540 * 541 * Currently, all array functions are static, so there is no need to 542 * provide the environment, like it is done in {@link ParameterFunction}. 543 * If any of the arguments evaluate to null, the result will also be null. 544 */ 545 public static class ArrayFunction implements Expression { 546 547 private final Method m; 548 private final List<Expression> args; 549 private final Class<?> arrayComponentType; 550 private final Object[] convertedArgs; 551 552 public ArrayFunction(Method m, List<Expression> args) { 553 this.m = m; 554 this.args = args; 555 Class<?>[] expectedParameterTypes = m.getParameterTypes(); 556 convertedArgs = new Object[expectedParameterTypes.length]; 557 arrayComponentType = expectedParameterTypes[0].getComponentType(); 558 } 559 560 @Override 561 public Object evaluate(Environment env) { 562 Object arrayArg = Array.newInstance(arrayComponentType, args.size()); 563 for (int i = 0; i < args.size(); ++i) { 564 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType); 565 if (o == null) { 566 return null; 567 } 568 Array.set(arrayArg, i, o); 569 } 570 convertedArgs[0] = arrayArg; 571 572 Object result = null; 573 try { 574 result = m.invoke(null, convertedArgs); 575 } catch (IllegalAccessException ex) { 576 throw new RuntimeException(ex); 577 } catch (IllegalArgumentException ex) { 578 throw new RuntimeException(ex); 579 } catch (InvocationTargetException ex) { 580 Main.error(ex); 581 return null; 582 } 583 return result; 584 } 585 } 586 587}