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

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

see #10062 - MapCSS: really deprecate coalesce()

File size: 32.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.awt.Color;
5import java.io.UnsupportedEncodingException;
6import java.lang.annotation.ElementType;
7import java.lang.annotation.Retention;
8import java.lang.annotation.RetentionPolicy;
9import java.lang.annotation.Target;
10import java.lang.reflect.Array;
11import java.lang.reflect.InvocationTargetException;
12import java.lang.reflect.Method;
13import java.net.URLEncoder;
14import java.nio.charset.StandardCharsets;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.List;
18import java.util.regex.Matcher;
19import java.util.regex.Pattern;
20import java.util.zip.CRC32;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.actions.search.SearchCompiler;
24import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
25import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.gui.mappaint.Cascade;
28import org.openstreetmap.josm.gui.mappaint.Environment;
29import org.openstreetmap.josm.io.XmlWriter;
30import org.openstreetmap.josm.tools.ColorHelper;
31import org.openstreetmap.josm.tools.Utils;
32
33/**
34 * Factory to generate Expressions.
35 *
36 * See {@link #createFunctionExpression}.
37 */
38public final class ExpressionFactory {
39
40 /**
41 * Marks functions which should be executed also when one or more arguments are null.
42 */
43 @Target(ElementType.METHOD)
44 @Retention(RetentionPolicy.RUNTIME)
45 static @interface NullableArguments {}
46
47 private static final List<Method> arrayFunctions = new ArrayList<>();
48 private static final List<Method> parameterFunctions = new ArrayList<>();
49 private static final List<Method> parameterFunctionsEnv = new ArrayList<>();
50
51 static {
52 for (Method m : Functions.class.getDeclaredMethods()) {
53 Class<?>[] paramTypes = m.getParameterTypes();
54 if (paramTypes.length == 1 && paramTypes[0].isArray()) {
55 arrayFunctions.add(m);
56 } else if (paramTypes.length >= 1 && paramTypes[0].equals(Environment.class)) {
57 parameterFunctionsEnv.add(m);
58 } else {
59 parameterFunctions.add(m);
60 }
61 }
62 try {
63 parameterFunctions.add(Math.class.getMethod("abs", float.class));
64 parameterFunctions.add(Math.class.getMethod("acos", double.class));
65 parameterFunctions.add(Math.class.getMethod("asin", double.class));
66 parameterFunctions.add(Math.class.getMethod("atan", double.class));
67 parameterFunctions.add(Math.class.getMethod("atan2", double.class, double.class));
68 parameterFunctions.add(Math.class.getMethod("ceil", double.class));
69 parameterFunctions.add(Math.class.getMethod("cos", double.class));
70 parameterFunctions.add(Math.class.getMethod("cosh", double.class));
71 parameterFunctions.add(Math.class.getMethod("exp", double.class));
72 parameterFunctions.add(Math.class.getMethod("floor", double.class));
73 parameterFunctions.add(Math.class.getMethod("log", double.class));
74 parameterFunctions.add(Math.class.getMethod("max", float.class, float.class));
75 parameterFunctions.add(Math.class.getMethod("min", float.class, float.class));
76 parameterFunctions.add(Math.class.getMethod("random"));
77 parameterFunctions.add(Math.class.getMethod("round", float.class));
78 parameterFunctions.add(Math.class.getMethod("signum", double.class));
79 parameterFunctions.add(Math.class.getMethod("sin", double.class));
80 parameterFunctions.add(Math.class.getMethod("sinh", double.class));
81 parameterFunctions.add(Math.class.getMethod("sqrt", double.class));
82 parameterFunctions.add(Math.class.getMethod("tan", double.class));
83 parameterFunctions.add(Math.class.getMethod("tanh", double.class));
84 } catch (NoSuchMethodException | SecurityException ex) {
85 throw new RuntimeException(ex);
86 }
87 }
88
89 private ExpressionFactory() {
90 // Hide default constructor for utils classes
91 }
92
93 /**
94 * List of functions that can be used in MapCSS expressions.
95 *
96 * First parameter can be of type {@link Environment} (if needed). This is
97 * automatically filled in by JOSM and the user only sees the remaining
98 * arguments.
99 * When one of the user supplied arguments cannot be converted the
100 * expected type or is null, the function is not called and it returns null
101 * immediately. Add the annotation {@link NullableArguments} to allow
102 * null arguments.
103 * Every method must be static.
104 */
105 @SuppressWarnings("UnusedDeclaration")
106 public static class Functions {
107
108 /**
109 * Identity function for compatibility with MapCSS specification.
110 * @param o any object
111 * @return {@code o} unchanged
112 */
113 public static Object eval(Object o) {
114 return o;
115 }
116
117 public static float plus(float... args) {
118 float res = 0;
119 for (float f : args) {
120 res += f;
121 }
122 return res;
123 }
124
125 public static Float minus(float... args) {
126 if (args.length == 0) {
127 return 0.0F;
128 }
129 if (args.length == 1) {
130 return -args[0];
131 }
132 float res = args[0];
133 for (int i = 1; i < args.length; ++i) {
134 res -= args[i];
135 }
136 return res;
137 }
138
139 public static float times(float... args) {
140 float res = 1;
141 for (float f : args) {
142 res *= f;
143 }
144 return res;
145 }
146
147 public static Float divided_by(float... args) {
148 if (args.length == 0) {
149 return 1.0F;
150 }
151 float res = args[0];
152 for (int i = 1; i < args.length; ++i) {
153 if (args[i] == 0.0F) {
154 return null;
155 }
156 res /= args[i];
157 }
158 return res;
159 }
160
161 /**
162 * Creates a list of values, e.g., for the {@code dashes} property.
163 * @see Arrays#asList(Object[])
164 */
165 public static List<Object> list(Object... args) {
166 return Arrays.asList(args);
167 }
168
169 /**
170 * Returns the number of elements in a list.
171 * @param lst the list
172 * @return length of the list
173 */
174 public static Integer count(List<?> lst) {
175 return lst.size();
176 }
177
178 /**
179 * Returns the first non-null object. The name originates from the {@code COALESCE} SQL function.
180 * @deprecated Deprecated in favour of {@link #any(Object...)} from the MapCSS standard.
181 */
182 @NullableArguments
183 @Deprecated
184 public static Object coalesce(Object... args) {
185 return any(args);
186 }
187
188 /**
189 * Returns the first non-null object.
190 * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>.
191 * @see #coalesce(Object...)
192 * @see Utils#firstNonNull(Object[])
193 */
194 @NullableArguments
195 public static Object any(Object... args) {
196 return Utils.firstNonNull(args);
197 }
198
199 /**
200 * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
201 * @since 5699
202 */
203 public static Object get(List<?> lst, float n) {
204 int idx = Math.round(n);
205 if (idx >= 0 && idx < lst.size()) {
206 return lst.get(idx);
207 }
208 return null;
209 }
210
211 /**
212 * Returns the minimum of the given numbers, or NaN if no number is given.
213 * @see Math#min(float, float)
214 */
215 public static float min(float... args) {
216 if (args.length == 0) {
217 return Float.NaN;
218 } else if (args.length == 1) {
219 return args[0];
220 } else {
221 float min = Math.min(args[0], args[1]);
222 for (int i = 2; i < args.length; i++) {
223 min = Math.min(min, args[i]);
224 }
225 return min;
226 }
227 }
228
229 /**
230 * Returns the maximum of the given numbers, or NaN if no number is given.
231 * @see Math#max(float, float)
232 */
233 public static float max(float... args) {
234 if (args.length == 0) {
235 return Float.NaN;
236 } else if (args.length == 1) {
237 return args[0];
238 } else {
239 float max = Math.max(args[0], args[1]);
240 for (int i = 2; i < args.length; i++) {
241 max = Math.max(max, args[i]);
242 }
243 return max;
244 }
245 }
246
247 /**
248 * Splits string {@code toSplit} at occurrences of the separator string {@code sep} and returns a list of matches.
249 * @see String#split(String)
250 * @since 5699
251 */
252 public static List<String> split(String sep, String toSplit) {
253 return Arrays.asList(toSplit.split(Pattern.quote(sep), -1));
254 }
255
256 /**
257 * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue (arguments from 0.0 to 1.0)
258 * @see Color#Color(float, float, float)
259 */
260 public static Color rgb(float r, float g, float b) {
261 try {
262 return new Color(r, g, b);
263 } catch (IllegalArgumentException e) {
264 return null;
265 }
266 }
267
268 public static Color rgba(float r, float g, float b, float alpha) {
269 try {
270 return new Color(r, g, b, alpha);
271 } catch (IllegalArgumentException e) {
272 return null;
273 }
274 }
275
276 /**
277 * Create color from hsb color model. (arguments form 0.0 to 1.0)
278 * @param h hue
279 * @param s saturation
280 * @param b brightness
281 * @return the corresponding color
282 */
283 public static Color hsb_color(float h, float s, float b) {
284 try {
285 return Color.getHSBColor(h, s, b);
286 } catch (IllegalArgumentException e) {
287 return null;
288 }
289 }
290
291 /**
292 * Creates a color value from an HTML notation, i.e., {@code #rrggbb}.
293 */
294 public static Color html2color(String html) {
295 return ColorHelper.html2color(html);
296 }
297
298 /**
299 * Computes the HTML notation ({@code #rrggbb}) for a color value).
300 */
301 public static String color2html(Color c) {
302 return ColorHelper.color2html(c);
303 }
304
305 /**
306 * Get the value of the red color channel in the rgb color model
307 * @return the red color channel in the range [0;1]
308 * @see java.awt.Color#getRed()
309 */
310 public static float red(Color c) {
311 return Utils.color_int2float(c.getRed());
312 }
313
314 /**
315 * Get the value of the green color channel in the rgb color model
316 * @return the green color channel in the range [0;1]
317 * @see java.awt.Color#getGreen()
318 */
319 public static float green(Color c) {
320 return Utils.color_int2float(c.getGreen());
321 }
322
323 /**
324 * Get the value of the blue color channel in the rgb color model
325 * @return the blue color channel in the range [0;1]
326 * @see java.awt.Color#getBlue()
327 */
328 public static float blue(Color c) {
329 return Utils.color_int2float(c.getBlue());
330 }
331
332 /**
333 * Get the value of the alpha channel in the rgba color model
334 * @return the alpha channel in the range [0;1]
335 * @see java.awt.Color#getAlpha()
336 */
337 public static float alpha(Color c) {
338 return Utils.color_int2float(c.getAlpha());
339 }
340
341 /**
342 * Assembles the strings to one.
343 * @see Utils#join
344 */
345 @NullableArguments
346 public static String concat(Object... args) {
347 return Utils.join("", Arrays.asList(args));
348 }
349
350 /**
351 * Assembles the strings to one, where the first entry is used as separator.
352 * @see Utils#join
353 */
354 @NullableArguments
355 public static String join(String... args) {
356 return Utils.join(args[0], Arrays.asList(args).subList(1, args.length));
357 }
358
359 /**
360 * Returns the value of the property {@code key}, e.g., {@code prop("width")}.
361 */
362 public static Object prop(final Environment env, String key) {
363 return prop(env, key, null);
364 }
365
366 /**
367 * Returns the value of the property {@code key} from layer {@code layer}.
368 */
369 public static Object prop(final Environment env, String key, String layer) {
370 return env.getCascade(layer).get(key);
371 }
372
373 /**
374 * Determines whether property {@code key} is set.
375 */
376 public static Boolean is_prop_set(final Environment env, String key) {
377 return is_prop_set(env, key, null);
378 }
379
380 /**
381 * Determines whether property {@code key} is set on layer {@code layer}.
382 */
383 public static Boolean is_prop_set(final Environment env, String key, String layer) {
384 return env.getCascade(layer).containsKey(key);
385 }
386
387 /**
388 * Gets the value of the key {@code key} from the object in question.
389 */
390 public static String tag(final Environment env, String key) {
391 return env.osm == null ? null : env.osm.get(key);
392 }
393
394 /**
395 * Gets the first non-null value of the key {@code key} from the object's parent(s).
396 */
397 public static String parent_tag(final Environment env, String key) {
398 if (env.parent == null) {
399 if (env.osm != null) {
400 // we don't have a matched parent, so just search all referrers
401 for (OsmPrimitive parent : env.osm.getReferrers()) {
402 String value = parent.get(key);
403 if (value != null) {
404 return value;
405 }
406 }
407 }
408 return null;
409 }
410 return env.parent.get(key);
411 }
412
413 public static String child_tag(final Environment env, String key) {
414 return env.child == null ? null : env.child.get(key);
415 }
416
417 /**
418 * Determines whether the object has a tag with the given key.
419 */
420 public static boolean has_tag_key(final Environment env, String key) {
421 return env.osm.hasKey(key);
422 }
423
424 /**
425 * Returns the index of node in parent way or member in parent relation.
426 */
427 public static Float index(final Environment env) {
428 if (env.index == null) {
429 return null;
430 }
431 return new Float(env.index + 1);
432 }
433
434 public static String role(final Environment env) {
435 return env.getRole();
436 }
437
438 public static boolean not(boolean b) {
439 return !b;
440 }
441
442 public static boolean greater_equal(float a, float b) {
443 return a >= b;
444 }
445
446 public static boolean less_equal(float a, float b) {
447 return a <= b;
448 }
449
450 public static boolean greater(float a, float b) {
451 return a > b;
452 }
453
454 public static boolean less(float a, float b) {
455 return a < b;
456 }
457
458 /**
459 * Determines if the objects {@code a} and {@code b} are equal.
460 * @see Object#equals(Object)
461 */
462 public static boolean equal(Object a, Object b) {
463 // make sure the casts are done in a meaningful way, so
464 // the 2 objects really can be considered equal
465 for (Class<?> klass : new Class<?>[]{Float.class, Boolean.class, Color.class, float[].class, String.class}) {
466 Object a2 = Cascade.convertTo(a, klass);
467 Object b2 = Cascade.convertTo(b, klass);
468 if (a2 != null && b2 != null && a2.equals(b2)) {
469 return true;
470 }
471 }
472 return false;
473 }
474
475 /**
476 * Determines whether the JOSM search with {@code searchStr} applies to the object.
477 */
478 public static Boolean JOSM_search(final Environment env, String searchStr) {
479 Match m;
480 try {
481 m = SearchCompiler.compile(searchStr, false, false);
482 } catch (ParseError ex) {
483 return null;
484 }
485 return m.match(env.osm);
486 }
487
488 /**
489 * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
490 * and defaults to {@code def} if that is null.
491 * @see org.openstreetmap.josm.data.Preferences#get(String, String)
492 */
493 public static String JOSM_pref(String key, String def) {
494 String res = Main.pref.get(key, null);
495 return res != null ? res : def;
496 }
497
498 /**
499 * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} color for key {@code key},
500 * and defaults to {@code def} if that is null.
501 * @see org.openstreetmap.josm.data.Preferences#getColor(String, java.awt.Color)
502 */
503 public static Color JOSM_pref_color(String key, Color def) {
504 Color res = Main.pref.getColor(key, null);
505 return res != null ? res : def;
506 }
507
508 /**
509 * Tests if string {@code target} matches pattern {@code pattern}
510 * @see Pattern#matches(String, CharSequence)
511 * @since 5699
512 */
513 public static boolean regexp_test(String pattern, String target) {
514 return Pattern.matches(pattern, target);
515 }
516
517 /**
518 * Tests if string {@code target} matches pattern {@code pattern}
519 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
520 * @since 5699
521 */
522 public static boolean regexp_test(String pattern, String target, String flags) {
523 int f = 0;
524 if (flags.contains("i")) {
525 f |= Pattern.CASE_INSENSITIVE;
526 }
527 if (flags.contains("s")) {
528 f |= Pattern.DOTALL;
529 }
530 if (flags.contains("m")) {
531 f |= Pattern.MULTILINE;
532 }
533 return Pattern.compile(pattern, f).matcher(target).matches();
534 }
535
536 /**
537 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
538 * The first element (index 0) is the complete match (i.e. string).
539 * Further elements correspond to the bracketed parts of the regular expression.
540 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
541 * @since 5701
542 */
543 public static List<String> regexp_match(String pattern, String target, String flags) {
544 int f = 0;
545 if (flags.contains("i")) {
546 f |= Pattern.CASE_INSENSITIVE;
547 }
548 if (flags.contains("s")) {
549 f |= Pattern.DOTALL;
550 }
551 if (flags.contains("m")) {
552 f |= Pattern.MULTILINE;
553 }
554 Matcher m = Pattern.compile(pattern, f).matcher(target);
555 return Utils.getMatches(m);
556 }
557
558 /**
559 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
560 * The first element (index 0) is the complete match (i.e. string).
561 * Further elements correspond to the bracketed parts of the regular expression.
562 * @since 5701
563 */
564 public static List<String> regexp_match(String pattern, String target) {
565 Matcher m = Pattern.compile(pattern).matcher(target);
566 return Utils.getMatches(m);
567 }
568
569 /**
570 * Returns the OSM id of the current object.
571 * @see OsmPrimitive#getUniqueId()
572 */
573 public static long osm_id(final Environment env) {
574 return env.osm.getUniqueId();
575 }
576
577 /**
578 * Translates some text for the current locale. The first argument is the text to translate,
579 * and the subsequent arguments are parameters for the string indicated by {@code {0}}, {@code {1}}, …
580 */
581 @NullableArguments
582 public static String tr(String... args) {
583 final String text = args[0];
584 System.arraycopy(args, 1, args, 0, args.length - 1);
585 return org.openstreetmap.josm.tools.I18n.tr(text, (Object[])args);
586 }
587
588 /**
589 * Returns the substring of {@code s} starting at index {@code begin} (inclusive, 0-indexed).
590 * @param s The base string
591 * @param begin The start index
592 * @return the substring
593 * @see String#substring(int)
594 */
595 public static String substring(String s, /* due to missing Cascade.convertTo for int*/ float begin) {
596 return s == null ? null : s.substring((int) begin);
597 }
598
599 /**
600 * Returns the substring of {@code s} starting at index {@code begin} (inclusive)
601 * and ending at index {@code end}, (exclusive, 0-indexed).
602 * @param s The base string
603 * @param begin The start index
604 * @param end The end index
605 * @return the substring
606 * @see String#substring(int, int)
607 */
608 public static String substring(String s, float begin, float end) {
609 return s == null ? null : s.substring((int) begin, (int) end);
610 }
611
612 /**
613 * Replaces in {@code s} every {@code} target} substring by {@code replacement}.
614 * * @see String#replace(CharSequence, CharSequence)
615 */
616 public static String replace(String s, String target, String replacement) {
617 return s == null ? null : s.replace(target, replacement);
618 }
619
620 /**
621 * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
622 * This is especially useful for data urls, e.g.
623 * <code>icon-image: concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
624 * @param s arbitrary string
625 * @return the encoded string
626 */
627 public static String URL_encode(String s) {
628 try {
629 return s == null ? null : URLEncoder.encode(s, "UTF-8");
630 } catch (UnsupportedEncodingException ex) {
631 throw new RuntimeException(ex);
632 }
633 }
634
635 /**
636 * XML-encode a string.
637 *
638 * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
639 * @param s arbitrary string
640 * @return the encoded string
641 */
642 public static String XML_encode(String s) {
643 return s == null ? null : XmlWriter.encode(s);
644 }
645
646 /**
647 * Calculates the CRC32 checksum from a string (based on RFC 1952).
648 * @param s the string
649 * @return long value from 0 to 2^32-1
650 */
651 public static long CRC32_checksum(String s) {
652 CRC32 cs = new CRC32();
653 cs.update(s.getBytes(StandardCharsets.UTF_8));
654 return cs.getValue();
655 }
656 }
657
658 /**
659 * Main method to create an function-like expression.
660 *
661 * @param name the name of the function or operator
662 * @param args the list of arguments (as expressions)
663 * @return the generated Expression. If no suitable function can be found,
664 * returns {@link NullExpression#INSTANCE}.
665 */
666 public static Expression createFunctionExpression(String name, List<Expression> args) {
667 if ("cond".equals(name) && args.size() == 3)
668 return new CondOperator(args.get(0), args.get(1), args.get(2));
669 else if ("and".equals(name))
670 return new AndOperator(args);
671 else if ("or".equals(name))
672 return new OrOperator(args);
673 else if ("length".equals(name) && args.size() == 1)
674 return new LengthFunction(args.get(0));
675
676 for (Method m : arrayFunctions) {
677 if (m.getName().equals(name))
678 return new ArrayFunction(m, args);
679 }
680 for (Method m : parameterFunctions) {
681 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length)
682 return new ParameterFunction(m, args, false);
683 }
684 for (Method m : parameterFunctionsEnv) {
685 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length-1)
686 return new ParameterFunction(m, args, true);
687 }
688 return NullExpression.INSTANCE;
689 }
690
691 /**
692 * Expression that always evaluates to null.
693 */
694 public static class NullExpression implements Expression {
695
696 /**
697 * The unique instance.
698 */
699 public static final NullExpression INSTANCE = new NullExpression();
700
701 @Override
702 public Object evaluate(Environment env) {
703 return null;
704 }
705 }
706
707 /**
708 * Conditional operator.
709 */
710 public static class CondOperator implements Expression {
711
712 private Expression condition, firstOption, secondOption;
713
714 public CondOperator(Expression condition, Expression firstOption, Expression secondOption) {
715 this.condition = condition;
716 this.firstOption = firstOption;
717 this.secondOption = secondOption;
718 }
719
720 @Override
721 public Object evaluate(Environment env) {
722 Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class);
723 if (b != null && b)
724 return firstOption.evaluate(env);
725 else
726 return secondOption.evaluate(env);
727 }
728 }
729
730 public static class AndOperator implements Expression {
731
732 private List<Expression> args;
733
734 public AndOperator(List<Expression> args) {
735 this.args = args;
736 }
737
738 @Override
739 public Object evaluate(Environment env) {
740 for (Expression arg : args) {
741 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
742 if (b == null || !b) {
743 return false;
744 }
745 }
746 return true;
747 }
748 }
749
750 public static class OrOperator implements Expression {
751
752 private List<Expression> args;
753
754 public OrOperator(List<Expression> args) {
755 this.args = args;
756 }
757
758 @Override
759 public Object evaluate(Environment env) {
760 for (Expression arg : args) {
761 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
762 if (b != null && b) {
763 return true;
764 }
765 }
766 return false;
767 }
768 }
769
770 /**
771 * Function to calculate the length of a string or list in a MapCSS eval
772 * expression.
773 *
774 * Separate implementation to support overloading for different
775 * argument types.
776 *
777 * The use for calculating the length of a list is deprecated, use
778 * {@link Functions#count(java.util.List)} instead (see #10061).
779 */
780 public static class LengthFunction implements Expression {
781
782 private Expression arg;
783
784 public LengthFunction(Expression args) {
785 this.arg = args;
786 }
787
788 @Override
789 public Object evaluate(Environment env) {
790 List<?> l = Cascade.convertTo(arg.evaluate(env), List.class);
791 if (l != null)
792 return l.size();
793 String s = Cascade.convertTo(arg.evaluate(env), String.class);
794 if (s != null)
795 return s.length();
796 return null;
797 }
798 }
799
800 /**
801 * Function that takes a certain number of argument with specific type.
802 *
803 * Implementation is based on a Method object.
804 * If any of the arguments evaluate to null, the result will also be null.
805 */
806 public static class ParameterFunction implements Expression {
807
808 private final Method m;
809 private final boolean nullable;
810 private final List<Expression> args;
811 private final Class<?>[] expectedParameterTypes;
812 private final boolean needsEnvironment;
813
814 public ParameterFunction(Method m, List<Expression> args, boolean needsEnvironment) {
815 this.m = m;
816 this.nullable = m.getAnnotation(NullableArguments.class) != null;
817 this.args = args;
818 this.expectedParameterTypes = m.getParameterTypes();
819 this.needsEnvironment = needsEnvironment;
820 }
821
822 @Override
823 public Object evaluate(Environment env) {
824 Object[] convertedArgs;
825
826 if (needsEnvironment) {
827 convertedArgs = new Object[args.size()+1];
828 convertedArgs[0] = env;
829 for (int i = 1; i < convertedArgs.length; ++i) {
830 convertedArgs[i] = Cascade.convertTo(args.get(i-1).evaluate(env), expectedParameterTypes[i]);
831 if (convertedArgs[i] == null && !nullable) {
832 return null;
833 }
834 }
835 } else {
836 convertedArgs = new Object[args.size()];
837 for (int i = 0; i < convertedArgs.length; ++i) {
838 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
839 if (convertedArgs[i] == null && !nullable) {
840 return null;
841 }
842 }
843 }
844 Object result = null;
845 try {
846 result = m.invoke(null, convertedArgs);
847 } catch (IllegalAccessException | IllegalArgumentException ex) {
848 throw new RuntimeException(ex);
849 } catch (InvocationTargetException ex) {
850 Main.error(ex);
851 return null;
852 }
853 return result;
854 }
855
856 @Override
857 public String toString() {
858 StringBuilder b = new StringBuilder("ParameterFunction~");
859 b.append(m.getName()).append("(");
860 for (int i = 0; i < args.size(); ++i) {
861 if (i > 0) b.append(",");
862 b.append(expectedParameterTypes[i]);
863 b.append(" ").append(args.get(i));
864 }
865 b.append(')');
866 return b.toString();
867 }
868
869 }
870
871 /**
872 * Function that takes an arbitrary number of arguments.
873 *
874 * Currently, all array functions are static, so there is no need to
875 * provide the environment, like it is done in {@link ParameterFunction}.
876 * If any of the arguments evaluate to null, the result will also be null.
877 */
878 public static class ArrayFunction implements Expression {
879
880 private final Method m;
881 private final boolean nullable;
882 private final List<Expression> args;
883 private final Class<?>[] expectedParameterTypes;
884 private final Class<?> arrayComponentType;
885
886 public ArrayFunction(Method m, List<Expression> args) {
887 this.m = m;
888 this.nullable = m.getAnnotation(NullableArguments.class) != null;
889 this.args = args;
890 this.expectedParameterTypes = m.getParameterTypes();
891 this.arrayComponentType = expectedParameterTypes[0].getComponentType();
892 }
893
894 @Override
895 public Object evaluate(Environment env) {
896 Object[] convertedArgs = new Object[expectedParameterTypes.length];
897 Object arrayArg = Array.newInstance(arrayComponentType, args.size());
898 for (int i = 0; i < args.size(); ++i) {
899 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
900 if (o == null && !nullable) {
901 return null;
902 }
903 Array.set(arrayArg, i, o);
904 }
905 convertedArgs[0] = arrayArg;
906
907 Object result = null;
908 try {
909 result = m.invoke(null, convertedArgs);
910 } catch (IllegalAccessException | IllegalArgumentException ex) {
911 throw new RuntimeException(ex);
912 } catch (InvocationTargetException ex) {
913 Main.error(ex);
914 return null;
915 }
916 return result;
917 }
918 @Override
919 public String toString() {
920 StringBuilder b = new StringBuilder("ArrayFunction~");
921 b.append(m.getName()).append("(");
922 for (int i = 0; i < args.size(); ++i) {
923 if (i > 0) b.append(",");
924 b.append(arrayComponentType);
925 b.append(" ").append(args.get(i));
926 }
927 b.append(')');
928 return b.toString();
929 }
930
931 }
932
933}
Note: See TracBrowser for help on using the repository browser.