1 | package org.apache.velocity.runtime.directive; |
2 | |
3 | /* |
4 | * Copyright 2000-2002,2004 The Apache Software Foundation. |
5 | * |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * you may not use this file except in compliance with the License. |
8 | * You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, software |
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * See the License for the specific language governing permissions and |
16 | * limitations under the License. |
17 | */ |
18 | |
19 | import java.io.Writer; |
20 | import java.io.IOException; |
21 | |
22 | import java.util.List; |
23 | import java.util.ArrayList; |
24 | |
25 | import org.apache.velocity.context.InternalContextAdapter; |
26 | |
27 | import org.apache.velocity.runtime.parser.node.Node; |
28 | import org.apache.velocity.runtime.parser.node.NodeUtils; |
29 | import org.apache.velocity.runtime.parser.Token; |
30 | import org.apache.velocity.runtime.parser.ParseException; |
31 | import org.apache.velocity.runtime.parser.ParserTreeConstants; |
32 | import org.apache.velocity.runtime.RuntimeServices; |
33 | |
34 | /** |
35 | * Macro.java |
36 | * |
37 | * Macro implements the macro definition directive of VTL. |
38 | * |
39 | * example : |
40 | * |
41 | * #macro( isnull $i ) |
42 | * #if( $i ) |
43 | * $i |
44 | * #end |
45 | * #end |
46 | * |
47 | * This object is used at parse time to mainly process and register the |
48 | * macro. It is used inline in the parser when processing a directive. |
49 | * |
50 | * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
51 | * @version $Id: Macro.java,v 1.16.4.1 2004/03/03 23:22:56 geirm Exp $ |
52 | */ |
53 | public class Macro extends Directive |
54 | { |
55 | private static boolean debugMode = false; |
56 | |
57 | /** |
58 | * Return name of this directive. |
59 | */ |
60 | public String getName() |
61 | { |
62 | return "macro"; |
63 | } |
64 | |
65 | /** |
66 | * Return 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 | */ |
77 | public boolean render(InternalContextAdapter context, |
78 | Writer writer, Node node) |
79 | throws IOException |
80 | { |
81 | /* |
82 | * do nothing : We never render. The VelocimacroProxy object does that |
83 | */ |
84 | |
85 | return true; |
86 | } |
87 | |
88 | public void init(RuntimeServices rs, InternalContextAdapter context, |
89 | Node node) |
90 | throws Exception |
91 | { |
92 | super.init(rs, context, node); |
93 | |
94 | /* |
95 | * again, don't do squat. We want the AST of the macro |
96 | * block to hang off of this but we don't want to |
97 | * init it... it's useless... |
98 | */ |
99 | |
100 | return; |
101 | } |
102 | |
103 | /** |
104 | * Used by Parser.java to process VMs withing the parsing process |
105 | * |
106 | * processAndRegister() doesn't actually render the macro to the output |
107 | * Processes the macro body into the internal representation used by the |
108 | * VelocimacroProxy objects, and if not currently used, adds it |
109 | * to the macro Factory |
110 | */ |
111 | public static void processAndRegister(RuntimeServices rs, Node node, |
112 | String sourceTemplate) |
113 | throws IOException, ParseException |
114 | { |
115 | /* |
116 | * There must be at least one arg to #macro, |
117 | * the name of the VM. Note that 0 following |
118 | * args is ok for naming blocks of HTML |
119 | */ |
120 | |
121 | int numArgs = node.jjtGetNumChildren(); |
122 | |
123 | /* |
124 | * this number is the # of args + 1. The + 1 |
125 | * is for the block tree |
126 | */ |
127 | |
128 | if (numArgs < 2) |
129 | { |
130 | |
131 | /* |
132 | * error - they didn't name the macro or |
133 | * define a block |
134 | */ |
135 | |
136 | rs.error("#macro error : Velocimacro must have name as 1st " + |
137 | "argument to #macro(). #args = " + numArgs); |
138 | |
139 | throw new MacroParseException("First argument to #macro() must be " + |
140 | " macro name."); |
141 | } |
142 | |
143 | /* |
144 | * lets make sure that the first arg is an ASTWord |
145 | */ |
146 | |
147 | int firstType = node.jjtGetChild(0).getType(); |
148 | |
149 | if(firstType != ParserTreeConstants.JJTWORD) |
150 | { |
151 | Token t = node.jjtGetChild(0).getFirstToken(); |
152 | |
153 | throw new MacroParseException("First argument to #macro() must be a" |
154 | + " token without surrounding \' or \", which specifies" |
155 | + " the macro name. Currently it is a " |
156 | + ParserTreeConstants.jjtNodeName[firstType]); |
157 | |
158 | } |
159 | |
160 | /* |
161 | * get the arguments to the use of the VM |
162 | */ |
163 | |
164 | String argArray[] = getArgArray(node); |
165 | |
166 | /* |
167 | * now, try and eat the code block. Pass the root. |
168 | */ |
169 | |
170 | List macroArray = |
171 | getASTAsStringArray(node.jjtGetChild(numArgs - 1)); |
172 | |
173 | /* |
174 | * make a big string out of our macro |
175 | */ |
176 | |
177 | StringBuffer temp = new StringBuffer(); |
178 | |
179 | for (int i=0; i < macroArray.size(); i++) |
180 | { |
181 | temp.append(macroArray.get(i)); |
182 | } |
183 | |
184 | String macroBody = temp.toString(); |
185 | |
186 | /* |
187 | * now, try to add it. The Factory controls permissions, |
188 | * so just give it a whack... |
189 | */ |
190 | |
191 | boolean bRet = rs.addVelocimacro(argArray[0], macroBody, |
192 | argArray, sourceTemplate); |
193 | |
194 | return; |
195 | } |
196 | |
197 | |
198 | /** |
199 | * creates an array containing the literal |
200 | * strings in the macro arguement |
201 | */ |
202 | private static String[] getArgArray(Node node) |
203 | { |
204 | /* |
205 | * remember : this includes the block tree |
206 | */ |
207 | |
208 | int numArgs = node.jjtGetNumChildren(); |
209 | |
210 | numArgs--; // avoid the block tree... |
211 | |
212 | String argArray[] = new String[numArgs]; |
213 | |
214 | int i = 0; |
215 | |
216 | /* |
217 | * eat the args |
218 | */ |
219 | |
220 | while (i < numArgs) |
221 | { |
222 | argArray[i] = node.jjtGetChild(i).getFirstToken().image; |
223 | |
224 | /* |
225 | * trim off the leading $ for the args after the macro name. |
226 | * saves everyone else from having to do it |
227 | */ |
228 | |
229 | if (i > 0) |
230 | { |
231 | if (argArray[i].startsWith("$")) |
232 | { |
233 | argArray[i] = argArray[i] |
234 | .substring(1, argArray[i].length()); |
235 | } |
236 | } |
237 | |
238 | i++; |
239 | } |
240 | |
241 | if (debugMode) |
242 | { |
243 | System.out.println("Macro.getArgArray() : #args = " + numArgs); |
244 | System.out.print(argArray[0] + "("); |
245 | |
246 | for (i = 1; i < numArgs; i++) |
247 | { |
248 | System.out.print(" " + argArray[i]); |
249 | } |
250 | |
251 | System.out.println(" )"); |
252 | } |
253 | |
254 | return argArray; |
255 | } |
256 | |
257 | /** |
258 | * Returns an array of the literal rep of the AST |
259 | */ |
260 | private static List getASTAsStringArray(Node rootNode) |
261 | { |
262 | /* |
263 | * this assumes that we are passed in the root |
264 | * node of the code block |
265 | */ |
266 | |
267 | Token t = rootNode.getFirstToken(); |
268 | Token tLast = rootNode.getLastToken(); |
269 | |
270 | /* |
271 | * now, run down the part of the tree bounded by |
272 | * our first and last tokens |
273 | */ |
274 | |
275 | ArrayList list = new ArrayList(); |
276 | |
277 | t = rootNode.getFirstToken(); |
278 | |
279 | while (t != tLast) |
280 | { |
281 | list.add(NodeUtils.tokenLiteral(t)); |
282 | t = t.next; |
283 | } |
284 | |
285 | /* |
286 | * make sure we get the last one... |
287 | */ |
288 | |
289 | list.add(NodeUtils.tokenLiteral(t)); |
290 | |
291 | return list; |
292 | } |
293 | } |