001package com.randomnoun.common.jexl.eval; 002 003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a 004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html) 005 */ 006 007import java.math.BigDecimal; 008import java.text.*; // for SimpleDateFormat & friends 009import java.util.*; 010 011 012 013import org.apache.log4j.Logger; 014 015import com.randomnoun.common.Text; 016import com.randomnoun.common.jexl.ast.*; 017import com.randomnoun.common.jexl.visitor.*; 018 019 020/** 021 * This class traverses (and evaluates) an expression AST graph. It 022 * traverses the AST in depth-first order (by extending the ObjectDepthFirst 023 * visitor class generated by jtb). This class implements the 024 * 'Visitor' design pattern. 025 * 026 * <p>Each visit() method in this class takes two parameters, the 027 * first being the node being visited, and the second contains 028 * an EvalContext object containing the variables and functions which can 029 * be referenced within the expression. The visitor (i.e. this class) is then 030 * passed to it's children nodes using the nodes' accept() method (generated 031 * by jtb). 032 * 033 * <p>Variables can be any wrappered primitive type (e.g. Long, Integer, ...), 034 * although mathematical evaluation (+, -, /, etc) are currently only 035 * implemented for Doubles and Longs. 036 * 037 * <p>This class can be considered thread-safe, since it holds no instance-wide state. 038 * (All methods that require access to a EvalContext have it passed in through a 039 * method parameter). i.e. many threads may use the same Evaluator instance to 040 * evaluate different expressions. 041 * 042 * <p>NB: In the javadocs below, "lhs" refers to the left-hand-side value of any binary 043 * operation, and "rhs" refers to the right-hand-side value; e.g. in "3 + 4", 044 * the lhs is "3", the rhs is "4" and the op is "+". 045 * 046 * @author knoxg 047 */ 048public class Evaluator 049 extends GJDepthFirst<Object, EvalContext> { 050 051 // We used to create a variable with this name before executing any function calls 052 // containing the Evaluator instance that is being used to evaluate the function. Not entirely sure why any more, so have commented it out. 053 // public static final String VAR_EVALUATOR = ".evaluator"; 054 055 056 /** Logger instance for this class */ 057 Logger logger = Logger.getLogger(Evaluator.class); 058 059 // could probably use generics these days 060 // and I'm pretty sure these are in java.util.functions as well 061 062 /** Binary operation interface; takes two parameters, returns a result. */ 063 interface BinaryOp { 064 Object op(Object a, Object b); 065 } 066 067 /** A binary operation on two parameters whose types are guaranteed to be Long. */ 068 abstract class LongOp implements BinaryOp { 069 public Object op(Object a, Object b) { 070 long longA = ((Long) a).longValue(); 071 long longB = (b == null ? 0 : ((Long) b).longValue()); 072 return new Long(longOp(longA, longB)); 073 } 074 abstract long longOp(long a, long b); 075 } 076 077 /** A binary operation on two parameters whose types are guaranteed to be Double. */ 078 abstract class DoubleOp implements BinaryOp { 079 public Object op(Object a, Object b) { 080 double doubleA = ((Double) a).doubleValue(); 081 double doubleB = (b == null ? 0 : ((Double) b).doubleValue()); 082 return new Double(doubleOp(doubleA, doubleB)); 083 } 084 abstract double doubleOp(double a, double b); 085 } 086 087 /** Coerce parameter 'b' into a compatible type of parameter 'a', to be used in a 088 * binary math op. If 'b' can't be coerced into 'a', then throw an EvalException. 089 * 090 * @param a First binary op parameter (the type of which we are casting to) 091 * @param b Second binary op parameter (the value we are casting) 092 * @return The value of 'b', coerced to be the same type of value 'a' 093 */ 094 public Object coerceType(Object a, Object b) { 095 // don't coerce nulls 096 if (a == null) { return null; } 097 098 Class<? extends Object> clazz = a.getClass(); 099 if (b == null || clazz.isInstance(b)) { return b; } 100 101 if (a instanceof Date) { 102 if (b instanceof String) { 103 // convert string to date 104 DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 105 try { 106 return sdf.parse((String) b); 107 } catch (java.text.ParseException pe) { 108 throw new EvalException("Could not parse date '" + b + "': " + pe.getMessage()); 109 } 110 } 111 } 112 113 if (a instanceof Double) { 114 if (b instanceof Number) { 115 return new Double(((Number) b).doubleValue()); 116 } 117 } 118 119 if (a instanceof Long) { 120 if (b instanceof Number) { 121 return new Long(((Number) b).longValue()); 122 } 123 } 124 125 if (a instanceof BigDecimal) { 126 if (b instanceof Number) { 127 // not sure if this keeps enough precision, but good enough for testing 128 return new BigDecimal(((Number) b).toString()); 129 } 130 } 131 132 throw new EvalException("cannot coerce " + b.getClass().getName() + " to " + clazz.getName()); 133 } 134 135 /** Binary math operation. Takes two parameters, the name of the op (only used in 136 * exception text), and BinaryOp classes for all supported types. (We only 137 * support math operations on Longs and Doubles at the moment, should be easy to 138 * add new types if required). If we were aiming for completeness, I'd implement 139 * Short, Int, Byte, Char, BigInteger and BigDecimal. 140 * 141 * <p>The rhs of the expression is coerced into the type of the lhs. 142 * 143 * @param a lhs parameter 144 * @param b rhs parameter 145 * @param op name of op 146 * @param longOp operation to perform if lhs is Long 147 * @param doubleOp operation to perform if lhs in Double 148 * @return the evaluated result of the op 149 */ 150 public Object mathOp(Object a, Object b, String op, BinaryOp longOp, BinaryOp doubleOp) { 151 b = coerceType(a, b); 152 153 if (a instanceof Long) { 154 return longOp.op(a, b); 155 } else if (a instanceof Double) { 156 return doubleOp.op(a, b); 157 } 158 159 throw new EvalException("'" + op + "' can only operator on numeric types (found '" + a + "' of type " + a.getClass().getName() + ")"); 160 } 161 162 /** Evaluate a TopLevelExpression node. The PRE text in this javadoc corresponds to 163 * the javacc expansion of this node; e.g. in expression.jj, the rule for TopLevelExpression 164 * is 165 * 166 * <pre> 167 * void TopLevelExpression(): 168 * {} 169 * { 170 * Expression() <EOF> 171 * } 172 * </pre> 173 * 174 * <p>jtb then creates two fields in the TopLevelExpression object, "expression" (containing 175 * the Expression() node) and "nodeToken" (containing the EOF token). This is documented 176 * in the form below (this is included for all jtb-generated visit methods): 177 * 178 * <PRE> 179 * expression -> Expression() 180 * nodeToken -> <EOF> 181 * </PRE> 182 */ 183 public Object visit(TopLevelExpression n, EvalContext context) { 184 return n.expression.accept(this, context); 185 } 186 187 /** Evaluate an Expression node. 188 * 189 * <PRE> 190 * conditionalAndExpression -> ConditionalAndExpression() 191 * nodeListOptional -> ( "||" ConditionalAndExpression() )* 192 * </PRE> 193 */ 194 public Object visit(Expression n, EvalContext context) { 195 NodeSequence seq; 196 Object lhs; 197 Object rhs; 198 199 lhs = n.conditionalAndExpression.accept(this, context); 200 201 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 202 seq = (NodeSequence) e.nextElement(); 203 rhs = seq.elementAt(1).accept(this, context); 204 205 if (!(lhs instanceof Boolean)) { 206 throw new EvalException("lhs must be boolean"); 207 } 208 209 if (!(rhs instanceof Boolean)) { 210 throw new EvalException("rhs must be boolean"); 211 } 212 213 // NB: performs short-cut evaluation 214 boolean result = ((Boolean) lhs).booleanValue() || ((Boolean) rhs).booleanValue(); 215 216 lhs = Boolean.valueOf(result); 217 } 218 219 return lhs; 220 } 221 222 /** Evaluate a ConditionalAndExpression node. 223 * 224 * <PRE> 225 * equalityExpression -> EqualityExpression() 226 * nodeListOptional -> ( "&&" EqualityExpression() )* 227 * </PRE> 228 */ 229 public Object visit(ConditionalAndExpression n, EvalContext context) { 230 NodeSequence seq; 231 Object lhs; 232 Object rhs; 233 234 lhs = n.equalityExpression.accept(this, context); 235 236 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 237 seq = (NodeSequence) e.nextElement(); 238 rhs = seq.elementAt(1).accept(this, context); 239 rhs = coerceType(lhs, rhs); 240 241 if (!(lhs instanceof Boolean)) { 242 throw new EvalException("lhs must be boolean"); 243 } 244 245 if (!(rhs instanceof Boolean)) { 246 throw new EvalException("rhs must be boolean"); 247 } 248 249 // NB: performs short-cut evaluation 250 boolean result = ((Boolean) lhs).booleanValue() && ((Boolean) rhs).booleanValue(); 251 252 lhs = Boolean.valueOf(result); 253 } 254 255 return lhs; 256 } 257 258 /** Evaluate a EqualityExpression node. 259 * 260 * <PRE> 261 * relationalExpression -> RelationalExpression() 262 * nodeListOptional -> ( ( "==" | "!=" ) RelationalExpression() )* 263 * </PRE> 264 */ 265 @SuppressWarnings("unchecked") 266 public Object visit(EqualityExpression n, EvalContext context) { 267 NodeSequence seq; 268 Object lhs; 269 Object rhs; 270 // String[] ops = { "==", "!=" }; 271 272 lhs = n.relationalExpression.accept(this, context); 273 274 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 275 seq = (NodeSequence) e.nextElement(); 276 rhs = seq.elementAt(1).accept(this, context); 277 rhs = coerceType(lhs, rhs); 278 279 if (!(lhs==null || lhs instanceof Comparable)) { 280 throw new EvalException("Cannot compare lhs"); 281 } 282 283 if (!(rhs==null || rhs instanceof Comparable)) { 284 // this may not be required (only lhs needs to be comparable to run .compareTo) 285 throw new EvalException("Cannot compare rhs"); 286 } 287 288 int which = ((NodeChoice) seq.elementAt(0)).which; 289 290 // this may throw an IllegalStateException if they're not comparable after all 291 int result = -1; 292 try { 293 // perform null checks first (allow nulls in either lhs or rhs of equality check) 294 if (lhs==null) { 295 return (which==0) ? Boolean.valueOf(rhs==null) : Boolean.valueOf(rhs!=null); 296 } else if (rhs==null) { 297 return (which==0) ? Boolean.valueOf(lhs==null) : Boolean.valueOf(lhs!=null); 298 } 299 result = ((Comparable<Object>) lhs).compareTo(rhs); 300 } catch (IllegalStateException ise) { 301 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise); 302 } 303 304 // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs); 305 switch (which) { 306 case 0: lhs = new Boolean(result == 0); break; 307 case 1: lhs = new Boolean(result != 0); break; 308 default: 309 throw new EvalException("Internal error - unexpected relational operation"); 310 } 311 } 312 313 return lhs; 314 } 315 316 /** Evaluate a RelationalExpression node. 317 * 318 * <PRE> 319 * additiveExpression -> AdditiveExpression() 320 * nodeListOptional -> ( ( "<" | ">" | "<=" | ">=" ) AdditiveExpression() )* 321 * </PRE> 322 */ 323 @SuppressWarnings("unchecked") 324 public Object visit(RelationalExpression n, EvalContext context) { 325 NodeSequence seq; 326 Object lhs; 327 Object rhs; 328 // String[] ops = { "<", ">", "<=", ">=" }; 329 330 lhs = n.additiveExpression.accept(this, context); 331 332 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 333 seq = (NodeSequence) e.nextElement(); 334 rhs = seq.elementAt(1).accept(this, context); 335 rhs = coerceType(lhs, rhs); 336 337 if (!(lhs instanceof Comparable)) { 338 throw new EvalException("Cannot compare lhs (found type '" + lhs.getClass().getName() + "')"); 339 } 340 341 if (!(rhs instanceof Comparable)) { 342 // this may not be required (only lhs needs to be comparable to run .compareTo) 343 throw new EvalException("Cannot compare rhs"); 344 } 345 346 int which = ((NodeChoice) seq.elementAt(0)).which; 347 int result = -1; 348 try { 349 result = ((Comparable<Object>) lhs).compareTo(rhs); 350 } catch (IllegalStateException ise) { 351 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise); 352 } 353 354 // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs); 355 switch (which) { 356 case 0: lhs = new Boolean(result < 0); break; 357 case 1: lhs = new Boolean(result > 0); break; 358 case 2: lhs = new Boolean(result <= 0); break; 359 case 3: lhs = new Boolean(result >= 0); break; 360 default: 361 throw new EvalException("Internal error - unexpected relational operation"); 362 } 363 } 364 365 return lhs; 366 } 367 368 /** Evaluate a AdditiveExpression node. 369 * 370 * <PRE> 371 * multiplicativeExpression -> MultiplicativeExpression() 372 * nodeListOptional -> ( ( "+" | "-" ) MultiplicativeExpression() )* 373 * </PRE> 374 */ 375 public Object visit(AdditiveExpression n, EvalContext context) { 376 NodeSequence seq; 377 Object lhs; 378 Object rhs; 379 // String[] ops = { "+", "-" }; 380 381 lhs = n.multiplicativeExpression.accept(this, context); 382 383 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 384 seq = (NodeSequence) e.nextElement(); 385 rhs = seq.elementAt(1).accept(this, context); 386 387 int which = ((NodeChoice) seq.elementAt(0)).which; 388 389 // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs); 390 switch (which) { 391 case 0: 392 if (lhs instanceof String) { 393 // String addition 394 lhs = ((String) lhs) + rhs.toString(); 395 } else { 396 // Numeric addition 397 lhs = mathOp(lhs, rhs, "+", 398 new LongOp() { public long longOp(long a, long b) { return a + b; } }, 399 new DoubleOp() { public double doubleOp(double a, double b) { return a + b; } }); 400 } 401 break; 402 case 1: 403 lhs = mathOp(lhs, rhs, "-", 404 new LongOp() { public long longOp(long a, long b) { return a - b; } }, 405 new DoubleOp() { public double doubleOp(double a, double b) { return a - b; } }); 406 break; 407 default: 408 throw new EvalException("Internal error - unexpected additive operation"); 409 } 410 } 411 412 return lhs; 413 } 414 415 /** Evaluate a MultiplicativeExpression node. 416 * 417 * <PRE> 418 * unaryExpression -> UnaryExpression() 419 * nodeListOptional -> ( ( "*" | "/" | "%" ) UnaryExpression() )* 420 * </PRE> 421 */ 422 public Object visit(MultiplicativeExpression n, EvalContext context) { 423 NodeSequence seq; 424 Object lhs; 425 Object rhs; 426 // String[] ops = { "*", "/", "%" }; 427 428 lhs = n.unaryExpression.accept(this, context); 429 430 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 431 seq = (NodeSequence) e.nextElement(); 432 rhs = seq.elementAt(1).accept(this, context); 433 434 int which = ((NodeChoice) seq.elementAt(0)).which; 435 436 // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs); 437 switch (which) { 438 case 0: 439 lhs = mathOp(lhs, rhs, "*", 440 new LongOp() { public long longOp(long a, long b) { return a * b; } }, 441 new DoubleOp() { public double doubleOp(double a, double b) { return a * b; } }); 442 break; 443 case 1: 444 lhs = mathOp(lhs, rhs, "/", 445 new LongOp() { public long longOp(long a, long b) { return a / b; } }, 446 new DoubleOp() { public double doubleOp(double a, double b) { return a / b; } }); 447 break; 448 case 2: 449 lhs = mathOp(lhs, rhs, "%", 450 new LongOp() { public long longOp(long a, long b) { return a % b; } }, 451 new DoubleOp() { public double doubleOp(double a, double b) { return a % b; } }); 452 break; 453 default: 454 throw new EvalException("Internal error - unexpected additive operation"); 455 } 456 } 457 458 return lhs; 459 } 460 461 /** Evaluate a UnaryExpression node. 462 * 463 * <PRE> 464 * nodeChoice -> ( "~" | "!" | "-" ) UnaryExpression() 465 * | PrimaryExpression() 466 * </PRE> 467 */ 468 public Object visit(UnaryExpression n, EvalContext context) { 469 NodeSequence seq; 470 Object lhs; 471 472 if (n.nodeChoice.which == 0) { 473 seq = (NodeSequence) n.nodeChoice.choice; 474 475 int which = ((NodeChoice) seq.elementAt(0)).which; 476 477 // String op = ((NodeToken) ((NodeChoice) nl.nodes.get(0)).choice).tokenImage; 478 switch (which) { 479 case 0: 480 // long rhs = makeNumeric ( ((Node) nl.nodes.get(0)).accept(this, context) ); 481 // return new Long(~rhs); 482 throw new EvalException("~ not supported"); 483 case 1: 484 lhs = seq.elementAt(1).accept(this, context); 485 if (!(lhs instanceof Boolean)) { 486 throw new EvalException("boolean type expected"); 487 } 488 return Boolean.valueOf(!((Boolean) lhs).booleanValue()); 489 case 2: 490 lhs = seq.elementAt(1).accept(this, context); 491 if (!(lhs instanceof Number)) { 492 throw new EvalException("numeric type expected"); 493 } 494 return mathOp(lhs, null, "-", new 495 LongOp() { public long longOp(long a, long b) { return -a; } }, 496 new DoubleOp() { public double doubleOp(double a, double b) { return -a; } } ); 497 default: 498 throw new EvalException("Internal error - unexpected unary operation"); 499 } 500 } else { 501 return n.nodeChoice.choice.accept(this, context); 502 } 503 } 504 505 /** Evaluate a PrimaryExpression node. 506 * 507 * <PRE> 508 * nodeChoice -> FunctionCall() 509 * | Name() 510 * | Literal() 511 * | "(" Expression() ")" 512 * </PRE> 513 */ 514 public Object visit(PrimaryExpression n, EvalContext context) { 515 NodeSequence seq; 516 int which = n.nodeChoice.which; 517 518 switch (which) { 519 case 0: 520 case 1: 521 case 2: 522 return n.nodeChoice.choice.accept(this, context); 523 case 3: 524 seq = (NodeSequence) n.nodeChoice.choice; 525 Object obj = seq.elementAt(1).accept(this, context); 526 527 ; 528 529 return obj; 530 default: 531 throw new EvalException("Internal parser error (PrimaryExpression)"); 532 } 533 } 534 535 /** Evaluate a Name node. 536 * 537 * <PRE> 538 * nodeToken -> <IDENTIFIER> 539 * nodeListOptional -> ( "." <IDENTIFIER> )* 540 * </PRE> 541 */ 542 public Object visit(Name n, EvalContext context) { 543 EvalContext evalContext = (EvalContext) context; 544 String varComponentName; 545 String varBaseName = n.nodeToken.tokenImage; 546 547 // logger.debug("Fetching var '" + varBaseName + "'"); 548 if (evalContext == null) { 549 throw new EvalException("Cannot retrieve variable '" + varBaseName + "' with a null evalContext"); 550 } 551 552 if (!evalContext.hasVariable(varBaseName)) { 553 throw new EvalException("Unknown variable '" + varBaseName + "'"); 554 } 555 556 Object value = evalContext.getVariable(varBaseName); 557 NodeSequence seq; 558 559 /* 560 if (value == null) { 561 logger.debug(" = null"); 562 } else { 563 logger.debug(" = " + value.getClass().getName() + " with value '" + value + "'"); 564 } 565 */ 566 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 567 seq = (NodeSequence) e.nextElement(); 568 varComponentName = ((NodeToken) seq.elementAt(1)).tokenImage; 569 570 //logger.debug("Fetching component '" + varComponentName + "' from var '" + varBaseName + "'"); 571 if (!evalContext.hasVariableComponent(value, varBaseName, varComponentName)) { 572 throw new EvalException("Unknown variable component '" + varComponentName + "' in variable '" + varBaseName + "'"); 573 } 574 575 value = evalContext.getVariableComponent(value, varBaseName, varComponentName); 576 varBaseName = varBaseName + "." + varComponentName; 577 } 578 579 return value; 580 } 581 582 /** Evaluate a FunctionCall node. 583 * 584 * <PRE> 585 * nodeToken -> <IDENTIFIER> 586 * arguments -> Arguments() 587 * </PRE> 588 */ 589 public Object visit(FunctionCall n, EvalContext context) { 590 @SuppressWarnings("unchecked") 591 List<Object> argumentList = (List<Object>) n.arguments.accept(this, context); 592 String functionName = n.nodeToken.tokenImage; 593 EvalContext evalContext = (EvalContext) context; 594 EvalFunction function = (EvalFunction) evalContext.getFunction(functionName); 595 if (function == null) { 596 throw new EvalException("Unknown function '" + functionName + "'"); 597 } 598 /* 599 if (evalContext.getVariable(VAR_EVALUATOR) != null) { 600 evalContext.setVariable(VAR_EVALUATOR, this); 601 } 602 */ 603 return function.evaluate(functionName, evalContext, argumentList); 604 } 605 606 /** Evaluates an Arguments node 607 * 608 * <PRE> 609 * nodeToken -> "(" 610 * nodeOptional -> [ ArgumentList() ] 611 * nodeToken1 -> ")" 612 * </PRE> 613 */ 614 public Object visit(Arguments n, EvalContext context) { 615 if (n.nodeOptional.present()) { 616 return n.nodeOptional.accept(this, context); 617 } else { 618 return new ArrayList<Object>(0); 619 } 620 } 621 622 /** Evaluate a ArgumentList node. 623 * 624 * <PRE> 625 * expression -> Expression() 626 * nodeListOptional -> ( "," Expression() )* 627 * </PRE> 628 */ 629 public Object visit(ArgumentList n, EvalContext context) { 630 NodeSequence seq; 631 List<Object> arguments = new ArrayList<Object>(); 632 633 arguments.add(n.expression.accept(this, context)); 634 635 for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) { 636 seq = (NodeSequence) e.nextElement(); 637 arguments.add(seq.elementAt(1).accept(this, context)); 638 } 639 640 return arguments; 641 } 642 643 /** Evaluate a Literal node. 644 * 645 * <PRE> 646 * nodeChoice -> <INTEGER_LITERAL> 647 * | <FLOATING_POINT_LITERAL> 648 * | <CHARACTER_LITERAL> 649 * | <STRING_LITERAL> 650 * | BooleanLiteral() 651 * | NullLiteral() 652 * </PRE> 653 * 654 */ 655 public Object visit(Literal n, EvalContext context) { 656 String token = null; 657 658 if (n.nodeChoice.choice instanceof NodeToken) { 659 token = ((NodeToken) n.nodeChoice.choice).tokenImage; 660 } 661 662 switch (n.nodeChoice.which) { 663 case 0: 664 return new Long(token); 665 case 1: 666 return new Double(token); 667 case 2: 668 return new Character(token.charAt(1)); 669 case 3: 670 return Text.unescapeJava(token.substring(1, token.length() - 1)); 671 } 672 673 // must be 'true', 'false', or 'null' 674 return n.nodeChoice.accept(this, context); 675 } 676 677 /** Evaluate a BooleanLiteral node. 678 * 679 * <PRE> 680 * nodeChoice -> "true" 681 * | "false" 682 * </PRE> 683 */ 684 public Object visit(BooleanLiteral n, EvalContext context) { 685 if (n.nodeChoice.which == 0) { 686 return Boolean.valueOf(true); 687 } else { 688 return Boolean.valueOf(false); 689 } 690 } 691 692 /** Evaluate a NullLiteral node. 693 * 694 * <PRE> 695 * nodeToken -> "null" 696 * </PRE> 697 */ 698 public Object visit(NullLiteral n, EvalContext context) { 699 return null; 700 } 701 702 /** This is never executed (we do not evaluate tokens) */ 703 public Object visit(NodeToken n, EvalContext context) { 704 return n.tokenImage; 705 } 706}