1 | package org.apache.velocity.runtime.directive; |
2 | |
3 | /* |
4 | * Copyright 2000-2001,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 | import java.io.BufferedReader; |
22 | import java.io.StringReader; |
23 | |
24 | import java.util.HashMap; |
25 | |
26 | import org.apache.velocity.context.InternalContextAdapter; |
27 | import org.apache.velocity.context.VMContext; |
28 | |
29 | import org.apache.velocity.runtime.visitor.VMReferenceMungeVisitor; |
30 | import org.apache.velocity.runtime.RuntimeServices; |
31 | import org.apache.velocity.runtime.parser.node.Node; |
32 | import org.apache.velocity.runtime.parser.Token; |
33 | import org.apache.velocity.runtime.parser.ParserTreeConstants; |
34 | import org.apache.velocity.runtime.parser.node.SimpleNode; |
35 | import org.apache.velocity.util.StringUtils; |
36 | |
37 | import org.apache.velocity.exception.MethodInvocationException; |
38 | |
39 | /** |
40 | * VelocimacroProxy.java |
41 | * |
42 | * a proxy Directive-derived object to fit with the current directive system |
43 | * |
44 | * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
45 | * @version $Id: VelocimacroProxy.java,v 1.27.4.1 2004/03/03 23:22:56 geirm Exp $ |
46 | */ |
47 | public class VelocimacroProxy extends Directive |
48 | { |
49 | private String macroName = ""; |
50 | private String macroBody = ""; |
51 | private String[] argArray = null; |
52 | private SimpleNode nodeTree = null; |
53 | private int numMacroArgs = 0; |
54 | private String namespace = ""; |
55 | |
56 | private boolean init = false; |
57 | private String[] callingArgs; |
58 | private int[] callingArgTypes; |
59 | private HashMap proxyArgHash = new HashMap(); |
60 | |
61 | |
62 | /** |
63 | * Return name of this Velocimacro. |
64 | */ |
65 | public String getName() |
66 | { |
67 | return macroName; |
68 | } |
69 | |
70 | /** |
71 | * Velocimacros are always LINE |
72 | * type directives. |
73 | */ |
74 | public int getType() |
75 | { |
76 | return LINE; |
77 | } |
78 | |
79 | /** |
80 | * sets the directive name of this VM |
81 | */ |
82 | public void setName( String name ) |
83 | { |
84 | macroName = name; |
85 | } |
86 | |
87 | /** |
88 | * sets the array of arguments specified in the macro definition |
89 | */ |
90 | public void setArgArray( String [] arr ) |
91 | { |
92 | argArray = arr; |
93 | |
94 | /* |
95 | * get the arg count from the arg array. remember that the arg array |
96 | * has the macro name as it's 0th element |
97 | */ |
98 | |
99 | numMacroArgs = argArray.length - 1; |
100 | } |
101 | |
102 | public void setNodeTree( SimpleNode tree ) |
103 | { |
104 | nodeTree = tree; |
105 | } |
106 | |
107 | /** |
108 | * returns the number of ars needed for this VM |
109 | */ |
110 | public int getNumArgs() |
111 | { |
112 | return numMacroArgs; |
113 | } |
114 | |
115 | /** |
116 | * Sets the orignal macro body. This is simply the cat of the macroArray, but the |
117 | * Macro object creates this once during parsing, and everyone shares it. |
118 | * Note : it must not be modified. |
119 | */ |
120 | public void setMacrobody( String mb ) |
121 | { |
122 | macroBody = mb; |
123 | } |
124 | |
125 | public void setNamespace( String ns ) |
126 | { |
127 | this.namespace = ns; |
128 | } |
129 | |
130 | /** |
131 | * Renders the macro using the context |
132 | */ |
133 | public boolean render( InternalContextAdapter context, Writer writer, Node node) |
134 | throws IOException, MethodInvocationException |
135 | { |
136 | try |
137 | { |
138 | /* |
139 | * it's possible the tree hasn't been parsed yet, so get |
140 | * the VMManager to parse and init it |
141 | */ |
142 | |
143 | if (nodeTree != null) |
144 | { |
145 | if ( !init ) |
146 | { |
147 | nodeTree.init( context, rsvc); |
148 | init = true; |
149 | } |
150 | |
151 | /* |
152 | * wrap the current context and add the VMProxyArg objects |
153 | */ |
154 | |
155 | VMContext vmc = new VMContext( context, rsvc ); |
156 | |
157 | for( int i = 1; i < argArray.length; i++) |
158 | { |
159 | /* |
160 | * we can do this as VMProxyArgs don't change state. They change |
161 | * the context. |
162 | */ |
163 | |
164 | VMProxyArg arg = (VMProxyArg) proxyArgHash.get( argArray[i] ); |
165 | vmc.addVMProxyArg( arg ); |
166 | } |
167 | |
168 | /* |
169 | * now render the VM |
170 | */ |
171 | |
172 | nodeTree.render( vmc, writer ); |
173 | } |
174 | else |
175 | { |
176 | rsvc.error( "VM error : " + macroName + ". Null AST"); |
177 | } |
178 | } |
179 | catch ( Exception e ) |
180 | { |
181 | /* |
182 | * if it's a MIE, it came from the render.... throw it... |
183 | */ |
184 | |
185 | if ( e instanceof MethodInvocationException) |
186 | { |
187 | throw (MethodInvocationException) e; |
188 | } |
189 | |
190 | rsvc.error("VelocimacroProxy.render() : exception VM = #" + macroName + |
191 | "() : " + StringUtils.stackTrace(e)); |
192 | } |
193 | |
194 | return true; |
195 | } |
196 | |
197 | /** |
198 | * The major meat of VelocimacroProxy, init() checks the # of arguments, patches the |
199 | * macro body, renders the macro into an AST, and then inits the AST, so it is ready |
200 | * for quick rendering. Note that this is only AST dependant stuff. Not context. |
201 | */ |
202 | public void init( RuntimeServices rs, InternalContextAdapter context, Node node) |
203 | throws Exception |
204 | { |
205 | super.init( rs, context, node ); |
206 | |
207 | /* |
208 | * how many args did we get? |
209 | */ |
210 | |
211 | int i = node.jjtGetNumChildren(); |
212 | |
213 | /* |
214 | * right number of args? |
215 | */ |
216 | |
217 | if ( getNumArgs() != i ) |
218 | { |
219 | rsvc.error("VM #" + macroName + ": error : too " |
220 | + ( (getNumArgs() > i) ? "few" : "many") + " arguments to macro. Wanted " |
221 | + getNumArgs() + " got " + i ); |
222 | |
223 | return; |
224 | } |
225 | |
226 | /* |
227 | * get the argument list to the instance use of the VM |
228 | */ |
229 | |
230 | callingArgs = getArgArray( node ); |
231 | |
232 | /* |
233 | * now proxy each arg in the context |
234 | */ |
235 | |
236 | setupMacro( callingArgs, callingArgTypes ); |
237 | return; |
238 | } |
239 | |
240 | /** |
241 | * basic VM setup. Sets up the proxy args for this |
242 | * use, and parses the tree |
243 | */ |
244 | public boolean setupMacro( String[] callArgs, int[] callArgTypes ) |
245 | { |
246 | setupProxyArgs( callArgs, callArgTypes ); |
247 | parseTree( callArgs ); |
248 | |
249 | return true; |
250 | } |
251 | |
252 | /** |
253 | * parses the macro. We need to do this here, at init time, or else |
254 | * the local-scope template feature is hard to get to work :) |
255 | */ |
256 | private void parseTree( String[] callArgs ) |
257 | { |
258 | try |
259 | { |
260 | BufferedReader br = new BufferedReader( new StringReader( macroBody ) ); |
261 | |
262 | /* |
263 | * now parse the macro - and don't dump the namespace |
264 | */ |
265 | |
266 | nodeTree = rsvc.parse( br, namespace, false ); |
267 | |
268 | /* |
269 | * now, to make null references render as proper schmoo |
270 | * we need to tweak the tree and change the literal of |
271 | * the appropriate references |
272 | * |
273 | * we only do this at init time, so it's the overhead |
274 | * is irrelevant |
275 | */ |
276 | |
277 | HashMap hm = new HashMap(); |
278 | |
279 | for( int i = 1; i < argArray.length; i++) |
280 | { |
281 | String arg = callArgs[i-1]; |
282 | |
283 | /* |
284 | * if the calling arg is indeed a reference |
285 | * then we add to the map. We ignore other |
286 | * stuff |
287 | */ |
288 | |
289 | if (arg.charAt(0) == '$') |
290 | { |
291 | hm.put( argArray[i], arg ); |
292 | } |
293 | } |
294 | |
295 | /* |
296 | * now make one of our reference-munging visitor, and |
297 | * let 'er rip |
298 | */ |
299 | |
300 | VMReferenceMungeVisitor v = new VMReferenceMungeVisitor( hm ); |
301 | nodeTree.jjtAccept( v, null ); |
302 | } |
303 | catch ( Exception e ) |
304 | { |
305 | rsvc.error("VelocimacroManager.parseTree() : exception " + macroName + |
306 | " : " + StringUtils.stackTrace(e)); |
307 | } |
308 | } |
309 | |
310 | private void setupProxyArgs( String[] callArgs, int [] callArgTypes ) |
311 | { |
312 | /* |
313 | * for each of the args, make a ProxyArg |
314 | */ |
315 | |
316 | for( int i = 1; i < argArray.length; i++) |
317 | { |
318 | VMProxyArg arg = new VMProxyArg( rsvc, argArray[i], callArgs[i-1], callArgTypes[i-1] ); |
319 | proxyArgHash.put( argArray[i], arg ); |
320 | } |
321 | } |
322 | |
323 | /** |
324 | * gets the args to the VM from the instance-use AST |
325 | */ |
326 | private String[] getArgArray( Node node ) |
327 | { |
328 | int numArgs = node.jjtGetNumChildren(); |
329 | |
330 | String args[] = new String[ numArgs ]; |
331 | callingArgTypes = new int[numArgs]; |
332 | |
333 | /* |
334 | * eat the args |
335 | */ |
336 | int i = 0; |
337 | Token t = null; |
338 | Token tLast = null; |
339 | |
340 | while( i < numArgs ) |
341 | { |
342 | args[i] = ""; |
343 | /* |
344 | * we want string literalss to lose the quotes. #foo( "blargh" ) should have 'blargh' patched |
345 | * into macro body. So for each arg in the use-instance, treat the stringlierals specially... |
346 | */ |
347 | |
348 | callingArgTypes[i] = node.jjtGetChild(i).getType(); |
349 | |
350 | |
351 | if (false && node.jjtGetChild(i).getType() == ParserTreeConstants.JJTSTRINGLITERAL ) |
352 | { |
353 | args[i] += node.jjtGetChild(i).getFirstToken().image.substring(1, node.jjtGetChild(i).getFirstToken().image.length() - 1); |
354 | } |
355 | else |
356 | { |
357 | /* |
358 | * just wander down the token list, concatenating everything together |
359 | */ |
360 | t = node.jjtGetChild(i).getFirstToken(); |
361 | tLast = node.jjtGetChild(i).getLastToken(); |
362 | |
363 | while( t != tLast ) |
364 | { |
365 | args[i] += t.image; |
366 | t = t.next; |
367 | } |
368 | |
369 | /* |
370 | * don't forget the last one... :) |
371 | */ |
372 | args[i] += t.image; |
373 | } |
374 | i++; |
375 | } |
376 | return args; |
377 | } |
378 | } |
379 | |
380 | |
381 | |
382 | |
383 | |
384 | |
385 | |
386 | |
387 | |
388 | |