Fix for JRUBY-2882. Handle error messages related to constructors better
[jruby.git] / src / org / jruby / javasupport / JavaClass.java
blob8b1220f2193799cae8ab381419ef562529ad9b0c
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 org.jruby.java.invokers.StaticFieldGetter;
39 import org.jruby.java.invokers.StaticMethodInvoker;
40 import org.jruby.java.invokers.InstanceFieldGetter;
41 import org.jruby.java.invokers.InstanceFieldSetter;
42 import org.jruby.java.invokers.InstanceMethodInvoker;
43 import org.jruby.java.invokers.StaticFieldSetter;
44 import java.io.ByteArrayOutputStream;
45 import java.io.InputStream;
46 import java.io.IOException;
47 import java.lang.reflect.Array;
48 import java.lang.reflect.Constructor;
49 import java.lang.reflect.Field;
50 import java.lang.reflect.Method;
51 import java.lang.reflect.Modifier;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.concurrent.locks.ReentrantLock;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
61 import org.jruby.Ruby;
62 import org.jruby.RubyArray;
63 import org.jruby.RubyBoolean;
64 import org.jruby.RubyClass;
65 import org.jruby.RubyFixnum;
66 import org.jruby.RubyInteger;
67 import org.jruby.RubyModule;
68 import org.jruby.RubyString;
69 import org.jruby.RubySymbol;
70 import org.jruby.anno.JRubyMethod;
71 import org.jruby.anno.JRubyClass;
72 import org.jruby.common.IRubyWarnings.ID;
73 import org.jruby.exceptions.RaiseException;
74 import org.jruby.internal.runtime.methods.DynamicMethod;
75 import org.jruby.java.addons.ArrayJavaAddons;
76 import org.jruby.java.proxies.ArrayJavaProxy;
77 import org.jruby.java.invokers.ConstructorInvoker;
78 import org.jruby.java.invokers.DynalangInstanceInvoker;
79 import org.jruby.javasupport.util.RuntimeHelpers;
80 import org.jruby.runtime.Arity;
81 import org.jruby.runtime.Block;
82 import org.jruby.runtime.CallType;
83 import org.jruby.runtime.ObjectAllocator;
84 import org.jruby.runtime.ThreadContext;
85 import org.jruby.runtime.Visibility;
86 import org.jruby.runtime.builtin.IRubyObject;
87 import org.jruby.runtime.callback.Callback;
88 import org.jruby.util.ByteList;
89 import org.jruby.util.IdUtil;
90 import org.jruby.util.SafePropertyAccessor;
92 @JRubyClass(name="Java::JavaClass", parent="Java::JavaObject")
93 public class JavaClass extends JavaObject {
95 // some null objects to simplify later code
96 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[] {};
97 private static final Method[] EMPTY_METHOD_ARRAY = new Method[] {};
98 private static final Constructor[] EMPTY_CONSTRUCTOR_ARRAY = new Constructor[] {};
99 private static final Field[] EMPTY_FIELD_ARRAY = new Field[] {};
101 private static class AssignedName {
102 // to override an assigned name, the type must be less than
103 // or equal to the assigned type. so a field name in a subclass
104 // will override an alias in a superclass, but not a method.
105 static final int RESERVED = 0;
106 static final int METHOD = 1;
107 static final int FIELD = 2;
108 static final int PROTECTED_METHOD = 3;
109 static final int WEAKLY_RESERVED = 4; // we'll be peeved, but not devastated, if you override
110 static final int ALIAS = 5;
111 // yes, protected fields are weaker than aliases. many conflicts
112 // in the old AWT code, for example, where you really want 'size'
113 // to mean the public method getSize, not the protected field 'size'.
114 static final int PROTECTED_FIELD = 6;
115 String name;
116 int type;
117 AssignedName () {}
118 AssignedName(String name, int type) {
119 this.name = name;
120 this.type = type;
124 // TODO: other reserved names?
125 private static final Map<String, AssignedName> RESERVED_NAMES = new HashMap<String, AssignedName>();
126 static {
127 RESERVED_NAMES.put("__id__", new AssignedName("__id__", AssignedName.RESERVED));
128 RESERVED_NAMES.put("__send__", new AssignedName("__send__", AssignedName.RESERVED));
129 RESERVED_NAMES.put("class", new AssignedName("class", AssignedName.RESERVED));
130 RESERVED_NAMES.put("initialize", new AssignedName("initialize", AssignedName.RESERVED));
131 RESERVED_NAMES.put("object_id", new AssignedName("object_id", AssignedName.RESERVED));
132 RESERVED_NAMES.put("private", new AssignedName("private", AssignedName.RESERVED));
133 RESERVED_NAMES.put("protected", new AssignedName("protected", AssignedName.RESERVED));
134 RESERVED_NAMES.put("public", new AssignedName("public", AssignedName.RESERVED));
136 // weakly reserved names
137 RESERVED_NAMES.put("id", new AssignedName("id", AssignedName.WEAKLY_RESERVED));
139 private static final Map<String, AssignedName> STATIC_RESERVED_NAMES = new HashMap<String, AssignedName>(RESERVED_NAMES);
140 static {
141 STATIC_RESERVED_NAMES.put("new", new AssignedName("new", AssignedName.RESERVED));
143 private static final Map<String, AssignedName> INSTANCE_RESERVED_NAMES = new HashMap<String, AssignedName>(RESERVED_NAMES);
145 private static abstract class NamedInstaller {
146 static final int STATIC_FIELD = 1;
147 static final int STATIC_METHOD = 2;
148 static final int INSTANCE_FIELD = 3;
149 static final int INSTANCE_METHOD = 4;
150 static final int CONSTRUCTOR = 5;
151 String name;
152 int type;
153 Visibility visibility = Visibility.PUBLIC;
154 boolean isProtected;
155 NamedInstaller () {}
156 NamedInstaller (String name, int type) {
157 this.name = name;
158 this.type = type;
160 abstract void install(RubyClass proxy);
161 // small hack to save a cast later on
162 boolean hasLocalMethod() {
163 return true;
165 boolean isPublic() {
166 return visibility == Visibility.PUBLIC;
168 boolean isProtected() {
169 return visibility == Visibility.PROTECTED;
173 private static abstract class FieldInstaller extends NamedInstaller {
174 Field field;
175 FieldInstaller(){}
176 FieldInstaller(String name, int type, Field field) {
177 super(name,type);
178 this.field = field;
182 private static class StaticFieldGetterInstaller extends FieldInstaller {
183 StaticFieldGetterInstaller(){}
184 StaticFieldGetterInstaller(String name, Field field) {
185 super(name,STATIC_FIELD,field);
187 void install(RubyClass proxy) {
188 if (Modifier.isPublic(field.getModifiers())) {
189 proxy.getSingletonClass().addMethod(name, new StaticFieldGetter(name, proxy, field));
194 private static class StaticFieldSetterInstaller extends FieldInstaller {
195 StaticFieldSetterInstaller(){}
196 StaticFieldSetterInstaller(String name, Field field) {
197 super(name,STATIC_FIELD,field);
199 void install(RubyClass proxy) {
200 if (Modifier.isPublic(field.getModifiers())) {
201 proxy.getSingletonClass().addMethod(name, new StaticFieldSetter(name, proxy, field));
206 private static class InstanceFieldGetterInstaller extends FieldInstaller {
207 InstanceFieldGetterInstaller(){}
208 InstanceFieldGetterInstaller(String name, Field field) {
209 super(name,INSTANCE_FIELD,field);
211 void install(RubyClass proxy) {
212 if (Modifier.isPublic(field.getModifiers())) {
213 proxy.addMethod(name, new InstanceFieldGetter(name, proxy, field));
218 private static class InstanceFieldSetterInstaller extends FieldInstaller {
219 InstanceFieldSetterInstaller(){}
220 InstanceFieldSetterInstaller(String name, Field field) {
221 super(name,INSTANCE_FIELD,field);
223 void install(RubyClass proxy) {
224 if (Modifier.isPublic(field.getModifiers())) {
225 proxy.addMethod(name, new InstanceFieldSetter(name, proxy, field));
230 private static abstract class MethodInstaller extends NamedInstaller {
231 private boolean haveLocalMethod;
232 protected List<Method> methods;
233 protected List<String> aliases;
234 MethodInstaller(){}
235 MethodInstaller(String name, int type) {
236 super(name,type);
239 // called only by initializing thread; no synchronization required
240 void addMethod(Method method, Class<?> javaClass) {
241 if (methods == null) {
242 methods = new ArrayList<Method>();
244 if (!Ruby.isSecurityRestricted()) {
245 method.setAccessible(true);
247 methods.add(method);
248 haveLocalMethod |= javaClass == method.getDeclaringClass();
251 // called only by initializing thread; no synchronization required
252 void addAlias(String alias) {
253 if (aliases == null) {
254 aliases = new ArrayList<String>();
256 if (!aliases.contains(alias))
257 aliases.add(alias);
260 // modified only by addMethod; no synchronization required
261 boolean hasLocalMethod () {
262 return haveLocalMethod;
266 private static class ConstructorInvokerInstaller extends MethodInstaller {
267 private boolean haveLocalConstructor;
268 protected List<Constructor> constructors;
270 ConstructorInvokerInstaller(String name) {
271 super(name,STATIC_METHOD);
274 // called only by initializing thread; no synchronization required
275 void addConstructor(Constructor ctor, Class<?> javaClass) {
276 if (constructors == null) {
277 constructors = new ArrayList<Constructor>();
279 if (!Ruby.isSecurityRestricted()) {
280 ctor.setAccessible(true);
282 constructors.add(ctor);
283 haveLocalConstructor |= javaClass == ctor.getDeclaringClass();
286 void install(RubyClass proxy) {
287 if (haveLocalConstructor) {
288 DynamicMethod method = new ConstructorInvoker(proxy, constructors);
289 proxy.addMethod(name, method);
294 private static class StaticMethodInvokerInstaller extends MethodInstaller {
295 StaticMethodInvokerInstaller(String name) {
296 super(name,STATIC_METHOD);
299 void install(RubyClass proxy) {
300 if (hasLocalMethod()) {
301 RubyClass singleton = proxy.getSingletonClass();
302 DynamicMethod method = new StaticMethodInvoker(singleton, methods);
303 singleton.addMethod(name, method);
304 if (aliases != null && isPublic() ) {
305 singleton.defineAliases(aliases, this.name);
306 aliases = null;
312 private static class InstanceMethodInvokerInstaller extends MethodInstaller {
313 InstanceMethodInvokerInstaller(String name) {
314 super(name,INSTANCE_METHOD);
316 void install(RubyClass proxy) {
317 if (hasLocalMethod()) {
318 DynamicMethod method;
319 if (SafePropertyAccessor.getBoolean("jruby.dynalang.enabled", false)) {
320 method = new DynalangInstanceInvoker(proxy, methods);
321 } else {
322 method = new InstanceMethodInvoker(proxy, methods);
324 proxy.addMethod(name, method);
325 if (aliases != null && isPublic()) {
326 proxy.defineAliases(aliases, this.name);
327 aliases = null;
333 private static class ConstantField {
334 static final int CONSTANT = Modifier.FINAL | Modifier.PUBLIC | Modifier.STATIC;
335 final Field field;
336 ConstantField(Field field) {
337 this.field = field;
339 void install(final RubyModule proxy) {
340 if (proxy.fastGetConstantAt(field.getName()) == null) {
341 // TODO: catch exception if constant is already set by other
342 // thread
343 if (!Ruby.isSecurityRestricted()) {
344 field.setAccessible(true);
346 try {
347 proxy.setConstant(field.getName(), JavaUtil.convertJavaToUsableRubyObject(proxy.getRuntime(), field.get(null)));
348 } catch (IllegalAccessException iae) {
349 throw proxy.getRuntime().newTypeError(
350 "illegal access on setting variable: " + iae.getMessage());
354 static boolean isConstant(final Field field) {
355 return (field.getModifiers() & CONSTANT) == CONSTANT &&
356 Character.isUpperCase(field.getName().charAt(0));
360 private final RubyModule JAVA_UTILITIES = getRuntime().getJavaSupport().getJavaUtilitiesModule();
362 private Map<String, AssignedName> staticAssignedNames;
363 private Map<String, AssignedName> instanceAssignedNames;
364 private Map<String, NamedInstaller> staticInstallers;
365 private Map<String, NamedInstaller> instanceInstallers;
366 private ConstructorInvokerInstaller constructorInstaller;
367 private List<ConstantField> constantFields;
368 // caching constructors, as they're accessed for each new instance
369 private volatile RubyArray constructors;
371 private volatile ArrayList<IRubyObject> proxyExtenders;
373 // proxy module for interfaces
374 private volatile RubyModule proxyModule;
376 // proxy class for concrete classes. also used for
377 // "concrete" interfaces, which is why we have two fields
378 private volatile RubyClass proxyClass;
380 // readable only by thread building proxy, so don't need to be
381 // volatile. used to handle recursive calls to getProxyClass/Module
382 // while proxy is being constructed (usually when a constant
383 // defined by a class is of the same type as that class).
384 private RubyModule unfinishedProxyModule;
385 private RubyClass unfinishedProxyClass;
387 private final ReentrantLock proxyLock = new ReentrantLock();
389 public RubyModule getProxyModule() {
390 // allow proxy to be read without synchronization. if proxy
391 // is under construction, only the building thread can see it.
392 RubyModule proxy;
393 if ((proxy = proxyModule) != null) {
394 // proxy is complete, return it
395 return proxy;
396 } else if (proxyLock.isHeldByCurrentThread()) {
397 // proxy is under construction, building thread can
398 // safely read non-volatile value
399 return unfinishedProxyModule;
401 return null;
404 public RubyClass getProxyClass() {
405 // allow proxy to be read without synchronization. if proxy
406 // is under construction, only the building thread can see it.
407 RubyClass proxy;
408 if ((proxy = proxyClass) != null) {
409 // proxy is complete, return it
410 return proxy;
411 } else if (proxyLock.isHeldByCurrentThread()) {
412 // proxy is under construction, building thread can
413 // safely read non-volatile value
414 return unfinishedProxyClass;
416 return null;
419 public void lockProxy() {
420 proxyLock.lock();
423 public void unlockProxy() {
424 proxyLock.unlock();
427 protected Map<String, AssignedName> getStaticAssignedNames() {
428 return staticAssignedNames;
430 protected Map<String, AssignedName> getInstanceAssignedNames() {
431 return instanceAssignedNames;
434 private JavaClass(Ruby runtime, Class<?> javaClass) {
435 super(runtime, (RubyClass) runtime.getJavaSupport().getJavaClassClass(), javaClass);
436 if (javaClass.isInterface()) {
437 initializeInterface(javaClass);
438 } else if (!(javaClass.isArray() || javaClass.isPrimitive())) {
439 // TODO: public only?
440 initializeClass(javaClass);
444 public boolean equals(Object other) {
445 return other instanceof JavaClass &&
446 this.getValue() == ((JavaClass)other).getValue();
449 private void initializeInterface(Class<?> javaClass) {
450 Map<String, AssignedName> staticNames = new HashMap<String, AssignedName>(STATIC_RESERVED_NAMES);
451 List<ConstantField> constantFields = new ArrayList<ConstantField>();
452 Field[] fields = EMPTY_FIELD_ARRAY;
453 try {
454 fields = javaClass.getDeclaredFields();
455 } catch (SecurityException e) {
456 try {
457 fields = javaClass.getFields();
458 } catch (SecurityException e2) {
461 for (int i = fields.length; --i >= 0; ) {
462 Field field = fields[i];
463 if (javaClass != field.getDeclaringClass()) continue;
464 if (ConstantField.isConstant(field)) {
465 constantFields.add(new ConstantField(field));
468 this.staticAssignedNames = staticNames;
469 this.constantFields = constantFields;
472 private void initializeClass(Class<?> javaClass) {
473 Class<?> superclass = javaClass.getSuperclass();
474 Map<String, AssignedName> staticNames;
475 Map<String, AssignedName> instanceNames;
476 if (superclass == null) {
477 staticNames = new HashMap<String, AssignedName>();
478 instanceNames = new HashMap<String, AssignedName>();
479 } else {
480 JavaClass superJavaClass = get(getRuntime(),superclass);
481 staticNames = new HashMap<String, AssignedName>(superJavaClass.getStaticAssignedNames());
482 instanceNames = new HashMap<String, AssignedName>(superJavaClass.getInstanceAssignedNames());
484 staticNames.putAll(STATIC_RESERVED_NAMES);
485 instanceNames.putAll(INSTANCE_RESERVED_NAMES);
486 Map<String, NamedInstaller> staticCallbacks = new HashMap<String, NamedInstaller>();
487 Map<String, NamedInstaller> instanceCallbacks = new HashMap<String, NamedInstaller>();
488 List<ConstantField> constantFields = new ArrayList<ConstantField>();
489 Field[] fields = EMPTY_FIELD_ARRAY;
490 try {
491 fields = javaClass.getFields();
492 } catch (SecurityException e) {
494 for (int i = fields.length; --i >= 0; ) {
495 Field field = fields[i];
496 if (javaClass != field.getDeclaringClass()) continue;
498 if (ConstantField.isConstant(field)) {
499 constantFields.add(new ConstantField(field));
500 continue;
502 String name = field.getName();
503 int modifiers = field.getModifiers();
504 if (Modifier.isStatic(modifiers)) {
505 AssignedName assignedName = staticNames.get(name);
506 if (assignedName != null && assignedName.type < AssignedName.FIELD)
507 continue;
508 staticNames.put(name,new AssignedName(name,AssignedName.FIELD));
509 staticCallbacks.put(name,new StaticFieldGetterInstaller(name,field));
510 if (!Modifier.isFinal(modifiers)) {
511 String setName = name + '=';
512 staticCallbacks.put(setName,new StaticFieldSetterInstaller(setName,field));
514 } else {
515 AssignedName assignedName = instanceNames.get(name);
516 if (assignedName != null && assignedName.type < AssignedName.FIELD)
517 continue;
518 instanceNames.put(name, new AssignedName(name,AssignedName.FIELD));
519 instanceCallbacks.put(name, new InstanceFieldGetterInstaller(name,field));
520 if (!Modifier.isFinal(modifiers)) {
521 String setName = name + '=';
522 instanceCallbacks.put(setName, new InstanceFieldSetterInstaller(setName,field));
526 // TODO: protected methods. this is going to require a rework
527 // of some of the mechanism.
528 Method[] methods = EMPTY_METHOD_ARRAY;
529 for (Class c = javaClass; c != null; c = c.getSuperclass()) {
530 try {
531 methods = javaClass.getMethods();
532 break;
533 } catch (SecurityException e) {
536 for (int i = methods.length; --i >= 0; ) {
537 // we need to collect all methods, though we'll only
538 // install the ones that are named in this class
539 Method method = methods[i];
540 String name = method.getName();
541 if (Modifier.isStatic(method.getModifiers())) {
542 AssignedName assignedName = staticNames.get(name);
543 if (assignedName == null) {
544 staticNames.put(name,new AssignedName(name,AssignedName.METHOD));
545 } else {
546 if (assignedName.type < AssignedName.METHOD)
547 continue;
548 if (assignedName.type != AssignedName.METHOD) {
549 staticCallbacks.remove(name);
550 staticCallbacks.remove(name+'=');
551 staticNames.put(name,new AssignedName(name,AssignedName.METHOD));
554 StaticMethodInvokerInstaller invoker = (StaticMethodInvokerInstaller)staticCallbacks.get(name);
555 if (invoker == null) {
556 invoker = new StaticMethodInvokerInstaller(name);
557 staticCallbacks.put(name,invoker);
559 invoker.addMethod(method,javaClass);
560 } else {
561 AssignedName assignedName = instanceNames.get(name);
562 if (assignedName == null) {
563 instanceNames.put(name,new AssignedName(name,AssignedName.METHOD));
564 } else {
565 if (assignedName.type < AssignedName.METHOD)
566 continue;
567 if (assignedName.type != AssignedName.METHOD) {
568 instanceCallbacks.remove(name);
569 instanceCallbacks.remove(name+'=');
570 instanceNames.put(name,new AssignedName(name,AssignedName.METHOD));
573 InstanceMethodInvokerInstaller invoker = (InstanceMethodInvokerInstaller)instanceCallbacks.get(name);
574 if (invoker == null) {
575 invoker = new InstanceMethodInvokerInstaller(name);
576 instanceCallbacks.put(name,invoker);
578 invoker.addMethod(method,javaClass);
581 // TODO: protected methods. this is going to require a rework
582 // of some of the mechanism.
583 Constructor[] constructors = EMPTY_CONSTRUCTOR_ARRAY;
584 try {
585 constructors = javaClass.getConstructors();
586 } catch (SecurityException e) {
588 for (int i = constructors.length; --i >= 0; ) {
589 // we need to collect all methods, though we'll only
590 // install the ones that are named in this class
591 Constructor ctor = constructors[i];
593 if (constructorInstaller == null) {
594 constructorInstaller = new ConstructorInvokerInstaller("__jcreate!");
596 constructorInstaller.addConstructor(ctor,javaClass);
599 this.staticAssignedNames = staticNames;
600 this.instanceAssignedNames = instanceNames;
601 this.staticInstallers = staticCallbacks;
602 this.instanceInstallers = instanceCallbacks;
603 this.constantFields = constantFields;
606 public void setupProxy(final RubyClass proxy) {
607 assert proxyLock.isHeldByCurrentThread();
608 proxy.defineFastMethod("__jsend!", __jsend_method);
609 final Class<?> javaClass = javaClass();
610 if (javaClass.isInterface()) {
611 setupInterfaceProxy(proxy);
612 return;
614 assert this.proxyClass == null;
615 this.unfinishedProxyClass = proxy;
616 if (javaClass.isArray() || javaClass.isPrimitive()) {
617 // see note below re: 2-field kludge
618 this.proxyClass = proxy;
619 this.proxyModule = proxy;
620 return;
623 for (ConstantField field: constantFields) {
624 field.install(proxy);
626 for (Iterator<NamedInstaller> iter = staticInstallers.values().iterator(); iter.hasNext(); ) {
627 NamedInstaller installer = iter.next();
628 if (installer.type == NamedInstaller.STATIC_METHOD && installer.hasLocalMethod()) {
629 assignAliases((MethodInstaller)installer,staticAssignedNames);
631 installer.install(proxy);
633 for (Iterator<NamedInstaller> iter = instanceInstallers.values().iterator(); iter.hasNext(); ) {
634 NamedInstaller installer = iter.next();
635 if (installer.type == NamedInstaller.INSTANCE_METHOD && installer.hasLocalMethod()) {
636 assignAliases((MethodInstaller)installer,instanceAssignedNames);
638 installer.install(proxy);
641 if (constructorInstaller != null) {
642 constructorInstaller.install(proxy);
645 // setup constants for public inner classes
646 Class<?>[] classes = EMPTY_CLASS_ARRAY;
647 try {
648 classes = javaClass.getClasses();
649 } catch (SecurityException e) {
651 for (int i = classes.length; --i >= 0; ) {
652 if (javaClass == classes[i].getDeclaringClass()) {
653 Class<?> clazz = classes[i];
654 String simpleName = getSimpleName(clazz);
656 if (simpleName.length() == 0) continue;
658 // Ignore bad constant named inner classes pending JRUBY-697
659 if (IdUtil.isConstant(simpleName) && proxy.getConstantAt(simpleName) == null) {
660 proxy.setConstant(simpleName,
661 Java.get_proxy_class(JAVA_UTILITIES,get(getRuntime(),clazz)));
665 // FIXME: bit of a kludge here (non-interface classes assigned to both
666 // class and module fields). simplifies proxy extender code, will go away
667 // when JI is overhauled (and proxy extenders are deprecated).
668 this.proxyClass = proxy;
669 this.proxyModule = proxy;
671 applyProxyExtenders();
673 // TODO: we can probably release our references to the constantFields
674 // array and static/instance callback hashes at this point.
677 private static void assignAliases(MethodInstaller installer, Map<String, AssignedName> assignedNames) {
678 String name = installer.name;
679 String rubyCasedName = JavaUtil.getRubyCasedName(name);
680 addUnassignedAlias(rubyCasedName,assignedNames,installer);
682 String javaPropertyName = JavaUtil.getJavaPropertyName(name);
683 String rubyPropertyName = null;
685 for (Method method: installer.methods) {
686 Class<?>[] argTypes = method.getParameterTypes();
687 Class<?> resultType = method.getReturnType();
688 int argCount = argTypes.length;
690 // Add property name aliases
691 if (javaPropertyName != null) {
692 if (rubyCasedName.startsWith("get_")) {
693 rubyPropertyName = rubyCasedName.substring(4);
694 if (argCount == 0 || // getFoo => foo
695 argCount == 1 && argTypes[0] == int.class) { // getFoo(int) => foo(int)
697 addUnassignedAlias(javaPropertyName,assignedNames,installer);
698 addUnassignedAlias(rubyPropertyName,assignedNames,installer);
700 } else if (rubyCasedName.startsWith("set_")) {
701 rubyPropertyName = rubyCasedName.substring(4);
702 if (argCount == 1 && resultType == void.class) { // setFoo(Foo) => foo=(Foo)
703 addUnassignedAlias(javaPropertyName+'=',assignedNames,installer);
704 addUnassignedAlias(rubyPropertyName+'=',assignedNames,installer);
706 } else if (rubyCasedName.startsWith("is_")) {
707 rubyPropertyName = rubyCasedName.substring(3);
708 if (resultType == boolean.class) { // isFoo() => foo, isFoo(*) => foo(*)
709 addUnassignedAlias(javaPropertyName,assignedNames,installer);
710 addUnassignedAlias(rubyPropertyName,assignedNames,installer);
715 // Additionally add ?-postfixed aliases to any boolean methods and properties.
716 if (resultType == boolean.class) {
717 // is_something?, contains_thing?
718 addUnassignedAlias(rubyCasedName+'?',assignedNames,installer);
719 if (rubyPropertyName != null) {
720 // something?
721 addUnassignedAlias(rubyPropertyName+'?',assignedNames,installer);
727 private static void addUnassignedAlias(String name, Map<String, AssignedName> assignedNames,
728 MethodInstaller installer) {
729 if (name != null) {
730 AssignedName assignedName = (AssignedName)assignedNames.get(name);
731 if (assignedName == null) {
732 installer.addAlias(name);
733 assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS));
734 } else if (assignedName.type == AssignedName.ALIAS) {
735 installer.addAlias(name);
736 } else if (assignedName.type > AssignedName.ALIAS) {
737 // TODO: there will be some additional logic in this branch
738 // dealing with conflicting protected fields.
739 installer.addAlias(name);
740 assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS));
745 // old (quasi-deprecated) interface class
746 private void setupInterfaceProxy(final RubyClass proxy) {
747 assert javaClass().isInterface();
748 assert proxyLock.isHeldByCurrentThread();
749 assert this.proxyClass == null;
750 this.proxyClass = proxy;
751 // nothing else to here - the module version will be
752 // included in the class.
755 public void setupInterfaceModule(final RubyModule module) {
756 assert javaClass().isInterface();
757 assert proxyLock.isHeldByCurrentThread();
758 assert this.proxyModule == null;
759 this.unfinishedProxyModule = module;
760 Class<?> javaClass = javaClass();
761 for (ConstantField field: constantFields) {
762 field.install(module);
764 // setup constants for public inner classes
765 Class<?>[] classes = EMPTY_CLASS_ARRAY;
766 try {
767 classes = javaClass.getClasses();
768 } catch (SecurityException e) {
770 for (int i = classes.length; --i >= 0; ) {
771 if (javaClass == classes[i].getDeclaringClass()) {
772 Class<?> clazz = classes[i];
773 String simpleName = getSimpleName(clazz);
774 if (simpleName.length() == 0) continue;
776 // Ignore bad constant named inner classes pending JRUBY-697
777 if (IdUtil.isConstant(simpleName) && module.getConstantAt(simpleName) == null) {
778 module.const_set(getRuntime().newString(simpleName),
779 Java.get_proxy_class(JAVA_UTILITIES,get(getRuntime(),clazz)));
784 this.proxyModule = module;
785 applyProxyExtenders();
788 public void addProxyExtender(final IRubyObject extender) {
789 lockProxy();
790 try {
791 if (!extender.respondsTo("extend_proxy")) {
792 throw getRuntime().newTypeError("proxy extender must have an extend_proxy method");
794 if (proxyModule == null) {
795 if (proxyExtenders == null) {
796 proxyExtenders = new ArrayList<IRubyObject>();
798 proxyExtenders.add(extender);
799 } else {
800 getRuntime().getWarnings().warn(ID.PROXY_EXTENDED_LATE, " proxy extender added after proxy class created for " + this);
801 extendProxy(extender);
803 } finally {
804 unlockProxy();
808 private void applyProxyExtenders() {
809 ArrayList<IRubyObject> extenders;
810 if ((extenders = proxyExtenders) != null) {
811 for (IRubyObject extender : extenders) {
812 extendProxy(extender);
814 proxyExtenders = null;
818 private void extendProxy(IRubyObject extender) {
819 extender.callMethod(getRuntime().getCurrentContext(), "extend_proxy", proxyModule);
822 @JRubyMethod(required = 1)
823 public IRubyObject extend_proxy(IRubyObject extender) {
824 addProxyExtender(extender);
825 return getRuntime().getNil();
828 public static JavaClass get(Ruby runtime, Class<?> klass) {
829 JavaClass javaClass = runtime.getJavaSupport().getJavaClassFromCache(klass);
830 if (javaClass == null) {
831 javaClass = createJavaClass(runtime,klass);
833 return javaClass;
836 public static RubyArray getRubyArray(Ruby runtime, Class<?>[] classes) {
837 IRubyObject[] javaClasses = new IRubyObject[classes.length];
838 for (int i = classes.length; --i >= 0; ) {
839 javaClasses[i] = get(runtime, classes[i]);
841 return runtime.newArrayNoCopy(javaClasses);
844 private static synchronized JavaClass createJavaClass(Ruby runtime, Class<?> klass) {
845 // double-check the cache now that we're synchronized
846 JavaClass javaClass = runtime.getJavaSupport().getJavaClassFromCache(klass);
847 if (javaClass == null) {
848 javaClass = new JavaClass(runtime, klass);
849 runtime.getJavaSupport().putJavaClassIntoCache(javaClass);
851 return javaClass;
854 public static RubyClass createJavaClassClass(Ruby runtime, RubyModule javaModule) {
855 // FIXME: Determine if a real allocator is needed here. Do people want to extend
856 // JavaClass? Do we want them to do that? Can you Class.new(JavaClass)? Should
857 // you be able to?
858 // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't intend for people to monkey with
859 // this type and it can't be marshalled. Confirm. JRUBY-415
860 RubyClass result = javaModule.defineClassUnder("JavaClass", javaModule.fastGetClass("JavaObject"), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
862 result.includeModule(runtime.fastGetModule("Comparable"));
864 result.defineAnnotatedMethods(JavaClass.class);
866 result.getMetaClass().undefineMethod("new");
867 result.getMetaClass().undefineMethod("allocate");
869 return result;
872 public static synchronized JavaClass forNameVerbose(Ruby runtime, String className) {
873 Class<?> klass = runtime.getJavaSupport().loadJavaClassVerbose(className);
874 return JavaClass.get(runtime, klass);
877 public static synchronized JavaClass forNameQuiet(Ruby runtime, String className) {
878 Class klass = runtime.getJavaSupport().loadJavaClassQuiet(className);
879 return JavaClass.get(runtime, klass);
882 @JRubyMethod(name = "for_name", required = 1, meta = true)
883 public static JavaClass for_name(IRubyObject recv, IRubyObject name) {
884 return forNameVerbose(recv.getRuntime(), name.asJavaString());
887 private static final Callback __jsend_method = new Callback() {
888 public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) {
889 String name = args[0].asJavaString();
891 DynamicMethod method = self.getMetaClass().searchMethod(name);
892 int v = method.getArity().getValue();
894 IRubyObject[] newArgs = new IRubyObject[args.length - 1];
895 System.arraycopy(args, 1, newArgs, 0, newArgs.length);
897 if(v < 0 || v == (newArgs.length)) {
898 return RuntimeHelpers.invoke(self.getRuntime().getCurrentContext(), self, name, newArgs, block);
899 } else {
900 RubyClass superClass = self.getMetaClass().getSuperClass();
901 return RuntimeHelpers.invokeAs(self.getRuntime().getCurrentContext(), superClass, self, name, newArgs, CallType.SUPER, block);
905 public Arity getArity() {
906 return Arity.optional();
910 @JRubyMethod
911 public RubyModule ruby_class() {
912 // Java.getProxyClass deals with sync issues, so we won't duplicate the logic here
913 return Java.getProxyClass(getRuntime(), this);
916 @JRubyMethod(name = "public?")
917 public RubyBoolean public_p() {
918 return getRuntime().newBoolean(Modifier.isPublic(javaClass().getModifiers()));
921 @JRubyMethod(name = "protected?")
922 public RubyBoolean protected_p() {
923 return getRuntime().newBoolean(Modifier.isProtected(javaClass().getModifiers()));
926 @JRubyMethod(name = "private?")
927 public RubyBoolean private_p() {
928 return getRuntime().newBoolean(Modifier.isPrivate(javaClass().getModifiers()));
931 public Class javaClass() {
932 return (Class) getValue();
935 @JRubyMethod(name = "final?")
936 public RubyBoolean final_p() {
937 return getRuntime().newBoolean(Modifier.isFinal(javaClass().getModifiers()));
940 @JRubyMethod(name = "interface?")
941 public RubyBoolean interface_p() {
942 return getRuntime().newBoolean(javaClass().isInterface());
945 @JRubyMethod(name = "array?")
946 public RubyBoolean array_p() {
947 return getRuntime().newBoolean(javaClass().isArray());
950 @JRubyMethod(name = "enum?")
951 public RubyBoolean enum_p() {
952 return getRuntime().newBoolean(javaClass().isEnum());
955 @JRubyMethod(name = "annotation?")
956 public RubyBoolean annotation_p() {
957 return getRuntime().newBoolean(javaClass().isAnnotation());
960 @JRubyMethod(name = "anonymous_class?")
961 public RubyBoolean anonymous_class_p() {
962 return getRuntime().newBoolean(javaClass().isAnonymousClass());
965 @JRubyMethod(name = "local_class?")
966 public RubyBoolean local_class_p() {
967 return getRuntime().newBoolean(javaClass().isLocalClass());
970 @JRubyMethod(name = "member_class?")
971 public RubyBoolean member_class_p() {
972 return getRuntime().newBoolean(javaClass().isMemberClass());
975 @JRubyMethod(name = "synthetic?")
976 public IRubyObject synthetic_p() {
977 return getRuntime().newBoolean(javaClass().isSynthetic());
980 @JRubyMethod(name = {"name", "to_s"})
981 public RubyString name() {
982 return getRuntime().newString(javaClass().getName());
985 @JRubyMethod
986 public IRubyObject canonical_name() {
987 String canonicalName = javaClass().getCanonicalName();
988 if (canonicalName != null) {
989 return getRuntime().newString(canonicalName);
991 return getRuntime().getNil();
994 @JRubyMethod(name = "package")
995 public IRubyObject get_package() {
996 return Java.getInstance(getRuntime(), javaClass().getPackage());
999 @JRubyMethod
1000 public IRubyObject class_loader() {
1001 return Java.getInstance(getRuntime(), javaClass().getClassLoader());
1004 @JRubyMethod
1005 public IRubyObject protection_domain() {
1006 return Java.getInstance(getRuntime(), javaClass().getProtectionDomain());
1009 @JRubyMethod(required = 1)
1010 public IRubyObject resource(IRubyObject name) {
1011 return Java.getInstance(getRuntime(), javaClass().getResource(name.asJavaString()));
1014 @JRubyMethod(required = 1)
1015 public IRubyObject resource_as_stream(IRubyObject name) {
1016 return Java.getInstance(getRuntime(), javaClass().getResourceAsStream(name.asJavaString()));
1019 @JRubyMethod(required = 1)
1020 public IRubyObject resource_as_string(IRubyObject name) {
1021 InputStream in = javaClass().getResourceAsStream(name.asJavaString());
1022 if (in == null) return getRuntime().getNil();
1023 ByteArrayOutputStream out = new ByteArrayOutputStream();
1024 try {
1025 int len;
1026 byte[] buf = new byte[4096];
1027 while ((len = in.read(buf)) >= 0) {
1028 out.write(buf, 0, len);
1030 } catch (IOException e) {
1031 throw getRuntime().newIOErrorFromException(e);
1033 return getRuntime().newString(new ByteList(out.toByteArray(), false));
1036 @SuppressWarnings("unchecked")
1037 @JRubyMethod(required = 1)
1038 public IRubyObject annotation(IRubyObject annoClass) {
1039 if (!(annoClass instanceof JavaClass)) {
1040 throw getRuntime().newTypeError(annoClass, getRuntime().getJavaSupport().getJavaClassClass());
1042 return Java.getInstance(getRuntime(), javaClass().getAnnotation(((JavaClass)annoClass).javaClass()));
1045 @JRubyMethod
1046 public IRubyObject annotations() {
1047 // note: intentionally returning the actual array returned from Java, rather
1048 // than wrapping it in a RubyArray. wave of the future, when java_class will
1049 // return the actual class, rather than a JavaClass wrapper.
1050 return Java.getInstance(getRuntime(), javaClass().getAnnotations());
1053 @JRubyMethod(name = "annotations?")
1054 public RubyBoolean annotations_p() {
1055 return getRuntime().newBoolean(javaClass().getAnnotations().length > 0);
1058 @JRubyMethod
1059 public IRubyObject declared_annotations() {
1060 // see note above re: return type
1061 return Java.getInstance(getRuntime(), javaClass().getDeclaredAnnotations());
1064 @JRubyMethod(name = "declared_annotations?")
1065 public RubyBoolean declared_annotations_p() {
1066 return getRuntime().newBoolean(javaClass().getDeclaredAnnotations().length > 0);
1069 @SuppressWarnings("unchecked")
1070 @JRubyMethod(name = "annotation_present?", required = 1)
1071 public IRubyObject annotation_present_p(IRubyObject annoClass) {
1072 if (!(annoClass instanceof JavaClass)) {
1073 throw getRuntime().newTypeError(annoClass, getRuntime().getJavaSupport().getJavaClassClass());
1075 return getRuntime().newBoolean(javaClass().isAnnotationPresent(((JavaClass)annoClass).javaClass()));
1078 @JRubyMethod
1079 public IRubyObject modifiers() {
1080 return getRuntime().newFixnum(javaClass().getModifiers());
1083 @JRubyMethod
1084 public IRubyObject declaring_class() {
1085 Class<?> clazz = javaClass().getDeclaringClass();
1086 if (clazz != null) {
1087 return JavaClass.get(getRuntime(), clazz);
1089 return getRuntime().getNil();
1092 @JRubyMethod
1093 public IRubyObject enclosing_class() {
1094 return Java.getInstance(getRuntime(), javaClass().getEnclosingClass());
1097 @JRubyMethod
1098 public IRubyObject enclosing_constructor() {
1099 Constructor<?> ctor = javaClass().getEnclosingConstructor();
1100 if (ctor != null) {
1101 return new JavaConstructor(getRuntime(), ctor);
1103 return getRuntime().getNil();
1106 @JRubyMethod
1107 public IRubyObject enclosing_method() {
1108 Method meth = javaClass().getEnclosingMethod();
1109 if (meth != null) {
1110 return new JavaMethod(getRuntime(), meth);
1112 return getRuntime().getNil();
1115 @JRubyMethod
1116 public IRubyObject enum_constants() {
1117 return Java.getInstance(getRuntime(), javaClass().getEnumConstants());
1120 @JRubyMethod
1121 public IRubyObject generic_interfaces() {
1122 return Java.getInstance(getRuntime(), javaClass().getGenericInterfaces());
1125 @JRubyMethod
1126 public IRubyObject generic_superclass() {
1127 return Java.getInstance(getRuntime(), javaClass().getGenericSuperclass());
1130 @JRubyMethod
1131 public IRubyObject type_parameters() {
1132 return Java.getInstance(getRuntime(), javaClass().getTypeParameters());
1135 @JRubyMethod
1136 public IRubyObject signers() {
1137 return Java.getInstance(getRuntime(), javaClass().getSigners());
1140 private static String getSimpleName(Class<?> clazz) {
1141 if (clazz.isArray()) {
1142 return getSimpleName(clazz.getComponentType()) + "[]";
1145 String className = clazz.getName();
1146 int len = className.length();
1147 int i = className.lastIndexOf('$');
1148 if (i != -1) {
1149 do {
1150 i++;
1151 } while (i < len && Character.isDigit(className.charAt(i)));
1152 return className.substring(i);
1155 return className.substring(className.lastIndexOf('.') + 1);
1158 @JRubyMethod
1159 public RubyString simple_name() {
1160 return getRuntime().newString(getSimpleName(javaClass()));
1163 @JRubyMethod
1164 public IRubyObject superclass() {
1165 Class<?> superclass = javaClass().getSuperclass();
1166 if (superclass == null) {
1167 return getRuntime().getNil();
1169 return JavaClass.get(getRuntime(), superclass);
1172 @JRubyMethod(name = "<=>", required = 1)
1173 public RubyFixnum op_cmp(IRubyObject other) {
1174 if (! (other instanceof JavaClass)) {
1175 throw getRuntime().newTypeError("<=> requires JavaClass (" + other.getType() + " given)");
1177 JavaClass otherClass = (JavaClass) other;
1178 if (this.javaClass() == otherClass.javaClass()) {
1179 return getRuntime().newFixnum(0);
1181 if (otherClass.javaClass().isAssignableFrom(this.javaClass())) {
1182 return getRuntime().newFixnum(-1);
1184 return getRuntime().newFixnum(1);
1187 @JRubyMethod
1188 public RubyArray java_instance_methods() {
1189 return java_methods(javaClass().getMethods(), false);
1192 @JRubyMethod
1193 public RubyArray declared_instance_methods() {
1194 return java_methods(javaClass().getDeclaredMethods(), false);
1197 private RubyArray java_methods(Method[] methods, boolean isStatic) {
1198 RubyArray result = getRuntime().newArray(methods.length);
1199 for (int i = 0; i < methods.length; i++) {
1200 Method method = methods[i];
1201 if (isStatic == Modifier.isStatic(method.getModifiers())) {
1202 result.append(JavaMethod.create(getRuntime(), method));
1205 return result;
1208 @JRubyMethod
1209 public RubyArray java_class_methods() {
1210 return java_methods(javaClass().getMethods(), true);
1213 @JRubyMethod
1214 public RubyArray declared_class_methods() {
1215 return java_methods(javaClass().getDeclaredMethods(), true);
1218 @JRubyMethod(required = 1, rest = true)
1219 public JavaMethod java_method(IRubyObject[] args) throws ClassNotFoundException {
1220 String methodName = args[0].asJavaString();
1221 Class<?>[] argumentTypes = buildArgumentTypes(args);
1222 return JavaMethod.create(getRuntime(), javaClass(), methodName, argumentTypes);
1225 @JRubyMethod(required = 1, rest = true)
1226 public JavaMethod declared_method(IRubyObject[] args) throws ClassNotFoundException {
1227 String methodName = args[0].asJavaString();
1228 Class<?>[] argumentTypes = buildArgumentTypes(args);
1229 return JavaMethod.createDeclared(getRuntime(), javaClass(), methodName, argumentTypes);
1232 @JRubyMethod(required = 1, rest = true)
1233 public JavaCallable declared_method_smart(IRubyObject[] args) throws ClassNotFoundException {
1234 String methodName = args[0].asJavaString();
1235 Class<?>[] argumentTypes = buildArgumentTypes(args);
1237 JavaCallable callable = getMatchingCallable(getRuntime(), javaClass(), methodName, argumentTypes);
1239 if (callable != null) return callable;
1241 throw getRuntime().newNameError("undefined method '" + methodName + "' for class '" + javaClass().getName() + "'",
1242 methodName);
1245 public static JavaCallable getMatchingCallable(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
1246 if ("<init>".equals(methodName)) {
1247 return JavaConstructor.getMatchingConstructor(runtime, javaClass, argumentTypes);
1248 } else {
1249 // FIXME: do we really want 'declared' methods? includes private/protected, and does _not_
1250 // include superclass methods
1251 return JavaMethod.getMatchingDeclaredMethod(runtime, javaClass, methodName, argumentTypes);
1255 private Class<?>[] buildArgumentTypes(IRubyObject[] args) throws ClassNotFoundException {
1256 if (args.length < 1) {
1257 throw getRuntime().newArgumentError(args.length, 1);
1259 Class<?>[] argumentTypes = new Class[args.length - 1];
1260 for (int i = 1; i < args.length; i++) {
1261 JavaClass type;
1262 if (args[i] instanceof JavaClass) {
1263 type = (JavaClass)args[i];
1264 } else if (args[i].respondsTo("java_class")) {
1265 type = (JavaClass)args[i].callMethod(getRuntime().getCurrentContext(), "java_class");
1266 } else {
1267 type = for_name(this, args[i]);
1269 argumentTypes[i - 1] = type.javaClass();
1271 return argumentTypes;
1274 @JRubyMethod
1275 public RubyArray constructors() {
1276 RubyArray ctors;
1277 if ((ctors = constructors) != null) return ctors;
1278 return constructors = buildConstructors(javaClass().getConstructors());
1281 @JRubyMethod
1282 public RubyArray classes() {
1283 return JavaClass.getRubyArray(getRuntime(), javaClass().getClasses());
1286 @JRubyMethod
1287 public RubyArray declared_classes() {
1288 Ruby runtime = getRuntime();
1289 RubyArray result = runtime.newArray();
1290 Class<?> javaClass = javaClass();
1291 try {
1292 Class<?>[] classes = javaClass.getDeclaredClasses();
1293 for (int i = 0; i < classes.length; i++) {
1294 if (Modifier.isPublic(classes[i].getModifiers())) {
1295 result.append(get(runtime, classes[i]));
1298 } catch (SecurityException e) {
1299 // restrictive security policy; no matter, we only want public
1300 // classes anyway
1301 try {
1302 Class<?>[] classes = javaClass.getClasses();
1303 for (int i = 0; i < classes.length; i++) {
1304 if (javaClass == classes[i].getDeclaringClass()) {
1305 result.append(get(runtime, classes[i]));
1308 } catch (SecurityException e2) {
1309 // very restrictive policy (disallows Member.PUBLIC)
1310 // we'd never actually get this far in that case
1313 return result;
1316 @JRubyMethod
1317 public RubyArray declared_constructors() {
1318 return buildConstructors(javaClass().getDeclaredConstructors());
1321 private RubyArray buildConstructors(Constructor<?>[] constructors) {
1322 RubyArray result = getRuntime().newArray(constructors.length);
1323 for (int i = 0; i < constructors.length; i++) {
1324 result.append(new JavaConstructor(getRuntime(), constructors[i]));
1326 return result;
1329 @JRubyMethod(rest = true)
1330 public JavaConstructor constructor(IRubyObject[] args) {
1331 try {
1332 Class<?>[] parameterTypes = buildClassArgs(args);
1333 Constructor<?> constructor = javaClass().getConstructor(parameterTypes);
1334 return new JavaConstructor(getRuntime(), constructor);
1335 } catch (NoSuchMethodException nsme) {
1336 throw getRuntime().newNameError("no matching java constructor", null);
1340 @JRubyMethod(rest = true)
1341 public JavaConstructor declared_constructor(IRubyObject[] args) {
1342 try {
1343 Class<?>[] parameterTypes = buildClassArgs(args);
1344 Constructor<?> constructor = javaClass().getDeclaredConstructor (parameterTypes);
1345 return new JavaConstructor(getRuntime(), constructor);
1346 } catch (NoSuchMethodException nsme) {
1347 throw getRuntime().newNameError("no matching java constructor", null);
1351 private Class<?>[] buildClassArgs(IRubyObject[] args) {
1352 JavaSupport javaSupport = getRuntime().getJavaSupport();
1353 Class<?>[] parameterTypes = new Class<?>[args.length];
1354 for (int i = args.length; --i >= 0; ) {
1355 String name = args[i].asJavaString();
1356 parameterTypes[i] = javaSupport.loadJavaClassVerbose(name);
1358 return parameterTypes;
1361 @JRubyMethod
1362 public JavaClass array_class() {
1363 return JavaClass.get(getRuntime(), Array.newInstance(javaClass(), 0).getClass());
1366 @JRubyMethod(required = 1)
1367 public JavaObject new_array(IRubyObject lengthArgument) {
1368 if (lengthArgument instanceof RubyInteger) {
1369 // one-dimensional array
1370 int length = (int) ((RubyInteger) lengthArgument).getLongValue();
1371 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), length));
1372 } else if (lengthArgument instanceof RubyArray) {
1373 // n-dimensional array
1374 List list = ((RubyArray)lengthArgument).getList();
1375 int length = list.size();
1376 if (length == 0) {
1377 throw getRuntime().newArgumentError("empty dimensions specifier for java array");
1379 int[] dimensions = new int[length];
1380 for (int i = length; --i >= 0; ) {
1381 IRubyObject dimensionLength = (IRubyObject)list.get(i);
1382 if ( !(dimensionLength instanceof RubyInteger) ) {
1383 throw getRuntime()
1384 .newTypeError(dimensionLength, getRuntime().getInteger());
1386 dimensions[i] = (int) ((RubyInteger) dimensionLength).getLongValue();
1388 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), dimensions));
1389 } else {
1390 throw getRuntime().newArgumentError(
1391 "invalid length or dimensions specifier for java array" +
1392 " - must be Integer or Array of Integer");
1396 public IRubyObject emptyJavaArray(ThreadContext context) {
1397 JavaArray javaArray = new JavaArray(getRuntime(), Array.newInstance(javaClass(), 0));
1398 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1400 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1401 proxy.dataWrapStruct(javaArray);
1403 return proxy;
1406 public IRubyObject javaArraySubarray(ThreadContext context, JavaArray fromArray, int index, int size) {
1407 int actualLength = Array.getLength(fromArray.getValue());
1408 if (index >= actualLength) {
1409 return context.getRuntime().getNil();
1410 } else {
1411 if (index + size > actualLength) {
1412 size = actualLength - index;
1415 Object newArray = Array.newInstance(javaClass(), size);
1416 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1417 System.arraycopy(fromArray.getValue(), index, newArray, 0, size);
1418 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1420 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1421 proxy.dataWrapStruct(javaArray);
1423 return proxy;
1428 * Contatenate two Java arrays into a new one. The component type of the
1429 * additional array must be assignable to the component type of the
1430 * original array.
1432 * @param context
1433 * @param original
1434 * @param additional
1435 * @return
1437 public IRubyObject concatArrays(ThreadContext context, JavaArray original, JavaArray additional) {
1438 int oldLength = (int)original.length().getLongValue();
1439 int addLength = (int)additional.length().getLongValue();
1440 Object newArray = Array.newInstance(javaClass(), oldLength + addLength);
1441 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1442 System.arraycopy(original.getValue(), 0, newArray, 0, oldLength);
1443 System.arraycopy(additional.getValue(), 0, newArray, oldLength, addLength);
1444 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1446 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1447 proxy.dataWrapStruct(javaArray);
1449 return proxy;
1453 * The slow version for when concatenating a Java array of a different type.
1455 * @param context
1456 * @param original
1457 * @param additional
1458 * @return
1460 public IRubyObject concatArrays(ThreadContext context, JavaArray original, IRubyObject additional) {
1461 int oldLength = (int)original.length().getLongValue();
1462 int addLength = (int)((RubyFixnum)RuntimeHelpers.invoke(context, additional, "length")).getLongValue();
1463 Object newArray = Array.newInstance(javaClass(), oldLength + addLength);
1464 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1465 System.arraycopy(original.getValue(), 0, newArray, 0, oldLength);
1466 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1467 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1468 proxy.dataWrapStruct(javaArray);
1470 Ruby runtime = context.getRuntime();
1471 for (int i = 0; i < addLength; i++) {
1472 RuntimeHelpers.invoke(context, proxy, "[]=", runtime.newFixnum(oldLength + i),
1473 RuntimeHelpers.invoke(context, additional, "[]", runtime.newFixnum(i)));
1476 return proxy;
1479 public IRubyObject javaArrayFromRubyArray(ThreadContext context, IRubyObject fromArray) {
1480 Ruby runtime = context.getRuntime();
1481 if (!(fromArray instanceof RubyArray)) {
1482 throw runtime.newTypeError(fromArray, runtime.getArray());
1484 RubyArray rubyArray = (RubyArray)fromArray;
1485 JavaArray javaArray = new JavaArray(getRuntime(), Array.newInstance(javaClass(), rubyArray.size()));
1486 ArrayJavaAddons.copyDataToJavaArray(context, rubyArray, javaArray);
1487 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1489 ArrayJavaProxy proxy = new ArrayJavaProxy(runtime, proxyClass);
1490 proxy.dataWrapStruct(javaArray);
1492 return proxy;
1495 @JRubyMethod
1496 public RubyArray fields() {
1497 return buildFieldResults(javaClass().getFields());
1500 @JRubyMethod
1501 public RubyArray declared_fields() {
1502 return buildFieldResults(javaClass().getDeclaredFields());
1505 private RubyArray buildFieldResults(Field[] fields) {
1506 RubyArray result = getRuntime().newArray(fields.length);
1507 for (int i = 0; i < fields.length; i++) {
1508 result.append(new JavaField(getRuntime(), fields[i]));
1510 return result;
1513 @JRubyMethod(required = 1)
1514 public JavaField field(IRubyObject name) {
1515 String stringName = name.asJavaString();
1516 Field field = null;
1517 try {
1518 field = javaClass().getField(stringName);
1519 return new JavaField(getRuntime(), field);
1520 } catch (NoSuchFieldException nsfe) {
1521 String newName = JavaUtil.getJavaCasedName(stringName);
1522 if(newName != null) {
1523 try {
1524 field = javaClass().getField(newName);
1525 return new JavaField(getRuntime(), field);
1526 } catch (NoSuchFieldException nsfe2) {}
1528 throw undefinedFieldError(stringName);
1532 @JRubyMethod(required = 1)
1533 public JavaField declared_field(IRubyObject name) {
1534 String stringName = name.asJavaString();
1535 Field field = null;
1536 try {
1537 field = javaClass().getDeclaredField(stringName);
1538 return new JavaField(getRuntime(), field);
1539 } catch (NoSuchFieldException nsfe) {
1540 String newName = JavaUtil.getJavaCasedName(stringName);
1541 if(newName != null) {
1542 try {
1543 field = javaClass().getDeclaredField(newName);
1544 return new JavaField(getRuntime(), field);
1545 } catch (NoSuchFieldException nsfe2) {}
1547 throw undefinedFieldError(stringName);
1551 private RaiseException undefinedFieldError(String name) {
1552 return getRuntime().newNameError("undefined field '" + name + "' for class '" + javaClass().getName() + "'", name);
1555 @JRubyMethod
1556 public RubyArray interfaces() {
1557 return JavaClass.getRubyArray(getRuntime(), javaClass().getInterfaces());
1560 @JRubyMethod(name = "primitive?")
1561 public RubyBoolean primitive_p() {
1562 return getRuntime().newBoolean(isPrimitive());
1565 @JRubyMethod(name = "assignable_from?", required = 1)
1566 public RubyBoolean assignable_from_p(IRubyObject other) {
1567 if (! (other instanceof JavaClass)) {
1568 throw getRuntime().newTypeError("assignable_from requires JavaClass (" + other.getType() + " given)");
1571 Class<?> otherClass = ((JavaClass) other).javaClass();
1572 return assignable(javaClass(), otherClass) ? getRuntime().getTrue() : getRuntime().getFalse();
1575 static boolean assignable(Class<?> thisClass, Class<?> otherClass) {
1576 if(!thisClass.isPrimitive() && otherClass == Void.TYPE ||
1577 thisClass.isAssignableFrom(otherClass)) {
1578 return true;
1581 otherClass = JavaUtil.primitiveToWrapper(otherClass);
1582 thisClass = JavaUtil.primitiveToWrapper(thisClass);
1584 if(thisClass.isAssignableFrom(otherClass)) {
1585 return true;
1587 if(Number.class.isAssignableFrom(thisClass)) {
1588 if(Number.class.isAssignableFrom(otherClass)) {
1589 return true;
1591 if(otherClass.equals(Character.class)) {
1592 return true;
1595 if(thisClass.equals(Character.class)) {
1596 if(Number.class.isAssignableFrom(otherClass)) {
1597 return true;
1600 return false;
1603 private boolean isPrimitive() {
1604 return javaClass().isPrimitive();
1607 @JRubyMethod
1608 public JavaClass component_type() {
1609 if (! javaClass().isArray()) {
1610 throw getRuntime().newTypeError("not a java array-class");
1612 return JavaClass.get(getRuntime(), javaClass().getComponentType());