001: // ============================================================================
002: // $Id: InvokeMethod.java,v 1.17 2006/05/22 03:28:53 davidahall Exp $
003: // Copyright (c) 2003-2005 David A. Hall
004: // ============================================================================
005: // The contents of this file are subject to the Common Development and
006: // Distribution License (CDDL), Version 1.0 (the License); you may not use this
007: // file except in compliance with the License. You should have received a copy
008: // of the the License along with this file: if not, a copy of the License is
009: // available from Sun Microsystems, Inc.
010: //
011: // http://www.sun.com/cddl/cddl.html
012: //
013: // From time to time, the license steward (initially Sun Microsystems, Inc.) may
014: // publish revised and/or new versions of the License. You may not use,
015: // distribute, or otherwise make this file available under subsequent versions
016: // of the License.
017: //
018: // Alternatively, the contents of this file may be used under the terms of the
019: // GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
020: // case the provisions of the LGPL are applicable instead of those above. If you
021: // wish to allow use of your version of this file only under the terms of the
022: // LGPL, and not to allow others to use your version of this file under the
023: // terms of the CDDL, indicate your decision by deleting the provisions above
024: // and replace them with the notice and other provisions required by the LGPL.
025: // If you do not delete the provisions above, a recipient may use your version
026: // of this file under the terms of either the CDDL or the LGPL.
027: //
028: // This library is distributed in the hope that it will be useful,
029: // but WITHOUT ANY WARRANTY; without even the implied warranty of
030: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
031: // ============================================================================
032:
033: package net.sf.jga.fn.property;
034:
035: import java.lang.reflect.InvocationTargetException;
036: import java.lang.reflect.Method;
037: import java.text.MessageFormat;
038: import net.sf.jga.fn.BinaryFunctor;
039: import net.sf.jga.fn.EvaluationException;
040: import net.sf.jga.fn.UnaryFunctor;
041: import net.sf.jga.fn.adaptor.ChainBinary;
042: import net.sf.jga.fn.adaptor.ChainUnary;
043: import net.sf.jga.util.ArrayUtils;
044:
045: /**
046: * Functor that invokes a method described at construction and returns the
047: * result or null if the result is void. The arguments must be passed to the
048: * functor in an array, and the values must be assignable to the corresponding
049: * classes given at construction.
050: * <p>
051: * Note that declaring the return type incorrectly can result in
052: * ClassCastExceptions being thrown when the functor is invoked: the compiler
053: * cannot check the return type of a reflectively loaded method.
054: * <p>
055: * Copyright © 2003-2005 David A. Hall
056: *
057: * @author <a href="mailto:davidahall@users.sourceforge.net">David A. Hall</a>
058: **/
059:
060: public class InvokeMethod<T, R> extends BinaryFunctor<T, Object[], R> {
061:
062: static final long serialVersionUID = -4632096384509133504L;
063:
064: // The class in which the method is accessed (not necessarily the class in which it is defined)
065: private Class<T> _objclass;
066:
067: // The name of the method to invoke
068: private String _methName;
069:
070: // The types of the arguments the method requires
071: private Class[] _argtypes;
072:
073: // The method to be invoked (lazily loaded)
074: private transient Method _meth;
075:
076: /**
077: * Builds a InvokeMethod for a given method, using the given class
078: * to find the desired method.
079: * @throws IllegalArgumentException if the method name is null or empty, or
080: * if the argument type array is null.
081: */
082: public InvokeMethod(Class<T> objClass, Method method) {
083: if (method == null) {
084: String msg = "Must supply method";
085: throw new IllegalArgumentException(msg);
086: }
087:
088: if (objClass == null) {
089: String msg = "Must supply object class";
090: throw new IllegalArgumentException(msg);
091: }
092:
093: if (!method.getDeclaringClass().isAssignableFrom(objClass)) {
094: String msg = "Method {0} not defined for class {1}";
095: Object[] args = { method.getName(), objClass.getName() };
096: throw new IllegalArgumentException(MessageFormat.format(
097: msg, args));
098: }
099:
100: _objclass = objClass;
101: _meth = method;
102: _methName = method.getName();
103: _argtypes = method.getParameterTypes();
104: }
105:
106: /**
107: * Builds an InvokeMethod for a given method, using the given class
108: * to find the desired method. Note that this is a convenience constructor
109: * for a common case: the case where the method takes a single argument.
110: * It is still necessary for the argument passed to the fn method to be an
111: * array, in this case it must be an array of length 1 containing a value of
112: * of type argtype. A ClassCastException may be thrown if this functor is
113: * called incorrectly: if the line number in the associated stack dump points
114: * to the class statement (on or about line 45), then the argument was likely
115: * passed without being wrapped in an array.
116: * @throws IllegalArgumentException if the method name is null or empty, or
117: * if the argument type array is null.
118: */
119: public InvokeMethod(Class<T> objClass, String methName,
120: Class argtype) {
121: this (objClass, methName, new Class[] { argtype });
122: }
123:
124: /**
125: * Builds a InvokeMethod for the given method, using the given class array
126: * to find the desired method.
127: * @throws IllegalArgumentException if the method name is null or empty, or
128: * if the argument type array is null.
129: */
130: public InvokeMethod(Class<T> objClass, String methName,
131: Class... argtypes) {
132: if (methName == null || methName.length() == 0) {
133: String msg = "Must supply method name";
134: throw new IllegalArgumentException(msg);
135: }
136:
137: if (objClass == null) {
138: String msg = "Must supply object class";
139: throw new IllegalArgumentException(msg);
140: }
141:
142: _methName = methName;
143: _objclass = objClass;
144: _argtypes = argtypes;
145:
146: try {
147: _meth = _objclass.getMethod(_methName, _argtypes);
148: } catch (NoSuchMethodException x) {
149: String msg = "No method {0} for class {1} that takes an argument(s) of type {2}";
150: Object[] args = new Object[] { _methName,
151: _objclass.getName(), ArrayUtils.toString(_argtypes) };
152: IllegalArgumentException iax = new IllegalArgumentException(
153: MessageFormat.format(msg, args));
154: iax.initCause(x);
155: throw iax;
156: }
157: }
158:
159: /**
160: * Returns the class on which the method is invoked.
161: */
162: // UNTESTED
163: public Class<T> getObjectType() {
164: return _objclass;
165: }
166:
167: /**
168: * Returns the name of the method that this functor invokes.
169: */
170: public String getMethodName() {
171: return _methName;
172: }
173:
174: /**
175: * Returns the type of the method
176: */
177: public Class<R> getReturnType() {
178: return (Class<R>) _meth.getReturnType();
179: }
180:
181: /**
182: * Lazy loads the method
183: */
184: public synchronized Method getMethod() throws NoSuchMethodException {
185: if (_meth == null)
186: _meth = _objclass.getMethod(_methName, _argtypes);
187:
188: return _meth;
189: }
190:
191: // BinaryFunctor interface
192:
193: public R fn(T obj, Object[] args) {
194: try {
195: // @SuppressWarnings
196: // There's nothing we can do about this other than warn the users
197: // to make sure that they don't use an inappropriate return type
198:
199: return (R) getMethod(/*_argtypes*/).invoke(obj, args);
200: } catch (NoSuchMethodException x) {
201: String msg = "No method {0} for class {1} that takes an argument(s) of type {2}";
202: Object[] msgargs = new Object[] { _methName,
203: _objclass.getName(), ArrayUtils.toString(_argtypes) };
204: throw new EvaluationException(MessageFormat.format(msg,
205: msgargs), x);
206: } catch (ClassCastException x) {
207: String msg = "ClassCastException: " + _objclass + "."
208: + _methName + "(" + ArrayUtils.toString(args) + ")";
209: throw new EvaluationException(msg, x);
210: } catch (IllegalAccessException x) {
211: String msg = _objclass + "." + _methName
212: + " is not accessible";
213: throw new EvaluationException(msg, x);
214: } catch (InvocationTargetException x) {
215: String xmsg = x.getMessage();
216: String msg = "InvocationException: " + _objclass + "."
217: + _methName + "(" + ArrayUtils.toString(args) + ")"
218: + (xmsg != null ? (":" + xmsg) : "");
219: throw new EvaluationException(msg, x);
220: }
221: }
222:
223: /**
224: * Calls the Visitor's <code>visit(InvokeMethod)</code> method, if it
225: * implements the nested Visitor interface.
226: */
227: public void accept(net.sf.jga.fn.Visitor v) {
228: if (v instanceof InvokeMethod.Visitor)
229: ((InvokeMethod.Visitor) v).visit(this );
230: else
231: v.visit(this );
232: }
233:
234: // Object overrides
235:
236: public String toString() {
237: return "InvokeMethod(" + _meth + ")";
238: }
239:
240: // AcyclicVisitor
241:
242: /**
243: * Interface for classes that may interpret a <b>InvokeMethod</b>
244: * function.
245: */
246: public interface Visitor extends net.sf.jga.fn.Visitor {
247: public void visit(InvokeMethod host);
248: }
249: }
|