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.Writer;
23  import java.io.IOException;
24  
25  import java.util.List;
26  import java.util.ArrayList;
27  
28  import org.apache.velocity.context.InternalContextAdapter;
29  import org.apache.velocity.exception.TemplateInitException;
30  
31  import org.apache.velocity.runtime.parser.node.Node;
32  import org.apache.velocity.runtime.parser.node.NodeUtils;
33  import org.apache.velocity.runtime.parser.Token;
34  import org.apache.velocity.runtime.parser.ParseException;
35  import org.apache.velocity.runtime.parser.ParserTreeConstants;
36  import org.apache.velocity.runtime.RuntimeServices;
37  
38  /**
39   *   Macro.java
40   *
41   *  Macro implements the macro definition directive of VTL.
42   *
43   *  example :
44   *
45   *  #macro( isnull $i )
46   *     #if( $i )
47   *         $i
48   *      #end
49   *  #end
50   *
51   *  This object is used at parse time to mainly process and register the
52   *  macro.  It is used inline in the parser when processing a directive.
53   *
54   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
55   * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
56   * @version $Id: Macro.java 471881 2006-11-06 21:21:10Z henning $
57   */
58  public class Macro extends Directive
59  {
60      private static  boolean debugMode = false;
61  
62      /**
63       * Return name of this directive.
64       * @return The name of this directive.
65       */
66      public String getName()
67      {
68          return "macro";
69      }
70  
71      /**
72       * Return type of this directive.
73       * @return The type of this directive.
74       */
75      public int getType()
76      {
77          return BLOCK;
78      }
79  
80      /**
81       *   render() doesn't do anything in the final output rendering.
82       *   There is no output from a #macro() directive.
83       * @param context
84       * @param writer
85       * @param node
86       * @return True if the directive rendered successfully.
87       * @throws IOException
88       */
89      public boolean render(InternalContextAdapter context,
90                             Writer writer, Node node)
91          throws IOException
92      {
93          /*
94           *  do nothing : We never render.  The VelocimacroProxy object does that
95           */
96  
97          return true;
98      }
99  
100     /**
101      * @see org.apache.velocity.runtime.directive.Directive#init(org.apache.velocity.runtime.RuntimeServices, org.apache.velocity.context.InternalContextAdapter, org.apache.velocity.runtime.parser.node.Node)
102      */
103     public void init(RuntimeServices rs, InternalContextAdapter context,
104                      Node node)
105        throws TemplateInitException
106     {
107         super.init(rs, context, node);
108 
109         /*
110          * again, don't do squat.  We want the AST of the macro
111          * block to hang off of this but we don't want to
112          * init it... it's useless...
113          */
114     }
115 
116     /**
117      *  Used by Parser.java to process VMs during the parsing process.
118      *
119      *  This method does not render the macro to the output stream,
120      *  but rather <i>processes the macro body</i> into the internal
121      *  representation used by {#link
122      *  org.apache.velocity.runtime.directive.VelocimacroProxy}
123      *  objects, and if not currently used, adds it to the macro
124      *  Factory.
125      * @param rs
126      * @param t
127      * @param node
128      * @param sourceTemplate
129      * @throws IOException
130      * @throws ParseException
131      */
132     public static void processAndRegister(RuntimeServices rs,  Token t, Node node,
133                                           String sourceTemplate)
134         throws IOException, ParseException
135     {
136         /*
137          *  There must be at least one arg to  #macro,
138          *  the name of the VM.  Note that 0 following
139          *  args is ok for naming blocks of HTML
140          */
141 
142         int numArgs = node.jjtGetNumChildren();
143 
144         /*
145          *  this number is the # of args + 1.  The + 1
146          *  is for the block tree
147          */
148 
149         if (numArgs < 2)
150         {
151 
152             /*
153              *  error - they didn't name the macro or
154              *  define a block
155              */
156 
157             rs.getLog().error("#macro error : Velocimacro must have name as 1st " +
158                               "argument to #macro(). #args = " + numArgs);
159 
160             throw new MacroParseException("First argument to #macro() must be " +
161                     " macro name.", sourceTemplate, t);
162         }
163 
164         /*
165          *  lets make sure that the first arg is an ASTWord
166          */
167 
168         int firstType = node.jjtGetChild(0).getType();
169 
170         if(firstType != ParserTreeConstants.JJTWORD)
171         {
172             throw new MacroParseException("First argument to #macro() must be a"
173                     + " token without surrounding \' or \", which specifies"
174                     + " the macro name.  Currently it is a "
175                     + ParserTreeConstants.jjtNodeName[firstType], sourceTemplate, t);
176         }
177 
178         /*
179          *  get the arguments to the use of the VM
180          */
181 
182         String argArray[] = getArgArray(node, rs);
183 
184         /*
185          *   now, try and eat the code block. Pass the root.
186          */
187 
188         List macroArray =
189             getASTAsStringArray(node.jjtGetChild(numArgs - 1));
190 
191         /*
192          *  make a big string out of our macro
193          */
194 
195         StringBuffer macroBody = new StringBuffer();
196 
197         for (int i=0; i < macroArray.size(); i++)
198         {
199             macroBody.append(macroArray.get(i));
200         }
201 
202         /*
203          * now, try to add it.  The Factory controls permissions,
204          * so just give it a whack...
205          */
206 
207         boolean macroAdded = rs.addVelocimacro(argArray[0],
208                                                macroBody.toString(),
209                                                argArray, sourceTemplate);
210 
211         if (!macroAdded && rs.getLog().isWarnEnabled())
212         {
213             StringBuffer msg = new StringBuffer("Failed to add macro: ");
214             macroToString(msg, argArray);
215             msg.append(" : source = ").append(sourceTemplate);
216             rs.getLog().warn(msg);
217         }
218     }
219 
220 
221     /**
222      * Creates an array containing the literal text from the macro
223      * arguement(s) (including the macro's name as the first arg).
224      *
225      * @param node The parse node from which to grok the argument
226      * list.  It's expected to include the block node tree (for the
227      * macro body).
228      * @param rsvc For debugging purposes only.
229      * @return array of arguments
230      */
231     private static String[] getArgArray(Node node, RuntimeServices rsvc)
232     {
233         /*
234          * Get the number of arguments for the macro, excluding the
235          * last child node which is the block tree containing the
236          * macro body.
237          */
238         int numArgs = node.jjtGetNumChildren();
239         numArgs--;  // avoid the block tree...
240 
241         String argArray[] = new String[numArgs];
242 
243         int i = 0;
244 
245         /*
246          *  eat the args
247          */
248 
249         while (i < numArgs)
250         {
251             argArray[i] = node.jjtGetChild(i).getFirstToken().image;
252 
253             /*
254              *  trim off the leading $ for the args after the macro name.
255              *  saves everyone else from having to do it
256              */
257 
258             if (i > 0)
259             {
260                 if (argArray[i].startsWith("$"))
261                 {
262                     argArray[i] = argArray[i]
263                         .substring(1, argArray[i].length());
264                 }
265             }
266 
267             i++;
268         }
269 
270         if (debugMode)
271         {
272             StringBuffer msg = new StringBuffer("Macro.getArgArray() : nbrArgs=");
273             msg.append(numArgs).append(" : ");
274             macroToString(msg, argArray);
275             rsvc.getLog().debug(msg);
276         }
277 
278         return argArray;
279     }
280 
281     /**
282      *  Returns an array of the literal rep of the AST
283      *  @param rootNode
284      *  @return list of Strings
285      */
286     private static List getASTAsStringArray(Node rootNode)
287     {
288         /*
289          *  this assumes that we are passed in the root
290          *  node of the code block
291          */
292 
293         Token t = rootNode.getFirstToken();
294         Token tLast = rootNode.getLastToken();
295 
296         /*
297          *  now, run down the part of the tree bounded by
298          *  our first and last tokens
299          */
300 
301         List list = new ArrayList();
302 
303         while (t != tLast)
304         {
305             list.add(NodeUtils.tokenLiteral(t));
306             t = t.next;
307         }
308 
309         /*
310          *  make sure we get the last one...
311          */
312 
313         list.add(NodeUtils.tokenLiteral(t));
314 
315         return list;
316     }
317 
318     /**
319      * For debugging purposes.  Formats the arguments from
320      * <code>argArray</code> and appends them to <code>buf</code>.
321      *
322      * @param buf A StringBuffer. If null, a new StringBuffer is allocated.
323      * @param argArray The Macro arguments to format
324      *
325      * @return A StringBuffer containing the formatted arguments. If a StringBuffer
326      *         has passed in as buf, this method returns it.
327      */
328     public static final StringBuffer macroToString(final StringBuffer buf,
329                                                    final String[] argArray)
330     {
331         StringBuffer ret = (buf == null) ? new StringBuffer() : buf;
332 
333         ret.append('#').append(argArray[0]).append("( ");
334         for (int i = 1; i < argArray.length; i++)
335         {
336             ret.append(' ').append(argArray[i]);
337         }
338         ret.append(" )");
339         return ret;
340     }
341 }