001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.mappaint.mapcss; 003 004 import static org.openstreetmap.josm.tools.Utils.equal; 005 006 import java.awt.Color; 007 import java.lang.reflect.Array; 008 import java.lang.reflect.InvocationTargetException; 009 import java.lang.reflect.Method; 010 import java.util.ArrayList; 011 import java.util.Arrays; 012 import java.util.List; 013 014 import org.openstreetmap.josm.Main; 015 import org.openstreetmap.josm.actions.search.SearchCompiler; 016 import org.openstreetmap.josm.actions.search.SearchCompiler.Match; 017 import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 018 import org.openstreetmap.josm.data.osm.OsmPrimitive; 019 import org.openstreetmap.josm.gui.mappaint.Cascade; 020 import org.openstreetmap.josm.gui.mappaint.Environment; 021 import org.openstreetmap.josm.tools.CheckParameterUtil; 022 import org.openstreetmap.josm.tools.ColorHelper; 023 import org.openstreetmap.josm.tools.Utils; 024 025 public interface Expression { 026 public Object evaluate(Environment env); 027 028 public static class LiteralExpression implements Expression { 029 Object literal; 030 031 public LiteralExpression(Object literal) { 032 CheckParameterUtil.ensureParameterNotNull(literal); 033 this.literal = literal; 034 } 035 036 @Override 037 public Object evaluate(Environment env) { 038 return literal; 039 } 040 041 @Override 042 public String toString() { 043 if (literal instanceof float[]) 044 return Arrays.toString((float[]) literal); 045 return "<"+literal.toString()+">"; 046 } 047 } 048 049 public static class FunctionExpression implements Expression { 050 String name; 051 List<Expression> args; 052 053 public FunctionExpression(String name, List<Expression> args) { 054 this.name = name; 055 this.args = args; 056 } 057 058 public static class EvalFunctions { 059 Environment env; 060 061 public Object eval(Object o) { 062 return o; 063 } 064 065 public static float plus(float... args) { 066 float res = 0; 067 for (float f : args) { 068 res += f; 069 } 070 return res; 071 } 072 073 public Float minus(float... args) { 074 if (args.length == 0) 075 return 0f; 076 if (args.length == 1) 077 return -args[0]; 078 float res = args[0]; 079 for (int i=1; i<args.length; ++i) { 080 res -= args[i]; 081 } 082 return res; 083 } 084 085 public static float times(float... args) { 086 float res = 1; 087 for (float f : args) { 088 res *= f; 089 } 090 return res; 091 } 092 093 public Float divided_by(float... args) { 094 if (args.length == 0) 095 return 1f; 096 float res = args[0]; 097 for (int i=1; i<args.length; ++i) { 098 if (args[i] == 0f) 099 return null; 100 res /= args[i]; 101 } 102 return res; 103 } 104 105 public static List list(Object... args) { 106 return Arrays.asList(args); 107 } 108 109 public Color rgb(float r, float g, float b) { 110 Color c = null; 111 try { 112 c = new Color(r, g, b); 113 } catch (IllegalArgumentException e) { 114 return null; 115 } 116 return c; 117 } 118 119 public Color html2color(String html) { 120 return ColorHelper.html2color(html); 121 } 122 123 public String color2html(Color c) { 124 return ColorHelper.color2html(c); 125 } 126 127 public float red(Color c) { 128 return Utils.color_int2float(c.getRed()); 129 } 130 131 public float green(Color c) { 132 return Utils.color_int2float(c.getGreen()); 133 } 134 135 public float blue(Color c) { 136 return Utils.color_int2float(c.getBlue()); 137 } 138 139 public String concat(Object... args) { 140 StringBuilder res = new StringBuilder(); 141 for (Object f : args) { 142 res.append(f.toString()); 143 } 144 return res.toString(); 145 } 146 147 public Object prop(String key) { 148 return prop(key, null); 149 } 150 151 public Object prop(String key, String layer) { 152 Cascade c; 153 if (layer == null) { 154 c = env.mc.getCascade(env.layer); 155 } else { 156 c = env.mc.getCascade(layer); 157 } 158 return c.get(key); 159 } 160 161 public Boolean is_prop_set(String key) { 162 return is_prop_set(key, null); 163 } 164 165 public Boolean is_prop_set(String key, String layer) { 166 Cascade c; 167 if (layer == null) { 168 // env.layer is null if expression is evaluated 169 // in ExpressionCondition, but MultiCascade.getCascade 170 // handles this 171 c = env.mc.getCascade(env.layer); 172 } else { 173 c = env.mc.getCascade(layer); 174 } 175 return c.containsKey(key); 176 } 177 178 public String tag(String key) { 179 return env.osm.get(key); 180 } 181 182 public String parent_tag(String key) { 183 if (env.parent == null) { 184 // we don't have a matched parent, so just search all referrers 185 for (OsmPrimitive parent : env.osm.getReferrers()) { 186 String value = parent.get(key); 187 if (value != null) 188 return value; 189 } 190 return null; 191 } 192 return env.parent.get(key); 193 } 194 195 public boolean has_tag_key(String key) { 196 return env.osm.hasKey(key); 197 } 198 199 public Float index() { 200 if (env.index == null) 201 return null; 202 return new Float(env.index + 1); 203 } 204 205 public String role() { 206 return env.getRole(); 207 } 208 209 public boolean not(boolean b) { 210 return !b; 211 } 212 213 public boolean greater_equal(float a, float b) { 214 return a >= b; 215 } 216 217 public boolean less_equal(float a, float b) { 218 return a <= b; 219 } 220 221 public boolean greater(float a, float b) { 222 return a > b; 223 } 224 225 public boolean less(float a, float b) { 226 return a < b; 227 } 228 229 public int length(String s) { 230 return s.length(); 231 } 232 233 @SuppressWarnings("unchecked") 234 public boolean equal(Object a, Object b) { 235 // make sure the casts are done in a meaningful way, so 236 // the 2 objects really can be considered equal 237 for (Class klass : new Class[] { 238 Float.class, Boolean.class, Color.class, float[].class, String.class }) { 239 Object a2 = Cascade.convertTo(a, klass); 240 Object b2 = Cascade.convertTo(b, klass); 241 if (a2 != null && b2 != null && a2.equals(b2)) 242 return true; 243 } 244 return false; 245 } 246 247 public Boolean JOSM_search(String s) { 248 Match m; 249 try { 250 m = SearchCompiler.compile(s, false, false); 251 } catch (ParseError ex) { 252 return null; 253 } 254 return m.match(env.osm); 255 } 256 257 public String JOSM_pref(String s, String def) { 258 String res = Main.pref.get(s, null); 259 return res != null ? res : def; 260 } 261 262 public Color JOSM_pref_color(String s, Color def) { 263 Color res = Main.pref.getColor(s, null); 264 return res != null ? res : def; 265 } 266 } 267 268 @Override 269 public Object evaluate(Environment env) { 270 if (equal(name, "cond")) { // this needs special handling since only one argument should be evaluated 271 if (args.size() != 3) 272 return null; 273 Boolean b = Cascade.convertTo(args.get(0).evaluate(env), boolean.class); 274 if (b == null) 275 return null; 276 return args.get(b ? 1 : 2).evaluate(env); 277 } 278 if (equal(name, "and")) { 279 for (Expression arg : args) { 280 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class); 281 if (b == null || !b) 282 return false; 283 } 284 return true; 285 } 286 if (equal(name, "or")) { 287 for (Expression arg : args) { 288 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class); 289 if (b != null && b) 290 return true; 291 } 292 return false; 293 } 294 EvalFunctions fn = new EvalFunctions(); 295 fn.env = env; 296 Method[] customMethods = EvalFunctions.class.getDeclaredMethods(); 297 List<Method> allMethods = new ArrayList<Method>(); 298 allMethods.addAll(Arrays.asList(customMethods)); 299 try { 300 allMethods.add(Math.class.getMethod("abs", float.class)); 301 allMethods.add(Math.class.getMethod("acos", double.class)); 302 allMethods.add(Math.class.getMethod("asin", double.class)); 303 allMethods.add(Math.class.getMethod("atan", double.class)); 304 allMethods.add(Math.class.getMethod("atan2", double.class, double.class)); 305 allMethods.add(Math.class.getMethod("ceil", double.class)); 306 allMethods.add(Math.class.getMethod("cos", double.class)); 307 allMethods.add(Math.class.getMethod("cosh", double.class)); 308 allMethods.add(Math.class.getMethod("exp", double.class)); 309 allMethods.add(Math.class.getMethod("floor", double.class)); 310 allMethods.add(Math.class.getMethod("log", double.class)); 311 allMethods.add(Math.class.getMethod("max", float.class, float.class)); 312 allMethods.add(Math.class.getMethod("min", float.class, float.class)); 313 allMethods.add(Math.class.getMethod("random")); 314 allMethods.add(Math.class.getMethod("round", float.class)); 315 allMethods.add(Math.class.getMethod("signum", double.class)); 316 allMethods.add(Math.class.getMethod("sin", double.class)); 317 allMethods.add(Math.class.getMethod("sinh", double.class)); 318 allMethods.add(Math.class.getMethod("sqrt", double.class)); 319 allMethods.add(Math.class.getMethod("tan", double.class)); 320 allMethods.add(Math.class.getMethod("tanh", double.class)); 321 } catch (NoSuchMethodException ex) { 322 throw new RuntimeException(ex); 323 } catch (SecurityException ex) { 324 throw new RuntimeException(ex); 325 } 326 for (Method m : allMethods) { 327 if (!m.getName().equals(name)) { 328 continue; 329 } 330 Class<?>[] expectedParameterTypes = m.getParameterTypes(); 331 Object[] convertedArgs = new Object[expectedParameterTypes.length]; 332 333 if (expectedParameterTypes.length == 1 && expectedParameterTypes[0].isArray()) 334 { 335 Class<?> arrayComponentType = expectedParameterTypes[0].getComponentType(); 336 Object arrayArg = Array.newInstance(arrayComponentType, args.size()); 337 for (int i=0; i<args.size(); ++i) 338 { 339 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType); 340 if (o == null) 341 return null; 342 Array.set(arrayArg, i, o); 343 } 344 convertedArgs[0] = arrayArg; 345 } else { 346 if (args.size() != expectedParameterTypes.length) { 347 continue; 348 } 349 for (int i=0; i<args.size(); ++i) { 350 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]); 351 if (convertedArgs[i] == null) 352 return null; 353 } 354 } 355 Object result = null; 356 try { 357 result = m.invoke(fn, convertedArgs); 358 } catch (IllegalAccessException ex) { 359 throw new RuntimeException(ex); 360 } catch (IllegalArgumentException ex) { 361 throw new RuntimeException(ex); 362 } catch (InvocationTargetException ex) { 363 System.err.println(ex); 364 return null; 365 } 366 return result; 367 } 368 return null; 369 } 370 371 @Override 372 public String toString() { 373 return name + "(" + Utils.join(", ", args) + ")"; 374 } 375 376 } 377 }