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.StringReader;
24 import java.io.StringWriter;
25
26 import org.apache.velocity.VelocityContext;
27 import org.apache.velocity.context.InternalContextAdapter;
28 import org.apache.velocity.context.InternalContextAdapterImpl;
29 import org.apache.velocity.exception.MethodInvocationException;
30 import org.apache.velocity.runtime.RuntimeServices;
31 import org.apache.velocity.runtime.log.Log;
32 import org.apache.velocity.runtime.parser.ParserTreeConstants;
33 import org.apache.velocity.runtime.parser.node.ASTReference;
34 import org.apache.velocity.runtime.parser.node.SimpleNode;
35
36 /**
37 * The function of this class is to proxy for the calling parameter to the VM.
38 *
39 * This class is designed to be used in conjunction with the VMContext class
40 * which knows how to get and set values via it, rather than a simple get()
41 * or put() from a hashtable-like object.
42 *
43 * There is probably a lot of undocumented subtlty here, so step lightly.
44 *
45 * We rely on the observation that an instance of this object has a constant
46 * state throughout its lifetime as it's bound to the use-instance of a VM.
47 * In other words, it's created by the VelocimacroProxy class, to represent
48 * one of the arguments to a VM in a specific template. Since the template
49 * is fixed (it's a file...), we don't have to worry that the args to the VM
50 * will change. Yes, the VM will be called in other templates, or in other
51 * places on the same template, bit those are different use-instances.
52 *
53 * These arguments can be, in the lingo of
54 * the parser, one of :
55 * <ul>
56 * <li> Reference() : anything that starts with '$'
57 * <li> StringLiteral() : something like "$foo" or "hello geir"
58 * <li> IntegerLiteral() : 1, 2 etc
59 * <li> FloatingPointLiteral() : 1.2, 2e5 etc
60 * <li> IntegerRange() : [ 1..2] or [$foo .. $bar]
61 * <li> ObjectArray() : [ "a", "b", "c"]
62 * <li> True() : true
63 * <li> False() : false
64 * <li>Word() : not likely - this is simply allowed by the parser so we can have
65 * syntactical sugar like #foreach($a in $b) where 'in' is the Word
66 * </ul>
67 * Now, Reference(), StringLit, IntegerLiteral, IntRange, ObjArr are all dynamic things, so
68 * their value is gotten with the use of a context. The others are constants. The trick
69 * we rely on is that the context rather than this class really represents the
70 * state of the argument. We are simply proxying for the thing, returning the proper value
71 * when asked, and storing the proper value in the appropriate context when asked.
72 *
73 * So, the hope here, so an instance of this can be shared across threads, is to
74 * keep any dynamic stuff out of it, relying on trick of having the appropriate
75 * context handed to us, and when a constant argument, letting VMContext punch that
76 * into a local context.
77 *
78 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
79 * @version $Id: VMProxyArg.java 473363 2006-11-10 15:19:09Z wglass $
80 */
81 public class VMProxyArg
82 {
83 /** in the event our type is switched - we don't care really what it is */
84 private static final int GENERALSTATIC = -1;
85
86 /** type of arg I will have */
87 private int type = 0;
88
89 /** the AST if the type is such that it's dynamic (ex. JJTREFERENCE ) */
90 private SimpleNode nodeTree = null;
91
92 /** reference for the object if we proxy for a static arg like an IntegerLiteral*/
93 private Object staticObject = null;
94
95 /** number of children in our tree if a reference */
96 private int numTreeChildren = 0;
97
98 /** our identity in the current context */
99 private String contextReference = null;
100
101 /** the reference we are proxying for */
102 private String callerReference = null;
103
104 /** the 'de-dollared' reference if we are a ref but don't have a method attached */
105 private String singleLevelRef = null;
106
107 /** by default, we are dynamic. safest */
108 private boolean constant = false;
109
110 private RuntimeServices rsvc = null;
111 private Log log = null;
112
113 /**
114 * ctor for current impl
115 *
116 * takes the reference literal we are proxying for, the literal
117 * the VM we are for is called with...
118 * @param rs
119 *
120 * @param contextRef reference arg in the definition of the VM, used in the VM
121 * @param callerRef reference used by the caller as an arg to the VM
122 * @param t type of arg : JJTREFERENCE, JJTTRUE, etc
123 */
124 public VMProxyArg( RuntimeServices rs, String contextRef, String callerRef, int t )
125 {
126 rsvc = rs;
127 log = rsvc.getLog();
128
129 contextReference = contextRef;
130 callerReference = callerRef;
131 type = t;
132
133 /*
134 * make our AST if necessary
135 */
136 setup();
137
138 /*
139 * if we are multi-node tree, then save the size to
140 * avoid fn call overhead
141 */
142 if( nodeTree != null)
143 {
144 numTreeChildren = nodeTree.jjtGetNumChildren();
145 }
146
147 /*
148 * if we are a reference, and 'scalar' (i.e. $foo )
149 * then get the de-dollared ref so we can
150 * hit our context directly, avoiding the AST
151 */
152 if ( type == ParserTreeConstants.JJTREFERENCE )
153 {
154 if ( numTreeChildren == 0)
155 {
156 /*
157 * do this properly and use the Reference node
158 */
159 singleLevelRef = ((ASTReference) nodeTree).getRootString();
160 }
161 }
162 }
163
164 /**
165 * tells if arg we are poxying for is
166 * dynamic or constant.
167 *
168 * @return true of constant, false otherwise
169 */
170 public boolean isConstant()
171 {
172 return constant;
173 }
174
175 /**
176 * Invoked by VMContext when Context.put() is called for a proxied reference.
177 *
178 * @param context context to modify via direct placement, or AST.setValue()
179 * @param o new value of reference
180 * @return Object currently null
181 */
182 public Object setObject( InternalContextAdapter context, Object o )
183 {
184 /*
185 * if we are a reference, we could be updating a property
186 */
187
188 if( type == ParserTreeConstants.JJTREFERENCE )
189 {
190 if( numTreeChildren > 0)
191 {
192 /*
193 * we are a property, and being updated such as
194 * #foo( $bar.BangStart)
195 */
196
197 try
198 {
199 ( (ASTReference) nodeTree).setValue( context, o );
200 }
201 catch( MethodInvocationException mie )
202 {
203 log.error("VMProxyArg.getObject() : method invocation error setting value", mie);
204 }
205 }
206 else
207 {
208 /*
209 * we are a 'single level' reference like $foo, so we can set
210 * out context directly
211 */
212
213 context.put( singleLevelRef, o);
214
215 // alternate impl : usercontext.put( singleLevelRef, o);
216 }
217 }
218 else
219 {
220 /*
221 * if we aren't a reference, then we simply switch type,
222 * get a new value, and it doesn't go into the context
223 *
224 * in current impl, this shouldn't happen.
225 */
226
227 type = GENERALSTATIC;
228 staticObject = o;
229
230 log.error("VMProxyArg.setObject() : Programmer error : I am a constant! No setting! : "
231 + contextReference + " / " + callerReference);
232 }
233
234 return null;
235 }
236
237
238 /**
239 * returns the value of the reference. Generally, this is only
240 * called for dynamic proxies, as the static ones should have
241 * been stored in the VMContext's localcontext store
242 *
243 * @param context Context to use for getting current value
244 * @return Object value
245 * @exception MethodInvocationException passes on potential exception from reference method call
246 */
247 public Object getObject( InternalContextAdapter context ) throws MethodInvocationException
248 {
249 try
250 {
251
252 /*
253 * we need to output based on our type
254 */
255
256 Object retObject = null;
257
258 if ( type == ParserTreeConstants.JJTREFERENCE )
259 {
260 /*
261 * two cases : scalar reference ($foo) or multi-level ($foo.bar....)
262 */
263
264 if ( numTreeChildren == 0)
265 {
266 /*
267 * if I am a single-level reference, can I not get get it out of my context?
268 */
269
270 retObject = context.get( singleLevelRef );
271 }
272 else
273 {
274 /*
275 * I need to let the AST produce it for me.
276 */
277
278 retObject = nodeTree.execute( null, context);
279 }
280 }
281 else if (type == ParserTreeConstants.JJTMAP)
282 {
283 retObject = nodeTree.value(context);
284 }
285 else if( type == ParserTreeConstants.JJTOBJECTARRAY )
286 {
287 retObject = nodeTree.value( context );
288 }
289 else if ( type == ParserTreeConstants.JJTINTEGERRANGE)
290 {
291 retObject = nodeTree.value( context );
292 }
293 else if( type == ParserTreeConstants.JJTTRUE )
294 {
295 retObject = staticObject;
296 }
297 else if ( type == ParserTreeConstants.JJTFALSE )
298 {
299 retObject = staticObject;
300 }
301 else if ( type == ParserTreeConstants.JJTSTRINGLITERAL )
302 {
303 retObject = nodeTree.value( context );
304 }
305 else if ( type == ParserTreeConstants.JJTINTEGERLITERAL )
306 {
307 retObject = staticObject;
308 }
309 else if ( type == ParserTreeConstants.JJTFLOATINGPOINTLITERAL )
310 {
311 retObject = staticObject;
312 }
313 else if ( type == ParserTreeConstants.JJTTEXT )
314 {
315 /*
316 * this really shouldn't happen. text is just a thowaway arg for #foreach()
317 */
318
319 try
320 {
321 StringWriter writer =new StringWriter();
322 nodeTree.render( context, writer );
323
324 retObject = writer;
325 }
326 /**
327 * pass through application level runtime exceptions
328 */
329 catch( RuntimeException e )
330 {
331 throw e;
332 }
333 catch (Exception e )
334 {
335 log.error("VMProxyArg.getObject() : error rendering reference", e);
336 }
337 }
338 else if( type == GENERALSTATIC )
339 {
340 retObject = staticObject;
341 }
342 else
343 {
344 log.error("Unsupported VM arg type : VM arg = " +
345 callerReference +" type = " + type +
346 "( VMProxyArg.getObject() )");
347 }
348
349 return retObject;
350 }
351 catch( MethodInvocationException mie )
352 {
353 /*
354 * not ideal, but otherwise we propogate out to the
355 * VMContext, and the Context interface's put/get
356 * don't throw. So this is a the best compromise
357 * I can think of
358 */
359
360 log.error("VMProxyArg.getObject() : method invocation error getting value", mie);
361 throw mie;
362 }
363 }
364
365 /**
366 * does the housekeeping upon creationg. If a dynamic type
367 * it needs to make an AST for further get()/set() operations
368 * Anything else is constant.
369 */
370 private void setup()
371 {
372 switch( type )
373 {
374
375 case ParserTreeConstants.JJTINTEGERRANGE :
376 case ParserTreeConstants.JJTREFERENCE :
377 case ParserTreeConstants.JJTOBJECTARRAY :
378 case ParserTreeConstants.JJTMAP :
379 case ParserTreeConstants.JJTSTRINGLITERAL :
380 case ParserTreeConstants.JJTTEXT :
381 {
382 /*
383 * dynamic types, just render
384 */
385
386 constant = false;
387
388 try
389 {
390 /*
391 * fakie : wrap in directive to get the parser to treat our args as args
392 * it doesn't matter that #include() can't take all these types, because we
393 * just want the parser to consider our arg as a Directive/VM arg rather than
394 * as if inline in schmoo
395 */
396
397 String buff ="#include(" + callerReference + " ) ";
398
399 //ByteArrayInputStream inStream = new ByteArrayInputStream( buff.getBytes() );
400
401 BufferedReader br = new BufferedReader( new StringReader( buff ) );
402
403 nodeTree = rsvc.parse(br, "VMProxyArg:" + callerReference, true);
404
405 /*
406 * now, our tree really is the first DirectiveArg(), and only one
407 */
408
409 nodeTree = (SimpleNode) nodeTree.jjtGetChild(0).jjtGetChild(0);
410
411 /*
412 * sanity check
413 */
414 if ( nodeTree != null)
415 {
416 if(nodeTree.getType() != type)
417 {
418 log.error("VMProxyArg.setup() : programmer error : type doesn't match node type.");
419 }
420
421 /*
422 * init. be a good citizen and give it an ICA
423 */
424
425 InternalContextAdapter ica
426 = new InternalContextAdapterImpl(new VelocityContext());
427
428 ica.pushCurrentTemplateName("VMProxyArg : "
429 + ParserTreeConstants.jjtNodeName[type]);
430
431 nodeTree.init(ica, rsvc);
432 }
433 }
434 /**
435 * pass through application level runtime exceptions
436 */
437 catch( RuntimeException e )
438 {
439 throw e;
440 }
441 catch ( Exception e )
442 {
443 log.error("VMProxyArg.setup() : exception " +
444 callerReference, e);
445 }
446
447 break;
448 }
449
450 case ParserTreeConstants.JJTTRUE :
451 {
452 constant = true;
453 staticObject = Boolean.TRUE;
454 break;
455 }
456
457 case ParserTreeConstants.JJTFALSE :
458 {
459 constant = true;
460 staticObject = Boolean.FALSE;
461 break;
462 }
463
464 case ParserTreeConstants.JJTINTEGERLITERAL :
465 {
466 constant = true;
467 staticObject = new Integer(callerReference);
468 break;
469 }
470
471 case ParserTreeConstants.JJTFLOATINGPOINTLITERAL :
472 {
473 constant = true;
474 staticObject = new Double(callerReference);
475 break;
476 }
477
478 case ParserTreeConstants.JJTWORD :
479 {
480 /*
481 * this is technically an error...
482 */
483
484 log.error("Unsupported arg type : " + callerReference +
485 " You most likely intended to call a VM with a string literal, so enclose with ' or \" characters. (VMProxyArg.setup())");
486 constant = true;
487 staticObject = callerReference;
488
489 break;
490 }
491
492 default :
493 {
494 log.error("VMProxyArg.setup() : unsupported type : "
495 + callerReference );
496 }
497 }
498 }
499
500 /*
501 * CODE FOR ALTERNATE IMPL : please ignore. I will remove when confortable with current.
502 */
503
504 /**
505 * not used in current impl
506 *
507 * Constructor for alternate impl where VelProxy class would make new
508 * VMProxyArg objects, and use this contructor to avoid reparsing the
509 * reference args
510 *
511 * that impl also had the VMProxyArg carry it's context.
512 * @param model
513 * @param c
514 */
515 public VMProxyArg( VMProxyArg model, InternalContextAdapter c )
516 {
517 contextReference = model.getContextReference();
518 callerReference = model.getCallerReference();
519 nodeTree = model.getNodeTree();
520 staticObject = model.getStaticObject();
521 type = model.getType();
522
523 if( nodeTree != null)
524 numTreeChildren = nodeTree.jjtGetNumChildren();
525
526 if ( type == ParserTreeConstants.JJTREFERENCE )
527 {
528 if ( numTreeChildren == 0)
529 {
530 /*
531 * use the reference node to do this...
532 */
533 singleLevelRef = ((ASTReference) nodeTree).getRootString();
534 }
535 }
536 }
537
538 /**
539 * @return The caller reference.
540 */
541 public String getCallerReference()
542 {
543 return callerReference;
544 }
545
546 /**
547 * @return The context reference.
548 */
549 public String getContextReference()
550 {
551 return contextReference;
552 }
553
554 /**
555 * @return The node tree.
556 */
557 public SimpleNode getNodeTree()
558 {
559 return nodeTree;
560 }
561
562 /**
563 * @return The static object.
564 */
565 public Object getStaticObject()
566 {
567 return staticObject;
568 }
569
570 /**
571 * @return The type.
572 */
573 public int getType()
574 {
575 return type;
576 }
577 }