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 return operatorError(node, operator, any, false);
367 }
368 }
369
370 @Override
371 public boolean startsWith(final JexlCache.Reference node, final JexlOperator operator, final Object left, final Object right) {
372 final boolean starts;
373 try {
374
375 final Object result = overloads(JexlOperator.STARTSWITH)
376 ? tryOverload(node, JexlOperator.STARTSWITH, left, right)
377 : null;
378 if (result instanceof Boolean) {
379 starts = (Boolean) result;
380 } else {
381
382 final Boolean matched = arithmetic.startsWith(left, right);
383 if (matched != null) {
384 starts = matched;
385 } else {
386
387 final Boolean duck = booleanDuckCall(METHOD_STARTS_WITH, left, right);
388 if (duck != null) {
389 starts = duck;
390 } else {
391
392 starts = arithmetic.equals(left, right);
393 }
394 }
395 }
396
397 return JexlOperator.STARTSWITH == operator == starts;
398 } catch (final Exception any) {
399 return operatorError(node, operator, any, false);
400 }
401 }
402
403 @Override
404 public boolean endsWith(final JexlCache.Reference node, final JexlOperator operator, final Object left, final Object right) {
405 try {
406 final boolean ends;
407
408 final Object result = overloads(JexlOperator.ENDSWITH)
409 ? tryOverload(node, JexlOperator.ENDSWITH, left, right)
410 : null;
411 if (result instanceof Boolean) {
412 ends = (Boolean) result;
413 } else {
414
415 final Boolean matched = arithmetic.endsWith(left, right);
416 if (matched != null) {
417 ends = matched;
418 } else {
419
420 final Boolean duck = booleanDuckCall(METHOD_ENDS_WITH, left, right);
421 if (duck != null) {
422 ends = duck;
423 } else {
424
425 ends = arithmetic.equals(left, right);
426 }
427 }
428 }
429
430 return JexlOperator.ENDSWITH == operator == ends;
431 } catch (final Exception any) {
432 return operatorError(node, operator, any, false);
433 }
434 }
435
436 @Override
437 public Object tryAssignOverload(final JexlCache.Reference node,
438 final JexlOperator operator,
439 final Consumer<Object> assignFun,
440 final Object... args) {
441 if (args.length < operator.getArity()) {
442 return JexlEngine.TRY_FAILED;
443 }
444 Object result = JexlEngine.TRY_FAILED;
445 try {
446
447 controlNullOperands(arithmetic, operator, args[0]);
448
449 if (overloads(operator)) {
450 result = tryOverload(node, operator, arguments(operator, args));
451 if (result != JexlEngine.TRY_FAILED) {
452 return result;
453 }
454 }
455
456 final JexlOperator base = operator.getBaseOperator();
457 if (base != null && overloads(base)) {
458 result = tryOverload(node, base, arguments(base, args));
459 }
460
461 if (result == JexlEngine.TRY_FAILED) {
462 result = performBaseOperation(operator, args);
463 }
464
465 if (result != JexlEngine.TRY_FAILED) {
466 assignFun.accept(result);
467
468 if (POSTFIX_OPS.contains(operator)) {
469 result = args[0];
470 }
471 }
472 return result;
473 } catch (final Exception any) {
474 return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
475 }
476 }
477
478
479
480
481
482
483
484
485 private Object performBaseOperation(final JexlOperator operator, final Object... args) {
486 switch (operator) {
487 case SELF_ADD: return arithmetic.add(args[0], args[1]);
488 case SELF_SUBTRACT: return arithmetic.subtract(args[0], args[1]);
489 case SELF_MULTIPLY: return arithmetic.multiply(args[0], args[1]);
490 case SELF_DIVIDE: return arithmetic.divide(args[0], args[1]);
491 case SELF_MOD: return arithmetic.mod(args[0], args[1]);
492 case SELF_AND: return arithmetic.and(args[0], args[1]);
493 case SELF_OR: return arithmetic.or(args[0], args[1]);
494 case SELF_XOR: return arithmetic.xor(args[0], args[1]);
495 case SELF_SHIFTLEFT: return arithmetic.shiftLeft(args[0], args[1]);
496 case SELF_SHIFTRIGHT: return arithmetic.shiftRight(args[0], args[1]);
497 case SELF_SHIFTRIGHTU: return arithmetic.shiftRightUnsigned(args[0], args[1]);
498 case INCREMENT_AND_GET:
499 case GET_AND_INCREMENT:
500 return arithmetic.increment(args[0]);
501 case DECREMENT_AND_GET:
502 case GET_AND_DECREMENT:
503 return arithmetic.decrement(args[0]);
504 default:
505 throw new UnsupportedOperationException(operator.getOperatorSymbol());
506 }
507 }
508
509 @Override
510 public Object tryOverload(final JexlCache.Reference node, final JexlOperator operator, final Object... args) {
511 controlNullOperands(arithmetic, operator, args);
512 try {
513 return tryEval(isCaching() ? node : null, operator, args);
514 } catch (final Exception any) {
515
516 return operatorError(node, operator, any, JexlEngine.TRY_FAILED);
517 }
518 }
519
520
521
522
523
524
525
526
527
528 private Object tryEval(final JexlCache.Reference node, final JexlOperator operator, final Object...args) {
529 if (node != null) {
530 final Object cached = node.getCache();
531 if (cached instanceof JexlMethod) {
532
533 final JexlMethod me = (JexlMethod) cached;
534 final Object eval = me.tryInvoke(operator.getMethodName(), arithmetic, args);
535 if (!me.tryFailed(eval)) {
536 return eval;
537 }
538 } else if (cached instanceof MethodKey) {
539
540 final MethodKey cachedKey = (MethodKey) cached;
541 final MethodKey key = new MethodKey(operator.getMethodName(), args);
542 if (key.equals(cachedKey)) {
543 return JexlEngine.TRY_FAILED;
544 }
545 }
546 }
547
548 JexlMethod vm = getOperator(operator, args);
549
550 if (vm == null) {
551 vm = getAlternateOverload(operator, args);
552 }
553
554 if (vm != null) {
555 final Object result = vm.tryInvoke(operator.getMethodName(), arithmetic, args);
556 if (node != null && !vm.tryFailed(result)) {
557 node.setCache(vm);
558 }
559 return result;
560 }
561 if (node != null) {
562
563 final MethodKey key = new MethodKey(operator.getMethodName(), args);
564 node.setCache(key);
565 }
566 return JexlEngine.TRY_FAILED;
567 }
568
569
570
571
572
573
574
575
576
577 private JexlMethod getAlternateOverload(final JexlOperator operator, final Object... args) {
578
579 if (CMP_OPS.contains(operator) && args.length == 2) {
580 JexlMethod cmp = getOperator(JexlOperator.COMPARE, args);
581 if (cmp != null) {
582 return new Operator.CompareMethod(operator, cmp);
583 }
584 cmp = getOperator(JexlOperator.COMPARE, args[1], args[0]);
585 if (cmp != null) {
586 return new Operator.AntiCompareMethod(operator, cmp);
587 }
588 }
589 return null;
590 }
591
592
593
594
595
596
597 private static class CompareMethod implements JexlMethod {
598 protected final JexlOperator operator;
599 protected final JexlMethod compare;
600
601 CompareMethod(final JexlOperator op, final JexlMethod m) {
602 operator = op;
603 compare = m;
604 }
605
606 @Override
607 public Class<?> getReturnType() {
608 return Boolean.TYPE;
609 }
610
611 @Override
612 public Object invoke(final Object arithmetic, final Object... params) throws Exception {
613 return operate((int) compare.invoke(arithmetic, params));
614 }
615
616 @Override
617 public boolean isCacheable() {
618 return true;
619 }
620
621 @Override
622 public boolean tryFailed(final Object rval) {
623 return rval == JexlEngine.TRY_FAILED;
624 }
625
626 @Override
627 public Object tryInvoke(final String name, final Object arithmetic, final Object... params) throws JexlException.TryFailed {
628 final Object cmp = compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params);
629 return cmp instanceof Integer? operate((int) cmp) : JexlEngine.TRY_FAILED;
630 }
631
632
633
634
635
636
637
638 protected boolean operate(final int cmp) {
639 switch(operator) {
640 case EQ: return cmp == 0;
641 case LT: return cmp < 0;
642 case LTE: return cmp <= 0;
643 case GT: return cmp > 0;
644 case GTE: return cmp >= 0;
645 default:
646 throw new ArithmeticException("unexpected operator " + operator);
647 }
648 }
649 }
650
651
652
653
654
655 private static class AntiCompareMethod extends CompareMethod {
656 AntiCompareMethod(final JexlOperator op, final JexlMethod m) {
657 super(op, m);
658 }
659
660 @Override
661 public Object invoke(final Object arithmetic, final Object... params) throws Exception {
662 return operate(-(int) compare.invoke(arithmetic, params[1], params[0]));
663 }
664
665 @Override
666 public Object tryInvoke(final String name, final Object arithmetic, final Object... params) throws JexlException.TryFailed {
667 final Object cmp = compare.tryInvoke(JexlOperator.COMPARE.getMethodName(), arithmetic, params[1], params[0]);
668 return cmp instanceof Integer? operate(-Integer.signum((Integer) cmp)) : JexlEngine.TRY_FAILED;
669 }
670 }
671 }