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 }