KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > velocity > runtime > parser > node > ASTReference


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 JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.lang.reflect.InvocationTargetException JavaDoc;
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 JavaDoc nullString;
59     private String JavaDoc rootString;
60     private boolean escaped = false;
61     private boolean computableReference = true;
62     private String JavaDoc escPrefix = "";
63     private String JavaDoc morePrefix = "";
64     private String JavaDoc identifier = "";
65
66     private String JavaDoc 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 JavaDoc jjtAccept(ParserVisitor visitor, Object JavaDoc data)
84     {
85         return visitor.visit(this, data);
86     }
87
88     public Object JavaDoc init(InternalContextAdapter context, Object JavaDoc data)
89         throws Exception JavaDoc
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 JavaDoc 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 JavaDoc execute(Object JavaDoc 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 JavaDoc 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 JavaDoc writer)
211         throws IOException JavaDoc, MethodInvocationException
212     {
213
214         if (referenceType == RUNT)
215         {
216             writer.write(rootString);
217             return true;
218         }
219
220         Object JavaDoc 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 JavaDoc value = execute(null, context);
308
309         if (value == null)
310         {
311             return false;
312         }
313         else if (value instanceof Boolean JavaDoc)
314         {
315             if (((Boolean JavaDoc) value).booleanValue())
316                 return true;
317             else
318                 return false;
319         }
320         else
321             return true;
322     }
323
324     public Object JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc getVariableValue(Context context, String JavaDoc 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 JavaDoc 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 JavaDoc literal()
656     {
657         if (literal != null)
658             return literal;
659
660         return super.literal();
661     }
662 }
663
Popular Tags