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

Last change on this file since 6360 was 6360, checked in by Don-vip, 10 years ago

Sonar/Findbugs - Hide Utility Class Constructor

File size: 19.2 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 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
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}
Note: See TracBrowser for help on using the repository browser.