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 }