001: package org.apache.velocity.runtime.parser.node;
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.lang.reflect.InvocationTargetException;
023:
024: import org.apache.commons.lang.ArrayUtils;
025: import org.apache.commons.lang.StringUtils;
026: import org.apache.velocity.app.event.EventHandlerUtil;
027: import org.apache.velocity.context.InternalContextAdapter;
028: import org.apache.velocity.exception.MethodInvocationException;
029: import org.apache.velocity.exception.TemplateInitException;
030: import org.apache.velocity.runtime.parser.Parser;
031: import org.apache.velocity.runtime.parser.ParserVisitor;
032: import org.apache.velocity.util.introspection.Info;
033: import org.apache.velocity.util.introspection.IntrospectionCacheData;
034: import org.apache.velocity.util.introspection.VelMethod;
035:
036: /**
037: * ASTMethod.java
038: *
039: * Method support for references : $foo.method()
040: *
041: * NOTE :
042: *
043: * introspection is now done at render time.
044: *
045: * Please look at the Parser.jjt file which is
046: * what controls the generation of this class.
047: *
048: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
049: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
050: * @version $Id: ASTMethod.java 489650 2006-12-22 13:31:58Z cbrisson $
051: */
052: public class ASTMethod extends SimpleNode {
053: private String methodName = "";
054: private int paramCount = 0;
055:
056: protected Info uberInfo;
057:
058: /**
059: * @param id
060: */
061: public ASTMethod(int id) {
062: super (id);
063: }
064:
065: /**
066: * @param p
067: * @param id
068: */
069: public ASTMethod(Parser p, int id) {
070: super (p, id);
071: }
072:
073: /**
074: * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.ParserVisitor, java.lang.Object)
075: */
076: public Object jjtAccept(ParserVisitor visitor, Object data) {
077: return visitor.visit(this , data);
078: }
079:
080: /**
081: * simple init - init our subtree and get what we can from
082: * the AST
083: * @param context
084: * @param data
085: * @return The init result
086: * @throws TemplateInitException
087: */
088: public Object init(InternalContextAdapter context, Object data)
089: throws TemplateInitException {
090: super .init(context, data);
091:
092: /*
093: * make an uberinfo - saves new's later on
094: */
095:
096: uberInfo = new Info(context.getCurrentTemplateName(),
097: getLine(), getColumn());
098: /*
099: * this is about all we can do
100: */
101:
102: methodName = getFirstToken().image;
103: paramCount = jjtGetNumChildren() - 1;
104:
105: return data;
106: }
107:
108: /**
109: * invokes the method. Returns null if a problem, the
110: * actual return if the method returns something, or
111: * an empty string "" if the method returns void
112: * @param o
113: * @param context
114: * @return Result or null.
115: * @throws MethodInvocationException
116: */
117: public Object execute(Object o, InternalContextAdapter context)
118: throws MethodInvocationException {
119: /*
120: * new strategy (strategery!) for introspection. Since we want
121: * to be thread- as well as context-safe, we *must* do it now,
122: * at execution time. There can be no in-node caching,
123: * but if we are careful, we can do it in the context.
124: */
125:
126: VelMethod method = null;
127:
128: Object[] params = new Object[paramCount];
129:
130: try {
131: /*
132: * sadly, we do need recalc the values of the args, as this can
133: * change from visit to visit
134: */
135:
136: final Class[] paramClasses = paramCount > 0 ? new Class[paramCount]
137: : ArrayUtils.EMPTY_CLASS_ARRAY;
138:
139: for (int j = 0; j < paramCount; j++) {
140: params[j] = jjtGetChild(j + 1).value(context);
141:
142: if (params[j] != null) {
143: paramClasses[j] = params[j].getClass();
144: }
145: }
146:
147: /*
148: * check the cache
149: */
150:
151: MethodCacheKey mck = new MethodCacheKey(methodName,
152: paramClasses);
153: IntrospectionCacheData icd = context.icacheGet(mck);
154:
155: /*
156: * like ASTIdentifier, if we have cache information, and the
157: * Class of Object o is the same as that in the cache, we are
158: * safe.
159: */
160:
161: if (icd != null
162: && (o != null && icd.contextData == o.getClass())) {
163:
164: /*
165: * get the method from the cache
166: */
167:
168: method = (VelMethod) icd.thingy;
169: } else {
170: /*
171: * otherwise, do the introspection, and then
172: * cache it
173: */
174:
175: method = rsvc.getUberspect().getMethod(
176: o,
177: methodName,
178: params,
179: new Info(context.getCurrentTemplateName(),
180: getLine(), getColumn()));
181:
182: if ((method != null) && (o != null)) {
183: icd = new IntrospectionCacheData();
184: icd.contextData = o.getClass();
185: icd.thingy = method;
186:
187: context.icachePut(mck, icd);
188: }
189: }
190:
191: /*
192: * if we still haven't gotten the method, either we are calling
193: * a method that doesn't exist (which is fine...) or I screwed
194: * it up.
195: */
196:
197: if (method == null) {
198: return null;
199: }
200: } catch (MethodInvocationException mie) {
201: /*
202: * this can come from the doIntrospection(), as the arg values
203: * are evaluated to find the right method signature. We just
204: * want to propogate it here, not do anything fancy
205: */
206:
207: throw mie;
208: }
209: /**
210: * pass through application level runtime exceptions
211: */
212: catch (RuntimeException e) {
213: throw e;
214: } catch (Exception e) {
215: /*
216: * can come from the doIntropection() also, from Introspector
217: */
218:
219: log
220: .error(
221: "ASTMethod.execute() : exception from introspection",
222: e);
223: return null;
224: }
225:
226: try {
227: /*
228: * get the returned object. It may be null, and that is
229: * valid for something declared with a void return type.
230: * Since the caller is expecting something to be returned,
231: * as long as things are peachy, we can return an empty
232: * String so ASTReference() correctly figures out that
233: * all is well.
234: */
235:
236: Object obj = method.invoke(o, params);
237:
238: if (obj == null) {
239: if (method.getReturnType() == Void.TYPE) {
240: return "";
241: }
242: }
243:
244: return obj;
245: } catch (InvocationTargetException ite) {
246: /*
247: * In the event that the invocation of the method
248: * itself throws an exception, we want to catch that
249: * wrap it, and throw. We don't log here as we want to figure
250: * out which reference threw the exception, so do that
251: * above
252: */
253:
254: /*
255: * let non-Exception Throwables go...
256: */
257:
258: Throwable t = ite.getTargetException();
259: if (t instanceof Exception) {
260: try {
261: return EventHandlerUtil.methodException(rsvc,
262: context, o.getClass(), methodName,
263: (Exception) t);
264: }
265:
266: /**
267: * If the event handler throws an exception, then wrap it
268: * in a MethodInvocationException. Don't pass through RuntimeExceptions like other
269: * similar catchall code blocks.
270: */
271: catch (Exception e) {
272: throw new MethodInvocationException(
273: "Invocation of method '" + methodName
274: + "' in " + o.getClass()
275: + " threw exception "
276: + e.toString(), e, methodName,
277: context.getCurrentTemplateName(), this
278: .getLine(), this .getColumn());
279: }
280: } else {
281: /*
282: * no event cartridge to override. Just throw
283: */
284:
285: throw new MethodInvocationException(
286: "Invocation of method '" + methodName
287: + "' in " + o.getClass()
288: + " threw exception "
289: + ite.getTargetException().toString(),
290: ite.getTargetException(), methodName, context
291: .getCurrentTemplateName(), this
292: .getLine(), this .getColumn());
293: }
294: }
295: /**
296: * pass through application level runtime exceptions
297: */
298: catch (RuntimeException e) {
299: throw e;
300: } catch (Exception e) {
301: log.error(
302: "ASTMethod.execute() : exception invoking method '"
303: + methodName + "' in " + o.getClass(), e);
304: return null;
305: }
306: }
307:
308: /**
309: * Internal class used as key for method cache. Combines
310: * ASTMethod fields with array of parameter classes. Has
311: * public access (and complete constructor) for unit test
312: * purposes.
313: */
314: public static class MethodCacheKey {
315: private final String methodName;
316: private final Class[] params;
317:
318: public MethodCacheKey(String methodName, Class[] params) {
319: /**
320: * Should never be initialized with nulls, but to be safe we refuse
321: * to accept them.
322: */
323: this .methodName = (methodName != null) ? methodName
324: : StringUtils.EMPTY;
325: this .params = (params != null) ? params
326: : ArrayUtils.EMPTY_CLASS_ARRAY;
327: }
328:
329: /**
330: * @see java.lang.Object#equals(java.lang.Object)
331: */
332: public boolean equals(Object o) {
333: /**
334: * note we skip the null test for methodName and params
335: * due to the earlier test in the constructor
336: */
337: if (o instanceof MethodCacheKey) {
338: final MethodCacheKey other = (MethodCacheKey) o;
339: if (params.length == other.params.length
340: && methodName.equals(other.methodName)) {
341: for (int i = 0; i < params.length; ++i) {
342: if (params[i] == null) {
343: if (params[i] != other.params[i]) {
344: return false;
345: }
346: } else if (!params[i].equals(other.params[i])) {
347: return false;
348: }
349: }
350: return true;
351: }
352: }
353: return false;
354: }
355:
356: /**
357: * @see java.lang.Object#hashCode()
358: */
359: public int hashCode() {
360: int result = 17;
361:
362: /**
363: * note we skip the null test for methodName and params
364: * due to the earlier test in the constructor
365: */
366: for (int i = 0; i < params.length; ++i) {
367: final Class param = params[i];
368: if (param != null) {
369: result = result * 37 + param.hashCode();
370: }
371: }
372:
373: result = result * 37 + methodName.hashCode();
374:
375: return result;
376: }
377: }
378:
379: /**
380: * @return Returns the methodName.
381: */
382: public String getMethodName() {
383: return methodName;
384: }
385:
386: }
|