001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
015: * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
016: * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
017: * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
018: * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
019: *
020: * Alternatively, the contents of this file may be used under the terms of
021: * either of the GNU General Public License Version 2 or later (the "GPL"),
022: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
023: * in which case the provisions of the GPL or the LGPL are applicable instead
024: * of those above. If you wish to allow use of your version of this file only
025: * under the terms of either the GPL or the LGPL, and not to allow others to
026: * use your version of this file under the terms of the CPL, indicate your
027: * decision by deleting the provisions above and replace them with the notice
028: * and other provisions required by the GPL or the LGPL. If you do not delete
029: * the provisions above, a recipient may use your version of this file under
030: * the terms of any one of the CPL, the GPL or the LGPL.
031: ***** END LICENSE BLOCK *****/package org.jruby.runtime.callback;
032:
033: import java.lang.reflect.InvocationTargetException;
034: import java.lang.reflect.Method;
035:
036: import org.jruby.exceptions.JumpException;
037: import org.jruby.exceptions.RaiseException;
038: import org.jruby.exceptions.MainExitException;
039: import org.jruby.exceptions.ThreadKill;
040: import org.jruby.runtime.Arity;
041: import org.jruby.runtime.Block;
042: import org.jruby.runtime.builtin.IRubyObject;
043:
044: /**
045: * A wrapper for <code>java.lang.reflect.Method</code> objects which implement Ruby methods.
046: */
047: public class ReflectionCallback implements Callback {
048: private Method method;
049: private Class type;
050: private String methodName;
051: private Class[] argumentTypes;
052: private boolean isRestArgs;
053: private Arity arity;
054: private boolean isStaticMethod;
055: private boolean fast;
056:
057: public ReflectionCallback(Class type, String methodName,
058: Class[] argumentTypes, boolean isRestArgs,
059: boolean isStaticMethod, Arity arity, boolean fast) {
060: this .type = type;
061: this .methodName = methodName;
062: this .argumentTypes = argumentTypes;
063: this .isRestArgs = isRestArgs;
064: this .isStaticMethod = isStaticMethod;
065: this .arity = arity;
066: this .fast = fast;
067:
068: assert type != null;
069: assert methodName != null;
070: assert arity != null;
071:
072: loadMethod(fast);
073: }
074:
075: private void loadMethod(boolean fast) {
076: Class[] args;
077:
078: if (isStaticMethod) {
079: Class[] types = new Class[argumentTypes.length + 1];
080: System.arraycopy(argumentTypes, 0, types, 1,
081: argumentTypes.length);
082: types[0] = IRubyObject.class;
083: args = types;
084: } else {
085: args = argumentTypes;
086: }
087:
088: // ENEBO: Perhaps slow but simple for now
089: if (!fast) {
090: Class[] types = new Class[args.length + 1];
091: System.arraycopy(args, 0, types, 0, args.length);
092: types[args.length] = Block.class;
093: args = types;
094: }
095:
096: try {
097: method = type.getMethod(methodName, args);
098: } catch (NoSuchMethodException e) {
099: throw new RuntimeException(
100: "NoSuchMethodException: Cannot get method \""
101: + methodName + "\" in class \""
102: + type.getName() + "\" by Reflection.");
103: } catch (SecurityException e) {
104: throw new RuntimeException(
105: "SecurityException: Cannot get method \""
106: + methodName + "\" in class \""
107: + type.getName() + "\" by Reflection.");
108: }
109: }
110:
111: /**
112: * Returns an object array that collects all rest arguments in its own object array which
113: * is then put into the last slot of the first object array. That is, assuming that this
114: * callback expects one required argument and any number of rest arguments, an input of
115: * <code>[1, 2, 3]</code> is transformed into <code>[1, [2, 3]]</code>.
116: */
117: protected final Object[] packageRestArgumentsForReflection(
118: final Object[] originalArgs) {
119: IRubyObject[] restArray = new IRubyObject[originalArgs.length
120: - (argumentTypes.length - 1)];
121: Object[] result = new Object[argumentTypes.length];
122: try {
123: System.arraycopy(originalArgs, argumentTypes.length - 1,
124: restArray, 0, originalArgs.length
125: - (argumentTypes.length - 1));
126: } catch (ArrayIndexOutOfBoundsException e) {
127: assert false : e;
128: return null;
129: }
130: System.arraycopy(originalArgs, 0, result, 0,
131: argumentTypes.length - 1);
132: result[argumentTypes.length - 1] = restArray;
133: return result;
134: }
135:
136: /**
137: * Invokes the Ruby method. Actually, this methods delegates to an internal version
138: * that may throw the usual Java reflection exceptions. Ruby exceptions are rethrown,
139: * other exceptions throw an AssertError and abort the execution of the Ruby program.
140: * They should never happen.
141: */
142: /**
143: * Calls a wrapped Ruby method for the specified receiver with the specified arguments.
144: */
145: public IRubyObject execute(IRubyObject recv, IRubyObject[] oargs,
146: Block block) {
147: arity.checkArity(recv.getRuntime(), oargs);
148:
149: Object[] methodArgs = oargs;
150:
151: if (isRestArgs) {
152: methodArgs = packageRestArgumentsForReflection(methodArgs);
153: }
154: try {
155: IRubyObject receiver = recv;
156: if (isStaticMethod) {
157: Object[] args = new Object[methodArgs.length
158: + (fast ? 1 : 2)];
159: System.arraycopy(methodArgs, 0, args, 1,
160: methodArgs.length);
161: args[0] = recv;
162: if (!fast)
163: args[methodArgs.length + 1] = block;
164: receiver = null;
165: methodArgs = args;
166: } else {
167: Object[] args = new Object[methodArgs.length
168: + (fast ? 0 : 1)];
169: System.arraycopy(methodArgs, 0, args, 0,
170: methodArgs.length);
171: if (!fast)
172: args[methodArgs.length] = block;
173: methodArgs = args;
174: }
175: return (IRubyObject) method.invoke(receiver, methodArgs);
176: } catch (InvocationTargetException e) {
177: if (e.getTargetException() instanceof RaiseException) {
178: throw (RaiseException) e.getTargetException();
179: } else if (e.getTargetException() instanceof JumpException) {
180: throw (JumpException) e.getTargetException();
181: } else if (e.getTargetException() instanceof ThreadKill) {
182: // allow it to bubble up
183: throw (ThreadKill) e.getTargetException();
184: } else if (e.getTargetException() instanceof Exception) {
185: if (e.getTargetException() instanceof MainExitException) {
186: throw (RuntimeException) e.getTargetException();
187: }
188: recv.getRuntime().getJavaSupport()
189: .handleNativeException(e.getTargetException());
190: return recv.getRuntime().getNil();
191: } else {
192: throw (Error) e.getTargetException();
193: }
194: } catch (IllegalAccessException e) {
195: StringBuffer message = new StringBuffer();
196: message.append(e.getMessage());
197: message.append(':');
198: message.append(" methodName=").append(methodName);
199: message.append(" recv=").append(recv.toString());
200: message.append(" type=").append(type.getName());
201: message.append(" methodArgs=[");
202: for (int i = 0; i < methodArgs.length; i++) {
203: message.append(methodArgs[i]);
204: message.append(' ');
205: }
206: message.append(']');
207: assert false : message.toString();
208: return null;
209: } catch (final IllegalArgumentException e) {
210: /* StringBuffer message = new StringBuffer();
211: message.append(e.getMessage());
212: message.append(':');
213: message.append(" methodName=").append(methodName);
214: message.append(" recv=").append(recv.toString());
215: message.append(" type=").append(type.getName());
216: message.append(" methodArgs=[");
217: for (int i = 0; i < methodArgs.length; i++) {
218: message.append(methodArgs[i]);
219: message.append(' ');
220: }
221: message.append(']');*/
222: assert false : e;
223: return null;
224: }
225: }
226:
227: /**
228: * Returns the arity of the wrapped Ruby method.
229: */
230: public Arity getArity() {
231: return arity;
232: }
233: }
|