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 mie.setReferenceName(rootString);
305 throw mie;
306 }
307 }
308
309
310
311
312
313
314
315
316
317
318
319 public boolean render(InternalContextAdapter context, Writer writer) throws IOException,
320 MethodInvocationException
321 {
322 if (referenceType == RUNT)
323 {
324 if (context.getAllowRendering())
325 {
326 writer.write(rootString);
327 }
328
329 return true;
330 }
331
332 Object value = execute(null, context);
333
334 String localNullString = null;
335
336
337
338
339
340
341
342 if (escaped)
343 {
344 localNullString = getNullString(context);
345
346 if (value == null)
347 {
348 if (context.getAllowRendering())
349 {
350 writer.write(escPrefix);
351 writer.write("\\");
352 writer.write(localNullString);
353 }
354 }
355 else
356 {
357 if (context.getAllowRendering())
358 {
359 writer.write(escPrefix);
360 writer.write(localNullString);
361 }
362 }
363 return true;
364 }
365
366
367
368
369
370
371
372 value = EventHandlerUtil.referenceInsert(rsvc, context, literal(), value);
373
374 String toString = null;
375 if (value != null)
376 {
377
378 if(value instanceof Renderable && ((Renderable)value).render(context,writer))
379 {
380 return true;
381 }
382
383 toString = value.toString();
384 }
385
386 if (value == null || toString == null)
387 {
388
389
390
391
392 if (context.getAllowRendering())
393 {
394 localNullString = getNullString(context);
395
396 writer.write(escPrefix);
397 writer.write(escPrefix);
398 writer.write(morePrefix);
399 writer.write(localNullString);
400 }
401
402 if (logOnNull && referenceType != QUIET_REFERENCE && log.isDebugEnabled())
403 {
404 log.debug("Null reference [template '" + getTemplateName()
405 + "', line " + this.getLine() + ", column " + this.getColumn() + "] : "
406 + this.literal() + " cannot be resolved.");
407 }
408 return true;
409 }
410 else
411 {
412
413
414
415
416 if (context.getAllowRendering())
417 {
418 writer.write(escPrefix);
419 writer.write(morePrefix);
420 writer.write(toString);
421 }
422
423 return true;
424 }
425 }
426
427
428
429
430
431
432
433
434
435
436
437
438 private String getNullString(InternalContextAdapter context)
439 {
440 Object callingArgument = context.get(".literal." + nullString);
441
442 if (callingArgument != null)
443 return ((Node) callingArgument).literal();
444 else
445 return nullString;
446 }
447
448
449
450
451
452
453
454
455
456
457 public boolean evaluate(InternalContextAdapter context)
458 throws MethodInvocationException
459 {
460 Object value = execute(null, context);
461
462 if (value == null)
463 {
464 return false;
465 }
466 else if (value instanceof Boolean)
467 {
468 if (((Boolean) value).booleanValue())
469 return true;
470 else
471 return false;
472 }
473 else
474 {
475 try
476 {
477 return value.toString() != null;
478 }
479 catch(Exception e)
480 {
481 throw new VelocityException("Reference evaluation threw an exception at "
482 + Log.formatFileString(this), e);
483 }
484 }
485 }
486
487
488
489
490 public Object value(InternalContextAdapter context)
491 throws MethodInvocationException
492 {
493 return (computableReference ? execute(null, context) : null);
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507 public boolean setValue( InternalContextAdapter context, Object value)
508 throws MethodInvocationException
509 {
510 if (jjtGetNumChildren() == 0)
511 {
512 context.put(rootString, value);
513 return true;
514 }
515
516
517
518
519
520
521
522 Object result = getVariableValue(context, rootString);
523
524 if (result == null)
525 {
526 String msg = "reference set is not a valid reference at "
527 + Log.formatFileString(uberInfo);
528 log.error(msg);
529 return false;
530 }
531
532
533
534
535
536 for (int i = 0; i < numChildren - 1; i++)
537 {
538 result = jjtGetChild(i).execute(result, context);
539
540 if (result == null)
541 {
542 if (strictRef)
543 {
544 String name = jjtGetChild(i+1).getFirstToken().image;
545 throw new MethodInvocationException("Attempted to access '"
546 + name + "' on a null value", null, name, uberInfo.getTemplateName(),
547 jjtGetChild(i+1).getLine(), jjtGetChild(i+1).getColumn());
548 }
549
550 String msg = "reference set is not a valid reference at "
551 + Log.formatFileString(uberInfo);
552 log.error(msg);
553
554 return false;
555 }
556 }
557
558
559
560
561
562
563
564 try
565 {
566 VelPropertySet vs =
567 rsvc.getUberspect().getPropertySet(result, identifier,
568 value, uberInfo);
569
570 if (vs == null)
571 {
572 if (strictRef)
573 {
574 throw new MethodInvocationException("Object '" + result.getClass().getName() +
575 "' does not contain property '" + identifier + "'", null, identifier,
576 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
577 }
578 else
579 {
580 return false;
581 }
582 }
583
584 vs.invoke(result, value);
585 }
586 catch(InvocationTargetException ite)
587 {
588
589
590
591
592 throw new MethodInvocationException(
593 "ASTReference : Invocation of method '"
594 + identifier + "' in " + result.getClass()
595 + " threw exception "
596 + ite.getTargetException().toString(),
597 ite.getTargetException(), identifier, getTemplateName(), this.getLine(), this.getColumn());
598 }
599
600
601
602 catch( RuntimeException e )
603 {
604 throw e;
605 }
606 catch(Exception e)
607 {
608
609
610
611 String msg = "ASTReference setValue() : exception : " + e
612 + " template at " + Log.formatFileString(uberInfo);
613 log.error(msg, e);
614 throw new VelocityException(msg, e);
615 }
616
617 return true;
618 }
619
620 private String getRoot()
621 {
622 Token t = getFirstToken();
623
624
625
626
627
628
629
630
631
632
633 int slashbang = t.image.indexOf("\\!");
634
635 if (slashbang != -1)
636 {
637
638
639
640
641
642
643
644
645
646
647
648
649 int i = 0;
650 int len = t.image.length();
651
652 i = t.image.indexOf('$');
653
654 if (i == -1)
655 {
656
657 log.error("ASTReference.getRoot() : internal error : "
658 + "no $ found for slashbang.");
659 computableReference = false;
660 nullString = t.image;
661 return nullString;
662 }
663
664 while (i < len && t.image.charAt(i) != '\\')
665 {
666 i++;
667 }
668
669
670
671 int start = i;
672 int count = 0;
673
674 while (i < len && t.image.charAt(i++) == '\\')
675 {
676 count++;
677 }
678
679
680
681
682
683
684 nullString = t.image.substring(0,start);
685 nullString += t.image.substring(start, start + count-1 );
686 nullString += t.image.substring(start+count);
687
688
689
690
691
692
693 computableReference = false;
694
695 return nullString;
696 }
697
698
699
700
701
702
703
704
705 escaped = false;
706
707 if (t.image.startsWith("\\"))
708 {
709
710
711
712
713 int i = 0;
714 int len = t.image.length();
715
716 while (i < len && t.image.charAt(i) == '\\')
717 {
718 i++;
719 }
720
721 if ((i % 2) != 0)
722 escaped = true;
723
724 if (i > 0)
725 escPrefix = t.image.substring(0, i / 2 );
726
727 t.image = t.image.substring(i);
728 }
729
730
731
732
733
734
735
736 int loc1 = t.image.lastIndexOf('$');
737
738
739
740
741
742
743 if (loc1 > 0)
744 {
745 morePrefix = morePrefix + t.image.substring(0, loc1);
746 t.image = t.image.substring(loc1);
747 }
748
749
750
751
752
753
754
755
756 nullString = literal();
757
758 if (t.image.startsWith("$!"))
759 {
760 referenceType = QUIET_REFERENCE;
761
762
763
764
765
766 if (!escaped)
767 nullString = "";
768
769 if (t.image.startsWith("$!{"))
770 {
771
772
773
774
775 return t.next.image;
776 }
777 else
778 {
779
780
781
782
783 return t.image.substring(2);
784 }
785 }
786 else if (t.image.equals("${"))
787 {
788
789
790
791
792 referenceType = FORMAL_REFERENCE;
793 return t.next.image;
794 }
795 else if (t.image.startsWith("$"))
796 {
797
798
799
800
801
802 referenceType = NORMAL_REFERENCE;
803 return t.image.substring(1);
804 }
805 else
806 {
807
808
809
810
811
812 referenceType = RUNT;
813 return t.image;
814 }
815
816 }
817
818
819
820
821
822
823
824 public Object getVariableValue(Context context, String variable) throws MethodInvocationException
825 {
826 Object obj = null;
827 try
828 {
829 obj = context.get(variable);
830 }
831 catch(RuntimeException e)
832 {
833 log.error("Exception calling reference $" + variable + " at "
834 + Log.formatFileString(uberInfo));
835 throw e;
836 }
837
838 if (strictRef && obj == null)
839 {
840 if (!context.containsKey(variable))
841 {
842 log.error("Variable $" + variable + " has not been set at "
843 + Log.formatFileString(uberInfo));
844 throw new MethodInvocationException("Variable $" + variable +
845 " has not been set", null, identifier,
846 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
847 }
848 }
849 return obj;
850 }
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865 public void setLiteral(String literal)
866 {
867
868
869
870
871 if( this.literal == null)
872 this.literal = literal;
873 }
874
875
876
877
878
879
880
881
882 public String literal()
883 {
884 if (literal != null)
885 return literal;
886
887
888 return super.literal();
889 }
890 }