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  import java.util.List;
25  
26  import org.apache.velocity.context.InternalContextAdapter;
27  import org.apache.velocity.exception.MacroOverflowException;
28  import org.apache.velocity.exception.VelocityException;
29  import org.apache.velocity.runtime.Renderable;
30  import org.apache.velocity.runtime.RuntimeConstants;
31  import org.apache.velocity.runtime.RuntimeServices;
32  import org.apache.velocity.runtime.directive.Macro.MacroArg;
33  import org.apache.velocity.runtime.log.Log;
34  import org.apache.velocity.runtime.parser.node.Node;
35  import org.apache.velocity.runtime.parser.node.SimpleNode;
36  
37  /**
38   *  VelocimacroProxy.java
39   *
40   *   a proxy Directive-derived object to fit with the current directive system
41   *
42   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
43   * @version $Id: VelocimacroProxy.java 754562 2009-03-14 23:59:44Z byron $
44   */
45  public class VelocimacroProxy extends Directive
46  {
47      private String macroName;
48      private List<Macro.MacroArg> macroArgs = null;
49      private String[] literalArgArray = null;
50      private SimpleNode nodeTree = null;
51      private int numMacroArgs = 0;
52      private boolean strictArguments;
53      private int maxCallDepth;
54      private String bodyReference;
55  
56      /**
57       * Return name of this Velocimacro.
58       * @return The name of this Velocimacro.
59       */
60      public String getName()
61      {
62          return  macroName;
63      }
64  
65      /**
66       * Velocimacros are always LINE type directives.
67       * @return The type of this directive.
68       */
69      public int getType()
70      {
71          return LINE;
72      }
73  
74      /**
75       * sets the directive name of this VM
76       * 
77       * @param name
78       */
79      public void setName(String name)
80      {
81          macroName = name;
82      }
83  
84      /**
85       * sets the array of arguments specified in the macro definition
86       * @param args  Array of macro arguments, containing the
87       *        #macro() arguments and default values.  the 0th is the name.
88       */
89      public void setMacroArgs(List<Macro.MacroArg> args)
90      {
91          macroArgs = args;
92          
93          // for performance reasons we precache these strings - they are needed in
94          // "render literal if null" functionality
95          literalArgArray = new String[macroArgs.size()];
96          for(int i = 0; i < macroArgs.size(); i++)
97          {
98              literalArgArray[i] = ".literal.$" + macroArgs.get(i);
99          }
100 
101         /*
102          * get the arg count from the arg array. remember that the arg array has the macro name as
103          * it's 0th element
104          */
105 
106         numMacroArgs = macroArgs.size() - 1;
107     }
108 
109     /**
110      * Return the list of macro arguments associated with this macro
111      */
112     public List<Macro.MacroArg> getMacroArgs()
113     {
114       return macroArgs;
115     }
116     
117     /**
118      * @param tree
119      */
120     public void setNodeTree(SimpleNode tree)
121     {
122         nodeTree = tree;
123     }
124 
125     /**
126      * returns the number of ars needed for this VM
127      * 
128      * @return The number of ars needed for this VM
129      */
130     public int getNumArgs()
131     {
132         return numMacroArgs;
133     }
134 
135     /**
136      * Initialize members of VelocimacroProxy.  called from MacroEntry
137      */
138     public void init(RuntimeServices rs)
139     {
140         rsvc = rs;
141       
142         // this is a very expensive call (ExtendedProperties is very slow)
143         strictArguments = rs.getConfiguration().getBoolean(
144             RuntimeConstants.VM_ARGUMENTS_STRICT, false);
145 
146         // get the macro call depth limit
147         maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
148 
149         // get name of the reference that refers to AST block passed to block macro call
150         bodyReference = rsvc.getString(RuntimeConstants.VM_BODY_REFERENCE, "bodyContent");
151     }
152 
153     public boolean render(InternalContextAdapter context, Writer writer, Node node)
154         throws IOException
155     {
156         return render(context, writer, node, null);
157     }
158     
159     /**
160      * Renders the macro using the context.
161      * 
162      * @param context Current rendering context
163      * @param writer Writer for output
164      * @param node AST that calls the macro
165      * @param body the macro body
166      * @return true if the directive rendered successfully.
167      * @throws IOException
168      */
169     public boolean render(InternalContextAdapter context, Writer writer,
170                           Node node, Renderable body)
171         throws IOException
172     {
173         int callArgNum = node.jjtGetNumChildren();
174         
175         // if this macro was invoked by a call directive, we might have a body AST here. 
176         Object oldBodyRef = null;
177         if (body != null)
178         {
179             oldBodyRef = context.get(bodyReference);
180             context.put(bodyReference, body);
181             callArgNum--;  // Remove the body AST from the arg count
182         }
183 
184         // is everything copacetic?
185         checkArgumentCount(node, callArgNum);
186         checkDepth(context);
187 
188         // put macro arg values and save the returned old/new value pairs
189         Object[][] values = handleArgValues(context, node, callArgNum);
190         try
191         {
192             // render the velocity macro
193             context.pushCurrentMacroName(macroName);
194             nodeTree.render(context, writer);
195             context.popCurrentMacroName();
196             return true;
197         }
198         catch (RuntimeException e)
199         {
200             throw e;
201         }
202         catch (Exception e)
203         {
204             String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()";
205             rsvc.getLog().error(msg, e);
206             throw new VelocityException(msg, e);
207         }
208         finally
209         {
210             // clean up after the args and bodyRef
211             // but only if they weren't overridden inside
212             Object current = context.get(bodyReference);
213             if (current == body)
214             {
215                 if (oldBodyRef != null)
216                 {
217                     context.put(bodyReference, oldBodyRef);
218                 }
219                 else
220                 {
221                     context.remove(bodyReference);
222                 }
223             }
224 
225             for (int i = 1; i < macroArgs.size(); i++)
226             {
227                 MacroArg macroArg = macroArgs.get(i);
228                 current = context.get(macroArg.name);
229                 if (current == values[i-1][1])
230                 {
231                     Object old = values[i-1][0];
232                     if (old != null)
233                     {
234                         context.put(macroArg.name, old);
235                     }
236                     else
237                     {
238                         context.remove(macroArg.name);
239                     }
240                 }
241             }
242         }
243     }
244 
245     /**
246      * Check whether the number of arguments given matches the number defined.
247      */
248     protected void checkArgumentCount(Node node, int callArgNum)
249     {
250         // Check if we have more calling arguments then the macro accepts
251         if (callArgNum > macroArgs.size() - 1)
252         {
253             if (strictArguments)
254             {
255                 throw new VelocityException("Provided " + callArgNum + " arguments but macro #" 
256                     + macroArgs.get(0).name + " accepts at most " + (macroArgs.size()-1)
257                     + " at " + Log.formatFileString(node));
258             }
259             else if (rsvc.getLog().isDebugEnabled())
260             {
261                 // Backward compatibility logging, Mainly for MacroForwardDefinedTestCase
262                 rsvc.getLog().debug("VM #" + macroArgs.get(0).name 
263                     + ": too many arguments to macro. Wanted " + (macroArgs.size()-1) 
264                     + " got " + callArgNum);
265             }
266         }
267     }
268 
269     /**
270      * check that we aren't already at the max call depth and throws
271      * a MacroOverflowException if we are there.
272      */
273     protected void checkDepth(InternalContextAdapter context)
274     {
275         if (maxCallDepth > 0 && maxCallDepth == context.getCurrentMacroCallDepth())
276         {
277             String templateName = context.getCurrentTemplateName();
278             Object[] stack = context.getMacroNameStack();
279 
280             StringBuffer out = new StringBuffer(100)
281                 .append("Max calling depth of ").append(maxCallDepth)
282                 .append(" was exceeded in macro '").append(macroName)
283                 .append("' with Call Stack:");
284             for (int i = 0; i < stack.length; i++)
285             {
286                 if (i != 0)
287                 {
288                     out.append("->");
289                 }
290                 out.append(stack[i]);
291             }
292             out.append(" at " + Log.formatFileString(this));
293             rsvc.getLog().error(out.toString());
294             
295             // clean out the macro stack, since we just broke it
296             while (context.getCurrentMacroCallDepth() > 0)
297             {
298                 context.popCurrentMacroName();
299             }
300             throw new MacroOverflowException(out.toString());
301         }
302     }
303 
304     /**
305      * Gets the macro argument values and puts them in the context under
306      * the argument names.  Store and return an array of old and new values
307      * paired for each argument name, for later cleanup.
308      */
309     protected Object[][] handleArgValues(InternalContextAdapter context,
310                                          Node node, int callArgNum)
311     {
312         Object[][] values = new Object[macroArgs.size()][2];
313           
314         // Move arguments into the macro's context. Start at one to skip macro name
315         for (int i = 1; i < macroArgs.size(); i++)
316         {
317             MacroArg macroArg = macroArgs.get(i);
318             values[i-1][0] = context.get(macroArg.name);
319 
320             // put the new value in
321             Object newVal = null;
322             if (i - 1 < callArgNum)
323             {
324                 // There's a calling value.
325                 newVal = node.jjtGetChild(i - 1).value(context);
326             }
327             else if (macroArg.defaultVal != null)
328             {
329                 // We don't have a calling value, but the macro defines a default value
330                 newVal = macroArg.defaultVal.value(context);
331             }
332             else if (strictArguments)
333             {
334                 // We come to this point if we don't have a calling value, and
335                 // there is no default value. Not enough arguments defined.
336                 int minArgNum = -1; //start at -1 to skip the macro name
337                 // Calculate minimum number of args required for macro
338                 for (MacroArg marg : macroArgs)
339                 {
340                     if (marg.defaultVal == null) minArgNum++;
341                 }
342                 throw new VelocityException("Need at least " + minArgNum + " argument for macro #"
343                     + macroArgs.get(0).name + " but only " + callArgNum + " where provided at "
344                     + Log.formatFileString(node));
345             }
346             else
347             {
348                 // Backward compatibility logging, Mainly for MacroForwardDefinedTestCase
349                 if (rsvc.getLog().isDebugEnabled())
350                 {
351                     rsvc.getLog().debug("VM #" + macroArgs.get(0).name 
352                      + ": too few arguments to macro. Wanted " + (macroArgs.size()-1) 
353                      + " got " + callArgNum);
354                 }
355                 break;
356             }
357 
358             context.put(macroArg.name, newVal);
359             values[i-1][1] = newVal;
360         }
361 
362         // return the array of replaced and new values
363         return values;
364     }
365 
366 }
367