001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.jexl2;
018
019 import java.lang.reflect.Array;
020 import java.lang.reflect.InvocationTargetException;
021 import java.util.Collection;
022 import java.util.HashMap;
023 import java.util.Iterator;
024 import java.util.Map;
025 import java.util.Set;
026
027 import org.apache.commons.jexl2.parser.SimpleNode;
028 import org.apache.commons.logging.Log;
029
030 import org.apache.commons.jexl2.parser.ASTFloatLiteral;
031 import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
032 import org.apache.commons.jexl2.parser.JexlNode;
033 import org.apache.commons.jexl2.parser.ASTAdditiveNode;
034 import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
035 import org.apache.commons.jexl2.parser.ASTAndNode;
036 import org.apache.commons.jexl2.parser.ASTAmbiguous;
037 import org.apache.commons.jexl2.parser.ASTArrayAccess;
038 import org.apache.commons.jexl2.parser.ASTArrayLiteral;
039 import org.apache.commons.jexl2.parser.ASTAssignment;
040 import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
041 import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
042 import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
043 import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
044 import org.apache.commons.jexl2.parser.ASTBlock;
045 import org.apache.commons.jexl2.parser.ASTConstructorNode;
046 import org.apache.commons.jexl2.parser.ASTDivNode;
047 import org.apache.commons.jexl2.parser.ASTEQNode;
048 import org.apache.commons.jexl2.parser.ASTERNode;
049 import org.apache.commons.jexl2.parser.ASTEmptyFunction;
050 import org.apache.commons.jexl2.parser.ASTFalseNode;
051 import org.apache.commons.jexl2.parser.ASTFunctionNode;
052 import org.apache.commons.jexl2.parser.ASTForeachStatement;
053 import org.apache.commons.jexl2.parser.ASTGENode;
054 import org.apache.commons.jexl2.parser.ASTGTNode;
055 import org.apache.commons.jexl2.parser.ASTIdentifier;
056 import org.apache.commons.jexl2.parser.ASTIfStatement;
057 import org.apache.commons.jexl2.parser.ASTJexlScript;
058 import org.apache.commons.jexl2.parser.ASTLENode;
059 import org.apache.commons.jexl2.parser.ASTLTNode;
060 import org.apache.commons.jexl2.parser.ASTMapEntry;
061 import org.apache.commons.jexl2.parser.ASTMapLiteral;
062 import org.apache.commons.jexl2.parser.ASTMethodNode;
063 import org.apache.commons.jexl2.parser.ASTModNode;
064 import org.apache.commons.jexl2.parser.ASTMulNode;
065 import org.apache.commons.jexl2.parser.ASTNENode;
066 import org.apache.commons.jexl2.parser.ASTNRNode;
067 import org.apache.commons.jexl2.parser.ASTNotNode;
068 import org.apache.commons.jexl2.parser.ASTNullLiteral;
069 import org.apache.commons.jexl2.parser.ASTNumberLiteral;
070 import org.apache.commons.jexl2.parser.ASTOrNode;
071 import org.apache.commons.jexl2.parser.ASTReference;
072 import org.apache.commons.jexl2.parser.ASTReferenceExpression;
073 import org.apache.commons.jexl2.parser.ASTReturnStatement;
074 import org.apache.commons.jexl2.parser.ASTSizeFunction;
075 import org.apache.commons.jexl2.parser.ASTSizeMethod;
076 import org.apache.commons.jexl2.parser.ASTStringLiteral;
077 import org.apache.commons.jexl2.parser.ASTTernaryNode;
078 import org.apache.commons.jexl2.parser.ASTTrueNode;
079 import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
080 import org.apache.commons.jexl2.parser.ASTWhileStatement;
081 import org.apache.commons.jexl2.parser.Node;
082 import org.apache.commons.jexl2.parser.ParserVisitor;
083
084 import org.apache.commons.jexl2.introspection.Uberspect;
085 import org.apache.commons.jexl2.introspection.JexlMethod;
086 import org.apache.commons.jexl2.introspection.JexlPropertyGet;
087 import org.apache.commons.jexl2.introspection.JexlPropertySet;
088 import org.apache.commons.jexl2.parser.ASTVar;
089
090 /**
091 * An interpreter of JEXL syntax.
092 *
093 * @since 2.0
094 */
095 public class Interpreter implements ParserVisitor {
096 /** The logger. */
097 protected final Log logger;
098 /** The uberspect. */
099 protected final Uberspect uberspect;
100 /** The arithmetic handler. */
101 protected final JexlArithmetic arithmetic;
102 /** The map of registered functions. */
103 protected final Map<String, Object> functions;
104 /** The map of registered functions. */
105 protected Map<String, Object> functors;
106 /** The context to store/retrieve variables. */
107 protected final JexlContext context;
108 /** Strict interpreter flag. Do not modify; will be made final/private in a later version. */
109 protected boolean strict;
110 /** Silent intepreter flag. Do not modify; will be made final/private in a later version. */
111 protected boolean silent;
112 /** Cache executors. */
113 protected final boolean cache;
114 /** Registers or arguments. */
115 protected Object[] registers = null;
116 /**
117 * Parameter names if any.
118 * Intended for use in debugging; not currently used externally.
119 * @since 2.1
120 */
121 @SuppressWarnings("unused")
122 private String[] parameters = null;
123
124 /**
125 * Cancellation support.
126 * @see #isCancelled()
127 * @since 2.1
128 */
129 private volatile boolean cancelled = false;
130
131 /** Empty parameters for method matching. */
132 protected static final Object[] EMPTY_PARAMS = new Object[0];
133
134 /**
135 * Creates an interpreter.
136 * @param jexl the engine creating this interpreter
137 * @param aContext the context to evaluate expression
138 * @deprecated
139 */
140 @Deprecated
141 public Interpreter(JexlEngine jexl, JexlContext aContext) {
142 this(jexl, aContext, !jexl.isLenient(), jexl.isSilent());
143 }
144
145 /**
146 * Creates an interpreter.
147 * @param jexl the engine creating this interpreter
148 * @param aContext the context to evaluate expression
149 * @param strictFlag whether this interpreter runs in strict mode
150 * @param silentFlag whether this interpreter runs in silent mode
151 * @since 2.1
152 */
153 public Interpreter(JexlEngine jexl, JexlContext aContext, boolean strictFlag, boolean silentFlag) {
154 this.logger = jexl.logger;
155 this.uberspect = jexl.uberspect;
156 this.arithmetic = jexl.arithmetic;
157 this.functions = jexl.functions;
158 this.strict = strictFlag;
159 this.silent = silentFlag;
160 this.cache = jexl.cache != null;
161 this.context = aContext != null? aContext : JexlEngine.EMPTY_CONTEXT;
162 this.functors = null;
163 }
164
165 /**
166 * Copy constructor.
167 * @param base the base to copy
168 * @since 2.1
169 */
170 protected Interpreter(Interpreter base) {
171 this.logger = base.logger;
172 this.uberspect = base.uberspect;
173 this.arithmetic = base.arithmetic;
174 this.functions = base.functions;
175 this.strict = base.strict;
176 this.silent = base.silent;
177 this.cache = base.cache;
178 this.context = base.context;
179 this.functors = base.functors;
180 }
181
182 /**
183 * Sets whether this interpreter considers unknown variables, methods and constructors as errors.
184 * @param flag true for strict, false for lenient
185 * @deprecated Do not use; will be removed in a later version
186 * @since 2.1
187 */
188 // TODO why add a method and then deprecate it?
189 @Deprecated
190 public void setStrict(boolean flag) {
191 this.strict = flag;
192 }
193
194 /**
195 * Sets whether this interpreter throws JexlException when encountering errors.
196 * @param flag true for silent, false for verbose
197 * @deprecated Do not use; will be removed in a later version
198 */
199 @Deprecated
200 public void setSilent(boolean flag) {
201 this.silent = flag;
202 }
203
204 /**
205 * Checks whether this interpreter considers unknown variables, methods and constructors as errors.
206 * @return true if strict, false otherwise
207 * @since 2.1
208 */
209 public boolean isStrict() {
210 return this.strict;
211 }
212
213 /**
214 * Checks whether this interpreter throws JexlException when encountering errors.
215 * @return true if silent, false otherwise
216 */
217 public boolean isSilent() {
218 return this.silent;
219 }
220
221 /**
222 * Interpret the given script/expression.
223 * <p>
224 * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
225 * </p>
226 * @param node the script or expression to interpret.
227 * @return the result of the interpretation.
228 * @throws JexlException if any error occurs during interpretation.
229 */
230 public Object interpret(JexlNode node) {
231 try {
232 return node.jjtAccept(this, null);
233 } catch (JexlException.Return xreturn) {
234 Object value = xreturn.getValue();
235 return value;
236 } catch (JexlException xjexl) {
237 if (silent) {
238 logger.warn(xjexl.getMessage(), xjexl.getCause());
239 return null;
240 }
241 throw xjexl;
242 } finally {
243 functors = null;
244 parameters = null;
245 registers = null;
246 }
247 }
248
249 /**
250 * Gets the context.
251 * @return the {@link JexlContext} used for evaluation.
252 * @since 2.1
253 */
254 protected JexlContext getContext() {
255 return context;
256 }
257
258 /**
259 * Gets the uberspect.
260 * @return an {@link Uberspect}
261 */
262 protected Uberspect getUberspect() {
263 return uberspect;
264 }
265
266 /**
267 * Sets this interpreter registers for bean access/assign expressions.
268 * <p>Use setFrame(...) instead.</p>
269 * @param theRegisters the array of registers
270 */
271 @Deprecated
272 protected void setRegisters(Object... theRegisters) {
273 if (theRegisters != null) {
274 String[] regStrs = new String[theRegisters.length];
275 for (int r = 0; r < regStrs.length; ++r) {
276 regStrs[r] = "#" + r;
277 }
278 this.parameters = regStrs;
279 }
280 this.registers = theRegisters;
281 }
282
283 /**
284 * Sets this interpreter parameters and arguments.
285 * @param frame the calling frame
286 * @since 2.1
287 */
288 protected void setFrame(JexlEngine.Frame frame) {
289 if (frame != null) {
290 this.parameters = frame.getParameters();
291 this.registers = frame.getRegisters();
292 } else {
293 this.parameters = null;
294 this.registers = null;
295 }
296 }
297
298 /**
299 * Finds the node causing a NPE for diadic operators.
300 * @param xrt the RuntimeException
301 * @param node the parent node
302 * @param left the left argument
303 * @param right the right argument
304 * @return the left, right or parent node
305 */
306 protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
307 if (xrt instanceof ArithmeticException
308 && JexlException.NULL_OPERAND == xrt.getMessage()) {
309 if (left == null) {
310 return node.jjtGetChild(0);
311 }
312 if (right == null) {
313 return node.jjtGetChild(1);
314 }
315 }
316 return node;
317 }
318
319 /**
320 * Triggered when variable can not be resolved.
321 * @param xjexl the JexlException ("undefined variable " + variable)
322 * @return throws JexlException if strict, null otherwise
323 */
324 protected Object unknownVariable(JexlException xjexl) {
325 if (strict) {
326 throw xjexl;
327 }
328 if (!silent) {
329 logger.warn(xjexl.getMessage());
330 }
331 return null;
332 }
333
334 /**
335 * Triggered when method, function or constructor invocation fails.
336 * @param xjexl the JexlException wrapping the original error
337 * @return throws JexlException if strict, null otherwise
338 */
339 protected Object invocationFailed(JexlException xjexl) {
340 if (strict || xjexl instanceof JexlException.Return) {
341 throw xjexl;
342 }
343 if (!silent) {
344 logger.warn(xjexl.getMessage(), xjexl.getCause());
345 }
346 return null;
347 }
348
349 /**
350 * Checks whether this interpreter execution was cancelled due to thread interruption.
351 * @return true if cancelled, false otherwise
352 * @since 2.1
353 */
354 protected boolean isCancelled() {
355 if (cancelled | Thread.interrupted()) {
356 cancelled = true;
357 }
358 return cancelled;
359 }
360
361 /**
362 * Resolves a namespace, eventually allocating an instance using context as constructor argument.
363 * The lifetime of such instances span the current expression or script evaluation.
364 *
365 * @param prefix the prefix name (may be null for global namespace)
366 * @param node the AST node
367 * @return the namespace instance
368 */
369 protected Object resolveNamespace(String prefix, JexlNode node) {
370 Object namespace = null;
371 // check whether this namespace is a functor
372 if (functors != null) {
373 namespace = functors.get(prefix);
374 if (namespace != null) {
375 return namespace;
376 }
377 }
378 // check if namespace if a resolver
379 if (context instanceof NamespaceResolver) {
380 namespace = ((NamespaceResolver) context).resolveNamespace(prefix);
381 }
382 if (namespace == null) {
383 namespace = functions.get(prefix);
384 if (prefix != null && namespace == null) {
385 throw new JexlException(node, "no such function namespace " + prefix);
386 }
387 }
388 // allow namespace to be instantiated as functor with context if possible, not an error otherwise
389 if (namespace instanceof Class<?>) {
390 Object[] args = new Object[]{context};
391 JexlMethod ctor = uberspect.getConstructorMethod(namespace, args, node);
392 if (ctor != null) {
393 try {
394 namespace = ctor.invoke(namespace, args);
395 if (functors == null) {
396 functors = new HashMap<String, Object>();
397 }
398 functors.put(prefix, namespace);
399 } catch (Exception xinst) {
400 throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
401 }
402 }
403 }
404 return namespace;
405 }
406
407 /** {@inheritDoc} */
408 public Object visit(ASTAdditiveNode node, Object data) {
409 /**
410 * The pattern for exception mgmt is to let the child*.jjtAccept
411 * out of the try/catch loop so that if one fails, the ex will
412 * traverse up to the interpreter.
413 * In cases where this is not convenient/possible, JexlException must
414 * be caught explicitly and rethrown.
415 */
416 Object left = node.jjtGetChild(0).jjtAccept(this, data);
417 for (int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
418 Object right = node.jjtGetChild(c).jjtAccept(this, data);
419 try {
420 JexlNode op = node.jjtGetChild(c - 1);
421 if (op instanceof ASTAdditiveOperator) {
422 String which = op.image;
423 if ("+".equals(which)) {
424 left = arithmetic.add(left, right);
425 continue;
426 }
427 if ("-".equals(which)) {
428 left = arithmetic.subtract(left, right);
429 continue;
430 }
431 throw new UnsupportedOperationException("unknown operator " + which);
432 }
433 throw new IllegalArgumentException("unknown operator " + op);
434 } catch (ArithmeticException xrt) {
435 JexlNode xnode = findNullOperand(xrt, node, left, right);
436 throw new JexlException(xnode, "+/- error", xrt);
437 }
438 }
439 return left;
440 }
441
442 /** {@inheritDoc} */
443 public Object visit(ASTAdditiveOperator node, Object data) {
444 throw new UnsupportedOperationException("Shoud not be called.");
445 }
446
447 /** {@inheritDoc} */
448 public Object visit(ASTAndNode node, Object data) {
449 Object left = node.jjtGetChild(0).jjtAccept(this, data);
450 try {
451 boolean leftValue = arithmetic.toBoolean(left);
452 if (!leftValue) {
453 return Boolean.FALSE;
454 }
455 } catch (RuntimeException xrt) {
456 throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
457 }
458 Object right = node.jjtGetChild(1).jjtAccept(this, data);
459 try {
460 boolean rightValue = arithmetic.toBoolean(right);
461 if (!rightValue) {
462 return Boolean.FALSE;
463 }
464 } catch (ArithmeticException xrt) {
465 throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
466 }
467 return Boolean.TRUE;
468 }
469
470 /** {@inheritDoc} */
471 public Object visit(ASTArrayAccess node, Object data) {
472 // first objectNode is the identifier
473 Object object = node.jjtGetChild(0).jjtAccept(this, data);
474 // can have multiple nodes - either an expression, integer literal or reference
475 int numChildren = node.jjtGetNumChildren();
476 for (int i = 1; i < numChildren; i++) {
477 JexlNode nindex = node.jjtGetChild(i);
478 if (nindex instanceof JexlNode.Literal<?>) {
479 object = nindex.jjtAccept(this, object);
480 } else {
481 Object index = nindex.jjtAccept(this, null);
482 object = getAttribute(object, index, nindex);
483 }
484 }
485
486 return object;
487 }
488
489 /** {@inheritDoc} */
490 public Object visit(ASTArrayLiteral node, Object data) {
491 Object literal = node.getLiteral();
492 if (literal == null) {
493 int childCount = node.jjtGetNumChildren();
494 Object[] array = new Object[childCount];
495 for (int i = 0; i < childCount; i++) {
496 Object entry = node.jjtGetChild(i).jjtAccept(this, data);
497 array[i] = entry;
498 }
499 literal = arithmetic.narrowArrayType(array);
500 node.setLiteral(literal);
501 }
502 return literal;
503 }
504
505 /** {@inheritDoc} */
506 public Object visit(ASTAssignment node, Object data) {
507 // left contains the reference to assign to
508 int register = -1;
509 JexlNode left = node.jjtGetChild(0);
510 if (left instanceof ASTIdentifier) {
511 ASTIdentifier var = (ASTIdentifier) left;
512 register = var.getRegister();
513 if (register < 0) {
514 throw new JexlException(left, "unknown variable " + left.image);
515 }
516 } else if (!(left instanceof ASTReference)) {
517 throw new JexlException(left, "illegal assignment form 0");
518 }
519 // right is the value expression to assign
520 Object right = node.jjtGetChild(1).jjtAccept(this, data);
521
522 // determine initial object & property:
523 JexlNode objectNode = null;
524 Object object = register >= 0 ? registers[register] : null;
525 JexlNode propertyNode = null;
526 Object property = null;
527 boolean isVariable = true;
528 int v = 0;
529 StringBuilder variableName = null;
530 // 1: follow children till penultimate, resolve dot/array
531 int last = left.jjtGetNumChildren() - 1;
532 // check we are not assigning a register itself
533 boolean isRegister = last < 0 && register >= 0;
534 // start at 1 if register
535 for (int c = register >= 0 ? 1 : 0; c < last; ++c) {
536 objectNode = left.jjtGetChild(c);
537 // evaluate the property within the object
538 object = objectNode.jjtAccept(this, object);
539 if (object != null) {
540 continue;
541 }
542 isVariable &= objectNode instanceof ASTIdentifier
543 || (objectNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) objectNode).isInteger());
544 // if we get null back as a result, check for an ant variable
545 if (isVariable) {
546 if (v == 0) {
547 variableName = new StringBuilder(left.jjtGetChild(0).image);
548 v = 1;
549 }
550 for (; v <= c; ++v) {
551 variableName.append('.');
552 variableName.append(left.jjtGetChild(v).image);
553 }
554 object = context.get(variableName.toString());
555 // disallow mixing ant & bean with same root; avoid ambiguity
556 if (object != null) {
557 isVariable = false;
558 }
559 } else {
560 throw new JexlException(objectNode, "illegal assignment form");
561 }
562 }
563 // 2: last objectNode will perform assignement in all cases
564 propertyNode = isRegister ? null : left.jjtGetChild(last);
565 boolean antVar = false;
566 if (propertyNode instanceof ASTIdentifier) {
567 ASTIdentifier identifier = (ASTIdentifier) propertyNode;
568 register = identifier.getRegister();
569 if (register >= 0) {
570 isRegister = true;
571 } else {
572 property = identifier.image;
573 antVar = true;
574 }
575 } else if (propertyNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) propertyNode).isInteger()) {
576 property = ((ASTNumberLiteral) propertyNode).getLiteral();
577 antVar = true;
578 } else if (propertyNode instanceof ASTArrayAccess) {
579 // first objectNode is the identifier
580 objectNode = propertyNode;
581 ASTArrayAccess narray = (ASTArrayAccess) objectNode;
582 Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
583 if (nobject == null) {
584 throw new JexlException(objectNode, "array element is null");
585 } else {
586 object = nobject;
587 }
588 // can have multiple nodes - either an expression, integer literal or
589 // reference
590 last = narray.jjtGetNumChildren() - 1;
591 for (int i = 1; i < last; i++) {
592 objectNode = narray.jjtGetChild(i);
593 if (objectNode instanceof JexlNode.Literal<?>) {
594 object = objectNode.jjtAccept(this, object);
595 } else {
596 Object index = objectNode.jjtAccept(this, null);
597 object = getAttribute(object, index, objectNode);
598 }
599 }
600 property = narray.jjtGetChild(last).jjtAccept(this, null);
601 } else if (!isRegister) {
602 throw new JexlException(objectNode, "illegal assignment form");
603 }
604 // deal with ant variable; set context
605 if (isRegister) {
606 registers[register] = right;
607 return right;
608 } else if (antVar) {
609 if (isVariable && object == null) {
610 if (variableName != null) {
611 if (last > 0) {
612 variableName.append('.');
613 }
614 variableName.append(property);
615 property = variableName.toString();
616 }
617 try {
618 context.set(String.valueOf(property), right);
619 } catch (UnsupportedOperationException xsupport) {
620 throw new JexlException(node, "context is readonly", xsupport);
621 }
622 return right;
623 }
624 }
625 if (property == null) {
626 // no property, we fail
627 throw new JexlException(propertyNode, "property is null");
628 }
629 if (object == null) {
630 // no object, we fail
631 throw new JexlException(objectNode, "bean is null");
632 }
633 // one before last, assign
634 setAttribute(object, property, right, propertyNode);
635 return right;
636 }
637
638 /** {@inheritDoc} */
639 public Object visit(ASTBitwiseAndNode node, Object data) {
640 Object left = node.jjtGetChild(0).jjtAccept(this, data);
641 Object right = node.jjtGetChild(1).jjtAccept(this, data);
642 try {
643 return arithmetic.bitwiseAnd(left, right);
644 } catch (ArithmeticException xrt) {
645 throw new JexlException(node, "& error", xrt);
646 }
647 }
648
649 /** {@inheritDoc} */
650 public Object visit(ASTBitwiseComplNode node, Object data) {
651 Object left = node.jjtGetChild(0).jjtAccept(this, data);
652 try {
653 return arithmetic.bitwiseComplement(left);
654 } catch (ArithmeticException xrt) {
655 throw new JexlException(node, "~ error", xrt);
656 }
657 }
658
659 /** {@inheritDoc} */
660 public Object visit(ASTBitwiseOrNode node, Object data) {
661 Object left = node.jjtGetChild(0).jjtAccept(this, data);
662 Object right = node.jjtGetChild(1).jjtAccept(this, data);
663 try {
664 return arithmetic.bitwiseOr(left, right);
665 } catch (ArithmeticException xrt) {
666 throw new JexlException(node, "| error", xrt);
667 }
668 }
669
670 /** {@inheritDoc} */
671 public Object visit(ASTBitwiseXorNode node, Object data) {
672 Object left = node.jjtGetChild(0).jjtAccept(this, data);
673 Object right = node.jjtGetChild(1).jjtAccept(this, data);
674 try {
675 return arithmetic.bitwiseXor(left, right);
676 } catch (ArithmeticException xrt) {
677 throw new JexlException(node, "^ error", xrt);
678 }
679 }
680
681 /** {@inheritDoc} */
682 public Object visit(ASTBlock node, Object data) {
683 int numChildren = node.jjtGetNumChildren();
684 Object result = null;
685 for (int i = 0; i < numChildren; i++) {
686 result = node.jjtGetChild(i).jjtAccept(this, data);
687 }
688 return result;
689 }
690
691 /** {@inheritDoc} */
692 public Object visit(ASTDivNode node, Object data) {
693 Object left = node.jjtGetChild(0).jjtAccept(this, data);
694 Object right = node.jjtGetChild(1).jjtAccept(this, data);
695 try {
696 return arithmetic.divide(left, right);
697 } catch (ArithmeticException xrt) {
698 if (!strict) {
699 return new Double(0.0);
700 }
701 JexlNode xnode = findNullOperand(xrt, node, left, right);
702 throw new JexlException(xnode, "divide error", xrt);
703 }
704 }
705
706 /** {@inheritDoc} */
707 public Object visit(ASTEmptyFunction node, Object data) {
708 Object o = node.jjtGetChild(0).jjtAccept(this, data);
709 if (o == null) {
710 return Boolean.TRUE;
711 }
712 if (o instanceof String && "".equals(o)) {
713 return Boolean.TRUE;
714 }
715 if (o.getClass().isArray() && ((Object[]) o).length == 0) {
716 return Boolean.TRUE;
717 }
718 if (o instanceof Collection<?>) {
719 return ((Collection<?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
720 }
721 // Map isn't a collection
722 if (o instanceof Map<?, ?>) {
723 return ((Map<?, ?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
724 }
725 return Boolean.FALSE;
726 }
727
728 /** {@inheritDoc} */
729 public Object visit(ASTEQNode node, Object data) {
730 Object left = node.jjtGetChild(0).jjtAccept(this, data);
731 Object right = node.jjtGetChild(1).jjtAccept(this, data);
732 try {
733 return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
734 } catch (ArithmeticException xrt) {
735 throw new JexlException(node, "== error", xrt);
736 }
737 }
738
739 /** {@inheritDoc} */
740 public Object visit(ASTFalseNode node, Object data) {
741 return Boolean.FALSE;
742 }
743
744 /** {@inheritDoc} */
745 public Object visit(ASTForeachStatement node, Object data) {
746 Object result = null;
747 /* first objectNode is the loop variable */
748 ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
749 ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
750 int register = loopVariable.getRegister();
751 /* second objectNode is the variable to iterate */
752 Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
753 // make sure there is a value to iterate on and a statement to execute
754 if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
755 /* third objectNode is the statement to execute */
756 JexlNode statement = node.jjtGetChild(2);
757 // get an iterator for the collection/array etc via the
758 // introspector.
759 Iterator<?> itemsIterator = uberspect.getIterator(iterableValue, node);
760 if (itemsIterator != null) {
761 while (itemsIterator.hasNext()) {
762 if (isCancelled()) {
763 throw new JexlException.Cancel(node);
764 }
765 // set loopVariable to value of iterator
766 Object value = itemsIterator.next();
767 if (register < 0) {
768 context.set(loopVariable.image, value);
769 } else {
770 registers[register] = value;
771 }
772 // execute statement
773 result = statement.jjtAccept(this, data);
774 }
775 }
776 }
777 return result;
778 }
779
780 /** {@inheritDoc} */
781 public Object visit(ASTGENode node, Object data) {
782 Object left = node.jjtGetChild(0).jjtAccept(this, data);
783 Object right = node.jjtGetChild(1).jjtAccept(this, data);
784 try {
785 return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
786 } catch (ArithmeticException xrt) {
787 throw new JexlException(node, ">= error", xrt);
788 }
789 }
790
791 /** {@inheritDoc} */
792 public Object visit(ASTGTNode node, Object data) {
793 Object left = node.jjtGetChild(0).jjtAccept(this, data);
794 Object right = node.jjtGetChild(1).jjtAccept(this, data);
795 try {
796 return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
797 } catch (ArithmeticException xrt) {
798 throw new JexlException(node, "> error", xrt);
799 }
800 }
801
802 /** {@inheritDoc} */
803 public Object visit(ASTERNode node, Object data) {
804 Object left = node.jjtGetChild(0).jjtAccept(this, data);
805 Object right = node.jjtGetChild(1).jjtAccept(this, data);
806 try {
807 // use arithmetic / pattern matching ?
808 if (right instanceof java.util.regex.Pattern || right instanceof String) {
809 return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
810 }
811 // left in right ? <=> right.contains(left) ?
812 // try contains on collection
813 if (right instanceof Set<?>) {
814 return ((Set<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
815 }
816 // try contains on map key
817 if (right instanceof Map<?, ?>) {
818 return ((Map<?, ?>) right).containsKey(left) ? Boolean.TRUE : Boolean.FALSE;
819 }
820 // try contains on collection
821 if (right instanceof Collection<?>) {
822 return ((Collection<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
823 }
824 // try a contains method (duck type set)
825 try {
826 Object[] argv = {left};
827 JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
828 if (vm != null) {
829 return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
830 } else if (arithmetic.narrowArguments(argv)) {
831 vm = uberspect.getMethod(right, "contains", argv, node);
832 if (vm != null) {
833 return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
834 }
835 }
836 } catch (InvocationTargetException e) {
837 throw new JexlException(node, "=~ invocation error", e.getCause());
838 } catch (Exception e) {
839 throw new JexlException(node, "=~ error", e);
840 }
841 // try iterative comparison
842 Iterator<?> it = uberspect.getIterator(right, node);
843 if (it != null) {
844 while (it.hasNext()) {
845 Object next = it.next();
846 if (next == left || (next != null && next.equals(left))) {
847 return Boolean.TRUE;
848 }
849 }
850 return Boolean.FALSE;
851 }
852 // defaults to equal
853 return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
854 } catch (ArithmeticException xrt) {
855 throw new JexlException(node, "=~ error", xrt);
856 }
857 }
858
859 /** {@inheritDoc} */
860 public Object visit(ASTIdentifier node, Object data) {
861 if (isCancelled()) {
862 throw new JexlException.Cancel(node);
863 }
864 String name = node.image;
865 if (data == null) {
866 int register = node.getRegister();
867 if (register >= 0) {
868 return registers[register];
869 }
870 Object value = context.get(name);
871 if (value == null
872 && !(node.jjtGetParent() instanceof ASTReference)
873 && !context.has(name)
874 && !isTernaryProtected(node)) {
875 JexlException xjexl = new JexlException.Variable(node, name);
876 return unknownVariable(xjexl);
877 }
878 return value;
879 } else {
880 return getAttribute(data, name, node);
881 }
882 }
883
884 /**
885 * @deprecated Do not use
886 */
887 @Deprecated
888 public Object visit(ASTFloatLiteral node, Object data) {
889 throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
890 }
891
892 /**
893 * @deprecated Do not use
894 */
895 @Deprecated
896 public Object visit(ASTIntegerLiteral node, Object data) {
897 throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
898 }
899
900 /** {@inheritDoc} */
901 public Object visit(ASTVar node, Object data) {
902 return visit((ASTIdentifier) node, data);
903 }
904
905 /** {@inheritDoc} */
906 public Object visit(ASTIfStatement node, Object data) {
907 int n = 0;
908 try {
909 Object result = null;
910 /* first objectNode is the condition */
911 Object expression = node.jjtGetChild(0).jjtAccept(this, data);
912 if (arithmetic.toBoolean(expression)) {
913 // first objectNode is true statement
914 n = 1;
915 result = node.jjtGetChild(1).jjtAccept(this, data);
916 } else {
917 // if there is a false, execute it. false statement is the second
918 // objectNode
919 if (node.jjtGetNumChildren() == 3) {
920 n = 2;
921 result = node.jjtGetChild(2).jjtAccept(this, data);
922 }
923 }
924 return result;
925 } catch (JexlException error) {
926 throw error;
927 } catch (ArithmeticException xrt) {
928 throw new JexlException(node.jjtGetChild(n), "if error", xrt);
929 }
930 }
931
932 /** {@inheritDoc} */
933 public Object visit(ASTNumberLiteral node, Object data) {
934 if (data != null && node.isInteger()) {
935 return getAttribute(data, node.getLiteral(), node);
936 }
937 return node.getLiteral();
938 }
939
940 /** {@inheritDoc} */
941 public Object visit(ASTJexlScript node, Object data) {
942 int numChildren = node.jjtGetNumChildren();
943 Object result = null;
944 for (int i = 0; i < numChildren; i++) {
945 JexlNode child = node.jjtGetChild(i);
946 result = child.jjtAccept(this, data);
947 }
948 return result;
949 }
950
951 /** {@inheritDoc} */
952 public Object visit(ASTLENode node, Object data) {
953 Object left = node.jjtGetChild(0).jjtAccept(this, data);
954 Object right = node.jjtGetChild(1).jjtAccept(this, data);
955 try {
956 return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
957 } catch (ArithmeticException xrt) {
958 throw new JexlException(node, "<= error", xrt);
959 }
960 }
961
962 /** {@inheritDoc} */
963 public Object visit(ASTLTNode node, Object data) {
964 Object left = node.jjtGetChild(0).jjtAccept(this, data);
965 Object right = node.jjtGetChild(1).jjtAccept(this, data);
966 try {
967 return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
968 } catch (ArithmeticException xrt) {
969 throw new JexlException(node, "< error", xrt);
970 }
971 }
972
973 /** {@inheritDoc} */
974 public Object visit(ASTMapEntry node, Object data) {
975 Object key = node.jjtGetChild(0).jjtAccept(this, data);
976 Object value = node.jjtGetChild(1).jjtAccept(this, data);
977 return new Object[]{key, value};
978 }
979
980 /** {@inheritDoc} */
981 public Object visit(ASTMapLiteral node, Object data) {
982 int childCount = node.jjtGetNumChildren();
983 Map<Object, Object> map = new HashMap<Object, Object>();
984
985 for (int i = 0; i < childCount; i++) {
986 Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
987 map.put(entry[0], entry[1]);
988 }
989
990 return map;
991 }
992
993 /**
994 * Calls a method (or function).
995 * <p>
996 * Method resolution is a follows:
997 * 1 - attempt to find a method in the bean passed as parameter;
998 * 2 - if this fails, narrow the arguments and try again
999 * 3 - if this still fails, seeks a Script or JexlMethod as a property of that bean.
1000 * </p>
1001 * @param node the method node
1002 * @param bean the bean this method should be invoked upon
1003 * @param methodNode the node carrying the method name
1004 * @param argb the first argument index, child of the method node
1005 * @return the result of the method invocation
1006 */
1007 private Object call(JexlNode node, Object bean, ASTIdentifier methodNode, int argb) {
1008 if (isCancelled()) {
1009 throw new JexlException.Cancel(node);
1010 }
1011 String methodName = methodNode.image;
1012 // evaluate the arguments
1013 int argc = node.jjtGetNumChildren() - argb;
1014 Object[] argv = new Object[argc];
1015 for (int i = 0; i < argc; i++) {
1016 argv[i] = node.jjtGetChild(i + argb).jjtAccept(this, null);
1017 }
1018
1019 JexlException xjexl = null;
1020 try {
1021 // attempt to reuse last executor cached in volatile JexlNode.value
1022 if (cache) {
1023 Object cached = node.jjtGetValue();
1024 if (cached instanceof JexlMethod) {
1025 JexlMethod me = (JexlMethod) cached;
1026 Object eval = me.tryInvoke(methodName, bean, argv);
1027 if (!me.tryFailed(eval)) {
1028 return eval;
1029 }
1030 }
1031 }
1032 boolean cacheable = cache;
1033 JexlMethod vm = uberspect.getMethod(bean, methodName, argv, node);
1034 // DG: If we can't find an exact match, narrow the parameters and try again
1035 if (vm == null) {
1036 if (arithmetic.narrowArguments(argv)) {
1037 vm = uberspect.getMethod(bean, methodName, argv, node);
1038 }
1039 if (vm == null) {
1040 Object functor = null;
1041 // could not find a method, try as a var
1042 if (bean == context) {
1043 int register = methodNode.getRegister();
1044 if (register >= 0) {
1045 functor = registers[register];
1046 } else {
1047 functor = context.get(methodName);
1048 }
1049 } else {
1050 JexlPropertyGet gfunctor = uberspect.getPropertyGet(bean, methodName, node);
1051 if (gfunctor != null) {
1052 functor = gfunctor.tryInvoke(bean, methodName);
1053 }
1054 }
1055 // script of jexl method will do
1056 if (functor instanceof Script) {
1057 return ((Script) functor).execute(context, argv.length > 0 ? argv : null);
1058 } else if (functor instanceof JexlMethod) {
1059 vm = (JexlMethod) functor;
1060 cacheable = false;
1061 } else {
1062 xjexl = new JexlException.Method(node, methodName);
1063 }
1064 }
1065 }
1066 if (xjexl == null) {
1067 // vm cannot be null if xjexl is null
1068 Object eval = vm.invoke(bean, argv);
1069 // cache executor in volatile JexlNode.value
1070 if (cacheable && vm.isCacheable()) {
1071 node.jjtSetValue(vm);
1072 }
1073 return eval;
1074 }
1075 } catch (InvocationTargetException e) {
1076 xjexl = new JexlException(node, "method invocation error", e.getCause());
1077 } catch (Exception e) {
1078 xjexl = new JexlException(node, "method error", e);
1079 }
1080 return invocationFailed(xjexl);
1081 }
1082
1083 /** {@inheritDoc} */
1084 public Object visit(ASTMethodNode node, Object data) {
1085 // the object to invoke the method on should be in the data argument
1086 if (data == null) {
1087 // if the method node is the first child of the (ASTReference) parent,
1088 // it is considered as calling a 'top level' function
1089 if (node.jjtGetParent().jjtGetChild(0) == node) {
1090 data = resolveNamespace(null, node);
1091 if (data == null) {
1092 data = context;
1093 }
1094 } else {
1095 throw new JexlException(node, "attempting to call method on null");
1096 }
1097 }
1098 // objectNode 0 is the identifier (method name), the others are parameters.
1099 ASTIdentifier methodNode = (ASTIdentifier) node.jjtGetChild(0);
1100 return call(node, data, methodNode, 1);
1101 }
1102
1103 /** {@inheritDoc} */
1104 public Object visit(ASTFunctionNode node, Object data) {
1105 // objectNode 0 is the prefix
1106 String prefix = node.jjtGetChild(0).image;
1107 Object namespace = resolveNamespace(prefix, node);
1108 // objectNode 1 is the identifier , the others are parameters.
1109 ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
1110 return call(node, namespace, functionNode, 2);
1111 }
1112
1113 /** {@inheritDoc} */
1114 public Object visit(ASTConstructorNode node, Object data) {
1115 if (isCancelled()) {
1116 throw new JexlException.Cancel(node);
1117 }
1118 // first child is class or class name
1119 Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
1120 // get the ctor args
1121 int argc = node.jjtGetNumChildren() - 1;
1122 Object[] argv = new Object[argc];
1123 for (int i = 0; i < argc; i++) {
1124 argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
1125 }
1126
1127 JexlException xjexl = null;
1128 try {
1129 // attempt to reuse last constructor cached in volatile JexlNode.value
1130 if (cache) {
1131 Object cached = node.jjtGetValue();
1132 if (cached instanceof JexlMethod) {
1133 JexlMethod mctor = (JexlMethod) cached;
1134 Object eval = mctor.tryInvoke(null, cobject, argv);
1135 if (!mctor.tryFailed(eval)) {
1136 return eval;
1137 }
1138 }
1139 }
1140 JexlMethod ctor = uberspect.getConstructorMethod(cobject, argv, node);
1141 // DG: If we can't find an exact match, narrow the parameters and try again
1142 if (ctor == null) {
1143 if (arithmetic.narrowArguments(argv)) {
1144 ctor = uberspect.getConstructorMethod(cobject, argv, node);
1145 }
1146 if (ctor == null) {
1147 xjexl = new JexlException.Method(node, cobject.toString());
1148 }
1149 }
1150 if (xjexl == null) {
1151 Object instance = ctor.invoke(cobject, argv);
1152 // cache executor in volatile JexlNode.value
1153 if (cache && ctor.isCacheable()) {
1154 node.jjtSetValue(ctor);
1155 }
1156 return instance;
1157 }
1158 } catch (InvocationTargetException e) {
1159 xjexl = new JexlException(node, "constructor invocation error", e.getCause());
1160 } catch (Exception e) {
1161 xjexl = new JexlException(node, "constructor error", e);
1162 }
1163 return invocationFailed(xjexl);
1164 }
1165
1166 /** {@inheritDoc} */
1167 public Object visit(ASTModNode node, Object data) {
1168 Object left = node.jjtGetChild(0).jjtAccept(this, data);
1169 Object right = node.jjtGetChild(1).jjtAccept(this, data);
1170 try {
1171 return arithmetic.mod(left, right);
1172 } catch (ArithmeticException xrt) {
1173 if (!strict) {
1174 return new Double(0.0);
1175 }
1176 JexlNode xnode = findNullOperand(xrt, node, left, right);
1177 throw new JexlException(xnode, "% error", xrt);
1178 }
1179 }
1180
1181 /** {@inheritDoc} */
1182 public Object visit(ASTMulNode node, Object data) {
1183 Object left = node.jjtGetChild(0).jjtAccept(this, data);
1184 Object right = node.jjtGetChild(1).jjtAccept(this, data);
1185 try {
1186 return arithmetic.multiply(left, right);
1187 } catch (ArithmeticException xrt) {
1188 JexlNode xnode = findNullOperand(xrt, node, left, right);
1189 throw new JexlException(xnode, "* error", xrt);
1190 }
1191 }
1192
1193 /** {@inheritDoc} */
1194 public Object visit(ASTNENode node, Object data) {
1195 Object left = node.jjtGetChild(0).jjtAccept(this, data);
1196 Object right = node.jjtGetChild(1).jjtAccept(this, data);
1197 try {
1198 return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
1199 } catch (ArithmeticException xrt) {
1200 JexlNode xnode = findNullOperand(xrt, node, left, right);
1201 throw new JexlException(xnode, "!= error", xrt);
1202 }
1203 }
1204
1205 /** {@inheritDoc} */
1206 public Object visit(ASTNRNode node, Object data) {
1207 Object left = node.jjtGetChild(0).jjtAccept(this, data);
1208 Object right = node.jjtGetChild(1).jjtAccept(this, data);
1209 try {
1210 if (right instanceof java.util.regex.Pattern || right instanceof String) {
1211 // use arithmetic / pattern matching
1212 return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
1213 }
1214 // try contains on collection
1215 if (right instanceof Set<?>) {
1216 return ((Set<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
1217 }
1218 // try contains on map key
1219 if (right instanceof Map<?, ?>) {
1220 return ((Map<?, ?>) right).containsKey(left) ? Boolean.FALSE : Boolean.TRUE;
1221 }
1222 // try contains on collection
1223 if (right instanceof Collection<?>) {
1224 return ((Collection<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
1225 }
1226 // try a contains method (duck type set)
1227 try {
1228 Object[] argv = {left};
1229 JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
1230 if (vm != null) {
1231 return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
1232 } else if (arithmetic.narrowArguments(argv)) {
1233 vm = uberspect.getMethod(right, "contains", argv, node);
1234 if (vm != null) {
1235 return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
1236 }
1237 }
1238 } catch (InvocationTargetException e) {
1239 throw new JexlException(node, "!~ invocation error", e.getCause());
1240 } catch (Exception e) {
1241 throw new JexlException(node, "!~ error", e);
1242 }
1243 // try iterative comparison
1244 Iterator<?> it = uberspect.getIterator(right, node.jjtGetChild(1));
1245 if (it != null) {
1246 while (it.hasNext()) {
1247 Object next = it.next();
1248 if (next == left || (next != null && next.equals(left))) {
1249 return Boolean.FALSE;
1250 }
1251 }
1252 return Boolean.TRUE;
1253 }
1254 // defaults to not equal
1255 return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
1256 } catch (ArithmeticException xrt) {
1257 throw new JexlException(node, "!~ error", xrt);
1258 }
1259 }
1260
1261 /** {@inheritDoc} */
1262 public Object visit(ASTNotNode node, Object data) {
1263 Object val = node.jjtGetChild(0).jjtAccept(this, data);
1264 return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
1265 }
1266
1267 /** {@inheritDoc} */
1268 public Object visit(ASTNullLiteral node, Object data) {
1269 return null;
1270 }
1271
1272 /** {@inheritDoc} */
1273 public Object visit(ASTOrNode node, Object data) {
1274 Object left = node.jjtGetChild(0).jjtAccept(this, data);
1275 try {
1276 boolean leftValue = arithmetic.toBoolean(left);
1277 if (leftValue) {
1278 return Boolean.TRUE;
1279 }
1280 } catch (ArithmeticException xrt) {
1281 throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
1282 }
1283 Object right = node.jjtGetChild(1).jjtAccept(this, data);
1284 try {
1285 boolean rightValue = arithmetic.toBoolean(right);
1286 if (rightValue) {
1287 return Boolean.TRUE;
1288 }
1289 } catch (ArithmeticException xrt) {
1290 throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
1291 }
1292 return Boolean.FALSE;
1293 }
1294
1295 /** {@inheritDoc} */
1296 public Object visit(ASTReference node, Object data) {
1297 // could be array access, identifier or map literal
1298 // followed by zero or more ("." and array access, method, size,
1299 // identifier or integer literal)
1300 int numChildren = node.jjtGetNumChildren();
1301 // pass first piece of data in and loop through children
1302 Object result = null;
1303 StringBuilder variableName = null;
1304 String propertyName = null;
1305 boolean isVariable = true;
1306 int v = 0;
1307 for (int c = 0; c < numChildren; c++) {
1308 if (isCancelled()) {
1309 throw new JexlException.Cancel(node);
1310 }
1311 JexlNode theNode = node.jjtGetChild(c);
1312 // integer literals may be part of an antish var name only if no bean was found so far
1313 if (result == null && theNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) theNode).isInteger()) {
1314 isVariable &= v > 0;
1315 } else {
1316 isVariable &= (theNode instanceof ASTIdentifier);
1317 result = theNode.jjtAccept(this, result);
1318 }
1319 // if we get null back a result, check for an ant variable
1320 if (result == null && isVariable) {
1321 if (v == 0) {
1322 variableName = new StringBuilder(node.jjtGetChild(0).image);
1323 v = 1;
1324 }
1325 for (; v <= c; ++v) {
1326 variableName.append('.');
1327 variableName.append(node.jjtGetChild(v).image);
1328 }
1329 result = context.get(variableName.toString());
1330 } else {
1331 propertyName = theNode.image;
1332 }
1333 }
1334 if (result == null) {
1335 if (isVariable && !isTernaryProtected(node)
1336 // variable unknow in context and not (from) a register
1337 && !(context.has(variableName.toString())
1338 || (numChildren == 1
1339 && node.jjtGetChild(0) instanceof ASTIdentifier
1340 && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) {
1341 JexlException xjexl = propertyName != null
1342 ? new JexlException.Property(node, propertyName)
1343 : new JexlException.Variable(node, variableName.toString());
1344 return unknownVariable(xjexl);
1345 }
1346 }
1347 return result;
1348 }
1349
1350 /**
1351 * {@inheritDoc}
1352 * @since 2.1
1353 */
1354 public Object visit(ASTReferenceExpression node, Object data) {
1355 ASTArrayAccess upper = node;
1356 return visit(upper, data);
1357 }
1358
1359 /**
1360 * {@inheritDoc}
1361 * @since 2.1
1362 */
1363 public Object visit(ASTReturnStatement node, Object data) {
1364 Object val = node.jjtGetChild(0).jjtAccept(this, data);
1365 throw new JexlException.Return(node, null, val);
1366 }
1367
1368 /**
1369 * Check if a null evaluated expression is protected by a ternary expression.
1370 * The rationale is that the ternary / elvis expressions are meant for the user to explictly take
1371 * control over the error generation; ie, ternaries can return null even if the engine in strict mode
1372 * would normally throw an exception.
1373 * @param node the expression node
1374 * @return true if nullable variable, false otherwise
1375 */
1376 private boolean isTernaryProtected(JexlNode node) {
1377 for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
1378 if (walk instanceof ASTTernaryNode) {
1379 return true;
1380 } else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
1381 break;
1382 }
1383 }
1384 return false;
1385 }
1386
1387 /** {@inheritDoc} */
1388 public Object visit(ASTSizeFunction node, Object data) {
1389 Object val = node.jjtGetChild(0).jjtAccept(this, data);
1390 if (val == null) {
1391 throw new JexlException(node, "size() : argument is null", null);
1392 }
1393 return Integer.valueOf(sizeOf(node, val));
1394 }
1395
1396 /** {@inheritDoc} */
1397 public Object visit(ASTSizeMethod node, Object data) {
1398 return Integer.valueOf(sizeOf(node, data));
1399 }
1400
1401 /** {@inheritDoc} */
1402 public Object visit(ASTStringLiteral node, Object data) {
1403 if (data != null) {
1404 return getAttribute(data, node.getLiteral(), node);
1405 }
1406 return node.image;
1407 }
1408
1409 /** {@inheritDoc} */
1410 public Object visit(ASTTernaryNode node, Object data) {
1411 Object condition = node.jjtGetChild(0).jjtAccept(this, data);
1412 if (node.jjtGetNumChildren() == 3) {
1413 if (condition != null && arithmetic.toBoolean(condition)) {
1414 return node.jjtGetChild(1).jjtAccept(this, data);
1415 } else {
1416 return node.jjtGetChild(2).jjtAccept(this, data);
1417 }
1418 }
1419 if (condition != null && arithmetic.toBoolean(condition)) {
1420 return condition;
1421 } else {
1422 return node.jjtGetChild(1).jjtAccept(this, data);
1423 }
1424 }
1425
1426 /** {@inheritDoc} */
1427 public Object visit(ASTTrueNode node, Object data) {
1428 return Boolean.TRUE;
1429 }
1430
1431 /** {@inheritDoc} */
1432 public Object visit(ASTUnaryMinusNode node, Object data) {
1433 JexlNode valNode = node.jjtGetChild(0);
1434 Object val = valNode.jjtAccept(this, data);
1435 try {
1436 Object number = arithmetic.negate(val);
1437 // attempt to recoerce to literal class
1438 if (valNode instanceof ASTNumberLiteral && number instanceof Number) {
1439 number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
1440 }
1441 return number;
1442 } catch (ArithmeticException xrt) {
1443 throw new JexlException(valNode, "arithmetic error", xrt);
1444 }
1445 }
1446
1447 /** {@inheritDoc} */
1448 public Object visit(ASTWhileStatement node, Object data) {
1449 Object result = null;
1450 /* first objectNode is the expression */
1451 Node expressionNode = node.jjtGetChild(0);
1452 while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
1453 if (isCancelled()) {
1454 throw new JexlException.Cancel(node);
1455 }
1456 // execute statement
1457 if (node.jjtGetNumChildren() > 1) {
1458 result = node.jjtGetChild(1).jjtAccept(this, data);
1459 }
1460 }
1461 return result;
1462 }
1463
1464 /**
1465 * Calculate the <code>size</code> of various types: Collection, Array,
1466 * Map, String, and anything that has a int size() method.
1467 * @param node the node that gave the value to size
1468 * @param val the object to get the size of.
1469 * @return the size of val
1470 */
1471 private int sizeOf(JexlNode node, Object val) {
1472 if (val instanceof Collection<?>) {
1473 return ((Collection<?>) val).size();
1474 } else if (val.getClass().isArray()) {
1475 return Array.getLength(val);
1476 } else if (val instanceof Map<?, ?>) {
1477 return ((Map<?, ?>) val).size();
1478 } else if (val instanceof String) {
1479 return ((String) val).length();
1480 } else {
1481 // check if there is a size method on the object that returns an
1482 // integer and if so, just use it
1483 Object[] params = new Object[0];
1484 JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
1485 if (vm != null && vm.getReturnType() == Integer.TYPE) {
1486 Integer result;
1487 try {
1488 result = (Integer) vm.invoke(val, params);
1489 } catch (Exception e) {
1490 throw new JexlException(node, "size() : error executing", e);
1491 }
1492 return result.intValue();
1493 }
1494 throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
1495 }
1496 }
1497
1498 /**
1499 * Gets an attribute of an object.
1500 *
1501 * @param object to retrieve value from
1502 * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1503 * key for a map
1504 * @return the attribute value
1505 */
1506 public Object getAttribute(Object object, Object attribute) {
1507 return getAttribute(object, attribute, null);
1508 }
1509
1510 /**
1511 * Gets an attribute of an object.
1512 *
1513 * @param object to retrieve value from
1514 * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1515 * key for a map
1516 * @param node the node that evaluated as the object
1517 * @return the attribute value
1518 */
1519 protected Object getAttribute(Object object, Object attribute, JexlNode node) {
1520 if (object == null) {
1521 throw new JexlException(node, "object is null");
1522 }
1523 if (isCancelled()) {
1524 throw new JexlException.Cancel(node);
1525 }
1526 // attempt to reuse last executor cached in volatile JexlNode.value
1527 if (node != null && cache) {
1528 Object cached = node.jjtGetValue();
1529 if (cached instanceof JexlPropertyGet) {
1530 JexlPropertyGet vg = (JexlPropertyGet) cached;
1531 Object value = vg.tryInvoke(object, attribute);
1532 if (!vg.tryFailed(value)) {
1533 return value;
1534 }
1535 }
1536 }
1537 JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
1538 if (vg != null) {
1539 try {
1540 Object value = vg.invoke(object);
1541 // cache executor in volatile JexlNode.value
1542 if (node != null && cache && vg.isCacheable()) {
1543 node.jjtSetValue(vg);
1544 }
1545 return value;
1546 } catch (Exception xany) {
1547 if (node == null) {
1548 throw new RuntimeException(xany);
1549 } else {
1550 JexlException xjexl = new JexlException.Property(node, attribute.toString());
1551 if (strict) {
1552 throw xjexl;
1553 }
1554 if (!silent) {
1555 logger.warn(xjexl.getMessage());
1556 }
1557 }
1558 }
1559 }
1560 return null;
1561 }
1562
1563 /**
1564 * Sets an attribute of an object.
1565 *
1566 * @param object to set the value to
1567 * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1568 * key for a map
1569 * @param value the value to assign to the object's attribute
1570 */
1571 public void setAttribute(Object object, Object attribute, Object value) {
1572 setAttribute(object, attribute, value, null);
1573 }
1574
1575 /**
1576 * Sets an attribute of an object.
1577 *
1578 * @param object to set the value to
1579 * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1580 * key for a map
1581 * @param value the value to assign to the object's attribute
1582 * @param node the node that evaluated as the object
1583 */
1584 protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
1585 if (isCancelled()) {
1586 throw new JexlException.Cancel(node);
1587 }
1588 // attempt to reuse last executor cached in volatile JexlNode.value
1589 if (node != null && cache) {
1590 Object cached = node.jjtGetValue();
1591 if (cached instanceof JexlPropertySet) {
1592 JexlPropertySet setter = (JexlPropertySet) cached;
1593 Object eval = setter.tryInvoke(object, attribute, value);
1594 if (!setter.tryFailed(eval)) {
1595 return;
1596 }
1597 }
1598 }
1599 JexlException xjexl = null;
1600 JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
1601 // if we can't find an exact match, narrow the value argument and try again
1602 if (vs == null) {
1603 // replace all numbers with the smallest type that will fit
1604 Object[] narrow = {value};
1605 if (arithmetic.narrowArguments(narrow)) {
1606 vs = uberspect.getPropertySet(object, attribute, narrow[0], node);
1607 }
1608 }
1609 if (vs != null) {
1610 try {
1611 // cache executor in volatile JexlNode.value
1612 vs.invoke(object, value);
1613 if (node != null && cache && vs.isCacheable()) {
1614 node.jjtSetValue(vs);
1615 }
1616 return;
1617 } catch (RuntimeException xrt) {
1618 if (node == null) {
1619 throw xrt;
1620 }
1621 xjexl = new JexlException(node, "set object property error", xrt);
1622 } catch (Exception xany) {
1623 if (node == null) {
1624 throw new RuntimeException(xany);
1625 }
1626 xjexl = new JexlException(node, "set object property error", xany);
1627 }
1628 }
1629 if (xjexl == null) {
1630 if (node == null) {
1631 String error = "unable to set object property"
1632 + ", class: " + object.getClass().getName()
1633 + ", property: " + attribute
1634 + ", argument: " + value.getClass().getSimpleName();
1635 throw new UnsupportedOperationException(error);
1636 }
1637 xjexl = new JexlException.Property(node, attribute.toString());
1638 }
1639 if (strict) {
1640 throw xjexl;
1641 }
1642 if (!silent) {
1643 logger.warn(xjexl.getMessage());
1644 }
1645 }
1646
1647 /**
1648 * Unused, satisfy ParserVisitor interface.
1649 * @param node a node
1650 * @param data the data
1651 * @return does not return
1652 */
1653 public Object visit(SimpleNode node, Object data) {
1654 throw new UnsupportedOperationException("Not supported yet.");
1655 }
1656
1657 /**
1658 * Unused, should throw in Parser.
1659 * @param node a node
1660 * @param data the data
1661 * @return does not return
1662 */
1663 public Object visit(ASTAmbiguous node, Object data) {
1664 throw new UnsupportedOperationException("unexpected type of node");
1665 }
1666 }