001: package org.apache.velocity.runtime.directive;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.BufferedReader;
023: import java.io.StringReader;
024: import java.io.StringWriter;
025:
026: import org.apache.velocity.VelocityContext;
027: import org.apache.velocity.context.InternalContextAdapter;
028: import org.apache.velocity.context.InternalContextAdapterImpl;
029: import org.apache.velocity.exception.MethodInvocationException;
030: import org.apache.velocity.runtime.RuntimeServices;
031: import org.apache.velocity.runtime.log.Log;
032: import org.apache.velocity.runtime.parser.ParserTreeConstants;
033: import org.apache.velocity.runtime.parser.node.ASTReference;
034: import org.apache.velocity.runtime.parser.node.SimpleNode;
035:
036: /**
037: * The function of this class is to proxy for the calling parameter to the VM.
038: *
039: * This class is designed to be used in conjunction with the VMContext class
040: * which knows how to get and set values via it, rather than a simple get()
041: * or put() from a hashtable-like object.
042: *
043: * There is probably a lot of undocumented subtlty here, so step lightly.
044: *
045: * We rely on the observation that an instance of this object has a constant
046: * state throughout its lifetime as it's bound to the use-instance of a VM.
047: * In other words, it's created by the VelocimacroProxy class, to represent
048: * one of the arguments to a VM in a specific template. Since the template
049: * is fixed (it's a file...), we don't have to worry that the args to the VM
050: * will change. Yes, the VM will be called in other templates, or in other
051: * places on the same template, bit those are different use-instances.
052: *
053: * These arguments can be, in the lingo of
054: * the parser, one of :
055: * <ul>
056: * <li> Reference() : anything that starts with '$'
057: * <li> StringLiteral() : something like "$foo" or "hello geir"
058: * <li> IntegerLiteral() : 1, 2 etc
059: * <li> FloatingPointLiteral() : 1.2, 2e5 etc
060: * <li> IntegerRange() : [ 1..2] or [$foo .. $bar]
061: * <li> ObjectArray() : [ "a", "b", "c"]
062: * <li> True() : true
063: * <li> False() : false
064: * <li>Word() : not likely - this is simply allowed by the parser so we can have
065: * syntactical sugar like #foreach($a in $b) where 'in' is the Word
066: * </ul>
067: * Now, Reference(), StringLit, IntegerLiteral, IntRange, ObjArr are all dynamic things, so
068: * their value is gotten with the use of a context. The others are constants. The trick
069: * we rely on is that the context rather than this class really represents the
070: * state of the argument. We are simply proxying for the thing, returning the proper value
071: * when asked, and storing the proper value in the appropriate context when asked.
072: *
073: * So, the hope here, so an instance of this can be shared across threads, is to
074: * keep any dynamic stuff out of it, relying on trick of having the appropriate
075: * context handed to us, and when a constant argument, letting VMContext punch that
076: * into a local context.
077: *
078: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
079: * @version $Id: VMProxyArg.java 473363 2006-11-10 15:19:09Z wglass $
080: */
081: public class VMProxyArg {
082: /** in the event our type is switched - we don't care really what it is */
083: private static final int GENERALSTATIC = -1;
084:
085: /** type of arg I will have */
086: private int type = 0;
087:
088: /** the AST if the type is such that it's dynamic (ex. JJTREFERENCE ) */
089: private SimpleNode nodeTree = null;
090:
091: /** reference for the object if we proxy for a static arg like an IntegerLiteral*/
092: private Object staticObject = null;
093:
094: /** number of children in our tree if a reference */
095: private int numTreeChildren = 0;
096:
097: /** our identity in the current context */
098: private String contextReference = null;
099:
100: /** the reference we are proxying for */
101: private String callerReference = null;
102:
103: /** the 'de-dollared' reference if we are a ref but don't have a method attached */
104: private String singleLevelRef = null;
105:
106: /** by default, we are dynamic. safest */
107: private boolean constant = false;
108:
109: private RuntimeServices rsvc = null;
110: private Log log = null;
111:
112: /**
113: * ctor for current impl
114: *
115: * takes the reference literal we are proxying for, the literal
116: * the VM we are for is called with...
117: * @param rs
118: *
119: * @param contextRef reference arg in the definition of the VM, used in the VM
120: * @param callerRef reference used by the caller as an arg to the VM
121: * @param t type of arg : JJTREFERENCE, JJTTRUE, etc
122: */
123: public VMProxyArg(RuntimeServices rs, String contextRef,
124: String callerRef, int t) {
125: rsvc = rs;
126: log = rsvc.getLog();
127:
128: contextReference = contextRef;
129: callerReference = callerRef;
130: type = t;
131:
132: /*
133: * make our AST if necessary
134: */
135: setup();
136:
137: /*
138: * if we are multi-node tree, then save the size to
139: * avoid fn call overhead
140: */
141: if (nodeTree != null) {
142: numTreeChildren = nodeTree.jjtGetNumChildren();
143: }
144:
145: /*
146: * if we are a reference, and 'scalar' (i.e. $foo )
147: * then get the de-dollared ref so we can
148: * hit our context directly, avoiding the AST
149: */
150: if (type == ParserTreeConstants.JJTREFERENCE) {
151: if (numTreeChildren == 0) {
152: /*
153: * do this properly and use the Reference node
154: */
155: singleLevelRef = ((ASTReference) nodeTree)
156: .getRootString();
157: }
158: }
159: }
160:
161: /**
162: * tells if arg we are poxying for is
163: * dynamic or constant.
164: *
165: * @return true of constant, false otherwise
166: */
167: public boolean isConstant() {
168: return constant;
169: }
170:
171: /**
172: * Invoked by VMContext when Context.put() is called for a proxied reference.
173: *
174: * @param context context to modify via direct placement, or AST.setValue()
175: * @param o new value of reference
176: * @return Object currently null
177: */
178: public Object setObject(InternalContextAdapter context, Object o) {
179: /*
180: * if we are a reference, we could be updating a property
181: */
182:
183: if (type == ParserTreeConstants.JJTREFERENCE) {
184: if (numTreeChildren > 0) {
185: /*
186: * we are a property, and being updated such as
187: * #foo( $bar.BangStart)
188: */
189:
190: try {
191: ((ASTReference) nodeTree).setValue(context, o);
192: } catch (MethodInvocationException mie) {
193: log
194: .error(
195: "VMProxyArg.getObject() : method invocation error setting value",
196: mie);
197: }
198: } else {
199: /*
200: * we are a 'single level' reference like $foo, so we can set
201: * out context directly
202: */
203:
204: context.put(singleLevelRef, o);
205:
206: // alternate impl : usercontext.put( singleLevelRef, o);
207: }
208: } else {
209: /*
210: * if we aren't a reference, then we simply switch type,
211: * get a new value, and it doesn't go into the context
212: *
213: * in current impl, this shouldn't happen.
214: */
215:
216: type = GENERALSTATIC;
217: staticObject = o;
218:
219: log
220: .error("VMProxyArg.setObject() : Programmer error : I am a constant! No setting! : "
221: + contextReference
222: + " / "
223: + callerReference);
224: }
225:
226: return null;
227: }
228:
229: /**
230: * returns the value of the reference. Generally, this is only
231: * called for dynamic proxies, as the static ones should have
232: * been stored in the VMContext's localcontext store
233: *
234: * @param context Context to use for getting current value
235: * @return Object value
236: * @exception MethodInvocationException passes on potential exception from reference method call
237: */
238: public Object getObject(InternalContextAdapter context)
239: throws MethodInvocationException {
240: try {
241:
242: /*
243: * we need to output based on our type
244: */
245:
246: Object retObject = null;
247:
248: if (type == ParserTreeConstants.JJTREFERENCE) {
249: /*
250: * two cases : scalar reference ($foo) or multi-level ($foo.bar....)
251: */
252:
253: if (numTreeChildren == 0) {
254: /*
255: * if I am a single-level reference, can I not get get it out of my context?
256: */
257:
258: retObject = context.get(singleLevelRef);
259: } else {
260: /*
261: * I need to let the AST produce it for me.
262: */
263:
264: retObject = nodeTree.execute(null, context);
265: }
266: } else if (type == ParserTreeConstants.JJTMAP) {
267: retObject = nodeTree.value(context);
268: } else if (type == ParserTreeConstants.JJTOBJECTARRAY) {
269: retObject = nodeTree.value(context);
270: } else if (type == ParserTreeConstants.JJTINTEGERRANGE) {
271: retObject = nodeTree.value(context);
272: } else if (type == ParserTreeConstants.JJTTRUE) {
273: retObject = staticObject;
274: } else if (type == ParserTreeConstants.JJTFALSE) {
275: retObject = staticObject;
276: } else if (type == ParserTreeConstants.JJTSTRINGLITERAL) {
277: retObject = nodeTree.value(context);
278: } else if (type == ParserTreeConstants.JJTINTEGERLITERAL) {
279: retObject = staticObject;
280: } else if (type == ParserTreeConstants.JJTFLOATINGPOINTLITERAL) {
281: retObject = staticObject;
282: } else if (type == ParserTreeConstants.JJTTEXT) {
283: /*
284: * this really shouldn't happen. text is just a thowaway arg for #foreach()
285: */
286:
287: try {
288: StringWriter writer = new StringWriter();
289: nodeTree.render(context, writer);
290:
291: retObject = writer;
292: }
293: /**
294: * pass through application level runtime exceptions
295: */
296: catch (RuntimeException e) {
297: throw e;
298: } catch (Exception e) {
299: log
300: .error(
301: "VMProxyArg.getObject() : error rendering reference",
302: e);
303: }
304: } else if (type == GENERALSTATIC) {
305: retObject = staticObject;
306: } else {
307: log.error("Unsupported VM arg type : VM arg = "
308: + callerReference + " type = " + type
309: + "( VMProxyArg.getObject() )");
310: }
311:
312: return retObject;
313: } catch (MethodInvocationException mie) {
314: /*
315: * not ideal, but otherwise we propogate out to the
316: * VMContext, and the Context interface's put/get
317: * don't throw. So this is a the best compromise
318: * I can think of
319: */
320:
321: log
322: .error(
323: "VMProxyArg.getObject() : method invocation error getting value",
324: mie);
325: throw mie;
326: }
327: }
328:
329: /**
330: * does the housekeeping upon creationg. If a dynamic type
331: * it needs to make an AST for further get()/set() operations
332: * Anything else is constant.
333: */
334: private void setup() {
335: switch (type) {
336:
337: case ParserTreeConstants.JJTINTEGERRANGE:
338: case ParserTreeConstants.JJTREFERENCE:
339: case ParserTreeConstants.JJTOBJECTARRAY:
340: case ParserTreeConstants.JJTMAP:
341: case ParserTreeConstants.JJTSTRINGLITERAL:
342: case ParserTreeConstants.JJTTEXT: {
343: /*
344: * dynamic types, just render
345: */
346:
347: constant = false;
348:
349: try {
350: /*
351: * fakie : wrap in directive to get the parser to treat our args as args
352: * it doesn't matter that #include() can't take all these types, because we
353: * just want the parser to consider our arg as a Directive/VM arg rather than
354: * as if inline in schmoo
355: */
356:
357: String buff = "#include(" + callerReference + " ) ";
358:
359: //ByteArrayInputStream inStream = new ByteArrayInputStream( buff.getBytes() );
360:
361: BufferedReader br = new BufferedReader(
362: new StringReader(buff));
363:
364: nodeTree = rsvc.parse(br, "VMProxyArg:"
365: + callerReference, true);
366:
367: /*
368: * now, our tree really is the first DirectiveArg(), and only one
369: */
370:
371: nodeTree = (SimpleNode) nodeTree.jjtGetChild(0)
372: .jjtGetChild(0);
373:
374: /*
375: * sanity check
376: */
377: if (nodeTree != null) {
378: if (nodeTree.getType() != type) {
379: log
380: .error("VMProxyArg.setup() : programmer error : type doesn't match node type.");
381: }
382:
383: /*
384: * init. be a good citizen and give it an ICA
385: */
386:
387: InternalContextAdapter ica = new InternalContextAdapterImpl(
388: new VelocityContext());
389:
390: ica.pushCurrentTemplateName("VMProxyArg : "
391: + ParserTreeConstants.jjtNodeName[type]);
392:
393: nodeTree.init(ica, rsvc);
394: }
395: }
396: /**
397: * pass through application level runtime exceptions
398: */
399: catch (RuntimeException e) {
400: throw e;
401: } catch (Exception e) {
402: log.error("VMProxyArg.setup() : exception "
403: + callerReference, e);
404: }
405:
406: break;
407: }
408:
409: case ParserTreeConstants.JJTTRUE: {
410: constant = true;
411: staticObject = Boolean.TRUE;
412: break;
413: }
414:
415: case ParserTreeConstants.JJTFALSE: {
416: constant = true;
417: staticObject = Boolean.FALSE;
418: break;
419: }
420:
421: case ParserTreeConstants.JJTINTEGERLITERAL: {
422: constant = true;
423: staticObject = new Integer(callerReference);
424: break;
425: }
426:
427: case ParserTreeConstants.JJTFLOATINGPOINTLITERAL: {
428: constant = true;
429: staticObject = new Double(callerReference);
430: break;
431: }
432:
433: case ParserTreeConstants.JJTWORD: {
434: /*
435: * this is technically an error...
436: */
437:
438: log
439: .error("Unsupported arg type : "
440: + callerReference
441: + " You most likely intended to call a VM with a string literal, so enclose with ' or \" characters. (VMProxyArg.setup())");
442: constant = true;
443: staticObject = callerReference;
444:
445: break;
446: }
447:
448: default: {
449: log.error("VMProxyArg.setup() : unsupported type : "
450: + callerReference);
451: }
452: }
453: }
454:
455: /*
456: * CODE FOR ALTERNATE IMPL : please ignore. I will remove when confortable with current.
457: */
458:
459: /**
460: * not used in current impl
461: *
462: * Constructor for alternate impl where VelProxy class would make new
463: * VMProxyArg objects, and use this contructor to avoid reparsing the
464: * reference args
465: *
466: * that impl also had the VMProxyArg carry it's context.
467: * @param model
468: * @param c
469: */
470: public VMProxyArg(VMProxyArg model, InternalContextAdapter c) {
471: contextReference = model.getContextReference();
472: callerReference = model.getCallerReference();
473: nodeTree = model.getNodeTree();
474: staticObject = model.getStaticObject();
475: type = model.getType();
476:
477: if (nodeTree != null)
478: numTreeChildren = nodeTree.jjtGetNumChildren();
479:
480: if (type == ParserTreeConstants.JJTREFERENCE) {
481: if (numTreeChildren == 0) {
482: /*
483: * use the reference node to do this...
484: */
485: singleLevelRef = ((ASTReference) nodeTree)
486: .getRootString();
487: }
488: }
489: }
490:
491: /**
492: * @return The caller reference.
493: */
494: public String getCallerReference() {
495: return callerReference;
496: }
497:
498: /**
499: * @return The context reference.
500: */
501: public String getContextReference() {
502: return contextReference;
503: }
504:
505: /**
506: * @return The node tree.
507: */
508: public SimpleNode getNodeTree() {
509: return nodeTree;
510: }
511:
512: /**
513: * @return The static object.
514: */
515: public Object getStaticObject() {
516: return staticObject;
517: }
518:
519: /**
520: * @return The type.
521: */
522: public int getType() {
523: return type;
524: }
525: }
|