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

Last change on this file since 7162 was 7162, checked in by bastiK, 10 years ago

fixes #10061 - mapcss eval: deprecate use of length() for lists, use new function count() instead

File size: 31.2 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 * @see Utils#firstNonNull(Object[])
181 */
182 @NullableArguments
183 public static Object coalesce(Object... args) {
184 return Utils.firstNonNull(args);
185 }
186
187 /**
188 * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
189 * @since 5699
190 */
191 public static Object get(List<?> lst, float n) {
192 int idx = Math.round(n);
193 if (idx >= 0 && idx < lst.size()) {
194 return lst.get(idx);
195 }
196 return null;
197 }
198
199 /**
200 * Splits string {@code toSplit} at occurrences of the separator string {@code sep} and returns a list of matches.
201 * @see String#split(String)
202 * @since 5699
203 */
204 public static List<String> split(String sep, String toSplit) {
205 return Arrays.asList(toSplit.split(Pattern.quote(sep), -1));
206 }
207
208 /**
209 * 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)
210 * @see Color#Color(float, float, float)
211 */
212 public static Color rgb(float r, float g, float b) {
213 try {
214 return new Color(r, g, b);
215 } catch (IllegalArgumentException e) {
216 return null;
217 }
218 }
219
220 public static Color rgba(float r, float g, float b, float alpha) {
221 try {
222 return new Color(r, g, b, alpha);
223 } catch (IllegalArgumentException e) {
224 return null;
225 }
226 }
227
228 /**
229 * Create color from hsb color model. (arguments form 0.0 to 1.0)
230 * @param h hue
231 * @param s saturation
232 * @param b brightness
233 * @return the corresponding color
234 */
235 public static Color hsb_color(float h, float s, float b) {
236 try {
237 return Color.getHSBColor(h, s, b);
238 } catch (IllegalArgumentException e) {
239 return null;
240 }
241 }
242
243 /**
244 * Creates a color value from an HTML notation, i.e., {@code #rrggbb}.
245 */
246 public static Color html2color(String html) {
247 return ColorHelper.html2color(html);
248 }
249
250 /**
251 * Computes the HTML notation ({@code #rrggbb}) for a color value).
252 */
253 public static String color2html(Color c) {
254 return ColorHelper.color2html(c);
255 }
256
257 /**
258 * Get the value of the red color channel in the rgb color model
259 * @return the red color channel in the range [0;1]
260 * @see java.awt.Color#getRed()
261 */
262 public static float red(Color c) {
263 return Utils.color_int2float(c.getRed());
264 }
265
266 /**
267 * Get the value of the green color channel in the rgb color model
268 * @return the green color channel in the range [0;1]
269 * @see java.awt.Color#getGreen()
270 */
271 public static float green(Color c) {
272 return Utils.color_int2float(c.getGreen());
273 }
274
275 /**
276 * Get the value of the blue color channel in the rgb color model
277 * @return the blue color channel in the range [0;1]
278 * @see java.awt.Color#getBlue()
279 */
280 public static float blue(Color c) {
281 return Utils.color_int2float(c.getBlue());
282 }
283
284 /**
285 * Get the value of the alpha channel in the rgba color model
286 * @return the alpha channel in the range [0;1]
287 * @see java.awt.Color#getAlpha()
288 */
289 public static float alpha(Color c) {
290 return Utils.color_int2float(c.getAlpha());
291 }
292
293 /**
294 * Assembles the strings to one.
295 * @see Utils#join
296 */
297 @NullableArguments
298 public static String concat(Object... args) {
299 return Utils.join("", Arrays.asList(args));
300 }
301
302 /**
303 * Assembles the strings to one, where the first entry is used as separator.
304 * @see Utils#join
305 */
306 @NullableArguments
307 public static String join(String... args) {
308 return Utils.join(args[0], Arrays.asList(args).subList(1, args.length));
309 }
310
311 /**
312 * Returns the value of the property {@code key}, e.g., {@code prop("width")}.
313 */
314 public static Object prop(final Environment env, String key) {
315 return prop(env, key, null);
316 }
317
318 /**
319 * Returns the value of the property {@code key} from layer {@code layer}.
320 */
321 public static Object prop(final Environment env, String key, String layer) {
322 return env.getCascade(layer).get(key);
323 }
324
325 /**
326 * Determines whether property {@code key} is set.
327 */
328 public static Boolean is_prop_set(final Environment env, String key) {
329 return is_prop_set(env, key, null);
330 }
331
332 /**
333 * Determines whether property {@code key} is set on layer {@code layer}.
334 */
335 public static Boolean is_prop_set(final Environment env, String key, String layer) {
336 return env.getCascade(layer).containsKey(key);
337 }
338
339 /**
340 * Gets the value of the key {@code key} from the object in question.
341 */
342 public static String tag(final Environment env, String key) {
343 return env.osm == null ? null : env.osm.get(key);
344 }
345
346 /**
347 * Gets the first non-null value of the key {@code key} from the object's parent(s).
348 */
349 public static String parent_tag(final Environment env, String key) {
350 if (env.parent == null) {
351 if (env.osm != null) {
352 // we don't have a matched parent, so just search all referrers
353 for (OsmPrimitive parent : env.osm.getReferrers()) {
354 String value = parent.get(key);
355 if (value != null) {
356 return value;
357 }
358 }
359 }
360 return null;
361 }
362 return env.parent.get(key);
363 }
364
365 public static String child_tag(final Environment env, String key) {
366 return env.child == null ? null : env.child.get(key);
367 }
368
369 /**
370 * Determines whether the object has a tag with the given key.
371 */
372 public static boolean has_tag_key(final Environment env, String key) {
373 return env.osm.hasKey(key);
374 }
375
376 /**
377 * Returns the index of node in parent way or member in parent relation.
378 */
379 public static Float index(final Environment env) {
380 if (env.index == null) {
381 return null;
382 }
383 return new Float(env.index + 1);
384 }
385
386 public static String role(final Environment env) {
387 return env.getRole();
388 }
389
390 public static boolean not(boolean b) {
391 return !b;
392 }
393
394 public static boolean greater_equal(float a, float b) {
395 return a >= b;
396 }
397
398 public static boolean less_equal(float a, float b) {
399 return a <= b;
400 }
401
402 public static boolean greater(float a, float b) {
403 return a > b;
404 }
405
406 public static boolean less(float a, float b) {
407 return a < b;
408 }
409
410 /**
411 * Determines if the objects {@code a} and {@code b} are equal.
412 * @see Object#equals(Object)
413 */
414 public static boolean equal(Object a, Object b) {
415 // make sure the casts are done in a meaningful way, so
416 // the 2 objects really can be considered equal
417 for (Class<?> klass : new Class<?>[]{Float.class, Boolean.class, Color.class, float[].class, String.class}) {
418 Object a2 = Cascade.convertTo(a, klass);
419 Object b2 = Cascade.convertTo(b, klass);
420 if (a2 != null && b2 != null && a2.equals(b2)) {
421 return true;
422 }
423 }
424 return false;
425 }
426
427 /**
428 * Determines whether the JOSM search with {@code searchStr} applies to the object.
429 */
430 public static Boolean JOSM_search(final Environment env, String searchStr) {
431 Match m;
432 try {
433 m = SearchCompiler.compile(searchStr, false, false);
434 } catch (ParseError ex) {
435 return null;
436 }
437 return m.match(env.osm);
438 }
439
440 /**
441 * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
442 * and defaults to {@code def} if that is null.
443 * @see org.openstreetmap.josm.data.Preferences#get(String, String)
444 */
445 public static String JOSM_pref(String key, String def) {
446 String res = Main.pref.get(key, null);
447 return res != null ? res : def;
448 }
449
450 /**
451 * Obtains the JOSM'key {@link org.openstreetmap.josm.data.Preferences} color for key {@code key},
452 * and defaults to {@code def} if that is null.
453 * @see org.openstreetmap.josm.data.Preferences#getColor(String, java.awt.Color)
454 */
455 public static Color JOSM_pref_color(String key, Color def) {
456 Color res = Main.pref.getColor(key, null);
457 return res != null ? res : def;
458 }
459
460 /**
461 * Tests if string {@code target} matches pattern {@code pattern}
462 * @see Pattern#matches(String, CharSequence)
463 * @since 5699
464 */
465 public static boolean regexp_test(String pattern, String target) {
466 return Pattern.matches(pattern, target);
467 }
468
469 /**
470 * Tests if string {@code target} matches pattern {@code pattern}
471 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
472 * @since 5699
473 */
474 public static boolean regexp_test(String pattern, String target, String flags) {
475 int f = 0;
476 if (flags.contains("i")) {
477 f |= Pattern.CASE_INSENSITIVE;
478 }
479 if (flags.contains("s")) {
480 f |= Pattern.DOTALL;
481 }
482 if (flags.contains("m")) {
483 f |= Pattern.MULTILINE;
484 }
485 return Pattern.compile(pattern, f).matcher(target).matches();
486 }
487
488 /**
489 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
490 * The first element (index 0) is the complete match (i.e. string).
491 * Further elements correspond to the bracketed parts of the regular expression.
492 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
493 * @since 5701
494 */
495 public static List<String> regexp_match(String pattern, String target, String flags) {
496 int f = 0;
497 if (flags.contains("i")) {
498 f |= Pattern.CASE_INSENSITIVE;
499 }
500 if (flags.contains("s")) {
501 f |= Pattern.DOTALL;
502 }
503 if (flags.contains("m")) {
504 f |= Pattern.MULTILINE;
505 }
506 Matcher m = Pattern.compile(pattern, f).matcher(target);
507 return Utils.getMatches(m);
508 }
509
510 /**
511 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
512 * The first element (index 0) is the complete match (i.e. string).
513 * Further elements correspond to the bracketed parts of the regular expression.
514 * @since 5701
515 */
516 public static List<String> regexp_match(String pattern, String target) {
517 Matcher m = Pattern.compile(pattern).matcher(target);
518 return Utils.getMatches(m);
519 }
520
521 /**
522 * Returns the OSM id of the current object.
523 * @see OsmPrimitive#getUniqueId()
524 */
525 public static long osm_id(final Environment env) {
526 return env.osm.getUniqueId();
527 }
528
529 /**
530 * Translates some text for the current locale. The first argument is the text to translate,
531 * and the subsequent arguments are parameters for the string indicated by {@code {0}}, {@code {1}}, …
532 */
533 @NullableArguments
534 public static String tr(String... args) {
535 final String text = args[0];
536 System.arraycopy(args, 1, args, 0, args.length - 1);
537 return org.openstreetmap.josm.tools.I18n.tr(text, (Object[])args);
538 }
539
540 /**
541 * Returns the substring of {@code s} starting at index {@code begin} (inclusive, 0-indexed).
542 * @param s The base string
543 * @param begin The start index
544 * @return the substring
545 * @see String#substring(int)
546 */
547 public static String substring(String s, /* due to missing Cascade.convertTo for int*/ float begin) {
548 return s == null ? null : s.substring((int) begin);
549 }
550
551 /**
552 * Returns the substring of {@code s} starting at index {@code begin} (inclusive)
553 * and ending at index {@code end}, (exclusive, 0-indexed).
554 * @param s The base string
555 * @param begin The start index
556 * @param end The end index
557 * @return the substring
558 * @see String#substring(int, int)
559 */
560 public static String substring(String s, float begin, float end) {
561 return s == null ? null : s.substring((int) begin, (int) end);
562 }
563
564 /**
565 * Replaces in {@code s} every {@code} target} substring by {@code replacement}.
566 * * @see String#replace(CharSequence, CharSequence)
567 */
568 public static String replace(String s, String target, String replacement) {
569 return s == null ? null : s.replace(target, replacement);
570 }
571
572 /**
573 * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
574 * This is especially useful for data urls, e.g.
575 * <code>icon-image: concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
576 * @param s arbitrary string
577 * @return the encoded string
578 */
579 public static String URL_encode(String s) {
580 try {
581 return s == null ? null : URLEncoder.encode(s, "UTF-8");
582 } catch (UnsupportedEncodingException ex) {
583 throw new RuntimeException(ex);
584 }
585 }
586
587 /**
588 * XML-encode a string.
589 *
590 * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
591 * @param s arbitrary string
592 * @return the encoded string
593 */
594 public static String XML_encode(String s) {
595 return s == null ? null : XmlWriter.encode(s);
596 }
597
598 /**
599 * Calculates the CRC32 checksum from a string (based on RFC 1952).
600 * @param s the string
601 * @return long value from 0 to 2^32-1
602 */
603 public static long CRC32_checksum(String s) {
604 CRC32 cs = new CRC32();
605 cs.update(s.getBytes(StandardCharsets.UTF_8));
606 return cs.getValue();
607 }
608 }
609
610 /**
611 * Main method to create an function-like expression.
612 *
613 * @param name the name of the function or operator
614 * @param args the list of arguments (as expressions)
615 * @return the generated Expression. If no suitable function can be found,
616 * returns {@link NullExpression#INSTANCE}.
617 */
618 public static Expression createFunctionExpression(String name, List<Expression> args) {
619 if ("cond".equals(name) && args.size() == 3)
620 return new CondOperator(args.get(0), args.get(1), args.get(2));
621 else if ("and".equals(name))
622 return new AndOperator(args);
623 else if ("or".equals(name))
624 return new OrOperator(args);
625 else if ("length".equals(name) && args.size() == 1)
626 return new LengthFunction(args.get(0));
627
628 for (Method m : arrayFunctions) {
629 if (m.getName().equals(name))
630 return new ArrayFunction(m, args);
631 }
632 for (Method m : parameterFunctions) {
633 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length)
634 return new ParameterFunction(m, args, false);
635 }
636 for (Method m : parameterFunctionsEnv) {
637 if (m.getName().equals(name) && args.size() == m.getParameterTypes().length-1)
638 return new ParameterFunction(m, args, true);
639 }
640 return NullExpression.INSTANCE;
641 }
642
643 /**
644 * Expression that always evaluates to null.
645 */
646 public static class NullExpression implements Expression {
647
648 /**
649 * The unique instance.
650 */
651 public static final NullExpression INSTANCE = new NullExpression();
652
653 @Override
654 public Object evaluate(Environment env) {
655 return null;
656 }
657 }
658
659 /**
660 * Conditional operator.
661 */
662 public static class CondOperator implements Expression {
663
664 private Expression condition, firstOption, secondOption;
665
666 public CondOperator(Expression condition, Expression firstOption, Expression secondOption) {
667 this.condition = condition;
668 this.firstOption = firstOption;
669 this.secondOption = secondOption;
670 }
671
672 @Override
673 public Object evaluate(Environment env) {
674 Boolean b = Cascade.convertTo(condition.evaluate(env), boolean.class);
675 if (b != null && b)
676 return firstOption.evaluate(env);
677 else
678 return secondOption.evaluate(env);
679 }
680 }
681
682 public static class AndOperator implements Expression {
683
684 private List<Expression> args;
685
686 public AndOperator(List<Expression> args) {
687 this.args = args;
688 }
689
690 @Override
691 public Object evaluate(Environment env) {
692 for (Expression arg : args) {
693 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
694 if (b == null || !b) {
695 return false;
696 }
697 }
698 return true;
699 }
700 }
701
702 public static class OrOperator implements Expression {
703
704 private List<Expression> args;
705
706 public OrOperator(List<Expression> args) {
707 this.args = args;
708 }
709
710 @Override
711 public Object evaluate(Environment env) {
712 for (Expression arg : args) {
713 Boolean b = Cascade.convertTo(arg.evaluate(env), boolean.class);
714 if (b != null && b) {
715 return true;
716 }
717 }
718 return false;
719 }
720 }
721
722 /**
723 * Function to calculate the length of a string or list in a MapCSS eval
724 * expression.
725 *
726 * Separate implementation to support overloading for different
727 * argument types.
728 *
729 * The use for calculating the length of a list is deprecated, use
730 * {@link Functions#count(java.util.List)} instead (see #10061).
731 */
732 public static class LengthFunction implements Expression {
733
734 private Expression arg;
735
736 public LengthFunction(Expression args) {
737 this.arg = args;
738 }
739
740 @Override
741 public Object evaluate(Environment env) {
742 List<?> l = Cascade.convertTo(arg.evaluate(env), List.class);
743 if (l != null)
744 return l.size();
745 String s = Cascade.convertTo(arg.evaluate(env), String.class);
746 if (s != null)
747 return s.length();
748 return null;
749 }
750 }
751
752 /**
753 * Function that takes a certain number of argument with specific type.
754 *
755 * Implementation is based on a Method object.
756 * If any of the arguments evaluate to null, the result will also be null.
757 */
758 public static class ParameterFunction implements Expression {
759
760 private final Method m;
761 private final boolean nullable;
762 private final List<Expression> args;
763 private final Class<?>[] expectedParameterTypes;
764 private final boolean needsEnvironment;
765
766 public ParameterFunction(Method m, List<Expression> args, boolean needsEnvironment) {
767 this.m = m;
768 this.nullable = m.getAnnotation(NullableArguments.class) != null;
769 this.args = args;
770 this.expectedParameterTypes = m.getParameterTypes();
771 this.needsEnvironment = needsEnvironment;
772 }
773
774 @Override
775 public Object evaluate(Environment env) {
776 Object[] convertedArgs;
777
778 if (needsEnvironment) {
779 convertedArgs = new Object[args.size()+1];
780 convertedArgs[0] = env;
781 for (int i = 1; i < convertedArgs.length; ++i) {
782 convertedArgs[i] = Cascade.convertTo(args.get(i-1).evaluate(env), expectedParameterTypes[i]);
783 if (convertedArgs[i] == null && !nullable) {
784 return null;
785 }
786 }
787 } else {
788 convertedArgs = new Object[args.size()];
789 for (int i = 0; i < convertedArgs.length; ++i) {
790 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
791 if (convertedArgs[i] == null && !nullable) {
792 return null;
793 }
794 }
795 }
796 Object result = null;
797 try {
798 result = m.invoke(null, convertedArgs);
799 } catch (IllegalAccessException | IllegalArgumentException ex) {
800 throw new RuntimeException(ex);
801 } catch (InvocationTargetException ex) {
802 Main.error(ex);
803 return null;
804 }
805 return result;
806 }
807
808 @Override
809 public String toString() {
810 StringBuilder b = new StringBuilder("ParameterFunction~");
811 b.append(m.getName()).append("(");
812 for (int i = 0; i < args.size(); ++i) {
813 if (i > 0) b.append(",");
814 b.append(expectedParameterTypes[i]);
815 b.append(" ").append(args.get(i));
816 }
817 b.append(')');
818 return b.toString();
819 }
820
821 }
822
823 /**
824 * Function that takes an arbitrary number of arguments.
825 *
826 * Currently, all array functions are static, so there is no need to
827 * provide the environment, like it is done in {@link ParameterFunction}.
828 * If any of the arguments evaluate to null, the result will also be null.
829 */
830 public static class ArrayFunction implements Expression {
831
832 private final Method m;
833 private final boolean nullable;
834 private final List<Expression> args;
835 private final Class<?>[] expectedParameterTypes;
836 private final Class<?> arrayComponentType;
837
838 public ArrayFunction(Method m, List<Expression> args) {
839 this.m = m;
840 this.nullable = m.getAnnotation(NullableArguments.class) != null;
841 this.args = args;
842 this.expectedParameterTypes = m.getParameterTypes();
843 this.arrayComponentType = expectedParameterTypes[0].getComponentType();
844 }
845
846 @Override
847 public Object evaluate(Environment env) {
848 Object[] convertedArgs = new Object[expectedParameterTypes.length];
849 Object arrayArg = Array.newInstance(arrayComponentType, args.size());
850 for (int i = 0; i < args.size(); ++i) {
851 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
852 if (o == null && !nullable) {
853 return null;
854 }
855 Array.set(arrayArg, i, o);
856 }
857 convertedArgs[0] = arrayArg;
858
859 Object result = null;
860 try {
861 result = m.invoke(null, convertedArgs);
862 } catch (IllegalAccessException | IllegalArgumentException ex) {
863 throw new RuntimeException(ex);
864 } catch (InvocationTargetException ex) {
865 Main.error(ex);
866 return null;
867 }
868 return result;
869 }
870 @Override
871 public String toString() {
872 StringBuilder b = new StringBuilder("ArrayFunction~");
873 b.append(m.getName()).append("(");
874 for (int i = 0; i < args.size(); ++i) {
875 if (i > 0) b.append(",");
876 b.append(arrayComponentType);
877 b.append(" ").append(args.get(i));
878 }
879 b.append(')');
880 return b.toString();
881 }
882
883 }
884
885}
Note: See TracBrowser for help on using the repository browser.