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.Writer;
023: import java.io.IOException;
024:
025: import java.util.List;
026: import java.util.ArrayList;
027:
028: import org.apache.velocity.context.InternalContextAdapter;
029: import org.apache.velocity.exception.TemplateInitException;
030:
031: import org.apache.velocity.runtime.parser.node.Node;
032: import org.apache.velocity.runtime.parser.node.NodeUtils;
033: import org.apache.velocity.runtime.parser.Token;
034: import org.apache.velocity.runtime.parser.ParseException;
035: import org.apache.velocity.runtime.parser.ParserTreeConstants;
036: import org.apache.velocity.runtime.RuntimeServices;
037:
038: /**
039: * Macro.java
040: *
041: * Macro implements the macro definition directive of VTL.
042: *
043: * example :
044: *
045: * #macro( isnull $i )
046: * #if( $i )
047: * $i
048: * #end
049: * #end
050: *
051: * This object is used at parse time to mainly process and register the
052: * macro. It is used inline in the parser when processing a directive.
053: *
054: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
055: * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
056: * @version $Id: Macro.java 471881 2006-11-06 21:21:10Z henning $
057: */
058: public class Macro extends Directive {
059: private static boolean debugMode = false;
060:
061: /**
062: * Return name of this directive.
063: * @return The name of this directive.
064: */
065: public String getName() {
066: return "macro";
067: }
068:
069: /**
070: * Return type of this directive.
071: * @return The type of this directive.
072: */
073: public int getType() {
074: return BLOCK;
075: }
076:
077: /**
078: * render() doesn't do anything in the final output rendering.
079: * There is no output from a #macro() directive.
080: * @param context
081: * @param writer
082: * @param node
083: * @return True if the directive rendered successfully.
084: * @throws IOException
085: */
086: public boolean render(InternalContextAdapter context,
087: Writer writer, Node node) throws IOException {
088: /*
089: * do nothing : We never render. The VelocimacroProxy object does that
090: */
091:
092: return true;
093: }
094:
095: /**
096: * @see org.apache.velocity.runtime.directive.Directive#init(org.apache.velocity.runtime.RuntimeServices, org.apache.velocity.context.InternalContextAdapter, org.apache.velocity.runtime.parser.node.Node)
097: */
098: public void init(RuntimeServices rs,
099: InternalContextAdapter context, Node node)
100: throws TemplateInitException {
101: super .init(rs, context, node);
102:
103: /*
104: * again, don't do squat. We want the AST of the macro
105: * block to hang off of this but we don't want to
106: * init it... it's useless...
107: */
108: }
109:
110: /**
111: * Used by Parser.java to process VMs during the parsing process.
112: *
113: * This method does not render the macro to the output stream,
114: * but rather <i>processes the macro body</i> into the internal
115: * representation used by {#link
116: * org.apache.velocity.runtime.directive.VelocimacroProxy}
117: * objects, and if not currently used, adds it to the macro
118: * Factory.
119: * @param rs
120: * @param t
121: * @param node
122: * @param sourceTemplate
123: * @throws IOException
124: * @throws ParseException
125: */
126: public static void processAndRegister(RuntimeServices rs, Token t,
127: Node node, String sourceTemplate) throws IOException,
128: ParseException {
129: /*
130: * There must be at least one arg to #macro,
131: * the name of the VM. Note that 0 following
132: * args is ok for naming blocks of HTML
133: */
134:
135: int numArgs = node.jjtGetNumChildren();
136:
137: /*
138: * this number is the # of args + 1. The + 1
139: * is for the block tree
140: */
141:
142: if (numArgs < 2) {
143:
144: /*
145: * error - they didn't name the macro or
146: * define a block
147: */
148:
149: rs.getLog().error(
150: "#macro error : Velocimacro must have name as 1st "
151: + "argument to #macro(). #args = "
152: + numArgs);
153:
154: throw new MacroParseException(
155: "First argument to #macro() must be "
156: + " macro name.", sourceTemplate, t);
157: }
158:
159: /*
160: * lets make sure that the first arg is an ASTWord
161: */
162:
163: int firstType = node.jjtGetChild(0).getType();
164:
165: if (firstType != ParserTreeConstants.JJTWORD) {
166: throw new MacroParseException(
167: "First argument to #macro() must be a"
168: + " token without surrounding \' or \", which specifies"
169: + " the macro name. Currently it is a "
170: + ParserTreeConstants.jjtNodeName[firstType],
171: sourceTemplate, t);
172: }
173:
174: /*
175: * get the arguments to the use of the VM
176: */
177:
178: String argArray[] = getArgArray(node, rs);
179:
180: /*
181: * now, try and eat the code block. Pass the root.
182: */
183:
184: List macroArray = getASTAsStringArray(node
185: .jjtGetChild(numArgs - 1));
186:
187: /*
188: * make a big string out of our macro
189: */
190:
191: StringBuffer macroBody = new StringBuffer();
192:
193: for (int i = 0; i < macroArray.size(); i++) {
194: macroBody.append(macroArray.get(i));
195: }
196:
197: /*
198: * now, try to add it. The Factory controls permissions,
199: * so just give it a whack...
200: */
201:
202: boolean macroAdded = rs.addVelocimacro(argArray[0], macroBody
203: .toString(), argArray, sourceTemplate);
204:
205: if (!macroAdded && rs.getLog().isWarnEnabled()) {
206: StringBuffer msg = new StringBuffer("Failed to add macro: ");
207: macroToString(msg, argArray);
208: msg.append(" : source = ").append(sourceTemplate);
209: rs.getLog().warn(msg);
210: }
211: }
212:
213: /**
214: * Creates an array containing the literal text from the macro
215: * arguement(s) (including the macro's name as the first arg).
216: *
217: * @param node The parse node from which to grok the argument
218: * list. It's expected to include the block node tree (for the
219: * macro body).
220: * @param rsvc For debugging purposes only.
221: * @return array of arguments
222: */
223: private static String[] getArgArray(Node node, RuntimeServices rsvc) {
224: /*
225: * Get the number of arguments for the macro, excluding the
226: * last child node which is the block tree containing the
227: * macro body.
228: */
229: int numArgs = node.jjtGetNumChildren();
230: numArgs--; // avoid the block tree...
231:
232: String argArray[] = new String[numArgs];
233:
234: int i = 0;
235:
236: /*
237: * eat the args
238: */
239:
240: while (i < numArgs) {
241: argArray[i] = node.jjtGetChild(i).getFirstToken().image;
242:
243: /*
244: * trim off the leading $ for the args after the macro name.
245: * saves everyone else from having to do it
246: */
247:
248: if (i > 0) {
249: if (argArray[i].startsWith("$")) {
250: argArray[i] = argArray[i].substring(1, argArray[i]
251: .length());
252: }
253: }
254:
255: i++;
256: }
257:
258: if (debugMode) {
259: StringBuffer msg = new StringBuffer(
260: "Macro.getArgArray() : nbrArgs=");
261: msg.append(numArgs).append(" : ");
262: macroToString(msg, argArray);
263: rsvc.getLog().debug(msg);
264: }
265:
266: return argArray;
267: }
268:
269: /**
270: * Returns an array of the literal rep of the AST
271: * @param rootNode
272: * @return list of Strings
273: */
274: private static List getASTAsStringArray(Node rootNode) {
275: /*
276: * this assumes that we are passed in the root
277: * node of the code block
278: */
279:
280: Token t = rootNode.getFirstToken();
281: Token tLast = rootNode.getLastToken();
282:
283: /*
284: * now, run down the part of the tree bounded by
285: * our first and last tokens
286: */
287:
288: List list = new ArrayList();
289:
290: while (t != tLast) {
291: list.add(NodeUtils.tokenLiteral(t));
292: t = t.next;
293: }
294:
295: /*
296: * make sure we get the last one...
297: */
298:
299: list.add(NodeUtils.tokenLiteral(t));
300:
301: return list;
302: }
303:
304: /**
305: * For debugging purposes. Formats the arguments from
306: * <code>argArray</code> and appends them to <code>buf</code>.
307: *
308: * @param buf A StringBuffer. If null, a new StringBuffer is allocated.
309: * @param argArray The Macro arguments to format
310: *
311: * @return A StringBuffer containing the formatted arguments. If a StringBuffer
312: * has passed in as buf, this method returns it.
313: */
314: public static final StringBuffer macroToString(
315: final StringBuffer buf, final String[] argArray) {
316: StringBuffer ret = (buf == null) ? new StringBuffer() : buf;
317:
318: ret.append('#').append(argArray[0]).append("( ");
319: for (int i = 1; i < argArray.length; i++) {
320: ret.append(' ').append(argArray[i]);
321: }
322: ret.append(" )");
323: return ret;
324: }
325: }
|