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