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

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

see #10063 - MapCSS: add min() and max() according to standard

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