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
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);
79 public JavaMethod(Ruby runtime
, Method method
) {
80 super(runtime
, runtime
.getJavaSupport().getJavaMethodClass());
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
) {
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() + "'",
110 public static JavaMethod
createDeclared(Ruby runtime
, Class
<?
> javaClass
, String methodName
, Class
<?
>[] argumentTypes
) {
112 return create(runtime
, javaClass
.getDeclaredMethod(methodName
, argumentTypes
));
113 } catch (NoSuchMethodException e
) {
114 throw runtime
.newNameError("undefined method '" + methodName
+ "' for class '" + javaClass
.getName() + "'",
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.
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
])) {
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
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();
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
)
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
)
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
);
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
);
288 public IRubyObject
type_parameters() {
289 return Java
.getInstance(getRuntime(), method
.getTypeParameters());
292 private IRubyObject
invokeWithExceptionHandling(Method method
, Object javaInvokee
, Object
[] arguments
) {
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
++) {
315 if (arguments
[i
] == null) {
318 str
.append(arguments
[i
].getClass().getName());
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() {