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.IOException;
024: import java.io.StringReader;
025: import java.io.Writer;
026: import java.util.HashMap;
027:
028: import org.apache.commons.lang.StringUtils;
029: import org.apache.velocity.context.InternalContextAdapter;
030: import org.apache.velocity.context.VMContext;
031: import org.apache.velocity.exception.MethodInvocationException;
032: import org.apache.velocity.exception.TemplateInitException;
033: import org.apache.velocity.runtime.RuntimeConstants;
034: import org.apache.velocity.runtime.RuntimeServices;
035: import org.apache.velocity.runtime.parser.ParserTreeConstants;
036: import org.apache.velocity.runtime.parser.Token;
037: import org.apache.velocity.runtime.parser.node.ASTDirective;
038: import org.apache.velocity.runtime.parser.node.Node;
039: import org.apache.velocity.runtime.parser.node.SimpleNode;
040: import org.apache.velocity.runtime.visitor.VMReferenceMungeVisitor;
041:
042: /**
043: * VelocimacroProxy.java
044: *
045: * a proxy Directive-derived object to fit with the current directive system
046: *
047: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
048: * @version $Id: VelocimacroProxy.java 471381 2006-11-05 08:56:58Z wglass $
049: */
050: public class VelocimacroProxy extends Directive {
051: private String macroName = "";
052: private String macroBody = "";
053: private String[] argArray = null;
054: private SimpleNode nodeTree = null;
055: private int numMacroArgs = 0;
056: private String namespace = "";
057:
058: private boolean init = false;
059: private String[] callingArgs;
060: private int[] callingArgTypes;
061: private HashMap proxyArgHash = new HashMap();
062:
063: private boolean strictArguments;
064:
065: /**
066: * Return name of this Velocimacro.
067: * @return The name of this Velocimacro.
068: */
069: public String getName() {
070: return macroName;
071: }
072:
073: /**
074: * Velocimacros are always LINE
075: * type directives.
076: * @return The type of this directive.
077: */
078: public int getType() {
079: return LINE;
080: }
081:
082: /**
083: * sets the directive name of this VM
084: * @param name
085: */
086: public void setName(String name) {
087: macroName = name;
088: }
089:
090: /**
091: * sets the array of arguments specified in the macro definition
092: * @param arr
093: */
094: public void setArgArray(String[] arr) {
095: argArray = arr;
096:
097: /*
098: * get the arg count from the arg array. remember that the arg array
099: * has the macro name as it's 0th element
100: */
101:
102: numMacroArgs = argArray.length - 1;
103: }
104:
105: /**
106: * @param tree
107: */
108: public void setNodeTree(SimpleNode tree) {
109: nodeTree = tree;
110: }
111:
112: /**
113: * returns the number of ars needed for this VM
114: * @return The number of ars needed for this VM
115: */
116: public int getNumArgs() {
117: return numMacroArgs;
118: }
119:
120: /**
121: * Sets the orignal macro body. This is simply the cat of the macroArray, but the
122: * Macro object creates this once during parsing, and everyone shares it.
123: * Note : it must not be modified.
124: * @param mb
125: */
126: public void setMacrobody(String mb) {
127: macroBody = mb;
128: }
129:
130: /**
131: * @param ns
132: */
133: public void setNamespace(String ns) {
134: this .namespace = ns;
135: }
136:
137: /**
138: * Renders the macro using the context
139: * @param context
140: * @param writer
141: * @param node
142: * @return True if the directive rendered successfully.
143: * @throws IOException
144: * @throws MethodInvocationException
145: */
146: public boolean render(InternalContextAdapter context,
147: Writer writer, Node node) throws IOException,
148: MethodInvocationException {
149: try {
150: /*
151: * it's possible the tree hasn't been parsed yet, so get
152: * the VMManager to parse and init it
153: */
154:
155: if (nodeTree != null) {
156: if (!init) {
157: nodeTree.init(context, rsvc);
158: init = true;
159: }
160:
161: /*
162: * wrap the current context and add the VMProxyArg objects
163: */
164:
165: VMContext vmc = new VMContext(context, rsvc);
166:
167: for (int i = 1; i < argArray.length; i++) {
168: /*
169: * we can do this as VMProxyArgs don't change state. They change
170: * the context.
171: */
172:
173: VMProxyArg arg = (VMProxyArg) proxyArgHash
174: .get(argArray[i]);
175: vmc.addVMProxyArg(arg);
176: }
177:
178: /*
179: * now render the VM
180: */
181:
182: nodeTree.render(vmc, writer);
183: } else {
184: rsvc.getLog().error(
185: "VM error " + macroName + ". Null AST");
186: }
187: }
188:
189: /*
190: * if it's a MIE, it came from the render.... throw it...
191: */
192: catch (MethodInvocationException e) {
193: throw e;
194: }
195:
196: /**
197: * pass through application level runtime exceptions
198: */
199: catch (RuntimeException e) {
200: throw e;
201: }
202:
203: catch (Exception e) {
204:
205: rsvc.getLog().error(
206: "VelocimacroProxy.render() : exception VM = #"
207: + macroName + "()", e);
208: }
209:
210: return true;
211: }
212:
213: /**
214: * The major meat of VelocimacroProxy, init() checks the # of arguments, patches the
215: * macro body, renders the macro into an AST, and then inits the AST, so it is ready
216: * for quick rendering. Note that this is only AST dependant stuff. Not context.
217: * @param rs
218: * @param context
219: * @param node
220: * @throws TemplateInitException
221: */
222: public void init(RuntimeServices rs,
223: InternalContextAdapter context, Node node)
224: throws TemplateInitException {
225: super .init(rs, context, node);
226:
227: /**
228: * Throw exception for invalid number of arguments?
229: */
230: strictArguments = rs.getConfiguration().getBoolean(
231: RuntimeConstants.VM_ARGUMENTS_STRICT, false);
232:
233: /*
234: * how many args did we get?
235: */
236:
237: int i = node.jjtGetNumChildren();
238:
239: /*
240: * right number of args?
241: */
242:
243: if (getNumArgs() != i) {
244: // If we have a not-yet defined macro, we do get no arguments because
245: // the syntax tree looks different than with a already defined macro.
246: // But we do know that we must be in a macro definition context somewhere up the
247: // syntax tree.
248: // Check for that, if it is true, suppress the error message.
249: // Fixes VELOCITY-71.
250:
251: for (Node parent = node.jjtGetParent(); parent != null;) {
252: if ((parent instanceof ASTDirective)
253: && StringUtils.equals(((ASTDirective) parent)
254: .getDirectiveName(), "macro")) {
255: return;
256: }
257: parent = parent.jjtGetParent();
258: }
259:
260: String errormsg = "VM #" + macroName + ": error : too "
261: + ((getNumArgs() > i) ? "few" : "many")
262: + " arguments to macro. Wanted " + getNumArgs()
263: + " got " + i;
264:
265: if (strictArguments) {
266: /**
267: * indicate col/line assuming it starts at 0 - this will be
268: * corrected one call up
269: */
270: throw new TemplateInitException(errormsg, context
271: .getCurrentTemplateName(), 0, 0);
272: } else {
273: rsvc.getLog().error(errormsg);
274: return;
275: }
276: }
277:
278: /*
279: * get the argument list to the instance use of the VM
280: */
281:
282: callingArgs = getArgArray(node);
283:
284: /*
285: * now proxy each arg in the context
286: */
287:
288: setupMacro(callingArgs, callingArgTypes);
289: }
290:
291: /**
292: * basic VM setup. Sets up the proxy args for this
293: * use, and parses the tree
294: * @param callArgs
295: * @param callArgTypes
296: * @return True if the proxy was setup successfully.
297: */
298: public boolean setupMacro(String[] callArgs, int[] callArgTypes) {
299: setupProxyArgs(callArgs, callArgTypes);
300: parseTree(callArgs);
301:
302: return true;
303: }
304:
305: /**
306: * parses the macro. We need to do this here, at init time, or else
307: * the local-scope template feature is hard to get to work :)
308: * @param callArgs
309: */
310: private void parseTree(String[] callArgs) {
311: try {
312: BufferedReader br = new BufferedReader(new StringReader(
313: macroBody));
314:
315: /*
316: * now parse the macro - and don't dump the namespace
317: */
318:
319: nodeTree = rsvc.parse(br, namespace, false);
320:
321: /*
322: * now, to make null references render as proper schmoo
323: * we need to tweak the tree and change the literal of
324: * the appropriate references
325: *
326: * we only do this at init time, so it's the overhead
327: * is irrelevant
328: */
329:
330: HashMap hm = new HashMap();
331:
332: for (int i = 1; i < argArray.length; i++) {
333: String arg = callArgs[i - 1];
334:
335: /*
336: * if the calling arg is indeed a reference
337: * then we add to the map. We ignore other
338: * stuff
339: */
340:
341: if (arg.charAt(0) == '$') {
342: hm.put(argArray[i], arg);
343: }
344: }
345:
346: /*
347: * now make one of our reference-munging visitor, and
348: * let 'er rip
349: */
350:
351: VMReferenceMungeVisitor v = new VMReferenceMungeVisitor(hm);
352: nodeTree.jjtAccept(v, null);
353: }
354: /**
355: * pass through application level runtime exceptions
356: */
357: catch (RuntimeException e) {
358: throw e;
359: } catch (Exception e) {
360: rsvc.getLog().error(
361: "VelocimacroManager.parseTree() : exception "
362: + macroName, e);
363: }
364: }
365:
366: private void setupProxyArgs(String[] callArgs, int[] callArgTypes) {
367: /*
368: * for each of the args, make a ProxyArg
369: */
370:
371: for (int i = 1; i < argArray.length; i++) {
372: VMProxyArg arg = new VMProxyArg(rsvc, argArray[i],
373: callArgs[i - 1], callArgTypes[i - 1]);
374: proxyArgHash.put(argArray[i], arg);
375: }
376: }
377:
378: /**
379: * gets the args to the VM from the instance-use AST
380: * @param node
381: * @return array of arguments
382: */
383: private String[] getArgArray(Node node) {
384: int numArgs = node.jjtGetNumChildren();
385:
386: String args[] = new String[numArgs];
387: callingArgTypes = new int[numArgs];
388:
389: /*
390: * eat the args
391: */
392: int i = 0;
393: Token t = null;
394: Token tLast = null;
395:
396: while (i < numArgs) {
397: args[i] = "";
398: /*
399: * we want string literalss to lose the quotes. #foo( "blargh" ) should have 'blargh' patched
400: * into macro body. So for each arg in the use-instance, treat the stringlierals specially...
401: */
402:
403: callingArgTypes[i] = node.jjtGetChild(i).getType();
404:
405: if (false && node.jjtGetChild(i).getType() == ParserTreeConstants.JJTSTRINGLITERAL) {
406: args[i] += node.jjtGetChild(i).getFirstToken().image
407: .substring(1, node.jjtGetChild(i)
408: .getFirstToken().image.length() - 1);
409: } else {
410: /*
411: * just wander down the token list, concatenating everything together
412: */
413: t = node.jjtGetChild(i).getFirstToken();
414: tLast = node.jjtGetChild(i).getLastToken();
415:
416: while (t != tLast) {
417: args[i] += t.image;
418: t = t.next;
419: }
420:
421: /*
422: * don't forget the last one... :)
423: */
424: args[i] += t.image;
425: }
426: i++;
427: }
428: return args;
429: }
430: }
|