JRUBY-1613: Tweak Java method aliasing in JavaClass to apply ? suffix to any method...
[jruby.git] / src / org / jruby / javasupport / JavaClass.java
blob1308f08733e2504084337772921b114706f3f698
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;
50 import java.util.Map;
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;
103 String name;
104 int type;
105 AssignedName () {}
106 AssignedName(String name, int type) {
107 this.name = name;
108 this.type = type;
112 // TODO: other reserved names?
113 private static final Map<String, AssignedName> RESERVED_NAMES = new HashMap<String, AssignedName>();
114 static {
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);
128 static {
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;
138 String name;
139 int type;
140 Visibility visibility = Visibility.PUBLIC;
141 boolean isProtected;
142 NamedCallback () {}
143 NamedCallback (String name, int type) {
144 this.name = name;
145 this.type = type;
147 abstract void install(RubyClass proxy);
148 // small hack to save a cast later on
149 boolean hasLocalMethod() {
150 return true;
152 boolean isPublic() {
153 return visibility == Visibility.PUBLIC;
155 boolean isProtected() {
156 return visibility == Visibility.PROTECTED;
160 private static abstract class FieldCallback extends NamedCallback {
161 Field field;
162 JavaField javaField;
163 FieldCallback(){}
164 FieldCallback(String name, int type, Field field) {
165 super(name,type);
166 this.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)),
203 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")),
224 Block.NULL_BLOCK);
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)),
246 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;
260 MethodCallback(){}
261 MethodCallback(String name, int type) {
262 super(name,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>();
270 methods.add(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))
280 aliases.add(alias);
283 // modified only by addMethod; no synchronization required
284 boolean hasLocalMethod () {
285 return haveLocalMethod;
288 // TODO: varargs?
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));
297 } else {
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));
310 methods = null;
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);
338 aliases = null;
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);
352 JavaMethod method;
353 if ((method = javaMethod) == null) {
354 // TODO: varargs?
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);
379 aliases = null;
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
391 len += 1;
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);
395 args = newArgs;
397 IRubyObject[] convertedArgs = new IRubyObject[len+1];
398 convertedArgs[0] = self.getInstanceVariables().fastGetInstanceVariable("@java_object");
399 int i = len;
400 if (blockGiven) {
401 convertedArgs[len] = args[len - 1];
402 i -= 1;
404 for (; --i >= 0; ) {
405 convertedArgs[i+1] = Java.ruby_to_java(self,args[i],Block.NULL_BLOCK);
407 JavaMethod method;
408 if ((method = javaMethod) == null) {
409 // TODO: varargs?
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;
426 final Field field;
427 ConstantField(Field field) {
428 this.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
434 // thread
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.
475 RubyModule proxy;
476 if ((proxy = proxyModule) != null) {
477 // proxy is complete, return it
478 return proxy;
479 } else if (proxyLock.isHeldByCurrentThread()) {
480 // proxy is under construction, building thread can
481 // safely read non-volatile value
482 return unfinishedProxyModule;
484 return null;
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.
490 RubyClass proxy;
491 if ((proxy = proxyClass) != null) {
492 // proxy is complete, return it
493 return proxy;
494 } else if (proxyLock.isHeldByCurrentThread()) {
495 // proxy is under construction, building thread can
496 // safely read non-volatile value
497 return unfinishedProxyClass;
499 return null;
502 public void lockProxy() {
503 proxyLock.lock();
506 public void unlockProxy() {
507 proxyLock.unlock();
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;
536 try {
537 fields = javaClass.getDeclaredFields();
538 } catch (SecurityException e) {
539 try {
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>();
562 } else {
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;
573 try {
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));
583 continue;
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)
590 continue;
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));
597 } else {
598 AssignedName assignedName = instanceNames.get(name);
599 if (assignedName != null && assignedName.type < AssignedName.FIELD)
600 continue;
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()) {
613 try {
614 methods = javaClass.getMethods();
615 break;
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));
628 } else {
629 if (assignedName.type < AssignedName.METHOD)
630 continue;
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);
643 } else {
644 AssignedName assignedName = instanceNames.get(name);
645 if (assignedName == null) {
646 instanceNames.put(name,new AssignedName(name,AssignedName.METHOD));
647 } else {
648 if (assignedName.type < AssignedName.METHOD)
649 continue;
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);
677 return;
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;
685 return;
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;
707 try {
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) {
780 // something?
781 addUnassignedAlias(rubyPropertyName+'?',assignedNames,callback);
787 private static void addUnassignedAlias(String name, Map<String, AssignedName> assignedNames,
788 MethodCallback callback) {
789 if (name != null) {
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;
842 try {
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) {
865 lockProxy();
866 try {
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);
875 } else {
876 getRuntime().getWarnings().warn(ID.PROXY_EXTENDED_LATE, " proxy extender added after proxy class created for " + this);
877 extendProxy(extender);
879 } finally {
880 unlockProxy();
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);
909 return javaClass;
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);
927 return 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
933 // you be able to?
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");
945 return result;
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);
975 } else {
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();
986 @JRubyMethod
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());
1061 @JRubyMethod
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());
1075 @JRubyMethod
1076 public IRubyObject class_loader() {
1077 return Java.getInstance(getRuntime(), javaClass().getClassLoader());
1080 @JRubyMethod
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();
1100 try {
1101 int len;
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()));
1121 @JRubyMethod
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);
1134 @JRubyMethod
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()));
1154 @JRubyMethod
1155 public IRubyObject modifiers() {
1156 return getRuntime().newFixnum(javaClass().getModifiers());
1159 @JRubyMethod
1160 public IRubyObject declaring_class() {
1161 Class<?> clazz = javaClass().getDeclaringClass();
1162 if (clazz != null) {
1163 return JavaClass.get(getRuntime(), clazz);
1165 return getRuntime().getNil();
1168 @JRubyMethod
1169 public IRubyObject enclosing_class() {
1170 return Java.getInstance(getRuntime(), javaClass().getEnclosingClass());
1173 @JRubyMethod
1174 public IRubyObject enclosing_constructor() {
1175 Constructor<?> ctor = javaClass().getEnclosingConstructor();
1176 if (ctor != null) {
1177 return new JavaConstructor(getRuntime(), ctor);
1179 return getRuntime().getNil();
1182 @JRubyMethod
1183 public IRubyObject enclosing_method() {
1184 Method meth = javaClass().getEnclosingMethod();
1185 if (meth != null) {
1186 return new JavaMethod(getRuntime(), meth);
1188 return getRuntime().getNil();
1191 @JRubyMethod
1192 public IRubyObject enum_constants() {
1193 return Java.getInstance(getRuntime(), javaClass().getEnumConstants());
1196 @JRubyMethod
1197 public IRubyObject generic_interfaces() {
1198 return Java.getInstance(getRuntime(), javaClass().getGenericInterfaces());
1201 @JRubyMethod
1202 public IRubyObject generic_superclass() {
1203 return Java.getInstance(getRuntime(), javaClass().getGenericSuperclass());
1206 @JRubyMethod
1207 public IRubyObject type_parameters() {
1208 return Java.getInstance(getRuntime(), javaClass().getTypeParameters());
1211 @JRubyMethod
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('$');
1224 if (i != -1) {
1225 do {
1226 i++;
1227 } while (i < len && Character.isDigit(className.charAt(i)));
1228 return className.substring(i);
1231 return className.substring(className.lastIndexOf('.') + 1);
1234 @JRubyMethod
1235 public RubyString simple_name() {
1236 return getRuntime().newString(getSimpleName(javaClass()));
1239 @JRubyMethod
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);
1263 @JRubyMethod
1264 public RubyArray java_instance_methods() {
1265 return java_methods(javaClass().getMethods(), false);
1268 @JRubyMethod
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));
1281 return result;
1284 @JRubyMethod
1285 public RubyArray java_class_methods() {
1286 return java_methods(javaClass().getMethods(), true);
1289 @JRubyMethod
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() + "'",
1318 methodName);
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);
1324 } else {
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++) {
1337 JavaClass type;
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");
1342 } else {
1343 type = for_name(this, args[i]);
1345 argumentTypes[i - 1] = type.javaClass();
1347 return argumentTypes;
1350 @JRubyMethod
1351 public RubyArray constructors() {
1352 RubyArray ctors;
1353 if ((ctors = constructors) != null) return ctors;
1354 return constructors = buildConstructors(javaClass().getConstructors());
1357 @JRubyMethod
1358 public RubyArray classes() {
1359 return JavaClass.getRubyArray(getRuntime(), javaClass().getClasses());
1362 @JRubyMethod
1363 public RubyArray declared_classes() {
1364 Ruby runtime = getRuntime();
1365 RubyArray result = runtime.newArray();
1366 Class<?> javaClass = javaClass();
1367 try {
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
1376 // classes anyway
1377 try {
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
1389 return result;
1392 @JRubyMethod
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]));
1402 return result;
1405 @JRubyMethod(rest = true)
1406 public JavaConstructor constructor(IRubyObject[] args) {
1407 try {
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) {
1418 try {
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;
1437 @JRubyMethod
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();
1452 if (length == 0) {
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) ) {
1459 throw getRuntime()
1460 .newTypeError(dimensionLength, getRuntime().getInteger());
1462 dimensions[i] = (int) ((RubyInteger) dimensionLength).getLongValue();
1464 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), dimensions));
1465 } else {
1466 throw getRuntime().newArgumentError(
1467 "invalid length or dimensions specifier for java array" +
1468 " - must be Integer or Array of Integer");
1472 @JRubyMethod
1473 public RubyArray fields() {
1474 return buildFieldResults(javaClass().getFields());
1477 @JRubyMethod
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]));
1487 return result;
1490 @JRubyMethod(required = 1)
1491 public JavaField field(IRubyObject name) {
1492 String stringName = name.asJavaString();
1493 try {
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();
1504 try {
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);
1516 @JRubyMethod
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)) {
1539 return true;
1542 otherClass = JavaUtil.primitiveToWrapper(otherClass);
1543 thisClass = JavaUtil.primitiveToWrapper(thisClass);
1545 if(thisClass.isAssignableFrom(otherClass)) {
1546 return true;
1548 if(Number.class.isAssignableFrom(thisClass)) {
1549 if(Number.class.isAssignableFrom(otherClass)) {
1550 return true;
1552 if(otherClass.equals(Character.class)) {
1553 return true;
1556 if(thisClass.equals(Character.class)) {
1557 if(Number.class.isAssignableFrom(otherClass)) {
1558 return true;
1561 return false;
1564 private boolean isPrimitive() {
1565 return javaClass().isPrimitive();
1568 @JRubyMethod
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());