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.IOException;
24  import java.io.StringReader;
25  import java.io.Writer;
26  import java.util.HashMap;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.velocity.context.InternalContextAdapter;
30  import org.apache.velocity.context.VMContext;
31  import org.apache.velocity.exception.MethodInvocationException;
32  import org.apache.velocity.exception.TemplateInitException;
33  import org.apache.velocity.runtime.RuntimeConstants;
34  import org.apache.velocity.runtime.RuntimeServices;
35  import org.apache.velocity.runtime.parser.ParserTreeConstants;
36  import org.apache.velocity.runtime.parser.Token;
37  import org.apache.velocity.runtime.parser.node.ASTDirective;
38  import org.apache.velocity.runtime.parser.node.Node;
39  import org.apache.velocity.runtime.parser.node.SimpleNode;
40  import org.apache.velocity.runtime.visitor.VMReferenceMungeVisitor;
41  
42  /**
43   *  VelocimacroProxy.java
44   *
45   *   a proxy Directive-derived object to fit with the current directive system
46   *
47   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
48   * @version $Id: VelocimacroProxy.java 471381 2006-11-05 08:56:58Z wglass $
49   */
50  public class VelocimacroProxy extends Directive
51  {
52      private String macroName = "";
53      private String macroBody = "";
54      private String[] argArray = null;
55      private SimpleNode nodeTree = null;
56      private int numMacroArgs = 0;
57      private String namespace = "";
58  
59      private boolean init = false;
60      private String[] callingArgs;
61      private int[]  callingArgTypes;
62      private HashMap proxyArgHash = new HashMap();
63  
64      private boolean strictArguments;
65      
66      /**
67       * Return name of this Velocimacro.
68       * @return The name of this Velocimacro.
69       */
70      public String getName()
71      {
72          return  macroName;
73      }
74  
75      /**
76       * Velocimacros are always LINE
77       * type directives.
78       * @return The type of this directive.
79       */
80      public int getType()
81      {
82          return LINE;
83      }
84  
85      /**
86       *   sets the directive name of this VM
87       * @param name
88       */
89      public void setName( String name )
90      {
91          macroName = name;
92      }
93  
94      /**
95       *  sets the array of arguments specified in the macro definition
96       * @param arr
97       */
98      public void setArgArray( String [] arr )
99      {
100         argArray = arr;
101 
102         /*
103          *  get the arg count from the arg array.  remember that the arg array
104          *  has the macro name as it's 0th element
105          */
106 
107         numMacroArgs = argArray.length - 1;
108     }
109 
110     /**
111      * @param tree
112      */
113     public void setNodeTree( SimpleNode tree )
114     {
115         nodeTree = tree;
116     }
117 
118     /**
119      *  returns the number of ars needed for this VM
120      * @return The number of ars needed for this VM
121      */
122     public int getNumArgs()
123     {
124         return numMacroArgs;
125     }
126 
127     /**
128      *   Sets the orignal macro body.  This is simply the cat of the macroArray, but the
129      *   Macro object creates this once during parsing, and everyone shares it.
130      *   Note : it must not be modified.
131      * @param mb
132      */
133     public void setMacrobody( String mb )
134     {
135         macroBody = mb;
136     }
137 
138     /**
139      * @param ns
140      */
141     public void setNamespace( String ns )
142     {
143         this.namespace = ns;
144     }
145 
146     /**
147      *   Renders the macro using the context
148      * @param context
149      * @param writer
150      * @param node
151      * @return True if the directive rendered successfully.
152      * @throws IOException
153      * @throws MethodInvocationException
154      */
155     public boolean render( InternalContextAdapter context, Writer writer, Node node)
156         throws IOException, MethodInvocationException
157     {
158         try
159         {
160             /*
161              *  it's possible the tree hasn't been parsed yet, so get
162              *  the VMManager to parse and init it
163              */
164 
165             if (nodeTree != null)
166             {
167                 if ( !init )
168                 {
169                     nodeTree.init( context, rsvc);
170                     init = true;
171                 }
172 
173                 /*
174                  *  wrap the current context and add the VMProxyArg objects
175                  */
176 
177                 VMContext vmc = new VMContext( context, rsvc );
178 
179                 for( int i = 1; i < argArray.length; i++)
180                 {
181                     /*
182                      *  we can do this as VMProxyArgs don't change state. They change
183                      *  the context.
184                      */
185 
186                     VMProxyArg arg = (VMProxyArg) proxyArgHash.get( argArray[i] );
187                     vmc.addVMProxyArg( arg );
188                 }
189 
190                 /*
191                  *  now render the VM
192                  */
193 
194                 nodeTree.render( vmc, writer );
195             }
196             else
197             {
198                 rsvc.getLog().error("VM error " + macroName + ". Null AST");
199             }
200         }
201 
202         /*
203          *  if it's a MIE, it came from the render.... throw it...
204          */
205         catch( MethodInvocationException e )
206         {
207             throw e;
208         }
209 
210         /**
211          * pass through application level runtime exceptions
212          */
213         catch( RuntimeException e )
214         {
215             throw e;
216         }
217 
218         catch ( Exception e )
219         {
220 
221             rsvc.getLog().error("VelocimacroProxy.render() : exception VM = #" +
222                                 macroName + "()", e);
223         }
224 
225         return true;
226     }
227 
228     /**
229      *   The major meat of VelocimacroProxy, init() checks the # of arguments, patches the
230      *   macro body, renders the macro into an AST, and then inits the AST, so it is ready
231      *   for quick rendering.  Note that this is only AST dependant stuff. Not context.
232      * @param rs
233      * @param context
234      * @param node
235      * @throws TemplateInitException
236      */
237     public void init( RuntimeServices rs, InternalContextAdapter context, Node node)
238        throws TemplateInitException
239     {
240         super.init( rs, context, node );
241 
242         /**
243          * Throw exception for invalid number of arguments?
244          */
245         strictArguments = rs.getConfiguration().getBoolean(RuntimeConstants.VM_ARGUMENTS_STRICT,false);
246         
247         /*
248          *  how many args did we get?
249          */
250 
251         int i  = node.jjtGetNumChildren();
252 
253         /*
254          *  right number of args?
255          */
256 
257         if ( getNumArgs() != i )
258         {
259             // If we have a not-yet defined macro, we do get no arguments because
260             // the syntax tree looks different than with a already defined macro.
261             // But we do know that we must be in a macro definition context somewhere up the
262             // syntax tree.
263             // Check for that, if it is true, suppress the error message.
264             // Fixes VELOCITY-71.
265 
266             for (Node parent = node.jjtGetParent(); parent != null; )
267             {
268                 if ((parent instanceof ASTDirective) && 
269                         StringUtils.equals(((ASTDirective) parent).getDirectiveName(), "macro"))
270                 {
271                     return;
272                 }
273                 parent = parent.jjtGetParent();
274             }
275             
276             String errormsg = "VM #" + macroName + ": error : too " +
277             ((getNumArgs() > i) ? "few" : "many") + 
278             " arguments to macro. Wanted " + getNumArgs() +
279             " got " + i;
280 
281             if (strictArguments)
282             {
283                 /**
284                  *  indicate col/line assuming it starts at 0 - this will be
285                  *  corrected one call up
286                  */
287                 throw new TemplateInitException(errormsg,
288                         context.getCurrentTemplateName(),
289                         0,
290                         0);
291             }
292             else
293             {
294                 rsvc.getLog().error(errormsg);
295                 return;
296             }
297         }
298 
299         /*
300          *  get the argument list to the instance use of the VM
301          */
302 
303          callingArgs = getArgArray( node );
304 
305         /*
306          *  now proxy each arg in the context
307          */
308 
309          setupMacro( callingArgs, callingArgTypes );
310     }
311 
312     /**
313      *  basic VM setup.  Sets up the proxy args for this
314      *  use, and parses the tree
315      * @param callArgs
316      * @param callArgTypes
317      * @return True if the proxy was setup successfully.
318      */
319     public boolean setupMacro( String[] callArgs, int[] callArgTypes )
320     {
321         setupProxyArgs( callArgs, callArgTypes );
322         parseTree( callArgs );
323 
324         return true;
325     }
326 
327     /**
328      *   parses the macro.  We need to do this here, at init time, or else
329      *   the local-scope template feature is hard to get to work :)
330      *   @param callArgs
331      */
332     private void parseTree( String[] callArgs )
333     {
334         try
335         {
336             BufferedReader br = new BufferedReader( new StringReader( macroBody ) );
337 
338             /*
339              *  now parse the macro - and don't dump the namespace
340              */
341 
342             nodeTree = rsvc.parse( br, namespace, false );
343 
344             /*
345              *  now, to make null references render as proper schmoo
346              *  we need to tweak the tree and change the literal of
347              *  the appropriate references
348              *
349              *  we only do this at init time, so it's the overhead
350              *  is irrelevant
351              */
352 
353             HashMap hm = new HashMap();
354 
355             for( int i = 1; i < argArray.length; i++)
356             {
357                 String arg = callArgs[i-1];
358 
359                 /*
360                  *  if the calling arg is indeed a reference
361                  *  then we add to the map.  We ignore other
362                  *  stuff
363                  */
364 
365                 if (arg.charAt(0) == '$')
366                 {
367                     hm.put( argArray[i], arg );
368                 }
369             }
370 
371             /*
372              *  now make one of our reference-munging visitor, and
373              *  let 'er rip
374              */
375 
376             VMReferenceMungeVisitor v = new VMReferenceMungeVisitor( hm );
377             nodeTree.jjtAccept( v, null );
378         }
379         /**
380          * pass through application level runtime exceptions
381          */
382         catch( RuntimeException e )
383         {
384             throw e;
385         }
386         catch ( Exception e )
387         {
388             rsvc.getLog().error("VelocimacroManager.parseTree() : exception " +
389                                 macroName, e);
390         }
391     }
392 
393     private void setupProxyArgs( String[] callArgs, int [] callArgTypes )
394     {
395         /*
396          * for each of the args, make a ProxyArg
397          */
398 
399         for( int i = 1; i < argArray.length; i++)
400         {
401             VMProxyArg arg = new VMProxyArg( rsvc, argArray[i], callArgs[i-1], callArgTypes[i-1] );
402             proxyArgHash.put( argArray[i], arg );
403         }
404     }
405 
406     /**
407      *   gets the args to the VM from the instance-use AST
408      *   @param node
409      *   @return array of arguments
410      */
411     private String[] getArgArray( Node node )
412     {
413         int numArgs = node.jjtGetNumChildren();
414 
415         String args[] = new String[ numArgs ];
416         callingArgTypes = new int[numArgs];
417 
418         /*
419          *  eat the args
420          */
421         int i = 0;
422         Token t = null;
423         Token tLast = null;
424 
425         while( i <  numArgs )
426         {
427             args[i] = "";
428             /*
429              *  we want string literalss to lose the quotes.  #foo( "blargh" ) should have 'blargh' patched
430              *  into macro body.  So for each arg in the use-instance, treat the stringlierals specially...
431              */
432 
433             callingArgTypes[i] = node.jjtGetChild(i).getType();
434 
435 
436             if (false &&  node.jjtGetChild(i).getType() == ParserTreeConstants.JJTSTRINGLITERAL )
437             {
438                 args[i] += node.jjtGetChild(i).getFirstToken().image.substring(1, node.jjtGetChild(i).getFirstToken().image.length() - 1);
439             }
440             else
441             {
442                 /*
443                  *  just wander down the token list, concatenating everything together
444                  */
445                 t = node.jjtGetChild(i).getFirstToken();
446                 tLast = node.jjtGetChild(i).getLastToken();
447 
448                 while( t != tLast )
449                 {
450                     args[i] += t.image;
451                     t = t.next;
452                 }
453 
454                 /*
455                  *  don't forget the last one... :)
456                  */
457                 args[i] += t.image;
458             }
459             i++;
460          }
461         return args;
462     }
463 }
464 
465 
466 
467 
468 
469 
470 
471 
472 
473