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