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 if (value.toString() == null)
474 {
475 return false;
476 }
477 else
478 return true;
479 }
480
481
482
483
484 public Object value(InternalContextAdapter context)
485 throws MethodInvocationException
486 {
487 return (computableReference ? execute(null, context) : null);
488 }
489
490
491
492
493
494
495
496
497
498
499
500
501 public boolean setValue( InternalContextAdapter context, Object value)
502 throws MethodInvocationException
503 {
504 if (jjtGetNumChildren() == 0)
505 {
506 context.put(rootString, value);
507 return true;
508 }
509
510
511
512
513
514
515
516 Object result = getVariableValue(context, rootString);
517
518 if (result == null)
519 {
520 String msg = "reference set is not a valid reference at "
521 + Log.formatFileString(uberInfo);
522 log.error(msg);
523 return false;
524 }
525
526
527
528
529
530 for (int i = 0; i < numChildren - 1; i++)
531 {
532 result = jjtGetChild(i).execute(result, context);
533
534 if (result == null)
535 {
536 if (strictRef)
537 {
538 String name = jjtGetChild(i+1).getFirstToken().image;
539 throw new MethodInvocationException("Attempted to access '"
540 + name + "' on a null value", null, name, uberInfo.getTemplateName(),
541 jjtGetChild(i+1).getLine(), jjtGetChild(i+1).getColumn());
542 }
543
544 String msg = "reference set is not a valid reference at "
545 + Log.formatFileString(uberInfo);
546 log.error(msg);
547
548 return false;
549 }
550 }
551
552
553
554
555
556
557
558 try
559 {
560 VelPropertySet vs =
561 rsvc.getUberspect().getPropertySet(result, identifier,
562 value, uberInfo);
563
564 if (vs == null)
565 {
566 if (strictRef)
567 {
568 throw new MethodInvocationException("Object '" + result.getClass().getName() +
569 "' does not contain property '" + identifier + "'", null, identifier,
570 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
571 }
572 else
573 {
574 return false;
575 }
576 }
577
578 vs.invoke(result, value);
579 }
580 catch(InvocationTargetException ite)
581 {
582
583
584
585
586 throw new MethodInvocationException(
587 "ASTReference : Invocation of method '"
588 + identifier + "' in " + result.getClass()
589 + " threw exception "
590 + ite.getTargetException().toString(),
591 ite.getTargetException(), identifier, getTemplateName(), this.getLine(), this.getColumn());
592 }
593
594
595
596 catch( RuntimeException e )
597 {
598 throw e;
599 }
600 catch(Exception e)
601 {
602
603
604
605 String msg = "ASTReference setValue() : exception : " + e
606 + " template at " + Log.formatFileString(uberInfo);
607 log.error(msg, e);
608 throw new VelocityException(msg, e);
609 }
610
611 return true;
612 }
613
614 private String getRoot()
615 {
616 Token t = getFirstToken();
617
618
619
620
621
622
623
624
625
626
627 int slashbang = t.image.indexOf("\\!");
628
629 if (slashbang != -1)
630 {
631
632
633
634
635
636
637
638
639
640
641
642
643 int i = 0;
644 int len = t.image.length();
645
646 i = t.image.indexOf('$');
647
648 if (i == -1)
649 {
650
651 log.error("ASTReference.getRoot() : internal error : "
652 + "no $ found for slashbang.");
653 computableReference = false;
654 nullString = t.image;
655 return nullString;
656 }
657
658 while (i < len && t.image.charAt(i) != '\\')
659 {
660 i++;
661 }
662
663
664
665 int start = i;
666 int count = 0;
667
668 while (i < len && t.image.charAt(i++) == '\\')
669 {
670 count++;
671 }
672
673
674
675
676
677
678 nullString = t.image.substring(0,start);
679 nullString += t.image.substring(start, start + count-1 );
680 nullString += t.image.substring(start+count);
681
682
683
684
685
686
687 computableReference = false;
688
689 return nullString;
690 }
691
692
693
694
695
696
697
698
699 escaped = false;
700
701 if (t.image.startsWith("\\"))
702 {
703
704
705
706
707 int i = 0;
708 int len = t.image.length();
709
710 while (i < len && t.image.charAt(i) == '\\')
711 {
712 i++;
713 }
714
715 if ((i % 2) != 0)
716 escaped = true;
717
718 if (i > 0)
719 escPrefix = t.image.substring(0, i / 2 );
720
721 t.image = t.image.substring(i);
722 }
723
724
725
726
727
728
729
730 int loc1 = t.image.lastIndexOf('$');
731
732
733
734
735
736
737 if (loc1 > 0)
738 {
739 morePrefix = morePrefix + t.image.substring(0, loc1);
740 t.image = t.image.substring(loc1);
741 }
742
743
744
745
746
747
748
749
750 nullString = literal();
751
752 if (t.image.startsWith("$!"))
753 {
754 referenceType = QUIET_REFERENCE;
755
756
757
758
759
760 if (!escaped)
761 nullString = "";
762
763 if (t.image.startsWith("$!{"))
764 {
765
766
767
768
769 return t.next.image;
770 }
771 else
772 {
773
774
775
776
777 return t.image.substring(2);
778 }
779 }
780 else if (t.image.equals("${"))
781 {
782
783
784
785
786 referenceType = FORMAL_REFERENCE;
787 return t.next.image;
788 }
789 else if (t.image.startsWith("$"))
790 {
791
792
793
794
795
796 referenceType = NORMAL_REFERENCE;
797 return t.image.substring(1);
798 }
799 else
800 {
801
802
803
804
805
806 referenceType = RUNT;
807 return t.image;
808 }
809
810 }
811
812
813
814
815
816
817
818 public Object getVariableValue(Context context, String variable) throws MethodInvocationException
819 {
820 Object obj = null;
821 try
822 {
823 obj = context.get(variable);
824 }
825 catch(RuntimeException e)
826 {
827 log.error("Exception calling reference $" + variable + " at "
828 + Log.formatFileString(uberInfo));
829 throw e;
830 }
831
832 if (strictRef && obj == null)
833 {
834 if (!context.containsKey(variable))
835 {
836 log.error("Variable $" + variable + " has not been set at "
837 + Log.formatFileString(uberInfo));
838 throw new MethodInvocationException("Variable $" + variable +
839 " has not been set", null, identifier,
840 uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn());
841 }
842 }
843 return obj;
844 }
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859 public void setLiteral(String literal)
860 {
861
862
863
864
865 if( this.literal == null)
866 this.literal = literal;
867 }
868
869
870
871
872
873
874
875
876 public String literal()
877 {
878 if (literal != null)
879 return literal;
880
881
882 return super.literal();
883 }
884 }