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 }