View Javadoc

1   package org.apache.velocity.runtime.directive;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.StringReader;
24  import java.io.StringWriter;
25  
26  import org.apache.velocity.VelocityContext;
27  import org.apache.velocity.context.InternalContextAdapter;
28  import org.apache.velocity.context.InternalContextAdapterImpl;
29  import org.apache.velocity.exception.MethodInvocationException;
30  import org.apache.velocity.runtime.RuntimeServices;
31  import org.apache.velocity.runtime.log.Log;
32  import org.apache.velocity.runtime.parser.ParserTreeConstants;
33  import org.apache.velocity.runtime.parser.node.ASTReference;
34  import org.apache.velocity.runtime.parser.node.SimpleNode;
35  
36  /**
37   *  The function of this class is to proxy for the calling parameter to the VM.
38   *
39   *  This class is designed to be used in conjunction with the VMContext class
40   *  which knows how to get and set values via it, rather than a simple get()
41   *  or put() from a hashtable-like object.
42   *
43   *  There is probably a lot of undocumented subtlty here, so step lightly.
44   *
45   *  We rely on the observation that an instance of this object has a constant
46   *  state throughout its lifetime as it's bound to the use-instance of a VM.
47   *  In other words, it's created by the VelocimacroProxy class, to represent
48   *  one of the arguments to a VM in a specific template.  Since the template
49   *  is fixed (it's a file...), we don't have to worry that the args to the VM
50   *  will change.  Yes, the VM will be called in other templates, or in other
51   *  places on the same template, bit those are different use-instances.
52   *
53   *  These arguments can be, in the lingo of
54   *  the parser, one of :
55   *   <ul>
56   *   <li> Reference() : anything that starts with '$'
57   *   <li> StringLiteral() : something like "$foo" or "hello geir"
58   *   <li> IntegerLiteral() : 1, 2 etc
59   *   <li> FloatingPointLiteral() : 1.2, 2e5 etc
60   *   <li> IntegerRange() : [ 1..2] or [$foo .. $bar]
61   *   <li> ObjectArray() : [ "a", "b", "c"]
62   *   <li> True() : true
63   *   <li> False() : false
64   *    <li>Word() : not likely - this is simply allowed by the parser so we can have
65   *             syntactical sugar like #foreach($a in $b)  where 'in' is the Word
66   *    </ul>
67   *  Now, Reference(), StringLit, IntegerLiteral, IntRange, ObjArr are all dynamic things, so
68   *  their value is gotten with the use of a context.  The others are constants.  The trick
69   *  we rely on is that the context rather than this class really represents the
70   *  state of the argument. We are simply proxying for the thing, returning the proper value
71   *  when asked, and storing the proper value in the appropriate context when asked.
72   *
73   *  So, the hope here, so an instance of this can be shared across threads, is to
74   *  keep any dynamic stuff out of it, relying on trick of having the appropriate
75   *  context handed to us, and when a constant argument, letting VMContext punch that
76   *  into a local context.
77   *
78   *  @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
79   *  @version $Id: VMProxyArg.java 473363 2006-11-10 15:19:09Z wglass $
80   */
81  public class VMProxyArg
82  {
83      /** in the event our type is switched - we don't care really what it is */
84      private static final int GENERALSTATIC = -1;
85  
86      /**  type of arg I will have */
87      private int type = 0;
88  
89      /**  the AST if the type is such that it's dynamic (ex. JJTREFERENCE ) */
90      private SimpleNode nodeTree = null;
91  
92      /**  reference for the object if we proxy for a static arg like an IntegerLiteral*/
93      private Object staticObject = null;
94  
95      /** number of children in our tree if a reference */
96      private int numTreeChildren = 0;
97  
98      /** our identity in the current context */
99      private String contextReference = null;
100 
101     /** the reference we are proxying for  */
102     private String callerReference = null;
103 
104     /** the 'de-dollared' reference if we are a ref but don't have a method attached */
105     private String singleLevelRef = null;
106 
107     /** by default, we are dynamic.  safest */
108     private boolean constant = false;
109 
110     private RuntimeServices rsvc = null;
111     private Log log = null;
112 
113     /**
114      *  ctor for current impl
115      *
116      *  takes the reference literal we are proxying for, the literal
117      *  the VM we are for is called with...
118      * @param rs
119      *
120      *  @param contextRef reference arg in the definition of the VM, used in the VM
121      *  @param callerRef  reference used by the caller as an arg to the VM
122      *  @param t  type of arg : JJTREFERENCE, JJTTRUE, etc
123      */
124     public VMProxyArg( RuntimeServices rs, String contextRef, String callerRef, int t )
125     {
126         rsvc = rs;
127         log = rsvc.getLog();
128 
129         contextReference = contextRef;
130         callerReference = callerRef;
131         type = t;
132 
133         /*
134          *  make our AST if necessary
135          */
136         setup();
137 
138         /*
139          *  if we are multi-node tree, then save the size to
140          *  avoid fn call overhead
141          */
142         if( nodeTree != null)
143         {
144             numTreeChildren = nodeTree.jjtGetNumChildren();
145         }
146 
147         /*
148          *  if we are a reference, and 'scalar' (i.e. $foo )
149          *  then get the de-dollared ref so we can
150          *  hit our context directly, avoiding the AST
151          */
152         if ( type == ParserTreeConstants.JJTREFERENCE )
153         {
154             if ( numTreeChildren == 0)
155             {
156                 /*
157                  * do this properly and use the Reference node
158                  */
159                  singleLevelRef = ((ASTReference) nodeTree).getRootString();
160             }
161         }
162     }
163 
164     /**
165      *  tells if arg we are poxying for is
166      *  dynamic or constant.
167      *
168      *  @return true of constant, false otherwise
169      */
170     public boolean isConstant()
171     {
172         return constant;
173     }
174 
175     /**
176      *  Invoked by VMContext when Context.put() is called for a proxied reference.
177      *
178      *  @param context context to modify via direct placement, or AST.setValue()
179      *  @param o  new value of reference
180      *  @return Object currently null
181      */
182     public Object setObject(  InternalContextAdapter context,  Object o )
183     {
184         /*
185          *  if we are a reference, we could be updating a property
186          */
187 
188         if( type == ParserTreeConstants.JJTREFERENCE )
189         {
190             if( numTreeChildren > 0)
191             {
192                 /*
193                  *  we are a property, and being updated such as
194                  *  #foo( $bar.BangStart)
195                  */
196 
197                 try
198                 {
199                     ( (ASTReference) nodeTree).setValue( context, o );
200                 }
201                 catch( MethodInvocationException mie )
202                 {
203                     log.error("VMProxyArg.getObject() : method invocation error setting value", mie);
204                 }
205            }
206             else
207             {
208                 /*
209                  *  we are a 'single level' reference like $foo, so we can set
210                  *  out context directly
211                  */
212 
213                 context.put( singleLevelRef, o);
214 
215                 // alternate impl : usercontext.put( singleLevelRef, o);
216              }
217         }
218         else
219         {
220             /*
221              *  if we aren't a reference, then we simply switch type,
222              *  get a new value, and it doesn't go into the context
223              *
224              *  in current impl, this shouldn't happen.
225              */
226 
227             type = GENERALSTATIC;
228             staticObject = o;
229 
230             log.error("VMProxyArg.setObject() : Programmer error : I am a constant!  No setting! : "
231                       + contextReference + " / " + callerReference);
232         }
233 
234         return null;
235     }
236 
237 
238     /**
239      *  returns the value of the reference.  Generally, this is only
240      *  called for dynamic proxies, as the static ones should have
241      *  been stored in the VMContext's localcontext store
242      *
243      *  @param context Context to use for getting current value
244      *  @return Object value
245      * @exception MethodInvocationException passes on potential exception from reference method call
246      */
247     public Object getObject( InternalContextAdapter context )  throws MethodInvocationException
248     {
249         try
250         {
251 
252             /*
253              *  we need to output based on our type
254              */
255 
256             Object retObject = null;
257 
258             if ( type == ParserTreeConstants.JJTREFERENCE )
259             {
260                 /*
261                  *  two     cases :  scalar reference ($foo) or multi-level ($foo.bar....)
262                  */
263 
264                 if ( numTreeChildren == 0)
265                 {
266                     /*
267                      *  if I am a single-level reference, can I not get get it out of my context?
268                      */
269 
270                     retObject = context.get( singleLevelRef );
271                 }
272                 else
273                 {
274                     /*
275                      *  I need to let the AST produce it for me.
276                      */
277 
278                     retObject = nodeTree.execute( null, context);
279                 }
280             }
281             else if (type == ParserTreeConstants.JJTMAP)
282             {
283                 retObject = nodeTree.value(context);
284             }
285             else if( type == ParserTreeConstants.JJTOBJECTARRAY )
286             {
287                 retObject = nodeTree.value( context );
288             }
289             else if ( type == ParserTreeConstants.JJTINTEGERRANGE)
290             {
291                 retObject = nodeTree.value( context );
292             }
293             else if( type == ParserTreeConstants.JJTTRUE )
294             {
295                 retObject = staticObject;
296             }
297             else if ( type == ParserTreeConstants.JJTFALSE )
298             {
299                 retObject = staticObject;
300             }
301             else if ( type == ParserTreeConstants.JJTSTRINGLITERAL )
302             {
303                 retObject =  nodeTree.value( context );
304             }
305             else if ( type == ParserTreeConstants.JJTINTEGERLITERAL )
306             {
307                 retObject = staticObject;
308             }
309             else if ( type == ParserTreeConstants.JJTFLOATINGPOINTLITERAL )
310             {
311                 retObject = staticObject;
312             }
313             else if ( type == ParserTreeConstants.JJTTEXT )
314             {
315                 /*
316                  *  this really shouldn't happen.  text is just a thowaway arg for #foreach()
317                  */
318 
319                 try
320                 {
321                     StringWriter writer =new StringWriter();
322                     nodeTree.render( context, writer );
323 
324                     retObject = writer;
325                 }
326                 /**
327                  * pass through application level runtime exceptions
328                  */
329                 catch( RuntimeException e )
330                 {
331                     throw e;
332                 }
333                 catch (Exception e )
334                 {
335                     log.error("VMProxyArg.getObject() : error rendering reference", e);
336                 }
337             }
338             else if( type ==  GENERALSTATIC )
339             {
340                 retObject = staticObject;
341             }
342             else
343             {
344                 log.error("Unsupported VM arg type : VM arg = " +
345                           callerReference +" type = " + type +
346                           "( VMProxyArg.getObject() )");
347             }
348 
349             return retObject;
350         }
351         catch( MethodInvocationException mie )
352         {
353             /*
354              *  not ideal, but otherwise we propogate out to the
355              *  VMContext, and the Context interface's put/get
356              *  don't throw. So this is a the best compromise
357              *  I can think of
358              */
359 
360             log.error("VMProxyArg.getObject() : method invocation error getting value", mie);
361             throw mie;
362         }
363     }
364 
365     /**
366      *  does the housekeeping upon creationg.  If a dynamic type
367      *  it needs to make an AST for further get()/set() operations
368      *  Anything else is constant.
369      */
370     private void setup()
371     {
372         switch( type )
373         {
374 
375             case ParserTreeConstants.JJTINTEGERRANGE :
376             case ParserTreeConstants.JJTREFERENCE :
377             case ParserTreeConstants.JJTOBJECTARRAY :
378             case ParserTreeConstants.JJTMAP :
379             case ParserTreeConstants.JJTSTRINGLITERAL :
380             case ParserTreeConstants.JJTTEXT :
381             {
382                 /*
383                  *  dynamic types, just render
384                  */
385 
386                 constant = false;
387 
388                 try
389                 {
390                     /*
391                      *  fakie : wrap in  directive to get the parser to treat our args as args
392                      *   it doesn't matter that #include() can't take all these types, because we
393                      *   just want the parser to consider our arg as a Directive/VM arg rather than
394                      *   as if inline in schmoo
395                      */
396 
397                     String buff ="#include(" + callerReference + " ) ";
398 
399                     //ByteArrayInputStream inStream = new ByteArrayInputStream( buff.getBytes() );
400 
401                     BufferedReader br = new BufferedReader( new StringReader( buff ) );
402 
403                     nodeTree = rsvc.parse(br, "VMProxyArg:" + callerReference, true);
404 
405                     /*
406                      *  now, our tree really is the first DirectiveArg(), and only one
407                      */
408 
409                     nodeTree = (SimpleNode) nodeTree.jjtGetChild(0).jjtGetChild(0);
410 
411                     /*
412                      * sanity check
413                      */
414                     if ( nodeTree != null)
415                     {
416                 	if(nodeTree.getType() != type)
417                 	{
418                 	    log.error("VMProxyArg.setup() : programmer error : type doesn't match node type.");
419                 	}
420 
421                 	/*
422                 	 *  init.  be a good citizen and give it an ICA
423                 	 */
424 
425                 	InternalContextAdapter ica
426                 		= new InternalContextAdapterImpl(new VelocityContext());
427 
428                 	ica.pushCurrentTemplateName("VMProxyArg : "
429                 		+ ParserTreeConstants.jjtNodeName[type]);
430 
431                 	nodeTree.init(ica, rsvc);
432                     }
433                 }
434                 /**
435                  * pass through application level runtime exceptions
436                  */
437                 catch( RuntimeException e )
438                 {
439                     throw e;
440                 }
441                 catch ( Exception e )
442                 {
443                     log.error("VMProxyArg.setup() : exception " +
444                               callerReference, e);
445                 }
446 
447                 break;
448             }
449 
450             case ParserTreeConstants.JJTTRUE :
451             {
452                 constant = true;
453                 staticObject = Boolean.TRUE;
454                 break;
455             }
456 
457             case ParserTreeConstants.JJTFALSE :
458             {
459                 constant = true;
460                 staticObject =  Boolean.FALSE;
461                 break;
462             }
463 
464             case ParserTreeConstants.JJTINTEGERLITERAL :
465             {
466                 constant = true;
467                 staticObject = new Integer(callerReference);
468                 break;
469             }
470 
471             case ParserTreeConstants.JJTFLOATINGPOINTLITERAL :
472             {
473                 constant = true;
474                 staticObject = new Double(callerReference);
475                 break;
476             }
477 
478             case ParserTreeConstants.JJTWORD :
479             {
480                 /*
481                  *  this is technically an error...
482                  */
483 
484                 log.error("Unsupported arg type : " + callerReference +
485                           " You most likely intended to call a VM with a string literal, so enclose with ' or \" characters. (VMProxyArg.setup())");
486                 constant = true;
487                 staticObject = callerReference;
488 
489                 break;
490             }
491 
492              default :
493             {
494                  log.error("VMProxyArg.setup() : unsupported type : "
495                            + callerReference  );
496             }
497         }
498     }
499 
500     /*
501      * CODE FOR ALTERNATE IMPL : please ignore.  I will remove when confortable with current.
502      */
503 
504     /**
505      *  not used in current impl
506      *
507      *  Constructor for alternate impl where VelProxy class would make new
508      *  VMProxyArg objects, and use this contructor to avoid reparsing the
509      *  reference args
510      *
511      *  that impl also had the VMProxyArg carry it's context.
512      * @param model
513      * @param c
514      */
515     public VMProxyArg( VMProxyArg model, InternalContextAdapter c )
516     {
517         contextReference = model.getContextReference();
518         callerReference = model.getCallerReference();
519         nodeTree = model.getNodeTree();
520         staticObject = model.getStaticObject();
521         type = model.getType();
522 
523        if( nodeTree != null)
524             numTreeChildren = nodeTree.jjtGetNumChildren();
525 
526         if ( type == ParserTreeConstants.JJTREFERENCE )
527         {
528             if ( numTreeChildren == 0)
529             {
530                 /*
531                  *  use the reference node to do this...
532                  */
533                 singleLevelRef = ((ASTReference) nodeTree).getRootString();
534             }
535         }
536     }
537 
538     /**
539      * @return The caller reference.
540      */
541     public String getCallerReference()
542     {
543         return callerReference;
544     }
545 
546     /**
547      * @return The context reference.
548      */
549     public String getContextReference()
550     {
551         return contextReference;
552     }
553 
554     /**
555      * @return The node tree.
556      */
557     public SimpleNode getNodeTree()
558     {
559         return nodeTree;
560     }
561 
562     /**
563      * @return The static object.
564      */
565     public Object getStaticObject()
566     {
567         return staticObject;
568     }
569 
570     /**
571      * @return The type.
572      */
573     public int getType()
574     {
575         return type;
576     }
577 }