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.IOException;
23  import java.io.Writer;
24  
25  import org.apache.velocity.context.InternalContextAdapter;
26  import org.apache.velocity.exception.TemplateInitException;
27  import org.apache.velocity.runtime.RuntimeServices;
28  import org.apache.velocity.runtime.parser.ParseException;
29  import org.apache.velocity.runtime.parser.ParserTreeConstants;
30  import org.apache.velocity.runtime.parser.Token;
31  import org.apache.velocity.runtime.parser.node.Node;
32  
33  /**
34   *  Macro implements the macro definition directive of VTL.
35   *
36   *  example :
37   *
38   *  #macro( isnull $i )
39   *     #if( $i )
40   *         $i
41   *      #end
42   *  #end
43   *
44   *  This object is used at parse time to mainly process and register the
45   *  macro.  It is used inline in the parser when processing a directive.
46   *
47   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
48   * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
49   * @version $Id: Macro.java 685685 2008-08-13 21:43:27Z nbubna $
50   */
51  public class Macro extends Directive
52  {
53      private static  boolean debugMode = false;
54  
55      /**
56       * Return name of this directive.
57       * @return The name of this directive.
58       */
59      public String getName()
60      {
61          return "macro";
62      }
63  
64      /**
65       * Return type of this directive.
66       * @return The type of this directive.
67       */
68      public int getType()
69      {
70          return BLOCK;
71      }
72  
73      /**
74       *   render() doesn't do anything in the final output rendering.
75       *   There is no output from a #macro() directive.
76       * @param context
77       * @param writer
78       * @param node
79       * @return True if the directive rendered successfully.
80       * @throws IOException
81       */
82      public boolean render(InternalContextAdapter context,
83                             Writer writer, Node node)
84          throws IOException
85      {
86          /*
87           *  do nothing : We never render.  The VelocimacroProxy object does that
88           */
89  
90          return true;
91      }
92  
93      /**
94       * @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)
95       */
96      public void init(RuntimeServices rs, InternalContextAdapter context,
97                       Node node)
98         throws TemplateInitException
99      {
100         super.init(rs, context, node);
101 
102         /*
103          * again, don't do squat.  We want the AST of the macro
104          * block to hang off of this but we don't want to
105          * init it... it's useless...
106          */
107     }
108 
109     /**
110      *  Used by Parser.java to process VMs during the parsing process.
111      *
112      *  This method does not render the macro to the output stream,
113      *  but rather <i>processes the macro body</i> into the internal
114      *  representation used by {#link
115      *  org.apache.velocity.runtime.directive.VelocimacroProxy}
116      *  objects, and if not currently used, adds it to the macro
117      *  Factory.
118      * @param rs
119      * @param t
120      * @param node
121      * @param sourceTemplate
122      * @throws IOException
123      * @throws ParseException
124      */
125     public static void processAndRegister(RuntimeServices rs,  Token t, Node node,
126                                           String sourceTemplate)
127         throws IOException, ParseException
128     {
129         /*
130          *  There must be at least one arg to  #macro,
131          *  the name of the VM.  Note that 0 following
132          *  args is ok for naming blocks of HTML
133          */
134 
135         int numArgs = node.jjtGetNumChildren();
136 
137         /*
138          *  this number is the # of args + 1.  The + 1
139          *  is for the block tree
140          */
141 
142         if (numArgs < 2)
143         {
144 
145             /*
146              *  error - they didn't name the macro or
147              *  define a block
148              */
149 
150             rs.getLog().error("#macro error : Velocimacro must have name as 1st " +
151                               "argument to #macro(). #args = " + numArgs);
152 
153             throw new MacroParseException("First argument to #macro() must be " +
154                     " macro name.", sourceTemplate, t);
155         }
156 
157         /*
158          *  lets make sure that the first arg is an ASTWord
159          */
160 
161         int firstType = node.jjtGetChild(0).getType();
162 
163         if(firstType != ParserTreeConstants.JJTWORD)
164         {
165             throw new MacroParseException("First argument to #macro() must be a"
166                     + " token without surrounding \' or \", which specifies"
167                     + " the macro name.  Currently it is a "
168                     + ParserTreeConstants.jjtNodeName[firstType], sourceTemplate, t);
169         }
170 
171         // get the arguments to the use of the VM - element 0 contains the macro name
172         String argArray[] = getArgArray(node, rs);
173 
174         /* 
175          * we already have the macro parsed as AST so there is no point to
176          * transform it into a String again
177          */ 
178         rs.addVelocimacro(argArray[0], node.jjtGetChild(numArgs - 1), argArray, sourceTemplate);
179         
180         /*
181          * Even if the add attempt failed, we don't log anything here.
182          * Logging must be done at VelocimacroFactory or VelocimacroManager because
183          * those classes know the real reason.
184          */ 
185     }
186 
187 
188     /**
189      * Creates an array containing the literal text from the macro
190      * arguement(s) (including the macro's name as the first arg).
191      *
192      * @param node The parse node from which to grok the argument
193      * list.  It's expected to include the block node tree (for the
194      * macro body).
195      * @param rsvc For debugging purposes only.
196      * @return array of arguments
197      */
198     private static String[] getArgArray(Node node, RuntimeServices rsvc)
199     {
200         /*
201          * Get the number of arguments for the macro, excluding the
202          * last child node which is the block tree containing the
203          * macro body.
204          */
205         int numArgs = node.jjtGetNumChildren();
206         numArgs--;  // avoid the block tree...
207 
208         String argArray[] = new String[numArgs];
209 
210         int i = 0;
211 
212         /*
213          *  eat the args
214          */
215 
216         while (i < numArgs)
217         {
218             argArray[i] = node.jjtGetChild(i).getFirstToken().image;
219 
220             /*
221              *  trim off the leading $ for the args after the macro name.
222              *  saves everyone else from having to do it
223              */
224 
225             if (i > 0)
226             {
227                 if (argArray[i].startsWith("$"))
228                 {
229                     argArray[i] = argArray[i]
230                         .substring(1, argArray[i].length());
231                 }
232             }
233 
234             i++;
235         }
236 
237         if (debugMode)
238         {
239             StringBuffer msg = new StringBuffer("Macro.getArgArray() : nbrArgs=");
240             msg.append(numArgs).append(" : ");
241             macroToString(msg, argArray);
242             rsvc.getLog().debug(msg);
243         }
244 
245         return argArray;
246     }
247 
248     /**
249      * For debugging purposes.  Formats the arguments from
250      * <code>argArray</code> and appends them to <code>buf</code>.
251      *
252      * @param buf A StringBuffer. If null, a new StringBuffer is allocated.
253      * @param argArray The Macro arguments to format
254      *
255      * @return A StringBuffer containing the formatted arguments. If a StringBuffer
256      *         has passed in as buf, this method returns it.
257      * @since 1.5
258      */
259     public static final StringBuffer macroToString(final StringBuffer buf,
260                                                    final String[] argArray)
261     {
262         StringBuffer ret = (buf == null) ? new StringBuffer() : buf;
263 
264         ret.append('#').append(argArray[0]).append("( ");
265         for (int i = 1; i < argArray.length; i++)
266         {
267             ret.append(' ').append(argArray[i]);
268         }
269         ret.append(" )");
270         return ret;
271     }
272 }