| 1 | package org.apache.velocity.runtime.parser.node; |
| 2 | |
| 3 | /* |
| 4 | * Copyright 2000-2002,2004 The Apache Software Foundation. |
| 5 | * |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License. |
| 17 | */ |
| 18 | |
| 19 | import java.io.Writer; |
| 20 | import java.io.IOException; |
| 21 | import java.lang.reflect.InvocationTargetException; |
| 22 | |
| 23 | import org.apache.velocity.context.Context; |
| 24 | import org.apache.velocity.context.InternalContextAdapter; |
| 25 | import org.apache.velocity.runtime.RuntimeConstants; |
| 26 | import org.apache.velocity.runtime.exception.ReferenceException; |
| 27 | import org.apache.velocity.runtime.parser.*; |
| 28 | |
| 29 | import org.apache.velocity.util.introspection.VelPropertySet; |
| 30 | import org.apache.velocity.util.introspection.Info; |
| 31 | |
| 32 | import org.apache.velocity.exception.MethodInvocationException; |
| 33 | |
| 34 | import org.apache.velocity.app.event.EventCartridge; |
| 35 | |
| 36 | /** |
| 37 | * This class is responsible for handling the references in |
| 38 | * VTL ($foo). |
| 39 | * |
| 40 | * Please look at the Parser.jjt file which is |
| 41 | * what controls the generation of this class. |
| 42 | * |
| 43 | * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> |
| 44 | * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
| 45 | * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> |
| 46 | * @author <a href="mailto:kjohnson@transparent.com>Kent Johnson</a> |
| 47 | * @version $Id: ASTReference.java,v 1.49.4.1 2004/03/03 23:22:59 geirm Exp $ |
| 48 | */ |
| 49 | public class ASTReference extends SimpleNode |
| 50 | { |
| 51 | /* Reference types */ |
| 52 | private static final int NORMAL_REFERENCE = 1; |
| 53 | private static final int FORMAL_REFERENCE = 2; |
| 54 | private static final int QUIET_REFERENCE = 3; |
| 55 | private static final int RUNT = 4; |
| 56 | |
| 57 | private int referenceType; |
| 58 | private String nullString; |
| 59 | private String rootString; |
| 60 | private boolean escaped = false; |
| 61 | private boolean computableReference = true; |
| 62 | private String escPrefix = ""; |
| 63 | private String morePrefix = ""; |
| 64 | private String identifier = ""; |
| 65 | |
| 66 | private String literal = null; |
| 67 | |
| 68 | private int numChildren = 0; |
| 69 | |
| 70 | protected Info uberInfo; |
| 71 | |
| 72 | public ASTReference(int id) |
| 73 | { |
| 74 | super(id); |
| 75 | } |
| 76 | |
| 77 | public ASTReference(Parser p, int id) |
| 78 | { |
| 79 | super(p, id); |
| 80 | } |
| 81 | |
| 82 | /** Accept the visitor. **/ |
| 83 | public Object jjtAccept(ParserVisitor visitor, Object data) |
| 84 | { |
| 85 | return visitor.visit(this, data); |
| 86 | } |
| 87 | |
| 88 | public Object init(InternalContextAdapter context, Object data) |
| 89 | throws Exception |
| 90 | { |
| 91 | /* |
| 92 | * init our children |
| 93 | */ |
| 94 | |
| 95 | super.init(context, data); |
| 96 | |
| 97 | /* |
| 98 | * the only thing we can do in init() is getRoot() |
| 99 | * as that is template based, not context based, |
| 100 | * so it's thread- and context-safe |
| 101 | */ |
| 102 | |
| 103 | rootString = getRoot(); |
| 104 | |
| 105 | numChildren = jjtGetNumChildren(); |
| 106 | |
| 107 | /* |
| 108 | * and if appropriate... |
| 109 | */ |
| 110 | |
| 111 | if (numChildren > 0 ) |
| 112 | { |
| 113 | identifier = jjtGetChild(numChildren - 1).getFirstToken().image; |
| 114 | } |
| 115 | |
| 116 | /* |
| 117 | * make an uberinfo - saves new's later on |
| 118 | */ |
| 119 | |
| 120 | uberInfo = new Info(context.getCurrentTemplateName(), |
| 121 | getLine(),getColumn()); |
| 122 | |
| 123 | return data; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Returns the 'root string', the reference key |
| 128 | */ |
| 129 | public String getRootString() |
| 130 | { |
| 131 | return rootString; |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * gets an Object that 'is' the value of the reference |
| 136 | * |
| 137 | * @param o unused Object parameter |
| 138 | * @param context context used to generate value |
| 139 | */ |
| 140 | public Object execute(Object o, InternalContextAdapter context) |
| 141 | throws MethodInvocationException |
| 142 | { |
| 143 | |
| 144 | if (referenceType == RUNT) |
| 145 | return null; |
| 146 | |
| 147 | /* |
| 148 | * get the root object from the context |
| 149 | */ |
| 150 | |
| 151 | Object result = getVariableValue(context, rootString); |
| 152 | |
| 153 | if (result == null) |
| 154 | { |
| 155 | return null; |
| 156 | } |
| 157 | |
| 158 | /* |
| 159 | * Iteratively work 'down' (it's flat...) the reference |
| 160 | * to get the value, but check to make sure that |
| 161 | * every result along the path is valid. For example: |
| 162 | * |
| 163 | * $hashtable.Customer.Name |
| 164 | * |
| 165 | * The $hashtable may be valid, but there is no key |
| 166 | * 'Customer' in the hashtable so we want to stop |
| 167 | * when we find a null value and return the null |
| 168 | * so the error gets logged. |
| 169 | */ |
| 170 | |
| 171 | try |
| 172 | { |
| 173 | for (int i = 0; i < numChildren; i++) |
| 174 | { |
| 175 | result = jjtGetChild(i).execute(result,context); |
| 176 | |
| 177 | if (result == null) |
| 178 | { |
| 179 | return null; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | return result; |
| 184 | } |
| 185 | catch(MethodInvocationException mie) |
| 186 | { |
| 187 | /* |
| 188 | * someone tossed their cookies |
| 189 | */ |
| 190 | |
| 191 | rsvc.error("Method " + mie.getMethodName() |
| 192 | + " threw exception for reference $" |
| 193 | + rootString |
| 194 | + " in template " + context.getCurrentTemplateName() |
| 195 | + " at " + " [" + this.getLine() + "," |
| 196 | + this.getColumn() + "]"); |
| 197 | |
| 198 | mie.setReferenceName(rootString); |
| 199 | throw mie; |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * gets the value of the reference and outputs it to the |
| 205 | * writer. |
| 206 | * |
| 207 | * @param context context of data to use in getting value |
| 208 | * @param writer writer to render to |
| 209 | */ |
| 210 | public boolean render(InternalContextAdapter context, Writer writer) |
| 211 | throws IOException, MethodInvocationException |
| 212 | { |
| 213 | |
| 214 | if (referenceType == RUNT) |
| 215 | { |
| 216 | writer.write(rootString); |
| 217 | return true; |
| 218 | } |
| 219 | |
| 220 | Object value = execute(null, context); |
| 221 | |
| 222 | /* |
| 223 | * if this reference is escaped (\$foo) then we want to do one of two things : |
| 224 | * 1) if this is a reference in the context, then we want to print $foo |
| 225 | * 2) if not, then \$foo (its considered shmoo, not VTL) |
| 226 | */ |
| 227 | |
| 228 | if (escaped) |
| 229 | { |
| 230 | if (value == null) |
| 231 | { |
| 232 | writer.write(escPrefix); |
| 233 | writer.write("\\"); |
| 234 | writer.write(nullString); |
| 235 | } |
| 236 | else |
| 237 | { |
| 238 | writer.write(escPrefix); |
| 239 | writer.write(nullString); |
| 240 | } |
| 241 | |
| 242 | return true; |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | * the normal processing |
| 247 | * |
| 248 | * if we have an event cartridge, get a new value object |
| 249 | */ |
| 250 | |
| 251 | EventCartridge ec = context.getEventCartridge(); |
| 252 | |
| 253 | if (ec != null) |
| 254 | { |
| 255 | value = ec.referenceInsert(literal(), value); |
| 256 | } |
| 257 | |
| 258 | /* |
| 259 | * if value is null... |
| 260 | */ |
| 261 | |
| 262 | if (value == null) |
| 263 | { |
| 264 | /* |
| 265 | * write prefix twice, because it's shmoo, so the \ don't escape each other... |
| 266 | */ |
| 267 | |
| 268 | writer.write(escPrefix); |
| 269 | writer.write(escPrefix); |
| 270 | writer.write(morePrefix); |
| 271 | writer.write(nullString); |
| 272 | |
| 273 | if (referenceType != QUIET_REFERENCE |
| 274 | && rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, |
| 275 | true)) |
| 276 | { |
| 277 | rsvc.warn(new ReferenceException("reference : template = " |
| 278 | + context.getCurrentTemplateName(), this)); |
| 279 | } |
| 280 | |
| 281 | return true; |
| 282 | } |
| 283 | else |
| 284 | { |
| 285 | /* |
| 286 | * non-null processing |
| 287 | */ |
| 288 | |
| 289 | writer.write(escPrefix); |
| 290 | writer.write(morePrefix); |
| 291 | writer.write(value.toString()); |
| 292 | |
| 293 | return true; |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Computes boolean value of this reference |
| 299 | * Returns the actual value of reference return type |
| 300 | * boolean, and 'true' if value is not null |
| 301 | * |
| 302 | * @param context context to compute value with |
| 303 | */ |
| 304 | public boolean evaluate(InternalContextAdapter context) |
| 305 | throws MethodInvocationException |
| 306 | { |
| 307 | Object value = execute(null, context); |
| 308 | |
| 309 | if (value == null) |
| 310 | { |
| 311 | return false; |
| 312 | } |
| 313 | else if (value instanceof Boolean) |
| 314 | { |
| 315 | if (((Boolean) value).booleanValue()) |
| 316 | return true; |
| 317 | else |
| 318 | return false; |
| 319 | } |
| 320 | else |
| 321 | return true; |
| 322 | } |
| 323 | |
| 324 | public Object value(InternalContextAdapter context) |
| 325 | throws MethodInvocationException |
| 326 | { |
| 327 | return (computableReference ? execute(null, context) : null); |
| 328 | } |
| 329 | |
| 330 | /** |
| 331 | * Sets the value of a complex reference (something like $foo.bar) |
| 332 | * Currently used by ASTSetReference() |
| 333 | * |
| 334 | * @see ASTSetDirective |
| 335 | * |
| 336 | * @param context context object containing this reference |
| 337 | * @param value Object to set as value |
| 338 | * @return true if successful, false otherwise |
| 339 | */ |
| 340 | public boolean setValue( InternalContextAdapter context, Object value) |
| 341 | throws MethodInvocationException |
| 342 | { |
| 343 | if (jjtGetNumChildren() == 0) |
| 344 | { |
| 345 | context.put(rootString, value); |
| 346 | return true; |
| 347 | } |
| 348 | |
| 349 | /* |
| 350 | * The rootOfIntrospection is the object we will |
| 351 | * retrieve from the Context. This is the base |
| 352 | * object we will apply reflection to. |
| 353 | */ |
| 354 | |
| 355 | Object result = getVariableValue(context, rootString); |
| 356 | |
| 357 | if (result == null) |
| 358 | { |
| 359 | rsvc.error(new ReferenceException("reference set : template = " |
| 360 | + context.getCurrentTemplateName(), this)); |
| 361 | return false; |
| 362 | } |
| 363 | |
| 364 | /* |
| 365 | * How many child nodes do we have? |
| 366 | */ |
| 367 | |
| 368 | for (int i = 0; i < numChildren - 1; i++) |
| 369 | { |
| 370 | result = jjtGetChild(i).execute(result, context); |
| 371 | |
| 372 | if (result == null) |
| 373 | { |
| 374 | rsvc.error(new ReferenceException("reference set : template = " |
| 375 | + context.getCurrentTemplateName(), this)); |
| 376 | return false; |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | /* |
| 381 | * We support two ways of setting the value in a #set($ref.foo = $value ) : |
| 382 | * 1) ref.setFoo( value ) |
| 383 | * 2) ref,put("foo", value ) to parallel the get() map introspection |
| 384 | */ |
| 385 | |
| 386 | try |
| 387 | { |
| 388 | VelPropertySet vs = |
| 389 | rsvc.getUberspect().getPropertySet(result, identifier, |
| 390 | value, uberInfo); |
| 391 | |
| 392 | if (vs == null) |
| 393 | return false; |
| 394 | |
| 395 | vs.invoke(result, value); |
| 396 | } |
| 397 | catch(InvocationTargetException ite) |
| 398 | { |
| 399 | /* |
| 400 | * this is possible |
| 401 | */ |
| 402 | |
| 403 | throw new MethodInvocationException( |
| 404 | "ASTReference : Invocation of method '" |
| 405 | + identifier + "' in " + result.getClass() |
| 406 | + " threw exception " |
| 407 | + ite.getTargetException().getClass(), |
| 408 | ite.getTargetException(), identifier ); |
| 409 | } |
| 410 | catch(Exception e) |
| 411 | { |
| 412 | /* |
| 413 | * maybe a security exception? |
| 414 | */ |
| 415 | rsvc.error("ASTReference setValue() : exception : " + e |
| 416 | + " template = " + context.getCurrentTemplateName() |
| 417 | + " [" + this.getLine() + "," + this.getColumn() + "]"); |
| 418 | return false; |
| 419 | } |
| 420 | |
| 421 | return true; |
| 422 | } |
| 423 | |
| 424 | private String getRoot() |
| 425 | { |
| 426 | Token t = getFirstToken(); |
| 427 | |
| 428 | /* |
| 429 | * we have a special case where something like |
| 430 | * $(\\)*!, where the user want's to see something |
| 431 | * like $!blargh in the output, but the ! prevents it from showing. |
| 432 | * I think that at this point, this isn't a reference. |
| 433 | */ |
| 434 | |
| 435 | /* so, see if we have "\\!" */ |
| 436 | |
| 437 | int slashbang = t.image.indexOf("\\!"); |
| 438 | |
| 439 | if (slashbang != -1) |
| 440 | { |
| 441 | /* |
| 442 | * lets do all the work here. I would argue that if this occurrs, |
| 443 | * it's not a reference at all, so preceeding \ characters in front |
| 444 | * of the $ are just schmoo. So we just do the escape processing |
| 445 | * trick (even | odd) and move on. This kind of breaks the rule |
| 446 | * pattern of $ and # but '!' really tosses a wrench into things. |
| 447 | */ |
| 448 | |
| 449 | /* |
| 450 | * count the escapes : even # -> not escaped, odd -> escaped |
| 451 | */ |
| 452 | |
| 453 | int i = 0; |
| 454 | int len = t.image.length(); |
| 455 | |
| 456 | i = t.image.indexOf('$'); |
| 457 | |
| 458 | if (i == -1) |
| 459 | { |
| 460 | /* yikes! */ |
| 461 | rsvc.error("ASTReference.getRoot() : internal error : " |
| 462 | + "no $ found for slashbang."); |
| 463 | computableReference = false; |
| 464 | nullString = t.image; |
| 465 | return nullString; |
| 466 | } |
| 467 | |
| 468 | while (i < len && t.image.charAt(i) != '\\') |
| 469 | { |
| 470 | i++; |
| 471 | } |
| 472 | |
| 473 | /* ok, i is the first \ char */ |
| 474 | |
| 475 | int start = i; |
| 476 | int count = 0; |
| 477 | |
| 478 | while (i < len && t.image.charAt(i++) == '\\') |
| 479 | { |
| 480 | count++; |
| 481 | } |
| 482 | |
| 483 | /* |
| 484 | * now construct the output string. We really don't care about |
| 485 | * leading slashes as this is not a reference. It's quasi-schmoo |
| 486 | */ |
| 487 | |
| 488 | nullString = t.image.substring(0,start); // prefix up to the first |
| 489 | nullString += t.image.substring(start, start + count-1 ); // get the slashes |
| 490 | nullString += t.image.substring(start+count); // and the rest, including the |
| 491 | |
| 492 | /* |
| 493 | * this isn't a valid reference, so lets short circuit the value |
| 494 | * and set calcs |
| 495 | */ |
| 496 | |
| 497 | computableReference = false; |
| 498 | |
| 499 | return nullString; |
| 500 | } |
| 501 | |
| 502 | /* |
| 503 | * we need to see if this reference is escaped. if so |
| 504 | * we will clean off the leading \'s and let the |
| 505 | * regular behavior determine if we should output this |
| 506 | * as \$foo or $foo later on in render(). Lazyness.. |
| 507 | */ |
| 508 | |
| 509 | escaped = false; |
| 510 | |
| 511 | if (t.image.startsWith("\\")) |
| 512 | { |
| 513 | /* |
| 514 | * count the escapes : even # -> not escaped, odd -> escaped |
| 515 | */ |
| 516 | |
| 517 | int i = 0; |
| 518 | int len = t.image.length(); |
| 519 | |
| 520 | while (i < len && t.image.charAt(i) == '\\') |
| 521 | { |
| 522 | i++; |
| 523 | } |
| 524 | |
| 525 | if ((i % 2) != 0) |
| 526 | escaped = true; |
| 527 | |
| 528 | if (i > 0) |
| 529 | escPrefix = t.image.substring(0, i / 2 ); |
| 530 | |
| 531 | t.image = t.image.substring(i); |
| 532 | } |
| 533 | |
| 534 | /* |
| 535 | * Look for preceeding stuff like '#' and '$' |
| 536 | * and snip it off, except for the |
| 537 | * last $ |
| 538 | */ |
| 539 | |
| 540 | int loc1 = t.image.lastIndexOf('$'); |
| 541 | |
| 542 | /* |
| 543 | * if we have extra stuff, loc > 0 |
| 544 | * ex. '#$foo' so attach that to |
| 545 | * the prefix. |
| 546 | */ |
| 547 | if (loc1 > 0) |
| 548 | { |
| 549 | morePrefix = morePrefix + t.image.substring(0, loc1); |
| 550 | t.image = t.image.substring(loc1); |
| 551 | } |
| 552 | |
| 553 | /* |
| 554 | * Now it should be clean. Get the literal in case this reference |
| 555 | * isn't backed by the context at runtime, and then figure out what |
| 556 | * we are working with. |
| 557 | */ |
| 558 | |
| 559 | nullString = literal(); |
| 560 | |
| 561 | if (t.image.startsWith("$!")) |
| 562 | { |
| 563 | referenceType = QUIET_REFERENCE; |
| 564 | |
| 565 | /* |
| 566 | * only if we aren't escaped do we want to null the output |
| 567 | */ |
| 568 | |
| 569 | if (!escaped) |
| 570 | nullString = ""; |
| 571 | |
| 572 | if (t.image.startsWith("$!{")) |
| 573 | { |
| 574 | /* |
| 575 | * ex : $!{provider.Title} |
| 576 | */ |
| 577 | |
| 578 | return t.next.image; |
| 579 | } |
| 580 | else |
| 581 | { |
| 582 | /* |
| 583 | * ex : $!provider.Title |
| 584 | */ |
| 585 | |
| 586 | return t.image.substring(2); |
| 587 | } |
| 588 | } |
| 589 | else if (t.image.equals("${")) |
| 590 | { |
| 591 | /* |
| 592 | * ex : ${provider.Title} |
| 593 | */ |
| 594 | |
| 595 | referenceType = FORMAL_REFERENCE; |
| 596 | return t.next.image; |
| 597 | } |
| 598 | else if (t.image.startsWith("$")) |
| 599 | { |
| 600 | /* |
| 601 | * just nip off the '$' so we have |
| 602 | * the root |
| 603 | */ |
| 604 | |
| 605 | referenceType = NORMAL_REFERENCE; |
| 606 | return t.image.substring(1); |
| 607 | } |
| 608 | else |
| 609 | { |
| 610 | /* |
| 611 | * this is a 'RUNT', which can happen in certain circumstances where |
| 612 | * the parser is fooled into believeing that an IDENTIFIER is a real |
| 613 | * reference. Another 'dreaded' MORE hack :). |
| 614 | */ |
| 615 | referenceType = RUNT; |
| 616 | return t.image; |
| 617 | } |
| 618 | |
| 619 | } |
| 620 | |
| 621 | public Object getVariableValue(Context context, String variable) |
| 622 | { |
| 623 | return context.get(variable); |
| 624 | } |
| 625 | |
| 626 | |
| 627 | /** |
| 628 | * Routine to allow the literal representation to be |
| 629 | * externally overridden. Used now in the VM system |
| 630 | * to override a reference in a VM tree with the |
| 631 | * literal of the calling arg to make it work nicely |
| 632 | * when calling arg is null. It seems a bit much, but |
| 633 | * does keep things consistant. |
| 634 | * |
| 635 | * Note, you can only set the literal once... |
| 636 | * |
| 637 | * @param literal String to render to when null |
| 638 | */ |
| 639 | public void setLiteral(String literal) |
| 640 | { |
| 641 | /* |
| 642 | * do only once |
| 643 | */ |
| 644 | |
| 645 | if( this.literal == null) |
| 646 | this.literal = literal; |
| 647 | } |
| 648 | |
| 649 | /** |
| 650 | * Override of the SimpleNode method literal() |
| 651 | * Returns the literal representation of the |
| 652 | * node. Should be something like |
| 653 | * $<token>. |
| 654 | */ |
| 655 | public String literal() |
| 656 | { |
| 657 | if (literal != null) |
| 658 | return literal; |
| 659 | |
| 660 | return super.literal(); |
| 661 | } |
| 662 | } |