1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.internal;
18
19 import java.util.Collections;
20 import java.util.EnumSet;
21 import java.util.Set;
22 import java.util.function.Consumer;
23
24 import org.apache.commons.jexl3.JexlArithmetic;
25 import org.apache.commons.jexl3.JexlCache;
26 import org.apache.commons.jexl3.JexlEngine;
27 import org.apache.commons.jexl3.JexlException;
28 import org.apache.commons.jexl3.JexlOperator;
29 import org.apache.commons.jexl3.internal.introspection.MethodExecutor;
30 import org.apache.commons.jexl3.internal.introspection.MethodKey;
31 import org.apache.commons.jexl3.introspection.JexlMethod;
32 import org.apache.commons.jexl3.introspection.JexlUberspect;
33 import org.apache.commons.jexl3.parser.JexlNode;
34
35
36
37
38
39
40 public final class Operator implements JexlOperator.Uberspect {
41 private static final String METHOD_IS_EMPTY = "isEmpty";
42 private static final String METHOD_SIZE = "size";
43 private static final String METHOD_CONTAINS = "contains";
44 private static final String METHOD_STARTS_WITH = "startsWith";
45 private static final String METHOD_ENDS_WITH = "endsWith";
46
47
48
49
50
51 private static final Set<JexlOperator> CMP_OPS =
52 EnumSet.of(JexlOperator.GT, JexlOperator.LT, JexlOperator.EQ, JexlOperator.GTE, JexlOperator.LTE);
53
54
55
56
57
58 private static final Set<JexlOperator> POSTFIX_OPS =
59 EnumSet.of(JexlOperator.GET_AND_INCREMENT, JexlOperator.GET_AND_DECREMENT);
60
61
62 private final JexlUberspect uberspect;
63
64
65 private final JexlArithmetic arithmetic;
66
67
68 private final Set<JexlOperator> overloads;
69
70
71 private final JexlArithmetic.Uberspect delegate;
72
73
74 private volatile int caching = -1;
75
76
77
78
79
80
81
82
83 public Operator(final JexlUberspect theUberspect, final JexlArithmetic theArithmetic) {
84 this.uberspect = theUberspect;
85 this.arithmetic = theArithmetic;
86 this.overloads = Collections.emptySet();
87 this.delegate = theUberspect.getArithmetic(theArithmetic);
88 }
89
90
91
92
93
94
95
96
97 public Operator(final JexlUberspect theUberspect,
98 final JexlArithmetic theArithmetic,
99 final Set<JexlOperator> theOverloads) {
100 this(theUberspect, theArithmetic, theOverloads, -1);
101 }
102
103
104
105
106
107
108
109
110
111 public Operator(final JexlUberspect theUberspect,
112 final JexlArithmetic theArithmetic,
113 final Set<JexlOperator> theOverloads,
114 final int theCache) {
115 this.uberspect = theUberspect;
116 this.arithmetic = theArithmetic;
117 this.overloads = theOverloads;
118 this.delegate = null;
119 this.caching = theCache;
120 }
121
122 @Override
123 public JexlMethod getOperator(final JexlOperator operator, final Object... args) {
124 if (delegate != null) {
125 return delegate.getOperator(operator, args);
126 }
127 if (overloads.contains(operator) && args != null && args.length == operator.getArity()) {
128 return uberspectOperator(arithmetic, operator, args);
129 }
130 return null;
131 }
132
133 @Override
134 public boolean overloads(final JexlOperator operator) {
135 return delegate != null
136 ? delegate.overloads(operator)
137 : overloads.contains(operator);
138 }
139
140
141
142
143 private boolean isCaching() {
144 int c = caching;
145 if (c < 0) {
146 synchronized(this) {
147 c = caching;
148 if (c < 0) {
149 final JexlEngine jexl = JexlEngine.getThreadEngine();
150 caching = c = jexl instanceof Engine && ((Engine) jexl).cache != null ? 1 : 0;
151 }
152 }
153 }
154 return c > 0;
155 }
156
157
158
159
160
161
162
163
164
165 private Object[] arguments(final JexlOperator operator, final Object...args) {
166 return operator.getArity() == 1 && args.length > 1 ? new Object[]{args[0]} : args;
167 }
168
169
170
171
172
173
174
175
176
177
178 private Boolean booleanDuckCall(final String methodName, final Object left, final Object right) throws Exception {
179 JexlMethod vm = uberspect.getMethod(left, methodName, right);
180 if (returnsBoolean(vm)) {
181 return (Boolean) vm.invoke(left, right);
182 }
183 final Object[] argv = { right };
184 if (arithmetic.narrowArguments(argv)) {
185 vm = uberspect.getMethod(left, methodName, argv);
186 if (returnsBoolean(vm)) {
187 return (Boolean) vm.invoke(left, argv);
188 }
189 }
190 return null;
191 }
192
193
194
195
196
197
198
199
200
201 private void controlNullOperands(final JexlArithmetic arithmetic, final JexlOperator operator, final Object...args) {
202 for (final Object arg : args) {
203
204 if (arg == null) {
205
206 if (arithmetic.isStrict(operator)) {
207 throw new JexlArithmetic.NullOperand();
208 }
209 break;
210 }
211 }
212 }
213
214
215
216
217
218
219
220
221
222
223
224 private <T> T operatorError(final JexlCache.Reference ref, final JexlOperator operator, final Throwable cause, final T alt) {
225 final JexlNode node = ref instanceof JexlNode ? (JexlNode) ref : null;
226 final Engine engine = (Engine) JexlEngine.getThreadEngine();
227 if (engine == null || engine.isStrict()) {
228 throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
229 }
230 if (engine.logger.isDebugEnabled()) {
231 engine.logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
232 }
233 return alt;
234 }
235
236
237
238
239
240
241
242
243
244
245 private JexlMethod uberspectOperator(final JexlArithmetic arithmetic,
246 final JexlOperator operator,
247 final Object... args) {
248 final JexlMethod me = uberspect.getMethod(arithmetic, operator.getMethodName(), args);
249 if (!(me instanceof MethodExecutor) ||
250 !JexlArithmetic.class.equals(((MethodExecutor) me).getMethod().getDeclaringClass())) {
251 return me;
252 }
253 return null;
254 }
255
256
257
258
259
260
261
262 private boolean returnsBoolean(final JexlMethod vm) {
263 if (vm != null) {
264 final Class<?> rc = vm.getReturnType();
265 return Boolean.TYPE.equals(rc) || Boolean.class.equals(rc);
266 }
267 return false;
268 }
269
270
271
272
273
274
275
276 private boolean returnsInteger(final JexlMethod vm) {
277 if (vm != null) {
278 final Class<?> rc = vm.getReturnType();
279 return Integer.TYPE.equals(rc) || Integer.class.equals(rc);
280 }
281 return false;
282 }
283
284 @Override
285 public Object empty(final JexlCache.Reference node, final Object object) {
286 if (object == null) {
287 return true;
288 }
289 Object result = overloads(JexlOperator.EMPTY)
290 ? tryOverload(node, JexlOperator.EMPTY, object)
291 : JexlEngine.TRY_FAILED;
292 if (result == JexlEngine.TRY_FAILED) {
293 result = arithmetic.isEmpty(object, null);
294 if (result == null) {
295 result = false;
296
297
298 final JexlMethod vm = uberspect.getMethod(object, METHOD_IS_EMPTY, InterpreterBase.EMPTY_PARAMS);
299 if (returnsBoolean(vm)) {
300 try {
301 result = vm.invoke(object, InterpreterBase.EMPTY_PARAMS);
302 } catch (final Exception any) {
303 return operatorError(node, JexlOperator.EMPTY, any, false);
304 }
305 }
306 }
307 }
308 return result;
309 }
310
311 @Override
312 public Object size(final JexlCache.Reference node, final Object object) {
313 if (object == null) {
314 return 0;
315 }
316 Object result = overloads(JexlOperator.SIZE)
317 ? tryOverload(node, JexlOperator.SIZE, object)
318 : JexlEngine.TRY_FAILED;
319 if (result == JexlEngine.TRY_FAILED) {
320 result = arithmetic.size(object, null);
321 if (result == null) {
322
323
324 final JexlMethod vm = uberspect.getMethod(object, METHOD_SIZE, InterpreterBase.EMPTY_PARAMS);
325 if (returnsInteger(vm)) {
326 try {
327 result = vm.invoke(object, InterpreterBase.EMPTY_PARAMS);
328 } catch (final Exception any) {
329 return operatorError(node, JexlOperator.SIZE, any, 0);
330 }
331 }
332 }
333 }
334 return result instanceof Number ? ((Number) result).intValue() : 0;
335 }
336
337 @Override
338 public boolean contains(final JexlCache.Reference node, final JexlOperator operator, final Object left, final Object right) {
339 final boolean contained;
340 try {
341
342 final Object result = overloads(JexlOperator.CONTAINS)
343 ? tryOverload(node, JexlOperator.CONTAINS, left, right)
344 : null;
345 if (result instanceof Boolean) {
346 contained = (Boolean) result;
347 } else {
348
349 final Boolean matched = arithmetic.contains(left, right);
350 if (matched != null) {
351 contained = matched;
352 } else {
353
354 final Boolean duck = booleanDuckCall(METHOD_CONTAINS, left, right);
355 if (duck != null) {
356 contained = duck;
357 } else {
358
359 contained = arithmetic.equals(left, right);
360 }
361 }
362 }
363
364 return JexlOperator.CONTAINS == operator == contained;
365 } catch (final Exception any) {
366 if (Thread.currentThread().isInterrupted()) {
367 throw new JexlException.Cancel(node instanceof JexlNode ? (JexlNode) node : null);
368 }
369 return operatorError(node, operator, any, false);
370 }
371 }
372
373 @Override
374 public boolean startsWith(final JexlCache.Reference node, final JexlOperator operator, final Object left, final Object right) {
375 final boolean starts;
376 try {
377
378 final Object result = overloads(JexlOperator.STARTSWITH)
379 ? tryOverload(node, JexlOperator.STARTSWITH, left, right)
380 : null;
381 if (result instanceof Boolean) {
382 starts = (Boolean) result;
383 } else {
384
385 final Boolean matched = arithmetic.startsWith(left, right);
386 if (matched != null) {
387 starts = matched;
388 } else {
389
390 final Boolean duck = booleanDuckCall(METHOD_STARTS_WITH, left, right);
391 if (duck != null) {
392 starts = duck;
393 } else {
394
395 starts = arithmetic.equals(left, right);
396 }
397 }
398 }
399
400 return JexlOperator.STARTSWITH == operator == starts;
401 } catch (final Exception any) {
402 return operatorError(node, operator, any, false);
403 }
404 }
405
406 @Override
407 public boolean endsWith(final JexlCache.Reference node, final JexlOperator operator, final Object left, final Object right) {
408 try {
409 final boolean ends;
410
411 final Object result = overloads(JexlOperator.ENDSWITH)
412 ? tryOverload(node, JexlOperator.ENDSWITH, left, right)
413 : null;
414 if (result instanceof Boolean) {
415 ends = (Boolean) result;
416 } else {
417
418 final Boolean matched = arithmetic.endsWith(left, right);
419 if (matched != null) {
420 ends = matched;
421 } else {
422
423 final Boolean duck = booleanDuckCall(METHOD_ENDS_WITH, left, right);
424 if (duck != null) {
425 ends = duck;
426 } else {
427
428 ends = arithmetic.equals(left, right);
429 }
430 }
431 }
432
433 return JexlOperator.ENDSWITH == operator == ends;
434 } catch (final Exception any) {
435 return operatorError(node, operator, any, false);
436 }
437 }
438
439 @Override
440 public Object tryAssignOverload(final JexlCache.Reference node,
441 final JexlOperator operator,
442 final Consumer<Object> assignFun,
443 final Object... args) {
444 if (args.length < operator.getArity()) {
445 return JexlEngine.TRY_FAILED;
446 }
447 Object result = JexlEngine.TRY_FAILED;
448 try {
449
450 controlNullOperands(arithmetic, operator, args[0]);
451
452 if (overloads(operator)) {
453 result = tryOverload(node, operator, arguments(operator, args));
454 if (result != JexlEngine.TRY_FAILED) {
455 return result;
456 }
457 }
458
459 final JexlOperator base = operator.getBaseOperator();
460 if (base != null && overloads(base)) {
461 result = tryOverload(node, base, arguments(base, args));
462 }
463
464 if (result == JexlEngine.TRY_FAILED) {
465 result = performBaseOperation(operator, args);
466 }
467
468 if (result != JexlEngine.TRY_FAILED) {
469 assignFun.accept(result);
470
471 if (POSTFIX_OPS.contains(operator)) {
472 result = args[0];
473 }
474 }
475 return result;
476 } catch (final Exception any) {
477 return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
478 }
479 }
480
481
482
483
484
485
486
487
488 private Object performBaseOperation(final JexlOperator operator, final Object... args) {
489 switch (operator) {
490 case SELF_ADD: return arithmetic.add(args[0], args[1]);
491 case SELF_SUBTRACT: return arithmetic.subtract(args[0], args[1]);
492 case SELF_MULTIPLY: return arithmetic.multiply(args[0], args[1]);
493 case SELF_DIVIDE: return arithmetic.divide(args[0], args[1]);
494 case SELF_MOD: return arithmetic.mod(args[0], args[1]);
495 case SELF_AND: return arithmetic.and(args[0], args[1]);
496 case SELF_OR: return arithmetic.or(args[0], args[1]);
497 case SELF_XOR: return arithmetic.xor(args[0], args[1]);
498 case SELF_SHIFTLEFT: return arithmetic.shiftLeft(args[0], args[1]);
499 case SELF_SHIFTRIGHT: return arithmetic.shiftRight(args[0], args[1]);
500 case SELF_SHIFTRIGHTU: return arithmetic.shiftRightUnsigned(args[0], args[1]);
501 case INCREMENT_AND_GET:
502 case GET_AND_INCREMENT:
503 return arithmetic.increment(args[0]);
504 case DECREMENT_AND_GET:
505 case GET_AND_DECREMENT:
506 return arithmetic.decrement(args[0]);
507 default:
508 throw new UnsupportedOperationException(operator.getOperatorSymbol());
509 }
510 }
511
512 @Override
513 public Object tryOverload(final JexlCache.Reference node, final JexlOperator operator, final Object... args) {
514 controlNullOperands(arithmetic, operator, args);
515 try {
516 return tryEval(isCaching() ? node : null, operator, args);
517 } catch (final Exception any) {
518
519 return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
520 }
521 }
522
523
524
525
526
527
528
529
530
531 private Object tryEval(final JexlCache.Reference node, final JexlOperator operator, final Object...args) {
532 if (node != null) {
533 final Object cached = node.getCache();
534 if (cached instanceof JexlMethod) {
535
536 final JexlMethod me = (JexlMethod) cached;
537 final Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
538 if (!me.tryFailed(eval)) {
539 return eval;
540 }
541 } else if (cached instanceof MethodKey) {
542
543 final MethodKey cachedKey = (MethodKey) cached;
544 final MethodKey key = new MethodKey(operator.getMethodName(), args);
545 if (key.equals(cachedKey)) {
546 return JexlEngine.TRY_FAILED;
547 }
548 }
549 }
550
551 JexlMethod vm = getOperator(operator, args);
552
553 if (vm == null) {
554 vm = getAlternateOverload(operator, args);
555 }
556
557 if (vm != null) {
558 final Object result = vm.tryInvoke(operator.getMethodName(), arithmetic, args);
559 if (node != null && !vm.tryFailed(result)) {
560 node.setCache(vm);
561 }
562 return result;
563 }
564 if (node != null) {
565
566 final MethodKey key = new MethodKey(operator.getMethodName(), args);
567 node.setCache(key);
568 }
569 return JexlEngine.TRY_FAILED;
570 }
571
572
573
574
575
576
577
578
579
580 private JexlMethod getAlternateOverload(final JexlOperator operator, final Object... args) {
581
582 if (CMP_OPS.contains(operator) && args.length == 2) {
583 JexlMethod cmp = getOperator(JexlOperator.COMPARE, args);
584 if (cmp != null) {
585 return new Operator.CompareMethod(operator, cmp);
586 }
587 cmp = getOperator(JexlOperator.COMPARE, args[1], args[0]);
588 if (cmp != null) {
589 return new Operator.AntiCompareMethod(operator, cmp);
590 }
591 }
592 return null;
593 }
594
595
596
597
598
599
600 private static class CompareMethod implements JexlMethod {
601 protected final JexlOperator operator;
602 protected final JexlMethod compare;
603
604 CompareMethod(final JexlOperator op, final JexlMethod m) {
605 operator = op;
606 compare = m;
607 }
608
609 @Override
610 public Class<?> getReturnType() {
611 return Boolean.TYPE;
612 }
613
614 @Override
615 public Object invoke(final Object arithmetic, final Object... params) throws Exception {
616 return operate((int) compare.invoke(arithmetic, params));
617 }
618
619 @Override
620 public boolean isCacheable() {
621 return true;
622 }
623
624 @Override
625 public boolean tryFailed(final Object rval) {
626 return rval == JexlEngine.TRY_FAILED;
627 }
628
629 @Override
630 public Object tryInvoke(final String name, final Object arithmetic, final Object... params) throws JexlException.TryFailed {
631 final Object cmp = compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params);
632 return cmp instanceof Integer? operate((int) cmp) : JexlEngine.TRY_FAILED;
633 }
634
635
636
637
638
639
640
641 protected boolean operate(final int cmp) {
642 switch(operator) {
643 case EQ: return cmp == 0;
644 case LT: return cmp < 0;
645 case LTE: return cmp <= 0;
646 case GT: return cmp > 0;
647 case GTE: return cmp >= 0;
648 default:
649 throw new ArithmeticException("unexpected operator " + operator);
650 }
651 }
652 }
653
654
655
656
657
658 private static class AntiCompareMethod extends CompareMethod {
659 AntiCompareMethod(final JexlOperator op, final JexlMethod m) {
660 super(op, m);
661 }
662
663 @Override
664 public Object invoke(final Object arithmetic, final Object... params) throws Exception {
665 return operate(-(int) compare.invoke(arithmetic, params[1], params[0]));
666 }
667
668 @Override
669 public Object tryInvoke(final String name, final Object arithmetic, final Object... params) throws JexlException.TryFailed {
670 final Object cmp = compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params[1], params[0]);
671 return cmp instanceof Integer? operate(-Integer.signum((Integer) cmp)) : JexlEngine.TRY_FAILED;
672 }
673 }
674 }