1 package org.apache.velocity.runtime.parser.node;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.Writer;
24 import java.lang.reflect.InvocationTargetException;
25
26 import org.apache.velocity.app.event.EventHandlerUtil;
27 import org.apache.velocity.context.Context;
28 import org.apache.velocity.context.InternalContextAdapter;
29 import org.apache.velocity.exception.MethodInvocationException;
30 import org.apache.velocity.exception.TemplateInitException;
31 import org.apache.velocity.exception.VelocityException;
32 import org.apache.velocity.runtime.RuntimeConstants;
33 import org.apache.velocity.runtime.Renderable;
34 import org.apache.velocity.runtime.log.Log;
35 import org.apache.velocity.runtime.parser.Parser;
36 import org.apache.velocity.runtime.parser.Token;
37 import org.apache.velocity.util.introspection.Info;
38 import org.apache.velocity.util.introspection.VelPropertySet;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class ASTReference extends SimpleNode
54 {
55
56 private static final int NORMAL_REFERENCE = 1;
57 private static final int FORMAL_REFERENCE = 2;
58 private static final int QUIET_REFERENCE = 3;
59 private static final int RUNT = 4;
60
61 private int referenceType;
62 private String nullString;
63 private String rootString;
64 private boolean escaped = false;
65 private boolean computableReference = true;
66 private boolean logOnNull = true;
67 private String escPrefix = "";
68 private String morePrefix = "";
69 private String identifier = "";
70
71 private String literal = null;
72
73
74
75
76 public boolean strictRef = false;
77
78 private int numChildren = 0;
79
80 protected Info uberInfo;
81
82
83
84
85 public ASTReference(int id)
86 {
87 super(id);
88 }
89
90
91
92
93
94 public ASTReference(Parser p, int id)
95 {
96 super(p, id);
97 }
98
99
100
101
102 public Object jjtAccept(ParserVisitor visitor, Object data)
103 {
104 return visitor.visit(this, data);
105 }
106
107
108
109
110 public Object init(InternalContextAdapter context, Object data)
111 throws TemplateInitException
112 {
113
114
115
116
117 super.init(context, data);
118
119
120
121
122
123
124
125 rootString = getRoot();
126
127 numChildren = jjtGetNumChildren();
128
129
130
131
132
133 if (numChildren > 0 )
134 {
135 identifier = jjtGetChild(numChildren - 1).getFirstToken().image;
136 }
137
138
139
140
141
142 uberInfo = new Info(getTemplateName(), getLine(),getColumn());
143
144
145
146
147 logOnNull =
148 rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true);
149
150 strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
151
152
153
154
155
156
157
158 if (strictRef && numChildren == 0)
159 {
160 logOnNull = false;
161
162 Node node = this.jjtGetParent();
163 if (node instanceof ASTNotNode
164 || node instanceof ASTExpression
165 || node instanceof ASTOrNode
166 || node instanceof ASTAndNode)
167 {
168
169 while (node != null)
170 {
171 if (node instanceof ASTIfStatement)
172 {
173 strictRef = false;
174 break;
175 }
176 node = node.jjtGetParent();
177 }
178 }
179 }
180
181 return data;
182 }
183
184
185
186
187
188 public String getRootString()
189 {
190 return rootString;
191 }
192
193
194
195
196
197
198
199
200
201 public Object execute(Object o, InternalContextAdapter context)
202 throws MethodInvocationException
203 {
204
205 if (referenceType == RUNT)
206 return null;
207
208
209
210
211
212 Object result = getVariableValue(context, rootString);
213
214 if (result == null && !strictRef)
215 {
216 return EventHandlerUtil.invalidGetMethod(rsvc, context,
217 "$" + rootString, null, null, uberInfo);
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233 try
234 {
235 Object previousResult = result;
236 int failedChild = -1;
237 for (int i = 0; i < numChildren; i++)
238 {
239 if (strictRef && result == null)
240 {
241
242
243
244
245 String name = jjtGetChild(i).getFirstToken().image;
246 throw new VelocityException("Attempted to access '"
247 + name + "' on a null value at "
248 + Log.formatFileString(uberInfo.getTemplateName(),
249 + jjtGetChild(i).getLine(), jjtGetChild(i).getColumn()));
250 }
251 previousResult = result;
252 result = jjtGetChild(i).execute(result,context);
253 if (result == null && !strictRef)
254
255 {
256 failedChild = i;
257 break;
258 }
259 }
260
261 if (result == null)
262 {
263 if (failedChild == -1)
264 {
265 result = EventHandlerUtil.invalidGetMethod(rsvc, context,
266 "$" + rootString, previousResult, null, uberInfo);
267 }
268 else
269 {
270 StringBuffer name = new StringBuffer("$").append(rootString);
271 for (int i = 0; i <= failedChild; i++)
272 {
273 Node node = jjtGetChild(i);
274 if (node instanceof ASTMethod)
275 {
276 name.append(".").append(((ASTMethod) node).getMethodName()).append("()");
277 }
278 else
279 {
280 name.append(".").append(node.getFirstToken().image);
281 }
282 }
283
284 if (jjtGetChild(failedChild) instanceof ASTMethod)
285 {
286 String methodName = ((ASTMethod) jjtGetChild(failedChild)).getMethodName();
287 result = EventHandlerUtil.invalidMethod(rsvc, context,
288 name.toString(), previousResult, methodName, uberInfo);
289 }
290 else
291 {
292 String property = jjtGetChild(failedChild).getFirstToken().image;
293 result = EventHandlerUtil.invalidGetMethod(rsvc, context,
294 name.toString(), previousResult, property, uberInfo);
295 }
296 }
297
298 }
299
300 return result;
301 }
302 catch(MethodInvocationException mie)
303 {
304
305
306
307
308 log.error("Method " + mie.getMethodName()
309 + " threw exception for reference $" + rootString + " in "
310 + Log.formatFileString(this));
311 mie.setReferenceName(rootString);
312 throw mie;
313 }
314 }
315
316
317
318
319
320
321
322
323
324
325
326 public boolean render(InternalContextAdapter context, Writer writer) throws IOException,
327 MethodInvocationException
328 {
329 if (referenceType == RUNT)
330 {
331 if (context.getAllowRendering())
332 {
333 writer.write(rootString);
334 }
335
336 return true;
337 }
338
339 Object value = execute(null, context);
340
341 String localNullString = null;
342
343
344
345
346
347
348
349 if (escaped)
350 {
351 localNullString = getNullString(context);
352
353 if (value == null)
354 {
355 if (context.getAllowRendering())
356 {
357 writer.write(escPrefix);
358 writer.write("\\");
359 writer.write(localNullString);
360 }
361 }
362 else
363 {
364 if (context.getAllowRendering())
365 {
366 writer.write(escPrefix);
367 writer.write(localNullString);
368 }
369 }
370 return true;
371 }
372
373
374
375
376
377
378
379 value = EventHandlerUtil.referenceInsert(rsvc, context, literal(), value);
380
381 String toString = null;
382 if (value != null)
383 {
384
385 if(value instanceof Renderable && ((Renderable)value).render(context,writer))
386 {
387 return true;
388 }
389
390 toString = value.toString();
391 }
392
393 if (value == null || toString == null)
394 {
395
396
397
398
399 if (context.getAllowRendering())
400 {
401 localNullString = getNullString(context);
402
403 writer.write(escPrefix);
404 writer.write(escPrefix);
405 writer.write(morePrefix);
406 writer.write(localNullString);
407 }
408
409 if (logOnNull && referenceType != QUIET_REFERENCE && log.isDebugEnabled())
410 {
411 log.debug("Null reference [template '" + getTemplateName()
412 + "', line " + this.getLine() + ", column " + this.getColumn() + "] : "
413 + this.literal() + " cannot be resolved.");
414 }
415 return true;
416 }
417 else
418 {
419
420
421
422
423 if (context.getAllowRendering())
424 {
425 writer.write(escPrefix);
426 writer.write(morePrefix);
427 writer.write(toString);
428 }
429
430 return true;
431 }
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445 private String getNullString(InternalContextAdapter context)
446 {
447 Object callingArgument = context.get(".literal." + nullString);
448
449 if (callingArgument != null)
450 return ((Node) callingArgument).literal();
451 else
452 return nullString;
453 }
454
455
456
457
458
459
460
461
462
463
464 public boolean evaluate(InternalContextAdapter context)
465 throws MethodInvocationException
466 {
467 Object value = execute(null, context);
468
469 if (value == null)
470 {
471 return false;
472 }
473 else if (value instanceof Boolean)
474 {
475 if (((Boolean) value).booleanValue())
476 return true;
477 else
478 return false;
479 }
480 else if (value.toString() == null)
481 {
482 return false;
483 }
484 else
485 return true;
486 }
487
488
489
490
491 public Object value(InternalContextAdapter context)
492 throws MethodInvocationException
493 {
494 return (computableReference ? execute(null, context) : null);
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508 public boolean setValue( InternalContextAdapter context, Object value)
509 throws MethodInvocationException
510 {
511 if (jjtGetNumChildren() == 0)
512 {
513 context.put(rootString, value);
514 return true;
515 }
516
517
518
519
520
521
522
523 Object result = getVariableValue(context, rootString);
524
525 if (result == null)
526 {
527 String msg = "reference set is not a valid reference at "
528 + Log.formatFileString(uberInfo);
529 log.error(msg);
530 return false;
531 }
532
533
534
535
536
537 for (int i = 0; i < numChildren - 1; i++)
538 {
539 result = jjtGetChild(i).execute(result, context);
540
541 if (result == null)
542 {
543 if (strictRef)
544 {
545 String name = jjtGetChild(i+1).getFirstToken().image;
546 throw new MethodInvocationException("Attempted to access '"
547 + name + "' on a null value", null, name, uberInfo.getTemplateName(),
548 jjtGetChild(i+1).getLine(), jjtGetChild(i+1).getColumn());
549 }
550
551 String msg = "reference set is not a valid reference at "
552 + Log.formatFileString(uberInfo);
553 log.error(msg);
554
555 return false;
556 }
557 }
558
559
560
561
562
563
564
565 try
566 {
567 VelPropertySet vs =
568 rsvc.getUberspect().getPropertySet(result, identifier,
569 value, uberInfo);
570
571 if (vs == null)
572 {
573 if (strictRef)
574 {
575 throw new MethodInvocationException("Object '" + result.getClass().getName() +
576 "' does not contain property '" + identifier + "'", null, identifier,
577 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
578 }
579 else
580 {
581 return false;
582 }
583 }
584
585 vs.invoke(result, value);
586 }
587 catch(InvocationTargetException ite)
588 {
589
590
591
592
593 throw new MethodInvocationException(
594 "ASTReference : Invocation of method '"
595 + identifier + "' in " + result.getClass()
596 + " threw exception "
597 + ite.getTargetException().toString(),
598 ite.getTargetException(), identifier, getTemplateName(), this.getLine(), this.getColumn());
599 }
600
601
602
603 catch( RuntimeException e )
604 {
605 throw e;
606 }
607 catch(Exception e)
608 {
609
610
611
612 String msg = "ASTReference setValue() : exception : " + e
613 + " template at " + Log.formatFileString(uberInfo);
614 log.error(msg, e);
615 throw new VelocityException(msg, e);
616 }
617
618 return true;
619 }
620
621 private String getRoot()
622 {
623 Token t = getFirstToken();
624
625
626
627
628
629
630
631
632
633
634 int slashbang = t.image.indexOf("\\!");
635
636 if (slashbang != -1)
637 {
638
639
640
641
642
643
644
645
646
647
648
649
650 int i = 0;
651 int len = t.image.length();
652
653 i = t.image.indexOf('$');
654
655 if (i == -1)
656 {
657
658 log.error("ASTReference.getRoot() : internal error : "
659 + "no $ found for slashbang.");
660 computableReference = false;
661 nullString = t.image;
662 return nullString;
663 }
664
665 while (i < len && t.image.charAt(i) != '\\')
666 {
667 i++;
668 }
669
670
671
672 int start = i;
673 int count = 0;
674
675 while (i < len && t.image.charAt(i++) == '\\')
676 {
677 count++;
678 }
679
680
681
682
683
684
685 nullString = t.image.substring(0,start);
686 nullString += t.image.substring(start, start + count-1 );
687 nullString += t.image.substring(start+count);
688
689
690
691
692
693
694 computableReference = false;
695
696 return nullString;
697 }
698
699
700
701
702
703
704
705
706 escaped = false;
707
708 if (t.image.startsWith("\\"))
709 {
710
711
712
713
714 int i = 0;
715 int len = t.image.length();
716
717 while (i < len && t.image.charAt(i) == '\\')
718 {
719 i++;
720 }
721
722 if ((i % 2) != 0)
723 escaped = true;
724
725 if (i > 0)
726 escPrefix = t.image.substring(0, i / 2 );
727
728 t.image = t.image.substring(i);
729 }
730
731
732
733
734
735
736
737 int loc1 = t.image.lastIndexOf('$');
738
739
740
741
742
743
744 if (loc1 > 0)
745 {
746 morePrefix = morePrefix + t.image.substring(0, loc1);
747 t.image = t.image.substring(loc1);
748 }
749
750
751
752
753
754
755
756
757 nullString = literal();
758
759 if (t.image.startsWith("$!"))
760 {
761 referenceType = QUIET_REFERENCE;
762
763
764
765
766
767 if (!escaped)
768 nullString = "";
769
770 if (t.image.startsWith("$!{"))
771 {
772
773
774
775
776 return t.next.image;
777 }
778 else
779 {
780
781
782
783
784 return t.image.substring(2);
785 }
786 }
787 else if (t.image.equals("${"))
788 {
789
790
791
792
793 referenceType = FORMAL_REFERENCE;
794 return t.next.image;
795 }
796 else if (t.image.startsWith("$"))
797 {
798
799
800
801
802
803 referenceType = NORMAL_REFERENCE;
804 return t.image.substring(1);
805 }
806 else
807 {
808
809
810
811
812
813 referenceType = RUNT;
814 return t.image;
815 }
816
817 }
818
819
820
821
822
823
824
825 public Object getVariableValue(Context context, String variable) throws MethodInvocationException
826 {
827 Object obj = null;
828 try
829 {
830 obj = context.get(variable);
831 }
832 catch(RuntimeException e)
833 {
834 log.error("Exception calling reference $" + variable + " at "
835 + Log.formatFileString(uberInfo));
836 throw e;
837 }
838
839 if (strictRef && obj == null)
840 {
841 if (!context.containsKey(variable))
842 {
843 log.error("Variable $" + variable + " has not been set at "
844 + Log.formatFileString(uberInfo));
845 throw new MethodInvocationException("Variable $" + variable +
846 " has not been set", null, identifier,
847 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
848 }
849 }
850 return obj;
851 }
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866 public void setLiteral(String literal)
867 {
868
869
870
871
872 if( this.literal == null)
873 this.literal = literal;
874 }
875
876
877
878
879
880
881
882
883 public String literal()
884 {
885 if (literal != null)
886 return literal;
887
888
889 return super.literal();
890 }
891 }