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 }