1 package com.randomnoun.common.jexl.eval;
2
3
4
5
6
7 import java.math.BigDecimal;
8 import java.text.*;
9 import java.util.*;
10
11
12
13 import org.apache.log4j.Logger;
14
15 import com.randomnoun.common.Text;
16 import com.randomnoun.common.jexl.ast.*;
17 import com.randomnoun.common.jexl.visitor.*;
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class Evaluator
49 extends GJDepthFirst<Object, EvalContext> {
50
51
52
53
54
55
56
57 Logger logger = Logger.getLogger(Evaluator.class);
58
59
60
61
62
63 interface BinaryOp {
64 Object op(Object a, Object b);
65 }
66
67
68 abstract class LongOp implements BinaryOp {
69 public Object op(Object a, Object b) {
70 long longA = ((Long) a).longValue();
71 long longB = (b == null ? 0 : ((Long) b).longValue());
72 return Long.valueOf(longOp(longA, longB));
73 }
74 abstract long longOp(long a, long b);
75 }
76
77
78 abstract class DoubleOp implements BinaryOp {
79 public Object op(Object a, Object b) {
80 double doubleA = ((Double) a).doubleValue();
81 double doubleB = (b == null ? 0 : ((Double) b).doubleValue());
82 return Double.valueOf(doubleOp(doubleA, doubleB));
83 }
84 abstract double doubleOp(double a, double b);
85 }
86
87
88
89
90
91
92
93
94 public Object coerceType(Object a, Object b) {
95
96 if (a == null) { return null; }
97
98 Class<? extends Object> clazz = a.getClass();
99 if (b == null || clazz.isInstance(b)) { return b; }
100
101 if (a instanceof Date) {
102 if (b instanceof String) {
103
104 DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
105 try {
106 return sdf.parse((String) b);
107 } catch (java.text.ParseException pe) {
108 throw new EvalException("Could not parse date '" + b + "': " + pe.getMessage());
109 }
110 }
111 }
112
113 if (a instanceof Double) {
114 if (b instanceof Number) {
115 return Double.valueOf(((Number) b).doubleValue());
116 }
117 }
118
119 if (a instanceof Long) {
120 if (b instanceof Number) {
121 return Long.valueOf(((Number) b).longValue());
122 }
123 }
124
125 if (a instanceof BigDecimal) {
126 if (b instanceof Number) {
127
128 return new BigDecimal(((Number) b).toString());
129 }
130 }
131
132 throw new EvalException("cannot coerce " + b.getClass().getName() + " to " + clazz.getName());
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public Object mathOp(Object a, Object b, String op, BinaryOp longOp, BinaryOp doubleOp) {
151 b = coerceType(a, b);
152
153 if (a instanceof Long) {
154 return longOp.op(a, b);
155 } else if (a instanceof Double) {
156 return doubleOp.op(a, b);
157 }
158
159 throw new EvalException("'" + op + "' can only operator on numeric types (found '" + a + "' of type " + a.getClass().getName() + ")");
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public Object visit(TopLevelExpression n, EvalContext context) {
184 return n.expression.accept(this, context);
185 }
186
187
188
189
190
191
192
193
194 public Object visit(Expression n, EvalContext context) {
195 NodeSequence seq;
196 Object lhs;
197 Object rhs;
198
199 lhs = n.conditionalAndExpression.accept(this, context);
200
201 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
202 seq = (NodeSequence) e.nextElement();
203 rhs = seq.elementAt(1).accept(this, context);
204
205 if (!(lhs instanceof Boolean)) {
206 throw new EvalException("lhs must be boolean");
207 }
208
209 if (!(rhs instanceof Boolean)) {
210 throw new EvalException("rhs must be boolean");
211 }
212
213
214 boolean result = ((Boolean) lhs).booleanValue() || ((Boolean) rhs).booleanValue();
215
216 lhs = Boolean.valueOf(result);
217 }
218
219 return lhs;
220 }
221
222
223
224
225
226
227
228
229 public Object visit(ConditionalAndExpression n, EvalContext context) {
230 NodeSequence seq;
231 Object lhs;
232 Object rhs;
233
234 lhs = n.equalityExpression.accept(this, context);
235
236 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
237 seq = (NodeSequence) e.nextElement();
238 rhs = seq.elementAt(1).accept(this, context);
239 rhs = coerceType(lhs, rhs);
240
241 if (!(lhs instanceof Boolean)) {
242 throw new EvalException("lhs must be boolean");
243 }
244
245 if (!(rhs instanceof Boolean)) {
246 throw new EvalException("rhs must be boolean");
247 }
248
249
250 boolean result = ((Boolean) lhs).booleanValue() && ((Boolean) rhs).booleanValue();
251
252 lhs = Boolean.valueOf(result);
253 }
254
255 return lhs;
256 }
257
258
259
260
261
262
263
264
265 @SuppressWarnings("unchecked")
266 public Object visit(EqualityExpression n, EvalContext context) {
267 NodeSequence seq;
268 Object lhs;
269 Object rhs;
270
271
272 lhs = n.relationalExpression.accept(this, context);
273
274 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
275 seq = (NodeSequence) e.nextElement();
276 rhs = seq.elementAt(1).accept(this, context);
277 rhs = coerceType(lhs, rhs);
278
279 if (!(lhs==null || lhs instanceof Comparable)) {
280 throw new EvalException("Cannot compare lhs");
281 }
282
283 if (!(rhs==null || rhs instanceof Comparable)) {
284
285 throw new EvalException("Cannot compare rhs");
286 }
287
288 int which = ((NodeChoice) seq.elementAt(0)).which;
289
290
291 int result = -1;
292 try {
293
294 if (lhs==null) {
295 return (which==0) ? Boolean.valueOf(rhs==null) : Boolean.valueOf(rhs!=null);
296 } else if (rhs==null) {
297 return (which==0) ? Boolean.valueOf(lhs==null) : Boolean.valueOf(lhs!=null);
298 }
299 result = ((Comparable<Object>) lhs).compareTo(rhs);
300 } catch (IllegalStateException ise) {
301 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise);
302 }
303
304
305 switch (which) {
306 case 0: lhs = Boolean.valueOf(result == 0); break;
307 case 1: lhs = Boolean.valueOf(result != 0); break;
308 default:
309 throw new EvalException("Internal error - unexpected relational operation");
310 }
311 }
312
313 return lhs;
314 }
315
316
317
318
319
320
321
322
323 @SuppressWarnings("unchecked")
324 public Object visit(RelationalExpression n, EvalContext context) {
325 NodeSequence seq;
326 Object lhs;
327 Object rhs;
328
329
330 lhs = n.additiveExpression.accept(this, context);
331
332 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
333 seq = (NodeSequence) e.nextElement();
334 rhs = seq.elementAt(1).accept(this, context);
335 rhs = coerceType(lhs, rhs);
336
337 if (!(lhs instanceof Comparable)) {
338 throw new EvalException("Cannot compare lhs (found type '" + lhs.getClass().getName() + "')");
339 }
340
341 if (!(rhs instanceof Comparable)) {
342
343 throw new EvalException("Cannot compare rhs");
344 }
345
346 int which = ((NodeChoice) seq.elementAt(0)).which;
347 int result = -1;
348 try {
349 result = ((Comparable<Object>) lhs).compareTo(rhs);
350 } catch (IllegalStateException ise) {
351 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise);
352 }
353
354
355 switch (which) {
356 case 0: lhs = Boolean.valueOf(result < 0); break;
357 case 1: lhs = Boolean.valueOf(result > 0); break;
358 case 2: lhs = Boolean.valueOf(result <= 0); break;
359 case 3: lhs = Boolean.valueOf(result >= 0); break;
360 default:
361 throw new EvalException("Internal error - unexpected relational operation");
362 }
363 }
364
365 return lhs;
366 }
367
368
369
370
371
372
373
374
375 public Object visit(AdditiveExpression n, EvalContext context) {
376 NodeSequence seq;
377 Object lhs;
378 Object rhs;
379
380
381 lhs = n.multiplicativeExpression.accept(this, context);
382
383 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
384 seq = (NodeSequence) e.nextElement();
385 rhs = seq.elementAt(1).accept(this, context);
386
387 int which = ((NodeChoice) seq.elementAt(0)).which;
388
389
390 switch (which) {
391 case 0:
392 if (lhs instanceof String) {
393
394 lhs = ((String) lhs) + rhs.toString();
395 } else {
396
397 lhs = mathOp(lhs, rhs, "+",
398 new LongOp() { public long longOp(long a, long b) { return a + b; } },
399 new DoubleOp() { public double doubleOp(double a, double b) { return a + b; } });
400 }
401 break;
402 case 1:
403 lhs = mathOp(lhs, rhs, "-",
404 new LongOp() { public long longOp(long a, long b) { return a - b; } },
405 new DoubleOp() { public double doubleOp(double a, double b) { return a - b; } });
406 break;
407 default:
408 throw new EvalException("Internal error - unexpected additive operation");
409 }
410 }
411
412 return lhs;
413 }
414
415
416
417
418
419
420
421
422 public Object visit(MultiplicativeExpression n, EvalContext context) {
423 NodeSequence seq;
424 Object lhs;
425 Object rhs;
426
427
428 lhs = n.unaryExpression.accept(this, context);
429
430 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
431 seq = (NodeSequence) e.nextElement();
432 rhs = seq.elementAt(1).accept(this, context);
433
434 int which = ((NodeChoice) seq.elementAt(0)).which;
435
436
437 switch (which) {
438 case 0:
439 lhs = mathOp(lhs, rhs, "*",
440 new LongOp() { public long longOp(long a, long b) { return a * b; } },
441 new DoubleOp() { public double doubleOp(double a, double b) { return a * b; } });
442 break;
443 case 1:
444 lhs = mathOp(lhs, rhs, "/",
445 new LongOp() { public long longOp(long a, long b) { return a / b; } },
446 new DoubleOp() { public double doubleOp(double a, double b) { return a / b; } });
447 break;
448 case 2:
449 lhs = mathOp(lhs, rhs, "%",
450 new LongOp() { public long longOp(long a, long b) { return a % b; } },
451 new DoubleOp() { public double doubleOp(double a, double b) { return a % b; } });
452 break;
453 default:
454 throw new EvalException("Internal error - unexpected additive operation");
455 }
456 }
457
458 return lhs;
459 }
460
461
462
463
464
465
466
467
468 public Object visit(UnaryExpression n, EvalContext context) {
469 NodeSequence seq;
470 Object lhs;
471
472 if (n.nodeChoice.which == 0) {
473 seq = (NodeSequence) n.nodeChoice.choice;
474
475 int which = ((NodeChoice) seq.elementAt(0)).which;
476
477
478 switch (which) {
479 case 0:
480
481
482 throw new EvalException("~ not supported");
483 case 1:
484 lhs = seq.elementAt(1).accept(this, context);
485 if (!(lhs instanceof Boolean)) {
486 throw new EvalException("boolean type expected");
487 }
488 return Boolean.valueOf(!((Boolean) lhs).booleanValue());
489 case 2:
490 lhs = seq.elementAt(1).accept(this, context);
491 if (!(lhs instanceof Number)) {
492 throw new EvalException("numeric type expected");
493 }
494 return mathOp(lhs, null, "-", new
495 LongOp() { public long longOp(long a, long b) { return -a; } },
496 new DoubleOp() { public double doubleOp(double a, double b) { return -a; } } );
497 default:
498 throw new EvalException("Internal error - unexpected unary operation");
499 }
500 } else {
501 return n.nodeChoice.choice.accept(this, context);
502 }
503 }
504
505
506
507
508
509
510
511
512
513
514 public Object visit(PrimaryExpression n, EvalContext context) {
515 NodeSequence seq;
516 int which = n.nodeChoice.which;
517
518 switch (which) {
519 case 0:
520 case 1:
521 case 2:
522 return n.nodeChoice.choice.accept(this, context);
523 case 3:
524 seq = (NodeSequence) n.nodeChoice.choice;
525 Object obj = seq.elementAt(1).accept(this, context);
526
527 ;
528
529 return obj;
530 default:
531 throw new EvalException("Internal parser error (PrimaryExpression)");
532 }
533 }
534
535
536
537
538
539
540
541
542 public Object visit(Name n, EvalContext context) {
543 EvalContext evalContext = (EvalContext) context;
544 String varComponentName;
545 String varBaseName = n.nodeToken.tokenImage;
546
547
548 if (evalContext == null) {
549 throw new EvalException("Cannot retrieve variable '" + varBaseName + "' with a null evalContext");
550 }
551
552 if (!evalContext.hasVariable(varBaseName)) {
553 throw new EvalException("Unknown variable '" + varBaseName + "'");
554 }
555
556 Object value = evalContext.getVariable(varBaseName);
557 NodeSequence seq;
558
559
560
561
562
563
564
565
566 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
567 seq = (NodeSequence) e.nextElement();
568 varComponentName = ((NodeToken) seq.elementAt(1)).tokenImage;
569
570
571 if (!evalContext.hasVariableComponent(value, varBaseName, varComponentName)) {
572 throw new EvalException("Unknown variable component '" + varComponentName + "' in variable '" + varBaseName + "'");
573 }
574
575 value = evalContext.getVariableComponent(value, varBaseName, varComponentName);
576 varBaseName = varBaseName + "." + varComponentName;
577 }
578
579 return value;
580 }
581
582
583
584
585
586
587
588
589 public Object visit(FunctionCall n, EvalContext context) {
590 @SuppressWarnings("unchecked")
591 List<Object> argumentList = (List<Object>) n.arguments.accept(this, context);
592 String functionName = n.nodeToken.tokenImage;
593 EvalContext evalContext = (EvalContext) context;
594 EvalFunction function = (EvalFunction) evalContext.getFunction(functionName);
595 if (function == null) {
596 throw new EvalException("Unknown function '" + functionName + "'");
597 }
598
599
600
601
602
603 return function.evaluate(functionName, evalContext, argumentList);
604 }
605
606
607
608
609
610
611
612
613
614 public Object visit(Arguments n, EvalContext context) {
615 if (n.nodeOptional.present()) {
616 return n.nodeOptional.accept(this, context);
617 } else {
618 return new ArrayList<Object>(0);
619 }
620 }
621
622
623
624
625
626
627
628
629 public Object visit(ArgumentList n, EvalContext context) {
630 NodeSequence seq;
631 List<Object> arguments = new ArrayList<Object>();
632
633 arguments.add(n.expression.accept(this, context));
634
635 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
636 seq = (NodeSequence) e.nextElement();
637 arguments.add(seq.elementAt(1).accept(this, context));
638 }
639
640 return arguments;
641 }
642
643
644
645
646
647
648
649
650
651
652
653
654
655 public Object visit(Literal n, EvalContext context) {
656 String token = null;
657
658 if (n.nodeChoice.choice instanceof NodeToken) {
659 token = ((NodeToken) n.nodeChoice.choice).tokenImage;
660 }
661
662 switch (n.nodeChoice.which) {
663 case 0:
664 return Long.valueOf(token);
665 case 1:
666 return Double.valueOf(token);
667 case 2:
668 return Character.valueOf(token.charAt(1));
669 case 3:
670 return Text.unescapeJava(token.substring(1, token.length() - 1));
671 }
672
673
674 return n.nodeChoice.accept(this, context);
675 }
676
677
678
679
680
681
682
683
684 public Object visit(BooleanLiteral n, EvalContext context) {
685 if (n.nodeChoice.which == 0) {
686 return Boolean.valueOf(true);
687 } else {
688 return Boolean.valueOf(false);
689 }
690 }
691
692
693
694
695
696
697
698 public Object visit(NullLiteral n, EvalContext context) {
699 return null;
700 }
701
702
703 public Object visit(NodeToken n, EvalContext context) {
704 return n.tokenImage;
705 }
706 }