EMMA Coverage Report (generated Tue May 18 22:13:27 CDT 2004)
[all classes][org.apache.velocity.runtime.parser.node]

COVERAGE SUMMARY FOR SOURCE FILE [ASTReference.java]

nameclass, %method, %block, %line, %
ASTReference.java100% (1/1)93%  (13/14)83%  (624/754)85%  (130.3/153)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ASTReference100% (1/1)93%  (13/14)83%  (624/754)85%  (130.3/153)
ASTReference (int): void 0%   (0/1)0%   (0/25)0%   (0/9)
setValue (InternalContextAdapter, Object): boolean 100% (1/1)44%  (69/157)47%  (10.8/23)
value (InternalContextAdapter): Object 100% (1/1)90%  (9/10)90%  (0.9/1)
getRoot (): String 100% (1/1)94%  (240/254)92%  (49/53)
execute (Object, InternalContextAdapter): Object 100% (1/1)98%  (78/80)93%  (13/14)
ASTReference (Parser, int): void 100% (1/1)100% (26/26)100% (9/9)
evaluate (InternalContextAdapter): boolean 100% (1/1)100% (22/22)100% (8/8)
getRootString (): String 100% (1/1)100% (3/3)100% (1/1)
getVariableValue (Context, String): Object 100% (1/1)100% (4/4)100% (1/1)
init (InternalContextAdapter, Object): Object 100% (1/1)100% (39/39)100% (7/7)
jjtAccept (ParserVisitor, Object): Object 100% (1/1)100% (5/5)100% (1/1)
literal (): String 100% (1/1)100% (9/9)100% (3/3)
render (InternalContextAdapter, Writer): boolean 100% (1/1)100% (113/113)100% (27/27)
setLiteral (String): void 100% (1/1)100% (7/7)100% (3/3)

1package 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 
19import java.io.Writer;
20import java.io.IOException;
21import java.lang.reflect.InvocationTargetException;
22 
23import org.apache.velocity.context.Context;
24import org.apache.velocity.context.InternalContextAdapter;
25import org.apache.velocity.runtime.RuntimeConstants;
26import org.apache.velocity.runtime.exception.ReferenceException;
27import org.apache.velocity.runtime.parser.*;
28 
29import org.apache.velocity.util.introspection.VelPropertySet;
30import org.apache.velocity.util.introspection.Info;
31 
32import org.apache.velocity.exception.MethodInvocationException;
33 
34import 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*/
49public 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}

[all classes][org.apache.velocity.runtime.parser.node]
EMMA 2.0.4015 (stable) (C) Vladimir Roubtsov