• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.apache.velocity.context.InternalContextAdapter;
23 import org.apache.velocity.exception.TemplateInitException;
24 import org.apache.velocity.runtime.RuntimeServices;
25 import org.apache.velocity.runtime.parser.ParseException;
26 import org.apache.velocity.runtime.parser.Token;
27 import org.apache.velocity.runtime.parser.node.Node;
28 import org.apache.velocity.runtime.parser.node.ParserTreeConstants;
29 
30 import java.io.IOException;
31 import java.io.Writer;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  *  Macro implements the macro definition directive of VTL.
37  *
38  *  example:
39  *
40  *  #macro( isnull $i )
41  *     #if( $i )
42  *         $i
43  *      #end
44  *  #end
45  *
46  *  This object is used at parse time to mainly process and register the
47  *  macro.  It is used inline in the parser when processing a directive.
48  *
49  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
50  * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
51  * @version $Id$
52  */
53 public class Macro extends Directive
54 {
55     private static  boolean debugMode = false;
56 
57     /**
58      * Return name of this directive.
59      * @return The name of this directive.
60      */
61     @Override
getName()62     public String getName()
63     {
64         return "macro";
65     }
66 
67     /**
68      * Return type of this directive.
69      * @return The type of this directive.
70      */
71     @Override
getType()72     public int getType()
73     {
74         return BLOCK;
75     }
76 
77     /**
78      * Since this class does no processing of content,
79      * there is never a need for an internal scope.
80      */
81     @Override
isScopeProvided()82     public boolean isScopeProvided()
83     {
84         return false;
85     }
86 
87     /**
88      *   render() doesn't do anything in the final output rendering.
89      *   There is no output from a #macro() directive.
90      * @param context
91      * @param writer
92      * @param node
93      * @return True if the directive rendered successfully.
94      * @throws IOException
95      */
96     @Override
render(InternalContextAdapter context, Writer writer, Node node)97     public boolean render(InternalContextAdapter context,
98                           Writer writer, Node node)
99         throws IOException
100     {
101         /*
102          *  do nothing: We never render.  The VelocimacroProxy object does that
103          */
104 
105         return true;
106     }
107 
108     /**
109      * @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)
110      */
111     @Override
init(RuntimeServices rs, InternalContextAdapter context, Node node)112     public void init(RuntimeServices rs, InternalContextAdapter context,
113                      Node node)
114        throws TemplateInitException
115     {
116         super.init(rs, context, node);
117 
118 
119         // Add this macro to the VelocimacroManager now that it has been initialized.
120         List<MacroArg> macroArgs = getArgArray(node, rsvc);
121         int numArgs = node.jjtGetNumChildren();
122         rsvc.addVelocimacro(macroArgs.get(0).name, node.jjtGetChild(numArgs - 1),
123             macroArgs, node.getTemplate());
124     }
125 
126     /**
127      * Check the argument types of a macro call, called by the parser to do validation
128      */
129     @Override
checkArgs(ArrayList<Integer> argtypes, Token t, String templateName)130     public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName)
131         throws ParseException
132     {
133         if (argtypes.size() < 1)
134         {
135             throw new MacroParseException("A macro definition requires at least a macro name"
136                 , templateName, t);
137         }
138 
139         /*
140          *  lets make sure that the first arg is an ASTWord
141          */
142         if(argtypes.get(0) != ParserTreeConstants.JJTWORD)
143         {
144             throw new MacroParseException("Macro argument 1"
145                     + " must be a token without surrounding \' or \""
146                     , templateName, t);
147         }
148 
149 
150         // We use this to flag if the default arguments are out of order. such as
151         // #macro($a $b=1 $c).  We enforce that all default parameters must be
152         // specified consecutively, and at the end of the argument list.
153         boolean consecutive = false;
154 
155         // All arguments other then the first must be either a reference
156         // or a directiveassign followed by a reference in the case a default
157         // value is specified.
158         for (int argPos = 1; argPos < argtypes.size(); argPos++)
159         {
160             if (argtypes.get(argPos) == ParserTreeConstants.JJTDIRECTIVEASSIGN)
161             {
162                // Absorb next argument type since parser enforces that these are in
163                // pairs, and we don't need to check the type of the second
164                // arg because it is done by the parser.
165                argPos++;
166                consecutive = true;
167             }
168             else if (argtypes.get(argPos) != ParserTreeConstants.JJTREFERENCE)
169             {
170                 throw new MacroParseException("Macro argument " + (argPos + 1)
171                   + " must be a reference", templateName, t);
172             }
173             else if (consecutive)
174             {
175                 // We have already found a default parameter e.g.; $x = 2, but
176                 // the next parameter was not a reference.
177                 throw new MacroParseException("Macro non-default argument follows a default argument at "
178                   , templateName, t);
179             }
180         }
181     }
182 
183     /**
184      * Creates an array containing the literal text from the macro
185      * argument(s) (including the macro's name as the first arg).
186      *
187      * @param node The parse node from which to grok the argument
188      * list.  It's expected to include the block node tree (for the
189      * macro body).
190      * @param rsvc For debugging purposes only.
191      * @return array of arguments
192      */
getArgArray(Node node, RuntimeServices rsvc)193     private static List<MacroArg> getArgArray(Node node, RuntimeServices rsvc)
194     {
195         /*
196          * Get the number of arguments for the macro, excluding the
197          * last child node which is the block tree containing the
198          * macro body.
199          */
200         int numArgs = node.jjtGetNumChildren();
201         numArgs--;  // avoid the block tree...
202 
203         ArrayList<MacroArg> macroArgs = new ArrayList<>();
204 
205         for (int i = 0; i < numArgs; i++)
206         {
207             Node curnode = node.jjtGetChild(i);
208             MacroArg macroArg = new MacroArg();
209             if (curnode.getType() == ParserTreeConstants.JJTDIRECTIVEASSIGN)
210             {
211                 // This is an argument with a default value
212             	macroArg.name = curnode.getFirstTokenImage();
213 
214                 // Inforced by the parser there will be an argument here.
215                 i++;
216                 curnode = node.jjtGetChild(i);
217                 macroArg.defaultVal = curnode;
218             }
219             else
220             {
221                 // An argument without a default value
222                	macroArg.name = curnode.getFirstTokenImage();
223             }
224 
225             // trim off the leading $ for the args after the macro name.
226             // saves everyone else from having to do it
227             if (i > 0 && macroArg.name.startsWith(String.valueOf(rsvc.getParserConfiguration().getDollarChar())))
228             {
229                 macroArg.name = macroArg.name.substring(1);
230             }
231 
232             macroArgs.add(macroArg);
233         }
234 
235         if (debugMode)
236         {
237             StringBuilder msg = new StringBuilder("Macro.getArgArray(): nbrArgs=");
238             msg.append(numArgs).append(": ");
239             macroToString(msg, macroArgs, rsvc);
240             rsvc.getLog("macro").debug(msg.toString());
241         }
242 
243         return macroArgs;
244     }
245 
246     /**
247      * MacroArgs holds the information for a single argument in a
248      * macro definition.  The arguments for a macro are passed around as a
249      * list of these objects.
250      */
251     public static class MacroArg
252     {
253        /**
254         * Name of the argument with '$' stripped off
255         */
256         public String name = null;
257 
258         /**
259          * If the argument was given a default value, then this contains
260          * the base of the AST tree of the value. Otherwise it is null.
261          */
262         public Node defaultVal = null;
263     }
264 
265     /**
266      * For debugging purposes.  Formats the arguments from
267      * <code>argArray</code> and appends them to <code>buf</code>.
268      *
269      * @param buf A StringBuilder. If null, a new StringBuilder is allocated.
270      * @param macroArgs  Array of macro arguments, containing the
271      *        #macro() arguments and default values.  the 0th is the name.
272      * @return A StringBuilder containing the formatted arguments. If a StringBuilder
273      *         has passed in as buf, this method returns it.
274      * @since 1.5
275      */
macroToString(final StringBuilder buf, List<MacroArg> macroArgs, RuntimeServices rsvc)276     public static StringBuilder macroToString(final StringBuilder buf, List<MacroArg> macroArgs, RuntimeServices rsvc)
277     {
278         StringBuilder ret = (buf == null) ? new StringBuilder() : buf;
279 
280         ret.append(rsvc.getParserConfiguration().getHashChar()).append(macroArgs.get(0).name).append("( ");
281         for (MacroArg marg : macroArgs)
282         {
283             ret.append(rsvc.getParserConfiguration().getDollarChar()).append(marg.name);
284             if (marg.defaultVal != null)
285             {
286               ret.append("=").append(marg.defaultVal);
287             }
288             ret.append(' ');
289         }
290         ret.append(" )");
291         return ret;
292     }
293 
294 }
295