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 }