Fix for JRUBY-2882. Handle error messages related to constructors better
[jruby.git] / src / org / jruby / javasupport / JavaMethod.java
blob6594aad47f83c6ea8196194d1383a443a867ea8d
1 /***** BEGIN LICENSE BLOCK *****
2 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Common Public
5 * License Version 1.0 (the "License"); you may not use this file
6 * except in compliance with the License. You may obtain a copy of
7 * the License at http://www.eclipse.org/legal/cpl-v10.html
9 * Software distributed under the License is distributed on an "AS
10 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 * implied. See the License for the specific language governing
12 * rights and limitations under the License.
14 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
15 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
16 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
17 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
18 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
19 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
20 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
21 * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
22 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
23 * Copyright (C) 2006 Kresten Krab Thorup <krab@gnu.org>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the CPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the CPL, the GPL or the LGPL.
36 ***** END LICENSE BLOCK *****/
37 package org.jruby.javasupport;
39 import java.lang.annotation.Annotation;
40 import java.lang.reflect.AccessibleObject;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.lang.reflect.Modifier;
44 import java.lang.reflect.Type;
46 import org.jruby.Ruby;
47 import org.jruby.RubyBoolean;
48 import org.jruby.RubyClass;
49 import org.jruby.RubyModule;
50 import org.jruby.RubyString;
51 import org.jruby.anno.JRubyClass;
52 import org.jruby.anno.JRubyMethod;
53 import org.jruby.javasupport.proxy.InternalJavaProxy;
54 import org.jruby.javasupport.proxy.JavaProxyClass;
55 import org.jruby.javasupport.proxy.JavaProxyMethod;
56 import org.jruby.runtime.ObjectAllocator;
57 import org.jruby.runtime.builtin.IRubyObject;
59 @JRubyClass(name="Java::JavaMethod")
60 public class JavaMethod extends JavaCallable {
61 private final Method method;
62 private final Class<?>[] parameterTypes;
63 private final JavaUtil.JavaConverter returnConverter;
65 public static RubyClass createJavaMethodClass(Ruby runtime, RubyModule javaModule) {
66 // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't intend for people to monkey with
67 // this type and it can't be marshalled. Confirm. JRUBY-415
68 RubyClass result =
69 javaModule.defineClassUnder("JavaMethod", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
71 JavaAccessibleObject.registerRubyMethods(runtime, result);
72 JavaCallable.registerRubyMethods(runtime, result);
74 result.defineAnnotatedMethods(JavaMethod.class);
76 return result;
79 public JavaMethod(Ruby runtime, Method method) {
80 super(runtime, runtime.getJavaSupport().getJavaMethodClass());
81 this.method = method;
82 this.parameterTypes = method.getParameterTypes();
84 // Special classes like Collections.EMPTY_LIST are inner classes that are private but
85 // implement public interfaces. Their methods are all public methods for the public
86 // interface. Let these public methods execute via setAccessible(true).
87 if (Modifier.isPublic(method.getModifiers()) &&
88 Modifier.isPublic(method.getClass().getModifiers()) &&
89 !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
90 accessibleObject().setAccessible(true);
93 returnConverter = JavaUtil.getJavaConverter(method.getReturnType());
96 public static JavaMethod create(Ruby runtime, Method method) {
97 return new JavaMethod(runtime, method);
100 public static JavaMethod create(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
101 try {
102 Method method = javaClass.getMethod(methodName, argumentTypes);
103 return create(runtime, method);
104 } catch (NoSuchMethodException e) {
105 throw runtime.newNameError("undefined method '" + methodName + "' for class '" + javaClass.getName() + "'",
106 methodName);
110 public static JavaMethod createDeclared(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
111 try {
112 return create(runtime, javaClass.getDeclaredMethod(methodName, argumentTypes));
113 } catch (NoSuchMethodException e) {
114 throw runtime.newNameError("undefined method '" + methodName + "' for class '" + javaClass.getName() + "'",
115 methodName);
119 public static JavaMethod getMatchingDeclaredMethod(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
120 // FIXME: do we really want 'declared' methods? includes private/protected, and does _not_
121 // include superclass methods. also, the getDeclared calls may throw SecurityException if
122 // we're running under a restrictive security policy.
123 try {
124 return create(runtime, javaClass.getDeclaredMethod(methodName, argumentTypes));
125 } catch (NoSuchMethodException e) {
126 // search through all declared methods to find a closest match
127 MethodSearch: for (Method method : javaClass.getDeclaredMethods()) {
128 if (method.getName().equals(methodName)) {
129 Class<?>[] targetTypes = method.getParameterTypes();
131 // for zero args case we can stop searching
132 if (targetTypes.length == 0 && argumentTypes.length == 0) {
133 return create(runtime, method);
136 TypeScan: for (int i = 0; i < argumentTypes.length; i++) {
137 if (i >= targetTypes.length) continue MethodSearch;
139 if (targetTypes[i].isAssignableFrom(argumentTypes[i])) {
140 continue TypeScan;
141 } else {
142 continue MethodSearch;
146 // if we get here, we found a matching method, use it
147 // TODO: choose narrowest method by continuing to search
148 return create(runtime, method);
152 // no matching method found
153 return null;
156 public boolean equals(Object other) {
157 return other instanceof JavaMethod &&
158 this.method == ((JavaMethod)other).method;
161 public int hashCode() {
162 return method.hashCode();
165 @JRubyMethod
166 public RubyString name() {
167 return getRuntime().newString(method.getName());
170 public int getArity() {
171 return parameterTypes.length;
174 @JRubyMethod(name = "public?")
175 public RubyBoolean public_p() {
176 return getRuntime().newBoolean(Modifier.isPublic(method.getModifiers()));
179 @JRubyMethod(name = "final?")
180 public RubyBoolean final_p() {
181 return getRuntime().newBoolean(Modifier.isFinal(method.getModifiers()));
184 @JRubyMethod(rest = true)
185 public IRubyObject invoke(IRubyObject[] args) {
186 if (args.length != 1 + getArity()) {
187 throw getRuntime().newArgumentError(args.length, 1 + getArity());
190 Object[] arguments = new Object[args.length - 1];
191 convertArguments(getRuntime(), arguments, args, 1);
193 IRubyObject invokee = args[0];
194 if(invokee.isNil()) {
195 return invokeWithExceptionHandling(method, null, arguments);
198 Object javaInvokee = JavaUtil.unwrapJavaObject(getRuntime(), invokee, "invokee not a java object").getValue();
200 if (! method.getDeclaringClass().isInstance(javaInvokee)) {
201 throw getRuntime().newTypeError("invokee not instance of method's class (" +
202 "got" + javaInvokee.getClass().getName() + " wanted " +
203 method.getDeclaringClass().getName() + ")");
207 // this test really means, that this is a ruby-defined subclass of a java class
209 if (javaInvokee instanceof InternalJavaProxy &&
210 // don't bother to check if final method, it won't
211 // be there (not generated, can't be!)
212 !Modifier.isFinal(method.getModifiers())) {
213 JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee)
214 .___getProxyClass();
215 JavaProxyMethod jpm;
216 if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null &&
217 jpm.hasSuperImplementation()) {
218 return invokeWithExceptionHandling(jpm.getSuperMethod(), javaInvokee, arguments);
221 return invokeWithExceptionHandling(method, javaInvokee, arguments);
224 public IRubyObject invoke(IRubyObject self, Object[] args) {
225 if (args.length != getArity()) {
226 throw getRuntime().newArgumentError(args.length, getArity());
229 if (! (self instanceof JavaObject)) {
230 throw getRuntime().newTypeError("invokee not a java object");
232 Object javaInvokee = ((JavaObject) self).getValue();
234 if (! method.getDeclaringClass().isInstance(javaInvokee)) {
235 throw getRuntime().newTypeError("invokee not instance of method's class (" +
236 "got" + javaInvokee.getClass().getName() + " wanted " +
237 method.getDeclaringClass().getName() + ")");
241 // this test really means, that this is a ruby-defined subclass of a java class
243 if (javaInvokee instanceof InternalJavaProxy &&
244 // don't bother to check if final method, it won't
245 // be there (not generated, can't be!)
246 !Modifier.isFinal(method.getModifiers())) {
247 JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee)
248 .___getProxyClass();
249 JavaProxyMethod jpm;
250 if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null &&
251 jpm.hasSuperImplementation()) {
252 return invokeWithExceptionHandling(jpm.getSuperMethod(), javaInvokee, args);
255 return invokeWithExceptionHandling(method, javaInvokee, args);
258 @JRubyMethod(rest = true)
259 public IRubyObject invoke_static(IRubyObject[] args) {
260 if (args.length != getArity()) {
261 throw getRuntime().newArgumentError(args.length, getArity());
263 Object[] arguments = new Object[args.length];
264 System.arraycopy(args, 0, arguments, 0, arguments.length);
265 convertArguments(getRuntime(), arguments, args, 0);
266 return invokeWithExceptionHandling(method, null, arguments);
269 public IRubyObject invoke_static(Object[] args) {
270 if (args.length != getArity()) {
271 throw getRuntime().newArgumentError(args.length, getArity());
274 return invokeWithExceptionHandling(method, null, args);
277 @JRubyMethod
278 public IRubyObject return_type() {
279 Class<?> klass = method.getReturnType();
281 if (klass.equals(void.class)) {
282 return getRuntime().getNil();
284 return JavaClass.get(getRuntime(), klass);
287 @JRubyMethod
288 public IRubyObject type_parameters() {
289 return Java.getInstance(getRuntime(), method.getTypeParameters());
292 private IRubyObject invokeWithExceptionHandling(Method method, Object javaInvokee, Object[] arguments) {
293 try {
294 Object result = method.invoke(javaInvokee, arguments);
295 return returnConverter.convert(getRuntime(), result);
296 } catch (IllegalArgumentException iae) {
297 throw getRuntime().newTypeError("for method " + method.getName() + " expected " + argument_types().inspect() + "; got: "
298 + dumpArgTypes(arguments)
299 + "; error: " + iae.getMessage());
300 } catch (IllegalAccessException iae) {
301 throw getRuntime().newTypeError("illegal access on '" + method.getName() + "': " + iae.getMessage());
302 } catch (InvocationTargetException ite) {
303 getRuntime().getJavaSupport().handleNativeException(ite.getTargetException());
304 // This point is only reached if there was an exception handler installed.
305 return getRuntime().getNil();
309 private String dumpArgTypes(Object[] arguments) {
310 StringBuilder str = new StringBuilder("[");
311 for (int i = 0; i < arguments.length; i++) {
312 if (i > 0) {
313 str.append(",");
315 if (arguments[i] == null) {
316 str.append("null");
317 } else {
318 str.append(arguments[i].getClass().getName());
321 str.append("]");
322 return str.toString();
325 private void convertArguments(Ruby runtime, Object[] arguments, Object[] args, int from) {
326 Class<?>[] types = parameterTypes;
327 for (int i = arguments.length; --i >= 0; ) {
328 arguments[i] = JavaUtil.convertArgument(runtime, args[i+from], types[i]);
332 public Class<?>[] getParameterTypes() {
333 return parameterTypes;
336 public Class<?>[] getExceptionTypes() {
337 return method.getExceptionTypes();
340 public Type[] getGenericParameterTypes() {
341 return method.getGenericParameterTypes();
344 public Type[] getGenericExceptionTypes() {
345 return method.getGenericExceptionTypes();
348 public Annotation[][] getParameterAnnotations() {
349 return method.getParameterAnnotations();
352 public boolean isVarArgs() {
353 return method.isVarArgs();
356 protected String nameOnInspection() {
357 return "#<" + getType().toString() + "/" + method.getName() + "(";
360 public RubyBoolean static_p() {
361 return getRuntime().newBoolean(isStatic());
364 public RubyBoolean bridge_p() {
365 return getRuntime().newBoolean(method.isBridge());
368 private boolean isStatic() {
369 return Modifier.isStatic(method.getModifiers());
372 public int getModifiers() {
373 return method.getModifiers();
376 public String toGenericString() {
377 return method.toGenericString();
380 protected AccessibleObject accessibleObject() {
381 return method;