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 746438 2009-02-21 05:41:24Z 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       * Since this class does no processing of content,
75       * there is never a need for an internal scope.
76       */
77      public boolean isScopeProvided()
78      {
79          return false;
80      }
81  
82      /**
83       *   render() doesn't do anything in the final output rendering.
84       *   There is no output from a #macro() directive.
85       * @param context
86       * @param writer
87       * @param node
88       * @return True if the directive rendered successfully.
89       * @throws IOException
90       */
91      public boolean render(InternalContextAdapter context,
92                             Writer writer, Node node)
93          throws IOException
94      {
95          /*
96           *  do nothing : We never render.  The VelocimacroProxy object does that
97           */
98  
99          return true;
100     }
101 
102     /**
103      * @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)
104      */
105     public void init(RuntimeServices rs, InternalContextAdapter context,
106                      Node node)
107        throws TemplateInitException
108     {
109         super.init(rs, context, node);
110 
111         
112         // Add this macro to the VelocimacroManager now that it has been initialized.        
113         String argArray[] = getArgArray(node, rs);
114         int numArgs = node.jjtGetNumChildren();
115         rs.addVelocimacro(argArray[0], node.jjtGetChild(numArgs - 1), argArray, node.getTemplateName());
116     }
117     
118     /**
119      *  Used by Parser.java to do further parameter checking for macro arguments.
120      */
121     public static void checkArgs(RuntimeServices rs,  Token t, Node node,
122                                           String sourceTemplate)
123         throws IOException, ParseException
124     {
125         /*
126          *  There must be at least one arg to  #macro,
127          *  the name of the VM.  Note that 0 following
128          *  args is ok for naming blocks of HTML
129          */
130         int numArgs = node.jjtGetNumChildren();
131 
132         /*
133          *  this number is the # of args + 1.  The + 1
134          *  is for the block tree
135          */
136         if (numArgs < 2)
137         {
138 
139             /*
140              *  error - they didn't name the macro or
141              *  define a block
142              */
143             rs.getLog().error("#macro error : Velocimacro must have name as 1st " +
144                               "argument to #macro(). #args = " + numArgs);
145 
146             throw new MacroParseException("First argument to #macro() must be " +
147                     " macro name", sourceTemplate, t);
148         }
149 
150         /*
151          *  lets make sure that the first arg is an ASTWord
152          */
153         int firstType = node.jjtGetChild(0).getType();
154         if(firstType != ParserTreeConstants.JJTWORD)
155         {
156             throw new MacroParseException("First argument to #macro() must be a"
157                     + " token without surrounding \' or \", which specifies"
158                     + " the macro name.  Currently it is a "
159                     + ParserTreeConstants.jjtNodeName[firstType], sourceTemplate, t);
160         }                
161     }
162 
163     /**
164      * Creates an array containing the literal text from the macro
165      * arguement(s) (including the macro's name as the first arg).
166      *
167      * @param node The parse node from which to grok the argument
168      * list.  It's expected to include the block node tree (for the
169      * macro body).
170      * @param rsvc For debugging purposes only.
171      * @return array of arguments
172      */
173     private static String[] getArgArray(Node node, RuntimeServices rsvc)
174     {
175         /*
176          * Get the number of arguments for the macro, excluding the
177          * last child node which is the block tree containing the
178          * macro body.
179          */
180         int numArgs = node.jjtGetNumChildren();
181         numArgs--;  // avoid the block tree...
182 
183         String argArray[] = new String[numArgs];
184 
185         int i = 0;
186 
187         /*
188          *  eat the args
189          */
190 
191         while (i < numArgs)
192         {
193             argArray[i] = node.jjtGetChild(i).getFirstToken().image;
194 
195             /*
196              *  trim off the leading $ for the args after the macro name.
197              *  saves everyone else from having to do it
198              */
199 
200             if (i > 0)
201             {
202                 if (argArray[i].startsWith("$"))
203                 {
204                     argArray[i] = argArray[i]
205                         .substring(1, argArray[i].length());
206                 }
207             }
208 
209             argArray[i] = argArray[i].intern();
210             i++;
211         }
212 
213         if (debugMode)
214         {
215             StringBuffer msg = new StringBuffer("Macro.getArgArray() : nbrArgs=");
216             msg.append(numArgs).append(" : ");
217             macroToString(msg, argArray);
218             rsvc.getLog().debug(msg);
219         }
220 
221         return argArray;
222     }
223 
224     /**
225      * For debugging purposes.  Formats the arguments from
226      * <code>argArray</code> and appends them to <code>buf</code>.
227      *
228      * @param buf A StringBuffer. If null, a new StringBuffer is allocated.
229      * @param argArray The Macro arguments to format
230      *
231      * @return A StringBuffer containing the formatted arguments. If a StringBuffer
232      *         has passed in as buf, this method returns it.
233      * @since 1.5
234      */
235     public static final StringBuffer macroToString(final StringBuffer buf,
236                                                    final String[] argArray)
237     {
238         StringBuffer ret = (buf == null) ? new StringBuffer() : buf;
239 
240         ret.append('#').append(argArray[0]).append("( ");
241         for (int i = 1; i < argArray.length; i++)
242         {
243             ret.append(' ').append(argArray[i]);
244         }
245         ret.append(" )");
246         return ret;
247     }
248 }