source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java@ 6506

Last change on this file since 6506 was 6506, checked in by simon04, 10 years ago

see #9414 - implement MapCSS-based tag checker/fixer

The file deprecated.mapcss contains all DeprecatedTags tests. The
latter is to be removed eventually.

File size: 19.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.tools.Utils.equal;
5
6import java.awt.Color;
7import java.lang.reflect.Array;
8import java.lang.reflect.InvocationTargetException;
9import java.lang.reflect.Method;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.List;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.actions.search.SearchCompiler;
18import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
19import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.gui.mappaint.Cascade;
22import org.openstreetmap.josm.gui.mappaint.Environment;
23import org.openstreetmap.josm.tools.ColorHelper;
24import org.openstreetmap.josm.tools.Utils;
25
26/**
27 * Factory to generate Expressions.
28 *
29 * See {@link #createFunctionExpression}.
30 */
31public final class ExpressionFactory {
32
33 private static final List<Method> arrayFunctions;
34 private static final List<Method> parameterFunctions;
35 private static final Functions FUNCTIONS_INSTANCE = new Functions();
36
37 static {
38 arrayFunctions = new ArrayList<Method>();
39 parameterFunctions = new ArrayList<Method>();
40 for (Method m : Functions.class.getDeclaredMethods()) {
41 Class<?>[] paramTypes = m.getParameterTypes();
42 if (paramTypes.length == 1 && paramTypes[0].isArray()) {
43 arrayFunctions.add(m);
44 } else {
45 parameterFunctions.add(m);
46 }
47 }
48 try {
49 parameterFunctions.add(Math.class.getMethod("abs", float.class));
50 parameterFunctions.add(Math.class.getMethod("acos", double.class));
51 parameterFunctions.add(Math.class.getMethod("asin", double.class));
52 parameterFunctions.add(Math.class.getMethod("atan", double.class));
53 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class));
54 parameterFunctions.add(Math.class.getMethod("ceil", double.class));
55 parameterFunctions.add(Math.class.getMethod("cos", double.class));
56 parameterFunctions.add(Math.class.getMethod("cosh", double.class));
57 parameterFunctions.add(Math.class.getMethod("exp", double.class));
58 parameterFunctions.add(Math.class.getMethod("floor", double.class));
59 parameterFunctions.add(Math.class.getMethod("log", double.class));
60 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class));
61 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class));
62 parameterFunctions.add(Math.class.getMethod("random"));
63 parameterFunctions.add(Math.class.getMethod("round", float.class));
64 parameterFunctions.add(Math.class.getMethod("signum", double.class));
65 parameterFunctions.add(Math.class.getMethod("sin", double.class));
66 parameterFunctions.add(Math.class.getMethod("sinh", double.class));
67 parameterFunctions.add(Math.class.getMethod("sqrt", double.class));
68 parameterFunctions.add(Math.class.getMethod("tan", double.class));
69 parameterFunctions.add(Math.class.getMethod("tanh", double.class));
70 } catch (NoSuchMethodException ex) {
71 throw new RuntimeException(ex);
72 } catch (SecurityException ex) {
73 throw new RuntimeException(ex);
74 }
75 }
76
77 private ExpressionFactory() {
78 // Hide default constructor for utils classes
79 }
80
81 public static class Functions {
82
83 Environment env;
84
85 public static Object eval(Object o) {
86 return o;
87 }
88
89 public static float plus(float... args) {
90 float res = 0;
91 for (float f : args) {
92 res += f;
93 }
94 return res;
95 }
96
97 public static Float minus(float... args) {
98 if (args.length == 0) {
99 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 public static String tr(String... args) {
363 final String text = args[0];
364 System.arraycopy(args, 1, args, 0, args.length - 1);
365 return org.openstreetmap.josm.tools.I18n.tr(text, args);
366 }
367 }
368
369 /**
370 * Main method to create an function-like expression.
371 *
372 * @param name the name of the function or operator
373 * @param args the list of arguments (as expressions)
374 * @return the generated Expression. If no suitable function can be found,
375 * returns {@link NullExpression#INSTANCE}.
376 */
377 public static Expression createFunctionExpression(String name, List<Expression> args) {
378 if (equal(name, "cond") && args.size() == 3)
379 return new CondOperator(args.get(0), args.get(1), args.get(2));
380 else if (equal(name, "and"))
381 return new AndOperator(args);
382 else if (equal(name, "or"))
383 return new OrOperator(args);
384 else if (equal(name, "length") && args.size() == 1)
385 return new LengthFunction(args.get(0));
386
387 for (Method m : arrayFunctions) {
388 if (m.getName().equals(name))
389 return new ArrayFunction(m, args);
390 }
391 for (Method m : parameterFunctions) {
392 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length)
393 return new ParameterFunction(m, args);
394 }
395 return NullExpression.INSTANCE;
396 }
397
398 /**
399 * Expression that always evaluates to null.
400 */
401 public static class NullExpression implements Expression {
402
403 final public static NullExpression INSTANCE = new NullExpression();
404
405 @Override
406 public Object evaluate(Environment env) {
407 return null;
408 }
409 }
410
411 /**
412 * Conditional operator.
413 */
414 public static class CondOperator implements Expression {
415
416 private Expression condition, firstOption, secondOption;
417
418 public CondOperator(Expression condition, Expression firstOption, Expression secondOption) {
419 this.condition = condition;
420 this.firstOption = firstOption;
421 this.secondOption = secondOption;
422 }
423
424 @Override
425 public Object evaluate(Environment env) {
426 Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class);
427 if (b != null && b)
428 return firstOption.evaluate(env);
429 else
430 return secondOption.evaluate(env);
431 }
432 }
433
434 public static class AndOperator implements Expression {
435
436 private List<Expression> args;
437
438 public AndOperator(List<Expression> args) {
439 this.args = args;
440 }
441
442 @Override
443 public Object evaluate(Environment env) {
444 for (Expression arg : args) {
445 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
446 if (b == null || !b) {
447 return false;
448 }
449 }
450 return true;
451 }
452 }
453
454 public static class OrOperator implements Expression {
455
456 private List<Expression> args;
457
458 public OrOperator(List<Expression> args) {
459 this.args = args;
460 }
461
462 @Override
463 public Object evaluate(Environment env) {
464 for (Expression arg : args) {
465 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
466 if (b != null && b) {
467 return true;
468 }
469 }
470 return false;
471 }
472 }
473
474 /**
475 * Function to calculate the length of a string or list in a MapCSS eval
476 * expression.
477 *
478 * Separate implementation to support overloading for different
479 * argument types.
480 */
481 public static class LengthFunction implements Expression {
482
483 private Expression arg;
484
485 public LengthFunction(Expression args) {
486 this.arg = args;
487 }
488
489 @Override
490 public Object evaluate(Environment env) {
491 List<?> l = Cascade.convertTo(arg.evaluate(env), List.class);
492 if (l != null)
493 return l.size();
494 String s = Cascade.convertTo(arg.evaluate(env), String.class);
495 if (s != null)
496 return s.length();
497 return null;
498 }
499 }
500
501 /**
502 * Function that takes a certain number of argument with specific type.
503 *
504 * Implementation is based on a Method object.
505 * If any of the arguments evaluate to null, the result will also be null.
506 */
507 public static class ParameterFunction implements Expression {
508
509 private final Method m;
510 private final List<Expression> args;
511 private final Class<?>[] expectedParameterTypes;
512
513 public ParameterFunction(Method m, List<Expression> args) {
514 this.m = m;
515 this.args = args;
516 expectedParameterTypes = m.getParameterTypes();
517 }
518
519 @Override
520 public Object evaluate(Environment env) {
521 FUNCTIONS_INSTANCE.env = env;
522 Object[] convertedArgs = new Object[expectedParameterTypes.length];
523 for (int i = 0; i < args.size(); ++i) {
524 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
525 if (convertedArgs[i] == null) {
526 return null;
527 }
528 }
529 Object result = null;
530 try {
531 result = m.invoke(FUNCTIONS_INSTANCE, convertedArgs);
532 } catch (IllegalAccessException ex) {
533 throw new RuntimeException(ex);
534 } catch (IllegalArgumentException ex) {
535 throw new RuntimeException(ex);
536 } catch (InvocationTargetException ex) {
537 Main.error(ex);
538 return null;
539 }
540 return result;
541 }
542 }
543
544 /**
545 * Function that takes an arbitrary number of arguments.
546 *
547 * Currently, all array functions are static, so there is no need to
548 * provide the environment, like it is done in {@link ParameterFunction}.
549 * If any of the arguments evaluate to null, the result will also be null.
550 */
551 public static class ArrayFunction implements Expression {
552
553 private final Method m;
554 private final List<Expression> args;
555 private final Class<?> arrayComponentType;
556 private final Object[] convertedArgs;
557
558 public ArrayFunction(Method m, List<Expression> args) {
559 this.m = m;
560 this.args = args;
561 Class<?>[] expectedParameterTypes = m.getParameterTypes();
562 convertedArgs = new Object[expectedParameterTypes.length];
563 arrayComponentType = expectedParameterTypes[0].getComponentType();
564 }
565
566 @Override
567 public Object evaluate(Environment env) {
568 Object arrayArg = Array.newInstance(arrayComponentType, args.size());
569 for (int i = 0; i < args.size(); ++i) {
570 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
571 if (o == null) {
572 return null;
573 }
574 Array.set(arrayArg, i, o);
575 }
576 convertedArgs[0] = arrayArg;
577
578 Object result = null;
579 try {
580 result = m.invoke(null, convertedArgs);
581 } catch (IllegalAccessException ex) {
582 throw new RuntimeException(ex);
583 } catch (IllegalArgumentException ex) {
584 throw new RuntimeException(ex);
585 } catch (InvocationTargetException ex) {
586 Main.error(ex);
587 return null;
588 }
589 return result;
590 }
591 }
592
593}
Note: See TracBrowser for help on using the repository browser.