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) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
15 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
16 * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
17 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
18 * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
19 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
20 * Copyright (C) 2006 Kresten Krab Thorup <krab@gnu.org>
21 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
22 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the CPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the CPL, the GPL or the LGPL.
35 ***** END LICENSE BLOCK *****/
36 package org
.jruby
.javasupport
;
38 import java
.io
.ByteArrayOutputStream
;
39 import java
.io
.InputStream
;
40 import java
.io
.IOException
;
41 import java
.lang
.reflect
.Array
;
42 import java
.lang
.reflect
.Constructor
;
43 import java
.lang
.reflect
.Field
;
44 import java
.lang
.reflect
.Method
;
45 import java
.lang
.reflect
.Modifier
;
46 import java
.util
.ArrayList
;
47 import java
.util
.HashMap
;
48 import java
.util
.Iterator
;
49 import java
.util
.List
;
51 import java
.util
.concurrent
.locks
.ReentrantLock
;
52 import java
.util
.regex
.Matcher
;
53 import java
.util
.regex
.Pattern
;
55 import org
.jruby
.Ruby
;
56 import org
.jruby
.RubyArray
;
57 import org
.jruby
.RubyBoolean
;
58 import org
.jruby
.RubyClass
;
59 import org
.jruby
.RubyFixnum
;
60 import org
.jruby
.RubyInteger
;
61 import org
.jruby
.RubyModule
;
62 import org
.jruby
.RubyProc
;
63 import org
.jruby
.RubyString
;
64 import org
.jruby
.anno
.JRubyMethod
;
65 import org
.jruby
.anno
.JRubyClass
;
66 import org
.jruby
.common
.IRubyWarnings
.ID
;
67 import org
.jruby
.exceptions
.RaiseException
;
68 import org
.jruby
.internal
.runtime
.methods
.DynamicMethod
;
69 import org
.jruby
.javasupport
.util
.RuntimeHelpers
;
70 import org
.jruby
.runtime
.Arity
;
71 import org
.jruby
.runtime
.Block
;
72 import org
.jruby
.runtime
.CallType
;
73 import org
.jruby
.runtime
.ObjectAllocator
;
74 import org
.jruby
.runtime
.Visibility
;
75 import org
.jruby
.runtime
.builtin
.IRubyObject
;
76 import org
.jruby
.runtime
.callback
.Callback
;
77 import org
.jruby
.util
.ByteList
;
78 import org
.jruby
.util
.IdUtil
;
79 import org
.jruby
.util
.collections
.IntHashMap
;
81 @JRubyClass(name
="Java::JavaClass", parent
="Java::JavaObject")
82 public class JavaClass
extends JavaObject
{
84 // some null objects to simplify later code
85 private static final Class
<?
>[] EMPTY_CLASS_ARRAY
= new Class
<?
>[] {};
86 private static final Method
[] EMPTY_METHOD_ARRAY
= new Method
[] {};
87 private static final Field
[] EMPTY_FIELD_ARRAY
= new Field
[] {};
89 private static class AssignedName
{
90 // to override an assigned name, the type must be less than
91 // or equal to the assigned type. so a field name in a subclass
92 // will override an alias in a superclass, but not a method.
93 static final int RESERVED
= 0;
94 static final int METHOD
= 1;
95 static final int FIELD
= 2;
96 static final int PROTECTED_METHOD
= 3;
97 static final int WEAKLY_RESERVED
= 4; // we'll be peeved, but not devastated, if you override
98 static final int ALIAS
= 5;
99 // yes, protected fields are weaker than aliases. many conflicts
100 // in the old AWT code, for example, where you really want 'size'
101 // to mean the public method getSize, not the protected field 'size'.
102 static final int PROTECTED_FIELD
= 6;
106 AssignedName(String name
, int type
) {
112 // TODO: other reserved names?
113 private static final Map
<String
, AssignedName
> RESERVED_NAMES
= new HashMap
<String
, AssignedName
>();
115 RESERVED_NAMES
.put("__id__", new AssignedName("__id__", AssignedName
.RESERVED
));
116 RESERVED_NAMES
.put("__send__", new AssignedName("__send__", AssignedName
.RESERVED
));
117 RESERVED_NAMES
.put("class", new AssignedName("class", AssignedName
.RESERVED
));
118 RESERVED_NAMES
.put("initialize", new AssignedName("initialize", AssignedName
.RESERVED
));
119 RESERVED_NAMES
.put("object_id", new AssignedName("object_id", AssignedName
.RESERVED
));
120 RESERVED_NAMES
.put("private", new AssignedName("private", AssignedName
.RESERVED
));
121 RESERVED_NAMES
.put("protected", new AssignedName("protected", AssignedName
.RESERVED
));
122 RESERVED_NAMES
.put("public", new AssignedName("public", AssignedName
.RESERVED
));
124 // weakly reserved names
125 RESERVED_NAMES
.put("id", new AssignedName("id", AssignedName
.WEAKLY_RESERVED
));
127 private static final Map
<String
, AssignedName
> STATIC_RESERVED_NAMES
= new HashMap
<String
, AssignedName
>(RESERVED_NAMES
);
129 STATIC_RESERVED_NAMES
.put("new", new AssignedName("new", AssignedName
.RESERVED
));
131 private static final Map
<String
, AssignedName
> INSTANCE_RESERVED_NAMES
= new HashMap
<String
, AssignedName
>(RESERVED_NAMES
);
133 private static abstract class NamedCallback
implements Callback
{
134 static final int STATIC_FIELD
= 1;
135 static final int STATIC_METHOD
= 2;
136 static final int INSTANCE_FIELD
= 3;
137 static final int INSTANCE_METHOD
= 4;
140 Visibility visibility
= Visibility
.PUBLIC
;
143 NamedCallback (String name
, int type
) {
147 abstract void install(RubyClass proxy
);
148 // small hack to save a cast later on
149 boolean hasLocalMethod() {
153 return visibility
== Visibility
.PUBLIC
;
155 boolean isProtected() {
156 return visibility
== Visibility
.PROTECTED
;
160 private static abstract class FieldCallback
extends NamedCallback
{
164 FieldCallback(String name
, int type
, Field field
) {
170 private class StaticFieldGetter
extends FieldCallback
{
171 StaticFieldGetter(){}
172 StaticFieldGetter(String name
, Field field
) {
173 super(name
,STATIC_FIELD
,field
);
175 void install(RubyClass proxy
) {
176 proxy
.getSingletonClass().defineFastMethod(this.name
,this,this.visibility
);
178 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
179 if (javaField
== null) {
180 javaField
= new JavaField(getRuntime(),field
);
182 return Java
.java_to_ruby(self
,javaField
.static_value(),Block
.NULL_BLOCK
);
184 public Arity
getArity() {
185 return Arity
.NO_ARGUMENTS
;
189 private class StaticFieldSetter
extends FieldCallback
{
190 StaticFieldSetter(){}
191 StaticFieldSetter(String name
, Field field
) {
192 super(name
,STATIC_FIELD
,field
);
194 void install(RubyClass proxy
) {
195 proxy
.getSingletonClass().defineFastMethod(this.name
,this,this.visibility
);
197 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
198 if (javaField
== null) {
199 javaField
= new JavaField(getRuntime(),field
);
201 return Java
.java_to_ruby(self
,
202 javaField
.set_static_value(Java
.ruby_to_java(self
,args
[0],Block
.NULL_BLOCK
)),
205 public Arity
getArity() {
206 return Arity
.ONE_ARGUMENT
;
210 private class InstanceFieldGetter
extends FieldCallback
{
211 InstanceFieldGetter(){}
212 InstanceFieldGetter(String name
, Field field
) {
213 super(name
,INSTANCE_FIELD
,field
);
215 void install(RubyClass proxy
) {
216 proxy
.defineFastMethod(this.name
,this,this.visibility
);
218 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
219 if (javaField
== null) {
220 javaField
= new JavaField(getRuntime(),field
);
222 return Java
.java_to_ruby(self
,
223 javaField
.value(self
.getInstanceVariables().fastGetInstanceVariable("@java_object")),
226 public Arity
getArity() {
227 return Arity
.NO_ARGUMENTS
;
231 private class InstanceFieldSetter
extends FieldCallback
{
232 InstanceFieldSetter(){}
233 InstanceFieldSetter(String name
, Field field
) {
234 super(name
,INSTANCE_FIELD
,field
);
236 void install(RubyClass proxy
) {
237 proxy
.defineFastMethod(this.name
,this,this.visibility
);
239 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
240 if (javaField
== null) {
241 javaField
= new JavaField(getRuntime(),field
);
243 return Java
.java_to_ruby(self
,
244 javaField
.set_value(self
.getInstanceVariables().fastGetInstanceVariable("@java_object"),
245 Java
.ruby_to_java(self
,args
[0],Block
.NULL_BLOCK
)),
248 public Arity
getArity() {
249 return Arity
.ONE_ARGUMENT
;
253 private static abstract class MethodCallback
extends NamedCallback
{
254 private boolean haveLocalMethod
;
255 private List
<Method
> methods
;
256 protected List
<String
> aliases
;
257 protected JavaMethod javaMethod
;
258 protected IntHashMap javaMethods
;
259 protected volatile boolean initialized
;
261 MethodCallback(String name
, int type
) {
265 // called only by initializing thread; no synchronization required
266 void addMethod(Method method
, Class
<?
> javaClass
) {
267 if (methods
== null) {
268 methods
= new ArrayList
<Method
>();
271 haveLocalMethod
|= javaClass
== method
.getDeclaringClass();
274 // called only by initializing thread; no synchronization required
275 void addAlias(String alias
) {
276 if (aliases
== null) {
277 aliases
= new ArrayList
<String
>();
279 if (!aliases
.contains(alias
))
283 // modified only by addMethod; no synchronization required
284 boolean hasLocalMethod () {
285 return haveLocalMethod
;
289 // TODO: rework Java.matching_methods_internal and
290 // ProxyData.method_cache, since we really don't need to be passing
291 // around RubyArray objects anymore.
292 synchronized void createJavaMethods(Ruby runtime
) {
293 if (!initialized
) { // read-volatile
294 if (methods
!= null) {
295 if (methods
.size() == 1) {
296 javaMethod
= JavaMethod
.create(runtime
, methods
.get(0));
298 javaMethods
= new IntHashMap();
299 for (Method method
: methods
) {
300 // TODO: deal with varargs
301 int arity
= method
.getParameterTypes().length
;
302 RubyArray methodsForArity
= (RubyArray
)javaMethods
.get(arity
);
303 if (methodsForArity
== null) {
304 methodsForArity
= RubyArray
.newArrayLight(runtime
);
305 javaMethods
.put(arity
,methodsForArity
);
307 methodsForArity
.append(JavaMethod
.create(runtime
,method
));
312 initialized
= true; // write-volatile
316 void raiseNoMatchingMethodError(IRubyObject proxy
, IRubyObject
[] args
, int start
) {
317 int len
= args
.length
;
318 List
<Object
> argTypes
= new ArrayList
<Object
>(len
- start
);
319 for (int i
= start
; i
< len
; i
++) {
320 argTypes
.add(((JavaClass
)((JavaObject
)args
[i
]).java_class()).getValue());
322 throw proxy
.getRuntime().newNameError("no " + this.name
+ " with arguments matching " + argTypes
+ " on object " + proxy
.callMethod(proxy
.getRuntime().getCurrentContext(),"inspect"), null);
326 private class StaticMethodInvoker
extends MethodCallback
{
327 StaticMethodInvoker(){}
328 StaticMethodInvoker(String name
) {
329 super(name
,STATIC_METHOD
);
332 void install(RubyClass proxy
) {
333 if (hasLocalMethod()) {
334 RubyClass singleton
= proxy
.getSingletonClass();
335 singleton
.defineFastMethod(this.name
,this,this.visibility
);
336 if (aliases
!= null && isPublic() ) {
337 singleton
.defineAliases(aliases
, this.name
);
343 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
344 createJavaMethods(self
.getRuntime());
346 // TODO: ok to convert args in place, rather than new array?
347 int len
= args
.length
;
348 IRubyObject
[] convertedArgs
= new IRubyObject
[len
];
349 for (int i
= len
; --i
>= 0; ) {
350 convertedArgs
[i
] = Java
.ruby_to_java(self
,args
[i
],Block
.NULL_BLOCK
);
353 if ((method
= javaMethod
) == null) {
355 RubyArray methods
= (RubyArray
)javaMethods
.get(len
);
356 if (methods
== null) {
357 raiseNoMatchingMethodError(self
,convertedArgs
,0);
359 method
= (JavaMethod
)Java
.matching_method_internal(JAVA_UTILITIES
, methods
, convertedArgs
, 0, len
);
361 return Java
.java_to_ruby(self
, method
.invoke_static(convertedArgs
), Block
.NULL_BLOCK
);
364 public Arity
getArity() {
365 return Arity
.OPTIONAL
;
369 private class InstanceMethodInvoker
extends MethodCallback
{
370 InstanceMethodInvoker(){}
371 InstanceMethodInvoker(String name
) {
372 super(name
,INSTANCE_METHOD
);
374 void install(RubyClass proxy
) {
375 if (hasLocalMethod()) {
376 proxy
.defineFastMethod(this.name
,this,this.visibility
);
377 if (aliases
!= null && isPublic()) {
378 proxy
.defineAliases(aliases
, this.name
);
384 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
385 createJavaMethods(self
.getRuntime());
387 // TODO: ok to convert args in place, rather than new array?
388 int len
= args
.length
;
389 boolean blockGiven
= block
.isGiven();
390 if (blockGiven
) { // convert block to argument
392 IRubyObject
[] newArgs
= new IRubyObject
[args
.length
+1];
393 System
.arraycopy(args
, 0, newArgs
, 0, args
.length
);
394 newArgs
[args
.length
] = RubyProc
.newProc(self
.getRuntime(), block
, Block
.Type
.LAMBDA
);
397 IRubyObject
[] convertedArgs
= new IRubyObject
[len
+1];
398 convertedArgs
[0] = self
.getInstanceVariables().fastGetInstanceVariable("@java_object");
401 convertedArgs
[len
] = args
[len
- 1];
405 convertedArgs
[i
+1] = Java
.ruby_to_java(self
,args
[i
],Block
.NULL_BLOCK
);
408 if ((method
= javaMethod
) == null) {
410 RubyArray methods
= (RubyArray
)javaMethods
.get(len
);
411 if (methods
== null) {
412 raiseNoMatchingMethodError(self
,convertedArgs
,1);
414 method
= (JavaMethod
)Java
.matching_method_internal(JAVA_UTILITIES
, methods
, convertedArgs
, 1, len
);
416 return Java
.java_to_ruby(self
, method
.invoke(convertedArgs
), Block
.NULL_BLOCK
);
419 public Arity
getArity() {
420 return Arity
.OPTIONAL
;
424 private static class ConstantField
{
425 static final int CONSTANT
= Modifier
.FINAL
| Modifier
.PUBLIC
| Modifier
.STATIC
;
427 ConstantField(Field field
) {
430 void install(final RubyModule proxy
) {
431 if (proxy
.fastGetConstantAt(field
.getName()) == null) {
432 JavaField javaField
= new JavaField(proxy
.getRuntime(),field
);
433 // TODO: catch exception if constant is already set by other
435 proxy
.const_set(javaField
.name(),Java
.java_to_ruby(proxy
,javaField
.static_value(),Block
.NULL_BLOCK
));
438 static boolean isConstant(final Field field
) {
439 return (field
.getModifiers() & CONSTANT
) == CONSTANT
&&
440 Character
.isUpperCase(field
.getName().charAt(0));
444 private final RubyModule JAVA_UTILITIES
= getRuntime().getJavaSupport().getJavaUtilitiesModule();
446 private Map
<String
, AssignedName
> staticAssignedNames
;
447 private Map
<String
, AssignedName
> instanceAssignedNames
;
448 private Map
<String
, NamedCallback
> staticCallbacks
;
449 private Map
<String
, NamedCallback
> instanceCallbacks
;
450 private List
<ConstantField
> constantFields
;
451 // caching constructors, as they're accessed for each new instance
452 private volatile RubyArray constructors
;
454 private volatile ArrayList
<IRubyObject
> proxyExtenders
;
456 // proxy module for interfaces
457 private volatile RubyModule proxyModule
;
459 // proxy class for concrete classes. also used for
460 // "concrete" interfaces, which is why we have two fields
461 private volatile RubyClass proxyClass
;
463 // readable only by thread building proxy, so don't need to be
464 // volatile. used to handle recursive calls to getProxyClass/Module
465 // while proxy is being constructed (usually when a constant
466 // defined by a class is of the same type as that class).
467 private RubyModule unfinishedProxyModule
;
468 private RubyClass unfinishedProxyClass
;
470 private final ReentrantLock proxyLock
= new ReentrantLock();
472 public RubyModule
getProxyModule() {
473 // allow proxy to be read without synchronization. if proxy
474 // is under construction, only the building thread can see it.
476 if ((proxy
= proxyModule
) != null) {
477 // proxy is complete, return it
479 } else if (proxyLock
.isHeldByCurrentThread()) {
480 // proxy is under construction, building thread can
481 // safely read non-volatile value
482 return unfinishedProxyModule
;
487 public RubyClass
getProxyClass() {
488 // allow proxy to be read without synchronization. if proxy
489 // is under construction, only the building thread can see it.
491 if ((proxy
= proxyClass
) != null) {
492 // proxy is complete, return it
494 } else if (proxyLock
.isHeldByCurrentThread()) {
495 // proxy is under construction, building thread can
496 // safely read non-volatile value
497 return unfinishedProxyClass
;
502 public void lockProxy() {
506 public void unlockProxy() {
510 protected Map
<String
, AssignedName
> getStaticAssignedNames() {
511 return staticAssignedNames
;
513 protected Map
<String
, AssignedName
> getInstanceAssignedNames() {
514 return instanceAssignedNames
;
517 private JavaClass(Ruby runtime
, Class
<?
> javaClass
) {
518 super(runtime
, (RubyClass
) runtime
.getJavaSupport().getJavaClassClass(), javaClass
);
519 if (javaClass
.isInterface()) {
520 initializeInterface(javaClass
);
521 } else if (!(javaClass
.isArray() || javaClass
.isPrimitive())) {
522 // TODO: public only?
523 initializeClass(javaClass
);
527 public boolean equals(Object other
) {
528 return other
instanceof JavaClass
&&
529 this.getValue() == ((JavaClass
)other
).getValue();
532 private void initializeInterface(Class
<?
> javaClass
) {
533 Map
<String
, AssignedName
> staticNames
= new HashMap
<String
, AssignedName
>(STATIC_RESERVED_NAMES
);
534 List
<ConstantField
> constantFields
= new ArrayList
<ConstantField
>();
535 Field
[] fields
= EMPTY_FIELD_ARRAY
;
537 fields
= javaClass
.getDeclaredFields();
538 } catch (SecurityException e
) {
540 fields
= javaClass
.getFields();
541 } catch (SecurityException e2
) {
544 for (int i
= fields
.length
; --i
>= 0; ) {
545 Field field
= fields
[i
];
546 if (javaClass
!= field
.getDeclaringClass()) continue;
547 if (ConstantField
.isConstant(field
)) {
548 constantFields
.add(new ConstantField(field
));
551 this.staticAssignedNames
= staticNames
;
552 this.constantFields
= constantFields
;
555 private void initializeClass(Class
<?
> javaClass
) {
556 Class
<?
> superclass
= javaClass
.getSuperclass();
557 Map
<String
, AssignedName
> staticNames
;
558 Map
<String
, AssignedName
> instanceNames
;
559 if (superclass
== null) {
560 staticNames
= new HashMap
<String
, AssignedName
>();
561 instanceNames
= new HashMap
<String
, AssignedName
>();
563 JavaClass superJavaClass
= get(getRuntime(),superclass
);
564 staticNames
= new HashMap
<String
, AssignedName
>(superJavaClass
.getStaticAssignedNames());
565 instanceNames
= new HashMap
<String
, AssignedName
>(superJavaClass
.getInstanceAssignedNames());
567 staticNames
.putAll(STATIC_RESERVED_NAMES
);
568 instanceNames
.putAll(INSTANCE_RESERVED_NAMES
);
569 Map
<String
, NamedCallback
> staticCallbacks
= new HashMap
<String
, NamedCallback
>();
570 Map
<String
, NamedCallback
> instanceCallbacks
= new HashMap
<String
, NamedCallback
>();
571 List
<ConstantField
> constantFields
= new ArrayList
<ConstantField
>();
572 Field
[] fields
= EMPTY_FIELD_ARRAY
;
574 fields
= javaClass
.getFields();
575 } catch (SecurityException e
) {
577 for (int i
= fields
.length
; --i
>= 0; ) {
578 Field field
= fields
[i
];
579 if (javaClass
!= field
.getDeclaringClass()) continue;
581 if (ConstantField
.isConstant(field
)) {
582 constantFields
.add(new ConstantField(field
));
585 String name
= field
.getName();
586 int modifiers
= field
.getModifiers();
587 if (Modifier
.isStatic(modifiers
)) {
588 AssignedName assignedName
= staticNames
.get(name
);
589 if (assignedName
!= null && assignedName
.type
< AssignedName
.FIELD
)
591 staticNames
.put(name
,new AssignedName(name
,AssignedName
.FIELD
));
592 staticCallbacks
.put(name
,new StaticFieldGetter(name
,field
));
593 if (!Modifier
.isFinal(modifiers
)) {
594 String setName
= name
+ '=';
595 staticCallbacks
.put(setName
,new StaticFieldSetter(setName
,field
));
598 AssignedName assignedName
= instanceNames
.get(name
);
599 if (assignedName
!= null && assignedName
.type
< AssignedName
.FIELD
)
601 instanceNames
.put(name
, new AssignedName(name
,AssignedName
.FIELD
));
602 instanceCallbacks
.put(name
, new InstanceFieldGetter(name
,field
));
603 if (!Modifier
.isFinal(modifiers
)) {
604 String setName
= name
+ '=';
605 instanceCallbacks
.put(setName
, new InstanceFieldSetter(setName
,field
));
609 // TODO: protected methods. this is going to require a rework
610 // of some of the mechanism.
611 Method
[] methods
= EMPTY_METHOD_ARRAY
;
612 for (Class c
= javaClass
; c
!= null; c
= c
.getSuperclass()) {
614 methods
= javaClass
.getMethods();
616 } catch (SecurityException e
) {
619 for (int i
= methods
.length
; --i
>= 0; ) {
620 // we need to collect all methods, though we'll only
621 // install the ones that are named in this class
622 Method method
= methods
[i
];
623 String name
= method
.getName();
624 if (Modifier
.isStatic(method
.getModifiers())) {
625 AssignedName assignedName
= staticNames
.get(name
);
626 if (assignedName
== null) {
627 staticNames
.put(name
,new AssignedName(name
,AssignedName
.METHOD
));
629 if (assignedName
.type
< AssignedName
.METHOD
)
631 if (assignedName
.type
!= AssignedName
.METHOD
) {
632 staticCallbacks
.remove(name
);
633 staticCallbacks
.remove(name
+'=');
634 staticNames
.put(name
,new AssignedName(name
,AssignedName
.METHOD
));
637 StaticMethodInvoker invoker
= (StaticMethodInvoker
)staticCallbacks
.get(name
);
638 if (invoker
== null) {
639 invoker
= new StaticMethodInvoker(name
);
640 staticCallbacks
.put(name
,invoker
);
642 invoker
.addMethod(method
,javaClass
);
644 AssignedName assignedName
= instanceNames
.get(name
);
645 if (assignedName
== null) {
646 instanceNames
.put(name
,new AssignedName(name
,AssignedName
.METHOD
));
648 if (assignedName
.type
< AssignedName
.METHOD
)
650 if (assignedName
.type
!= AssignedName
.METHOD
) {
651 instanceCallbacks
.remove(name
);
652 instanceCallbacks
.remove(name
+'=');
653 instanceNames
.put(name
,new AssignedName(name
,AssignedName
.METHOD
));
656 InstanceMethodInvoker invoker
= (InstanceMethodInvoker
)instanceCallbacks
.get(name
);
657 if (invoker
== null) {
658 invoker
= new InstanceMethodInvoker(name
);
659 instanceCallbacks
.put(name
,invoker
);
661 invoker
.addMethod(method
,javaClass
);
664 this.staticAssignedNames
= staticNames
;
665 this.instanceAssignedNames
= instanceNames
;
666 this.staticCallbacks
= staticCallbacks
;
667 this.instanceCallbacks
= instanceCallbacks
;
668 this.constantFields
= constantFields
;
671 public void setupProxy(final RubyClass proxy
) {
672 assert proxyLock
.isHeldByCurrentThread();
673 proxy
.defineFastMethod("__jsend!", __jsend_method
);
674 final Class
<?
> javaClass
= javaClass();
675 if (javaClass
.isInterface()) {
676 setupInterfaceProxy(proxy
);
679 assert this.proxyClass
== null;
680 this.unfinishedProxyClass
= proxy
;
681 if (javaClass
.isArray() || javaClass
.isPrimitive()) {
682 // see note below re: 2-field kludge
683 this.proxyClass
= proxy
;
684 this.proxyModule
= proxy
;
688 for (ConstantField field
: constantFields
) {
689 field
.install(proxy
);
691 for (Iterator
<NamedCallback
> iter
= staticCallbacks
.values().iterator(); iter
.hasNext(); ) {
692 NamedCallback callback
= iter
.next();
693 if (callback
.type
== NamedCallback
.STATIC_METHOD
&& callback
.hasLocalMethod()) {
694 assignAliases((MethodCallback
)callback
,staticAssignedNames
);
696 callback
.install(proxy
);
698 for (Iterator
<NamedCallback
> iter
= instanceCallbacks
.values().iterator(); iter
.hasNext(); ) {
699 NamedCallback callback
= iter
.next();
700 if (callback
.type
== NamedCallback
.INSTANCE_METHOD
&& callback
.hasLocalMethod()) {
701 assignAliases((MethodCallback
)callback
,instanceAssignedNames
);
703 callback
.install(proxy
);
705 // setup constants for public inner classes
706 Class
<?
>[] classes
= EMPTY_CLASS_ARRAY
;
708 classes
= javaClass
.getClasses();
709 } catch (SecurityException e
) {
711 for (int i
= classes
.length
; --i
>= 0; ) {
712 if (javaClass
== classes
[i
].getDeclaringClass()) {
713 Class
<?
> clazz
= classes
[i
];
714 String simpleName
= getSimpleName(clazz
);
716 if (simpleName
.length() == 0) continue;
718 // Ignore bad constant named inner classes pending JRUBY-697
719 if (IdUtil
.isConstant(simpleName
) && proxy
.getConstantAt(simpleName
) == null) {
720 proxy
.setConstant(simpleName
,
721 Java
.get_proxy_class(JAVA_UTILITIES
,get(getRuntime(),clazz
)));
725 // FIXME: bit of a kludge here (non-interface classes assigned to both
726 // class and module fields). simplifies proxy extender code, will go away
727 // when JI is overhauled (and proxy extenders are deprecated).
728 this.proxyClass
= proxy
;
729 this.proxyModule
= proxy
;
731 applyProxyExtenders();
733 // TODO: we can probably release our references to the constantFields
734 // array and static/instance callback hashes at this point.
737 private static void assignAliases(MethodCallback callback
, Map
<String
, AssignedName
> assignedNames
) {
738 String name
= callback
.name
;
739 String rubyCasedName
= getRubyCasedName(name
);
740 addUnassignedAlias(rubyCasedName
,assignedNames
,callback
);
742 String javaPropertyName
= getJavaPropertyName(name
);
743 String rubyPropertyName
= null;
745 for (Method method
: callback
.methods
) {
746 Class
<?
>[] argTypes
= method
.getParameterTypes();
747 Class
<?
> resultType
= method
.getReturnType();
748 int argCount
= argTypes
.length
;
750 // Add property name aliases
751 if (javaPropertyName
!= null) {
752 if (rubyCasedName
.startsWith("get_")) {
753 rubyPropertyName
= rubyCasedName
.substring(4);
754 if (argCount
== 0 || // getFoo => foo
755 argCount
== 1 && argTypes
[0] == int.class) { // getFoo(int) => foo(int)
757 addUnassignedAlias(javaPropertyName
,assignedNames
,callback
);
758 addUnassignedAlias(rubyPropertyName
,assignedNames
,callback
);
760 } else if (rubyCasedName
.startsWith("set_")) {
761 rubyPropertyName
= rubyCasedName
.substring(4);
762 if (argCount
== 1 && resultType
== void.class) { // setFoo(Foo) => foo=(Foo)
763 addUnassignedAlias(javaPropertyName
+'=',assignedNames
,callback
);
764 addUnassignedAlias(rubyPropertyName
+'=',assignedNames
,callback
);
766 } else if (rubyCasedName
.startsWith("is_")) {
767 rubyPropertyName
= rubyCasedName
.substring(3);
768 if (resultType
== boolean.class) { // isFoo() => foo, isFoo(*) => foo(*)
769 addUnassignedAlias(javaPropertyName
,assignedNames
,callback
);
770 addUnassignedAlias(rubyPropertyName
,assignedNames
,callback
);
775 // Additionally add ?-postfixed aliases to any boolean methods and properties.
776 if (resultType
== boolean.class) {
777 // is_something?, contains_thing?
778 addUnassignedAlias(rubyCasedName
+'?',assignedNames
,callback
);
779 if (rubyPropertyName
!= null) {
781 addUnassignedAlias(rubyPropertyName
+'?',assignedNames
,callback
);
787 private static void addUnassignedAlias(String name
, Map
<String
, AssignedName
> assignedNames
,
788 MethodCallback callback
) {
790 AssignedName assignedName
= (AssignedName
)assignedNames
.get(name
);
791 if (assignedName
== null) {
792 callback
.addAlias(name
);
793 assignedNames
.put(name
,new AssignedName(name
,AssignedName
.ALIAS
));
794 } else if (assignedName
.type
== AssignedName
.ALIAS
) {
795 callback
.addAlias(name
);
796 } else if (assignedName
.type
> AssignedName
.ALIAS
) {
797 // TODO: there will be some additional logic in this branch
798 // dealing with conflicting protected fields.
799 callback
.addAlias(name
);
800 assignedNames
.put(name
,new AssignedName(name
,AssignedName
.ALIAS
));
805 private static final Pattern JAVA_PROPERTY_CHOPPER
= Pattern
.compile("(get|set|is)([A-Z0-9])(.*)");
806 public static String
getJavaPropertyName(String beanMethodName
) {
807 Matcher m
= JAVA_PROPERTY_CHOPPER
.matcher(beanMethodName
);
809 if (!m
.find()) return null;
810 String javaPropertyName
= m
.group(2).toLowerCase() + m
.group(3);
811 return javaPropertyName
;
814 private static final Pattern CAMEL_CASE_SPLITTER
= Pattern
.compile("([a-z][0-9]*)([A-Z])");
815 public static String
getRubyCasedName(String javaCasedName
) {
816 Matcher m
= CAMEL_CASE_SPLITTER
.matcher(javaCasedName
);
817 return m
.replaceAll("$1_$2").toLowerCase();
821 // old (quasi-deprecated) interface class
822 private void setupInterfaceProxy(final RubyClass proxy
) {
823 assert javaClass().isInterface();
824 assert proxyLock
.isHeldByCurrentThread();
825 assert this.proxyClass
== null;
826 this.proxyClass
= proxy
;
827 // nothing else to here - the module version will be
828 // included in the class.
831 public void setupInterfaceModule(final RubyModule module
) {
832 assert javaClass().isInterface();
833 assert proxyLock
.isHeldByCurrentThread();
834 assert this.proxyModule
== null;
835 this.unfinishedProxyModule
= module
;
836 Class
<?
> javaClass
= javaClass();
837 for (ConstantField field
: constantFields
) {
838 field
.install(module
);
840 // setup constants for public inner classes
841 Class
<?
>[] classes
= EMPTY_CLASS_ARRAY
;
843 classes
= javaClass
.getClasses();
844 } catch (SecurityException e
) {
846 for (int i
= classes
.length
; --i
>= 0; ) {
847 if (javaClass
== classes
[i
].getDeclaringClass()) {
848 Class
<?
> clazz
= classes
[i
];
849 String simpleName
= getSimpleName(clazz
);
850 if (simpleName
.length() == 0) continue;
852 // Ignore bad constant named inner classes pending JRUBY-697
853 if (IdUtil
.isConstant(simpleName
) && module
.getConstantAt(simpleName
) == null) {
854 module
.const_set(getRuntime().newString(simpleName
),
855 Java
.get_proxy_class(JAVA_UTILITIES
,get(getRuntime(),clazz
)));
860 this.proxyModule
= module
;
861 applyProxyExtenders();
864 public void addProxyExtender(final IRubyObject extender
) {
867 if (!extender
.respondsTo("extend_proxy")) {
868 throw getRuntime().newTypeError("proxy extender must have an extend_proxy method");
870 if (proxyModule
== null) {
871 if (proxyExtenders
== null) {
872 proxyExtenders
= new ArrayList
<IRubyObject
>();
874 proxyExtenders
.add(extender
);
876 getRuntime().getWarnings().warn(ID
.PROXY_EXTENDED_LATE
, " proxy extender added after proxy class created for " + this);
877 extendProxy(extender
);
884 private void applyProxyExtenders() {
885 ArrayList
<IRubyObject
> extenders
;
886 if ((extenders
= proxyExtenders
) != null) {
887 for (IRubyObject extender
: extenders
) {
888 extendProxy(extender
);
890 proxyExtenders
= null;
894 private void extendProxy(IRubyObject extender
) {
895 extender
.callMethod(getRuntime().getCurrentContext(), "extend_proxy", proxyModule
);
898 @JRubyMethod(required
= 1)
899 public IRubyObject
extend_proxy(IRubyObject extender
) {
900 addProxyExtender(extender
);
901 return getRuntime().getNil();
904 public static JavaClass
get(Ruby runtime
, Class
<?
> klass
) {
905 JavaClass javaClass
= runtime
.getJavaSupport().getJavaClassFromCache(klass
);
906 if (javaClass
== null) {
907 javaClass
= createJavaClass(runtime
,klass
);
912 public static RubyArray
getRubyArray(Ruby runtime
, Class
<?
>[] classes
) {
913 IRubyObject
[] javaClasses
= new IRubyObject
[classes
.length
];
914 for (int i
= classes
.length
; --i
>= 0; ) {
915 javaClasses
[i
] = get(runtime
, classes
[i
]);
917 return runtime
.newArrayNoCopy(javaClasses
);
920 private static synchronized JavaClass
createJavaClass(Ruby runtime
, Class
<?
> klass
) {
921 // double-check the cache now that we're synchronized
922 JavaClass javaClass
= runtime
.getJavaSupport().getJavaClassFromCache(klass
);
923 if (javaClass
== null) {
924 javaClass
= new JavaClass(runtime
, klass
);
925 runtime
.getJavaSupport().putJavaClassIntoCache(javaClass
);
930 public static RubyClass
createJavaClassClass(Ruby runtime
, RubyModule javaModule
) {
931 // FIXME: Determine if a real allocator is needed here. Do people want to extend
932 // JavaClass? Do we want them to do that? Can you Class.new(JavaClass)? Should
934 // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't intend for people to monkey with
935 // this type and it can't be marshalled. Confirm. JRUBY-415
936 RubyClass result
= javaModule
.defineClassUnder("JavaClass", javaModule
.fastGetClass("JavaObject"), ObjectAllocator
.NOT_ALLOCATABLE_ALLOCATOR
);
938 result
.includeModule(runtime
.fastGetModule("Comparable"));
940 result
.defineAnnotatedMethods(JavaClass
.class);
942 result
.getMetaClass().undefineMethod("new");
943 result
.getMetaClass().undefineMethod("allocate");
948 public static synchronized JavaClass
forNameVerbose(Ruby runtime
, String className
) {
949 Class
<?
> klass
= runtime
.getJavaSupport().loadJavaClassVerbose(className
);
950 return JavaClass
.get(runtime
, klass
);
953 public static synchronized JavaClass
forNameQuiet(Ruby runtime
, String className
) {
954 Class klass
= runtime
.getJavaSupport().loadJavaClassQuiet(className
);
955 return JavaClass
.get(runtime
, klass
);
958 @JRubyMethod(name
= "for_name", required
= 1, meta
= true)
959 public static JavaClass
for_name(IRubyObject recv
, IRubyObject name
) {
960 return forNameVerbose(recv
.getRuntime(), name
.asJavaString());
963 private static final Callback __jsend_method
= new Callback() {
964 public IRubyObject
execute(IRubyObject self
, IRubyObject
[] args
, Block block
) {
965 String name
= args
[0].asJavaString();
967 DynamicMethod method
= self
.getMetaClass().searchMethod(name
);
968 int v
= method
.getArity().getValue();
970 IRubyObject
[] newArgs
= new IRubyObject
[args
.length
- 1];
971 System
.arraycopy(args
, 1, newArgs
, 0, newArgs
.length
);
973 if(v
< 0 || v
== (newArgs
.length
)) {
974 return RuntimeHelpers
.invoke(self
.getRuntime().getCurrentContext(), self
, name
, newArgs
, CallType
.FUNCTIONAL
, block
);
976 RubyClass superClass
= self
.getMetaClass().getSuperClass();
977 return RuntimeHelpers
.invokeAs(self
.getRuntime().getCurrentContext(), superClass
, self
, name
, newArgs
, CallType
.SUPER
, block
);
981 public Arity
getArity() {
982 return Arity
.optional();
987 public RubyModule
ruby_class() {
988 // Java.getProxyClass deals with sync issues, so we won't duplicate the logic here
989 return Java
.getProxyClass(getRuntime(), this);
992 @JRubyMethod(name
= "public?")
993 public RubyBoolean
public_p() {
994 return getRuntime().newBoolean(Modifier
.isPublic(javaClass().getModifiers()));
997 @JRubyMethod(name
= "protected?")
998 public RubyBoolean
protected_p() {
999 return getRuntime().newBoolean(Modifier
.isProtected(javaClass().getModifiers()));
1002 @JRubyMethod(name
= "private?")
1003 public RubyBoolean
private_p() {
1004 return getRuntime().newBoolean(Modifier
.isPrivate(javaClass().getModifiers()));
1007 public Class
javaClass() {
1008 return (Class
) getValue();
1011 @JRubyMethod(name
= "final?")
1012 public RubyBoolean
final_p() {
1013 return getRuntime().newBoolean(Modifier
.isFinal(javaClass().getModifiers()));
1016 @JRubyMethod(name
= "interface?")
1017 public RubyBoolean
interface_p() {
1018 return getRuntime().newBoolean(javaClass().isInterface());
1021 @JRubyMethod(name
= "array?")
1022 public RubyBoolean
array_p() {
1023 return getRuntime().newBoolean(javaClass().isArray());
1026 @JRubyMethod(name
= "enum?")
1027 public RubyBoolean
enum_p() {
1028 return getRuntime().newBoolean(javaClass().isEnum());
1031 @JRubyMethod(name
= "annotation?")
1032 public RubyBoolean
annotation_p() {
1033 return getRuntime().newBoolean(javaClass().isAnnotation());
1036 @JRubyMethod(name
= "anonymous_class?")
1037 public RubyBoolean
anonymous_class_p() {
1038 return getRuntime().newBoolean(javaClass().isAnonymousClass());
1041 @JRubyMethod(name
= "local_class?")
1042 public RubyBoolean
local_class_p() {
1043 return getRuntime().newBoolean(javaClass().isLocalClass());
1046 @JRubyMethod(name
= "member_class?")
1047 public RubyBoolean
member_class_p() {
1048 return getRuntime().newBoolean(javaClass().isMemberClass());
1051 @JRubyMethod(name
= "synthetic?")
1052 public IRubyObject
synthetic_p() {
1053 return getRuntime().newBoolean(javaClass().isSynthetic());
1056 @JRubyMethod(name
= {"name", "to_s"})
1057 public RubyString
name() {
1058 return getRuntime().newString(javaClass().getName());
1062 public IRubyObject
canonical_name() {
1063 String canonicalName
= javaClass().getCanonicalName();
1064 if (canonicalName
!= null) {
1065 return getRuntime().newString(canonicalName
);
1067 return getRuntime().getNil();
1070 @JRubyMethod(name
= "package")
1071 public IRubyObject
get_package() {
1072 return Java
.getInstance(getRuntime(), javaClass().getPackage());
1076 public IRubyObject
class_loader() {
1077 return Java
.getInstance(getRuntime(), javaClass().getClassLoader());
1081 public IRubyObject
protection_domain() {
1082 return Java
.getInstance(getRuntime(), javaClass().getProtectionDomain());
1085 @JRubyMethod(required
= 1)
1086 public IRubyObject
resource(IRubyObject name
) {
1087 return Java
.getInstance(getRuntime(), javaClass().getResource(name
.asJavaString()));
1090 @JRubyMethod(required
= 1)
1091 public IRubyObject
resource_as_stream(IRubyObject name
) {
1092 return Java
.getInstance(getRuntime(), javaClass().getResourceAsStream(name
.asJavaString()));
1095 @JRubyMethod(required
= 1)
1096 public IRubyObject
resource_as_string(IRubyObject name
) {
1097 InputStream in
= javaClass().getResourceAsStream(name
.asJavaString());
1098 if (in
== null) return getRuntime().getNil();
1099 ByteArrayOutputStream out
= new ByteArrayOutputStream();
1102 byte[] buf
= new byte[4096];
1103 while ((len
= in
.read(buf
)) >= 0) {
1104 out
.write(buf
, 0, len
);
1106 } catch (IOException e
) {
1107 throw getRuntime().newIOErrorFromException(e
);
1109 return getRuntime().newString(new ByteList(out
.toByteArray(), false));
1112 @SuppressWarnings("unchecked")
1113 @JRubyMethod(required
= 1)
1114 public IRubyObject
annotation(IRubyObject annoClass
) {
1115 if (!(annoClass
instanceof JavaClass
)) {
1116 throw getRuntime().newTypeError(annoClass
, getRuntime().getJavaSupport().getJavaClassClass());
1118 return Java
.getInstance(getRuntime(), javaClass().getAnnotation(((JavaClass
)annoClass
).javaClass()));
1122 public IRubyObject
annotations() {
1123 // note: intentionally returning the actual array returned from Java, rather
1124 // than wrapping it in a RubyArray. wave of the future, when java_class will
1125 // return the actual class, rather than a JavaClass wrapper.
1126 return Java
.getInstance(getRuntime(), javaClass().getAnnotations());
1129 @JRubyMethod(name
= "annotations?")
1130 public RubyBoolean
annotations_p() {
1131 return getRuntime().newBoolean(javaClass().getAnnotations().length
> 0);
1135 public IRubyObject
declared_annotations() {
1136 // see note above re: return type
1137 return Java
.getInstance(getRuntime(), javaClass().getDeclaredAnnotations());
1140 @JRubyMethod(name
= "declared_annotations?")
1141 public RubyBoolean
declared_annotations_p() {
1142 return getRuntime().newBoolean(javaClass().getDeclaredAnnotations().length
> 0);
1145 @SuppressWarnings("unchecked")
1146 @JRubyMethod(name
= "annotation_present?", required
= 1)
1147 public IRubyObject
annotation_present_p(IRubyObject annoClass
) {
1148 if (!(annoClass
instanceof JavaClass
)) {
1149 throw getRuntime().newTypeError(annoClass
, getRuntime().getJavaSupport().getJavaClassClass());
1151 return getRuntime().newBoolean(javaClass().isAnnotationPresent(((JavaClass
)annoClass
).javaClass()));
1155 public IRubyObject
modifiers() {
1156 return getRuntime().newFixnum(javaClass().getModifiers());
1160 public IRubyObject
declaring_class() {
1161 Class
<?
> clazz
= javaClass().getDeclaringClass();
1162 if (clazz
!= null) {
1163 return JavaClass
.get(getRuntime(), clazz
);
1165 return getRuntime().getNil();
1169 public IRubyObject
enclosing_class() {
1170 return Java
.getInstance(getRuntime(), javaClass().getEnclosingClass());
1174 public IRubyObject
enclosing_constructor() {
1175 Constructor
<?
> ctor
= javaClass().getEnclosingConstructor();
1177 return new JavaConstructor(getRuntime(), ctor
);
1179 return getRuntime().getNil();
1183 public IRubyObject
enclosing_method() {
1184 Method meth
= javaClass().getEnclosingMethod();
1186 return new JavaMethod(getRuntime(), meth
);
1188 return getRuntime().getNil();
1192 public IRubyObject
enum_constants() {
1193 return Java
.getInstance(getRuntime(), javaClass().getEnumConstants());
1197 public IRubyObject
generic_interfaces() {
1198 return Java
.getInstance(getRuntime(), javaClass().getGenericInterfaces());
1202 public IRubyObject
generic_superclass() {
1203 return Java
.getInstance(getRuntime(), javaClass().getGenericSuperclass());
1207 public IRubyObject
type_parameters() {
1208 return Java
.getInstance(getRuntime(), javaClass().getTypeParameters());
1212 public IRubyObject
signers() {
1213 return Java
.getInstance(getRuntime(), javaClass().getSigners());
1216 private static String
getSimpleName(Class
<?
> clazz
) {
1217 if (clazz
.isArray()) {
1218 return getSimpleName(clazz
.getComponentType()) + "[]";
1221 String className
= clazz
.getName();
1222 int len
= className
.length();
1223 int i
= className
.lastIndexOf('$');
1227 } while (i
< len
&& Character
.isDigit(className
.charAt(i
)));
1228 return className
.substring(i
);
1231 return className
.substring(className
.lastIndexOf('.') + 1);
1235 public RubyString
simple_name() {
1236 return getRuntime().newString(getSimpleName(javaClass()));
1240 public IRubyObject
superclass() {
1241 Class
<?
> superclass
= javaClass().getSuperclass();
1242 if (superclass
== null) {
1243 return getRuntime().getNil();
1245 return JavaClass
.get(getRuntime(), superclass
);
1248 @JRubyMethod(name
= "<=>", required
= 1)
1249 public RubyFixnum
op_cmp(IRubyObject other
) {
1250 if (! (other
instanceof JavaClass
)) {
1251 throw getRuntime().newTypeError("<=> requires JavaClass (" + other
.getType() + " given)");
1253 JavaClass otherClass
= (JavaClass
) other
;
1254 if (this.javaClass() == otherClass
.javaClass()) {
1255 return getRuntime().newFixnum(0);
1257 if (otherClass
.javaClass().isAssignableFrom(this.javaClass())) {
1258 return getRuntime().newFixnum(-1);
1260 return getRuntime().newFixnum(1);
1264 public RubyArray
java_instance_methods() {
1265 return java_methods(javaClass().getMethods(), false);
1269 public RubyArray
declared_instance_methods() {
1270 return java_methods(javaClass().getDeclaredMethods(), false);
1273 private RubyArray
java_methods(Method
[] methods
, boolean isStatic
) {
1274 RubyArray result
= getRuntime().newArray(methods
.length
);
1275 for (int i
= 0; i
< methods
.length
; i
++) {
1276 Method method
= methods
[i
];
1277 if (isStatic
== Modifier
.isStatic(method
.getModifiers())) {
1278 result
.append(JavaMethod
.create(getRuntime(), method
));
1285 public RubyArray
java_class_methods() {
1286 return java_methods(javaClass().getMethods(), true);
1290 public RubyArray
declared_class_methods() {
1291 return java_methods(javaClass().getDeclaredMethods(), true);
1294 @JRubyMethod(required
= 1, rest
= true)
1295 public JavaMethod
java_method(IRubyObject
[] args
) throws ClassNotFoundException
{
1296 String methodName
= args
[0].asJavaString();
1297 Class
<?
>[] argumentTypes
= buildArgumentTypes(args
);
1298 return JavaMethod
.create(getRuntime(), javaClass(), methodName
, argumentTypes
);
1301 @JRubyMethod(required
= 1, rest
= true)
1302 public JavaMethod
declared_method(IRubyObject
[] args
) throws ClassNotFoundException
{
1303 String methodName
= args
[0].asJavaString();
1304 Class
<?
>[] argumentTypes
= buildArgumentTypes(args
);
1305 return JavaMethod
.createDeclared(getRuntime(), javaClass(), methodName
, argumentTypes
);
1308 @JRubyMethod(required
= 1, rest
= true)
1309 public JavaCallable
declared_method_smart(IRubyObject
[] args
) throws ClassNotFoundException
{
1310 String methodName
= args
[0].asJavaString();
1311 Class
<?
>[] argumentTypes
= buildArgumentTypes(args
);
1313 JavaCallable callable
= getMatchingCallable(getRuntime(), javaClass(), methodName
, argumentTypes
);
1315 if (callable
!= null) return callable
;
1317 throw getRuntime().newNameError("undefined method '" + methodName
+ "' for class '" + javaClass().getName() + "'",
1321 public static JavaCallable
getMatchingCallable(Ruby runtime
, Class
<?
> javaClass
, String methodName
, Class
<?
>[] argumentTypes
) {
1322 if ("<init>".equals(methodName
)) {
1323 return JavaConstructor
.getMatchingConstructor(runtime
, javaClass
, argumentTypes
);
1325 // FIXME: do we really want 'declared' methods? includes private/protected, and does _not_
1326 // include superclass methods
1327 return JavaMethod
.getMatchingDeclaredMethod(runtime
, javaClass
, methodName
, argumentTypes
);
1331 private Class
<?
>[] buildArgumentTypes(IRubyObject
[] args
) throws ClassNotFoundException
{
1332 if (args
.length
< 1) {
1333 throw getRuntime().newArgumentError(args
.length
, 1);
1335 Class
<?
>[] argumentTypes
= new Class
[args
.length
- 1];
1336 for (int i
= 1; i
< args
.length
; i
++) {
1338 if (args
[i
] instanceof JavaClass
) {
1339 type
= (JavaClass
)args
[i
];
1340 } else if (args
[i
].respondsTo("java_class")) {
1341 type
= (JavaClass
)args
[i
].callMethod(getRuntime().getCurrentContext(), "java_class");
1343 type
= for_name(this, args
[i
]);
1345 argumentTypes
[i
- 1] = type
.javaClass();
1347 return argumentTypes
;
1351 public RubyArray
constructors() {
1353 if ((ctors
= constructors
) != null) return ctors
;
1354 return constructors
= buildConstructors(javaClass().getConstructors());
1358 public RubyArray
classes() {
1359 return JavaClass
.getRubyArray(getRuntime(), javaClass().getClasses());
1363 public RubyArray
declared_classes() {
1364 Ruby runtime
= getRuntime();
1365 RubyArray result
= runtime
.newArray();
1366 Class
<?
> javaClass
= javaClass();
1368 Class
<?
>[] classes
= javaClass
.getDeclaredClasses();
1369 for (int i
= 0; i
< classes
.length
; i
++) {
1370 if (Modifier
.isPublic(classes
[i
].getModifiers())) {
1371 result
.append(get(runtime
, classes
[i
]));
1374 } catch (SecurityException e
) {
1375 // restrictive security policy; no matter, we only want public
1378 Class
<?
>[] classes
= javaClass
.getClasses();
1379 for (int i
= 0; i
< classes
.length
; i
++) {
1380 if (javaClass
== classes
[i
].getDeclaringClass()) {
1381 result
.append(get(runtime
, classes
[i
]));
1384 } catch (SecurityException e2
) {
1385 // very restrictive policy (disallows Member.PUBLIC)
1386 // we'd never actually get this far in that case
1393 public RubyArray
declared_constructors() {
1394 return buildConstructors(javaClass().getDeclaredConstructors());
1397 private RubyArray
buildConstructors(Constructor
<?
>[] constructors
) {
1398 RubyArray result
= getRuntime().newArray(constructors
.length
);
1399 for (int i
= 0; i
< constructors
.length
; i
++) {
1400 result
.append(new JavaConstructor(getRuntime(), constructors
[i
]));
1405 @JRubyMethod(rest
= true)
1406 public JavaConstructor
constructor(IRubyObject
[] args
) {
1408 Class
<?
>[] parameterTypes
= buildClassArgs(args
);
1409 Constructor
<?
> constructor
= javaClass().getConstructor(parameterTypes
);
1410 return new JavaConstructor(getRuntime(), constructor
);
1411 } catch (NoSuchMethodException nsme
) {
1412 throw getRuntime().newNameError("no matching java constructor", null);
1416 @JRubyMethod(rest
= true)
1417 public JavaConstructor
declared_constructor(IRubyObject
[] args
) {
1419 Class
<?
>[] parameterTypes
= buildClassArgs(args
);
1420 Constructor
<?
> constructor
= javaClass().getDeclaredConstructor (parameterTypes
);
1421 return new JavaConstructor(getRuntime(), constructor
);
1422 } catch (NoSuchMethodException nsme
) {
1423 throw getRuntime().newNameError("no matching java constructor", null);
1427 private Class
<?
>[] buildClassArgs(IRubyObject
[] args
) {
1428 JavaSupport javaSupport
= getRuntime().getJavaSupport();
1429 Class
<?
>[] parameterTypes
= new Class
<?
>[args
.length
];
1430 for (int i
= args
.length
; --i
>= 0; ) {
1431 String name
= args
[i
].asJavaString();
1432 parameterTypes
[i
] = javaSupport
.loadJavaClassVerbose(name
);
1434 return parameterTypes
;
1438 public JavaClass
array_class() {
1439 return JavaClass
.get(getRuntime(), Array
.newInstance(javaClass(), 0).getClass());
1442 @JRubyMethod(required
= 1)
1443 public JavaObject
new_array(IRubyObject lengthArgument
) {
1444 if (lengthArgument
instanceof RubyInteger
) {
1445 // one-dimensional array
1446 int length
= (int) ((RubyInteger
) lengthArgument
).getLongValue();
1447 return new JavaArray(getRuntime(), Array
.newInstance(javaClass(), length
));
1448 } else if (lengthArgument
instanceof RubyArray
) {
1449 // n-dimensional array
1450 List list
= ((RubyArray
)lengthArgument
).getList();
1451 int length
= list
.size();
1453 throw getRuntime().newArgumentError("empty dimensions specifier for java array");
1455 int[] dimensions
= new int[length
];
1456 for (int i
= length
; --i
>= 0; ) {
1457 IRubyObject dimensionLength
= (IRubyObject
)list
.get(i
);
1458 if ( !(dimensionLength
instanceof RubyInteger
) ) {
1460 .newTypeError(dimensionLength
, getRuntime().getInteger());
1462 dimensions
[i
] = (int) ((RubyInteger
) dimensionLength
).getLongValue();
1464 return new JavaArray(getRuntime(), Array
.newInstance(javaClass(), dimensions
));
1466 throw getRuntime().newArgumentError(
1467 "invalid length or dimensions specifier for java array" +
1468 " - must be Integer or Array of Integer");
1473 public RubyArray
fields() {
1474 return buildFieldResults(javaClass().getFields());
1478 public RubyArray
declared_fields() {
1479 return buildFieldResults(javaClass().getDeclaredFields());
1482 private RubyArray
buildFieldResults(Field
[] fields
) {
1483 RubyArray result
= getRuntime().newArray(fields
.length
);
1484 for (int i
= 0; i
< fields
.length
; i
++) {
1485 result
.append(new JavaField(getRuntime(), fields
[i
]));
1490 @JRubyMethod(required
= 1)
1491 public JavaField
field(IRubyObject name
) {
1492 String stringName
= name
.asJavaString();
1494 Field field
= javaClass().getField(stringName
);
1495 return new JavaField(getRuntime(), field
);
1496 } catch (NoSuchFieldException nsfe
) {
1497 throw undefinedFieldError(stringName
);
1501 @JRubyMethod(required
= 1)
1502 public JavaField
declared_field(IRubyObject name
) {
1503 String stringName
= name
.asJavaString();
1505 Field field
= javaClass().getDeclaredField(stringName
);
1506 return new JavaField(getRuntime(), field
);
1507 } catch (NoSuchFieldException nsfe
) {
1508 throw undefinedFieldError(stringName
);
1512 private RaiseException
undefinedFieldError(String name
) {
1513 return getRuntime().newNameError("undefined field '" + name
+ "' for class '" + javaClass().getName() + "'", name
);
1517 public RubyArray
interfaces() {
1518 return JavaClass
.getRubyArray(getRuntime(), javaClass().getInterfaces());
1521 @JRubyMethod(name
= "primitive?")
1522 public RubyBoolean
primitive_p() {
1523 return getRuntime().newBoolean(isPrimitive());
1526 @JRubyMethod(name
= "assignable_from?", required
= 1)
1527 public RubyBoolean
assignable_from_p(IRubyObject other
) {
1528 if (! (other
instanceof JavaClass
)) {
1529 throw getRuntime().newTypeError("assignable_from requires JavaClass (" + other
.getType() + " given)");
1532 Class
<?
> otherClass
= ((JavaClass
) other
).javaClass();
1533 return assignable(javaClass(), otherClass
) ?
getRuntime().getTrue() : getRuntime().getFalse();
1536 static boolean assignable(Class
<?
> thisClass
, Class
<?
> otherClass
) {
1537 if(!thisClass
.isPrimitive() && otherClass
== Void
.TYPE
||
1538 thisClass
.isAssignableFrom(otherClass
)) {
1542 otherClass
= JavaUtil
.primitiveToWrapper(otherClass
);
1543 thisClass
= JavaUtil
.primitiveToWrapper(thisClass
);
1545 if(thisClass
.isAssignableFrom(otherClass
)) {
1548 if(Number
.class.isAssignableFrom(thisClass
)) {
1549 if(Number
.class.isAssignableFrom(otherClass
)) {
1552 if(otherClass
.equals(Character
.class)) {
1556 if(thisClass
.equals(Character
.class)) {
1557 if(Number
.class.isAssignableFrom(otherClass
)) {
1564 private boolean isPrimitive() {
1565 return javaClass().isPrimitive();
1569 public JavaClass
component_type() {
1570 if (! javaClass().isArray()) {
1571 throw getRuntime().newTypeError("not a java array-class");
1573 return JavaClass
.get(getRuntime(), javaClass().getComponentType());