* Eliminate visibility checking for almost all fcall and vcall paths.
[jruby.git] / src / org / jruby / javasupport / JavaClass.java
blob2ce2a6dda0cdde83f6b0fce1e413c0be13d50f80
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.proxies.ArrayJavaProxy;
76 import org.jruby.java.invokers.ConstructorInvoker;
77 import org.jruby.java.invokers.DynalangInstanceInvoker;
78 import org.jruby.javasupport.util.RuntimeHelpers;
79 import org.jruby.runtime.Arity;
80 import org.jruby.runtime.Block;
81 import org.jruby.runtime.CallType;
82 import org.jruby.runtime.ObjectAllocator;
83 import org.jruby.runtime.ThreadContext;
84 import org.jruby.runtime.Visibility;
85 import org.jruby.runtime.builtin.IRubyObject;
86 import org.jruby.runtime.callback.Callback;
87 import org.jruby.util.ByteList;
88 import org.jruby.util.IdUtil;
89 import org.jruby.util.SafePropertyAccessor;
91 @JRubyClass(name="Java::JavaClass", parent="Java::JavaObject")
92 public class JavaClass extends JavaObject {
94 // some null objects to simplify later code
95 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[] {};
96 private static final Method[] EMPTY_METHOD_ARRAY = new Method[] {};
97 private static final Constructor[] EMPTY_CONSTRUCTOR_ARRAY = new Constructor[] {};
98 private static final Field[] EMPTY_FIELD_ARRAY = new Field[] {};
100 private static class AssignedName {
101 // to override an assigned name, the type must be less than
102 // or equal to the assigned type. so a field name in a subclass
103 // will override an alias in a superclass, but not a method.
104 static final int RESERVED = 0;
105 static final int METHOD = 1;
106 static final int FIELD = 2;
107 static final int PROTECTED_METHOD = 3;
108 static final int WEAKLY_RESERVED = 4; // we'll be peeved, but not devastated, if you override
109 static final int ALIAS = 5;
110 // yes, protected fields are weaker than aliases. many conflicts
111 // in the old AWT code, for example, where you really want 'size'
112 // to mean the public method getSize, not the protected field 'size'.
113 static final int PROTECTED_FIELD = 6;
114 String name;
115 int type;
116 AssignedName () {}
117 AssignedName(String name, int type) {
118 this.name = name;
119 this.type = type;
123 // TODO: other reserved names?
124 private static final Map<String, AssignedName> RESERVED_NAMES = new HashMap<String, AssignedName>();
125 static {
126 RESERVED_NAMES.put("__id__", new AssignedName("__id__", AssignedName.RESERVED));
127 RESERVED_NAMES.put("__send__", new AssignedName("__send__", AssignedName.RESERVED));
128 RESERVED_NAMES.put("class", new AssignedName("class", AssignedName.RESERVED));
129 RESERVED_NAMES.put("initialize", new AssignedName("initialize", AssignedName.RESERVED));
130 RESERVED_NAMES.put("object_id", new AssignedName("object_id", AssignedName.RESERVED));
131 RESERVED_NAMES.put("private", new AssignedName("private", AssignedName.RESERVED));
132 RESERVED_NAMES.put("protected", new AssignedName("protected", AssignedName.RESERVED));
133 RESERVED_NAMES.put("public", new AssignedName("public", AssignedName.RESERVED));
135 // weakly reserved names
136 RESERVED_NAMES.put("id", new AssignedName("id", AssignedName.WEAKLY_RESERVED));
138 private static final Map<String, AssignedName> STATIC_RESERVED_NAMES = new HashMap<String, AssignedName>(RESERVED_NAMES);
139 static {
140 STATIC_RESERVED_NAMES.put("new", new AssignedName("new", AssignedName.RESERVED));
142 private static final Map<String, AssignedName> INSTANCE_RESERVED_NAMES = new HashMap<String, AssignedName>(RESERVED_NAMES);
144 private static abstract class NamedInstaller {
145 static final int STATIC_FIELD = 1;
146 static final int STATIC_METHOD = 2;
147 static final int INSTANCE_FIELD = 3;
148 static final int INSTANCE_METHOD = 4;
149 static final int CONSTRUCTOR = 5;
150 String name;
151 int type;
152 Visibility visibility = Visibility.PUBLIC;
153 boolean isProtected;
154 NamedInstaller () {}
155 NamedInstaller (String name, int type) {
156 this.name = name;
157 this.type = type;
159 abstract void install(RubyClass proxy);
160 // small hack to save a cast later on
161 boolean hasLocalMethod() {
162 return true;
164 boolean isPublic() {
165 return visibility == Visibility.PUBLIC;
167 boolean isProtected() {
168 return visibility == Visibility.PROTECTED;
172 private static abstract class FieldInstaller extends NamedInstaller {
173 Field field;
174 FieldInstaller(){}
175 FieldInstaller(String name, int type, Field field) {
176 super(name,type);
177 this.field = field;
181 private static class StaticFieldGetterInstaller extends FieldInstaller {
182 StaticFieldGetterInstaller(){}
183 StaticFieldGetterInstaller(String name, Field field) {
184 super(name,STATIC_FIELD,field);
186 void install(RubyClass proxy) {
187 if (Modifier.isPublic(field.getModifiers())) {
188 proxy.getSingletonClass().addMethod(name, new StaticFieldGetter(name, proxy, field));
193 private static class StaticFieldSetterInstaller extends FieldInstaller {
194 StaticFieldSetterInstaller(){}
195 StaticFieldSetterInstaller(String name, Field field) {
196 super(name,STATIC_FIELD,field);
198 void install(RubyClass proxy) {
199 if (Modifier.isPublic(field.getModifiers())) {
200 proxy.getSingletonClass().addMethod(name, new StaticFieldSetter(name, proxy, field));
205 private static class InstanceFieldGetterInstaller extends FieldInstaller {
206 InstanceFieldGetterInstaller(){}
207 InstanceFieldGetterInstaller(String name, Field field) {
208 super(name,INSTANCE_FIELD,field);
210 void install(RubyClass proxy) {
211 if (Modifier.isPublic(field.getModifiers())) {
212 proxy.addMethod(name, new InstanceFieldGetter(name, proxy, field));
217 private static class InstanceFieldSetterInstaller extends FieldInstaller {
218 InstanceFieldSetterInstaller(){}
219 InstanceFieldSetterInstaller(String name, Field field) {
220 super(name,INSTANCE_FIELD,field);
222 void install(RubyClass proxy) {
223 if (Modifier.isPublic(field.getModifiers())) {
224 proxy.addMethod(name, new InstanceFieldSetter(name, proxy, field));
229 private static abstract class MethodInstaller extends NamedInstaller {
230 private boolean haveLocalMethod;
231 protected List<Method> methods;
232 protected List<String> aliases;
233 MethodInstaller(){}
234 MethodInstaller(String name, int type) {
235 super(name,type);
238 // called only by initializing thread; no synchronization required
239 void addMethod(Method method, Class<?> javaClass) {
240 if (methods == null) {
241 methods = new ArrayList<Method>();
243 if (!Ruby.isSecurityRestricted()) {
244 method.setAccessible(true);
246 methods.add(method);
247 haveLocalMethod |= javaClass == method.getDeclaringClass();
250 // called only by initializing thread; no synchronization required
251 void addAlias(String alias) {
252 if (aliases == null) {
253 aliases = new ArrayList<String>();
255 if (!aliases.contains(alias))
256 aliases.add(alias);
259 // modified only by addMethod; no synchronization required
260 boolean hasLocalMethod () {
261 return haveLocalMethod;
265 private static class ConstructorInvokerInstaller extends MethodInstaller {
266 private boolean haveLocalConstructor;
267 protected List<Constructor> constructors;
269 ConstructorInvokerInstaller(String name) {
270 super(name,STATIC_METHOD);
273 // called only by initializing thread; no synchronization required
274 void addConstructor(Constructor ctor, Class<?> javaClass) {
275 if (constructors == null) {
276 constructors = new ArrayList<Constructor>();
278 if (!Ruby.isSecurityRestricted()) {
279 ctor.setAccessible(true);
281 constructors.add(ctor);
282 haveLocalConstructor |= javaClass == ctor.getDeclaringClass();
285 void install(RubyClass proxy) {
286 if (haveLocalConstructor) {
287 DynamicMethod method = new ConstructorInvoker(proxy, constructors);
288 proxy.addMethod(name, method);
293 private static class StaticMethodInvokerInstaller extends MethodInstaller {
294 StaticMethodInvokerInstaller(String name) {
295 super(name,STATIC_METHOD);
298 void install(RubyClass proxy) {
299 if (hasLocalMethod()) {
300 RubyClass singleton = proxy.getSingletonClass();
301 DynamicMethod method = new StaticMethodInvoker(singleton, methods);
302 singleton.addMethod(name, method);
303 if (aliases != null && isPublic() ) {
304 singleton.defineAliases(aliases, this.name);
305 aliases = null;
311 private static class InstanceMethodInvokerInstaller extends MethodInstaller {
312 InstanceMethodInvokerInstaller(String name) {
313 super(name,INSTANCE_METHOD);
315 void install(RubyClass proxy) {
316 if (hasLocalMethod()) {
317 DynamicMethod method;
318 if (SafePropertyAccessor.getBoolean("jruby.dynalang.enabled", false)) {
319 method = new DynalangInstanceInvoker(proxy, methods);
320 } else {
321 method = new InstanceMethodInvoker(proxy, methods);
323 proxy.addMethod(name, method);
324 if (aliases != null && isPublic()) {
325 proxy.defineAliases(aliases, this.name);
326 aliases = null;
332 private static class ConstantField {
333 static final int CONSTANT = Modifier.FINAL | Modifier.PUBLIC | Modifier.STATIC;
334 final Field field;
335 ConstantField(Field field) {
336 this.field = field;
338 void install(final RubyModule proxy) {
339 if (proxy.fastGetConstantAt(field.getName()) == null) {
340 // TODO: catch exception if constant is already set by other
341 // thread
342 if (!Ruby.isSecurityRestricted()) {
343 field.setAccessible(true);
345 try {
346 proxy.setConstant(field.getName(), JavaUtil.convertJavaToUsableRubyObject(proxy.getRuntime(), field.get(null)));
347 } catch (IllegalAccessException iae) {
348 throw proxy.getRuntime().newTypeError(
349 "illegal access on setting variable: " + iae.getMessage());
353 static boolean isConstant(final Field field) {
354 return (field.getModifiers() & CONSTANT) == CONSTANT &&
355 Character.isUpperCase(field.getName().charAt(0));
359 private final RubyModule JAVA_UTILITIES = getRuntime().getJavaSupport().getJavaUtilitiesModule();
361 private Map<String, AssignedName> staticAssignedNames;
362 private Map<String, AssignedName> instanceAssignedNames;
363 private Map<String, NamedInstaller> staticInstallers;
364 private Map<String, NamedInstaller> instanceInstallers;
365 private ConstructorInvokerInstaller constructorInstaller;
366 private List<ConstantField> constantFields;
367 // caching constructors, as they're accessed for each new instance
368 private volatile RubyArray constructors;
370 private volatile ArrayList<IRubyObject> proxyExtenders;
372 // proxy module for interfaces
373 private volatile RubyModule proxyModule;
375 // proxy class for concrete classes. also used for
376 // "concrete" interfaces, which is why we have two fields
377 private volatile RubyClass proxyClass;
379 // readable only by thread building proxy, so don't need to be
380 // volatile. used to handle recursive calls to getProxyClass/Module
381 // while proxy is being constructed (usually when a constant
382 // defined by a class is of the same type as that class).
383 private RubyModule unfinishedProxyModule;
384 private RubyClass unfinishedProxyClass;
386 private final ReentrantLock proxyLock = new ReentrantLock();
388 public RubyModule getProxyModule() {
389 // allow proxy to be read without synchronization. if proxy
390 // is under construction, only the building thread can see it.
391 RubyModule proxy;
392 if ((proxy = proxyModule) != null) {
393 // proxy is complete, return it
394 return proxy;
395 } else if (proxyLock.isHeldByCurrentThread()) {
396 // proxy is under construction, building thread can
397 // safely read non-volatile value
398 return unfinishedProxyModule;
400 return null;
403 public RubyClass getProxyClass() {
404 // allow proxy to be read without synchronization. if proxy
405 // is under construction, only the building thread can see it.
406 RubyClass proxy;
407 if ((proxy = proxyClass) != null) {
408 // proxy is complete, return it
409 return proxy;
410 } else if (proxyLock.isHeldByCurrentThread()) {
411 // proxy is under construction, building thread can
412 // safely read non-volatile value
413 return unfinishedProxyClass;
415 return null;
418 public void lockProxy() {
419 proxyLock.lock();
422 public void unlockProxy() {
423 proxyLock.unlock();
426 protected Map<String, AssignedName> getStaticAssignedNames() {
427 return staticAssignedNames;
429 protected Map<String, AssignedName> getInstanceAssignedNames() {
430 return instanceAssignedNames;
433 private JavaClass(Ruby runtime, Class<?> javaClass) {
434 super(runtime, (RubyClass) runtime.getJavaSupport().getJavaClassClass(), javaClass);
435 if (javaClass.isInterface()) {
436 initializeInterface(javaClass);
437 } else if (!(javaClass.isArray() || javaClass.isPrimitive())) {
438 // TODO: public only?
439 initializeClass(javaClass);
443 public boolean equals(Object other) {
444 return other instanceof JavaClass &&
445 this.getValue() == ((JavaClass)other).getValue();
448 private void initializeInterface(Class<?> javaClass) {
449 Map<String, AssignedName> staticNames = new HashMap<String, AssignedName>(STATIC_RESERVED_NAMES);
450 List<ConstantField> constantFields = new ArrayList<ConstantField>();
451 Field[] fields = EMPTY_FIELD_ARRAY;
452 try {
453 fields = javaClass.getDeclaredFields();
454 } catch (SecurityException e) {
455 try {
456 fields = javaClass.getFields();
457 } catch (SecurityException e2) {
460 for (int i = fields.length; --i >= 0; ) {
461 Field field = fields[i];
462 if (javaClass != field.getDeclaringClass()) continue;
463 if (ConstantField.isConstant(field)) {
464 constantFields.add(new ConstantField(field));
467 this.staticAssignedNames = staticNames;
468 this.constantFields = constantFields;
471 private void initializeClass(Class<?> javaClass) {
472 Class<?> superclass = javaClass.getSuperclass();
473 Map<String, AssignedName> staticNames;
474 Map<String, AssignedName> instanceNames;
475 if (superclass == null) {
476 staticNames = new HashMap<String, AssignedName>();
477 instanceNames = new HashMap<String, AssignedName>();
478 } else {
479 JavaClass superJavaClass = get(getRuntime(),superclass);
480 staticNames = new HashMap<String, AssignedName>(superJavaClass.getStaticAssignedNames());
481 instanceNames = new HashMap<String, AssignedName>(superJavaClass.getInstanceAssignedNames());
483 staticNames.putAll(STATIC_RESERVED_NAMES);
484 instanceNames.putAll(INSTANCE_RESERVED_NAMES);
485 Map<String, NamedInstaller> staticCallbacks = new HashMap<String, NamedInstaller>();
486 Map<String, NamedInstaller> instanceCallbacks = new HashMap<String, NamedInstaller>();
487 List<ConstantField> constantFields = new ArrayList<ConstantField>();
488 Field[] fields = EMPTY_FIELD_ARRAY;
489 try {
490 fields = javaClass.getFields();
491 } catch (SecurityException e) {
493 for (int i = fields.length; --i >= 0; ) {
494 Field field = fields[i];
495 if (javaClass != field.getDeclaringClass()) continue;
497 if (ConstantField.isConstant(field)) {
498 constantFields.add(new ConstantField(field));
499 continue;
501 String name = field.getName();
502 int modifiers = field.getModifiers();
503 if (Modifier.isStatic(modifiers)) {
504 AssignedName assignedName = staticNames.get(name);
505 if (assignedName != null && assignedName.type < AssignedName.FIELD)
506 continue;
507 staticNames.put(name,new AssignedName(name,AssignedName.FIELD));
508 staticCallbacks.put(name,new StaticFieldGetterInstaller(name,field));
509 if (!Modifier.isFinal(modifiers)) {
510 String setName = name + '=';
511 staticCallbacks.put(setName,new StaticFieldSetterInstaller(setName,field));
513 } else {
514 AssignedName assignedName = instanceNames.get(name);
515 if (assignedName != null && assignedName.type < AssignedName.FIELD)
516 continue;
517 instanceNames.put(name, new AssignedName(name,AssignedName.FIELD));
518 instanceCallbacks.put(name, new InstanceFieldGetterInstaller(name,field));
519 if (!Modifier.isFinal(modifiers)) {
520 String setName = name + '=';
521 instanceCallbacks.put(setName, new InstanceFieldSetterInstaller(setName,field));
525 // TODO: protected methods. this is going to require a rework
526 // of some of the mechanism.
527 Method[] methods = EMPTY_METHOD_ARRAY;
528 for (Class c = javaClass; c != null; c = c.getSuperclass()) {
529 try {
530 methods = javaClass.getMethods();
531 break;
532 } catch (SecurityException e) {
535 for (int i = methods.length; --i >= 0; ) {
536 // we need to collect all methods, though we'll only
537 // install the ones that are named in this class
538 Method method = methods[i];
539 String name = method.getName();
540 if (Modifier.isStatic(method.getModifiers())) {
541 AssignedName assignedName = staticNames.get(name);
542 if (assignedName == null) {
543 staticNames.put(name,new AssignedName(name,AssignedName.METHOD));
544 } else {
545 if (assignedName.type < AssignedName.METHOD)
546 continue;
547 if (assignedName.type != AssignedName.METHOD) {
548 staticCallbacks.remove(name);
549 staticCallbacks.remove(name+'=');
550 staticNames.put(name,new AssignedName(name,AssignedName.METHOD));
553 StaticMethodInvokerInstaller invoker = (StaticMethodInvokerInstaller)staticCallbacks.get(name);
554 if (invoker == null) {
555 invoker = new StaticMethodInvokerInstaller(name);
556 staticCallbacks.put(name,invoker);
558 invoker.addMethod(method,javaClass);
559 } else {
560 AssignedName assignedName = instanceNames.get(name);
561 if (assignedName == null) {
562 instanceNames.put(name,new AssignedName(name,AssignedName.METHOD));
563 } else {
564 if (assignedName.type < AssignedName.METHOD)
565 continue;
566 if (assignedName.type != AssignedName.METHOD) {
567 instanceCallbacks.remove(name);
568 instanceCallbacks.remove(name+'=');
569 instanceNames.put(name,new AssignedName(name,AssignedName.METHOD));
572 InstanceMethodInvokerInstaller invoker = (InstanceMethodInvokerInstaller)instanceCallbacks.get(name);
573 if (invoker == null) {
574 invoker = new InstanceMethodInvokerInstaller(name);
575 instanceCallbacks.put(name,invoker);
577 invoker.addMethod(method,javaClass);
580 // TODO: protected methods. this is going to require a rework
581 // of some of the mechanism.
582 Constructor[] constructors = EMPTY_CONSTRUCTOR_ARRAY;
583 try {
584 constructors = javaClass.getConstructors();
585 } catch (SecurityException e) {
587 for (int i = constructors.length; --i >= 0; ) {
588 // we need to collect all methods, though we'll only
589 // install the ones that are named in this class
590 Constructor ctor = constructors[i];
592 if (constructorInstaller == null) {
593 constructorInstaller = new ConstructorInvokerInstaller("__jcreate!");
595 constructorInstaller.addConstructor(ctor,javaClass);
598 this.staticAssignedNames = staticNames;
599 this.instanceAssignedNames = instanceNames;
600 this.staticInstallers = staticCallbacks;
601 this.instanceInstallers = instanceCallbacks;
602 this.constantFields = constantFields;
605 public void setupProxy(final RubyClass proxy) {
606 assert proxyLock.isHeldByCurrentThread();
607 proxy.defineFastMethod("__jsend!", __jsend_method);
608 final Class<?> javaClass = javaClass();
609 if (javaClass.isInterface()) {
610 setupInterfaceProxy(proxy);
611 return;
613 assert this.proxyClass == null;
614 this.unfinishedProxyClass = proxy;
615 if (javaClass.isArray() || javaClass.isPrimitive()) {
616 // see note below re: 2-field kludge
617 this.proxyClass = proxy;
618 this.proxyModule = proxy;
619 return;
622 for (ConstantField field: constantFields) {
623 field.install(proxy);
625 for (Iterator<NamedInstaller> iter = staticInstallers.values().iterator(); iter.hasNext(); ) {
626 NamedInstaller installer = iter.next();
627 if (installer.type == NamedInstaller.STATIC_METHOD && installer.hasLocalMethod()) {
628 assignAliases((MethodInstaller)installer,staticAssignedNames);
630 installer.install(proxy);
632 for (Iterator<NamedInstaller> iter = instanceInstallers.values().iterator(); iter.hasNext(); ) {
633 NamedInstaller installer = iter.next();
634 if (installer.type == NamedInstaller.INSTANCE_METHOD && installer.hasLocalMethod()) {
635 assignAliases((MethodInstaller)installer,instanceAssignedNames);
637 installer.install(proxy);
640 if (constructorInstaller != null) {
641 constructorInstaller.install(proxy);
644 // setup constants for public inner classes
645 Class<?>[] classes = EMPTY_CLASS_ARRAY;
646 try {
647 classes = javaClass.getClasses();
648 } catch (SecurityException e) {
650 for (int i = classes.length; --i >= 0; ) {
651 if (javaClass == classes[i].getDeclaringClass()) {
652 Class<?> clazz = classes[i];
653 String simpleName = getSimpleName(clazz);
655 if (simpleName.length() == 0) continue;
657 // Ignore bad constant named inner classes pending JRUBY-697
658 if (IdUtil.isConstant(simpleName) && proxy.getConstantAt(simpleName) == null) {
659 proxy.setConstant(simpleName,
660 Java.get_proxy_class(JAVA_UTILITIES,get(getRuntime(),clazz)));
664 // FIXME: bit of a kludge here (non-interface classes assigned to both
665 // class and module fields). simplifies proxy extender code, will go away
666 // when JI is overhauled (and proxy extenders are deprecated).
667 this.proxyClass = proxy;
668 this.proxyModule = proxy;
670 applyProxyExtenders();
672 // TODO: we can probably release our references to the constantFields
673 // array and static/instance callback hashes at this point.
676 private static void assignAliases(MethodInstaller installer, Map<String, AssignedName> assignedNames) {
677 String name = installer.name;
678 String rubyCasedName = JavaUtil.getRubyCasedName(name);
679 addUnassignedAlias(rubyCasedName,assignedNames,installer);
681 String javaPropertyName = JavaUtil.getJavaPropertyName(name);
682 String rubyPropertyName = null;
684 for (Method method: installer.methods) {
685 Class<?>[] argTypes = method.getParameterTypes();
686 Class<?> resultType = method.getReturnType();
687 int argCount = argTypes.length;
689 // Add property name aliases
690 if (javaPropertyName != null) {
691 if (rubyCasedName.startsWith("get_")) {
692 rubyPropertyName = rubyCasedName.substring(4);
693 if (argCount == 0 || // getFoo => foo
694 argCount == 1 && argTypes[0] == int.class) { // getFoo(int) => foo(int)
696 addUnassignedAlias(javaPropertyName,assignedNames,installer);
697 addUnassignedAlias(rubyPropertyName,assignedNames,installer);
699 } else if (rubyCasedName.startsWith("set_")) {
700 rubyPropertyName = rubyCasedName.substring(4);
701 if (argCount == 1 && resultType == void.class) { // setFoo(Foo) => foo=(Foo)
702 addUnassignedAlias(javaPropertyName+'=',assignedNames,installer);
703 addUnassignedAlias(rubyPropertyName+'=',assignedNames,installer);
705 } else if (rubyCasedName.startsWith("is_")) {
706 rubyPropertyName = rubyCasedName.substring(3);
707 if (resultType == boolean.class) { // isFoo() => foo, isFoo(*) => foo(*)
708 addUnassignedAlias(javaPropertyName,assignedNames,installer);
709 addUnassignedAlias(rubyPropertyName,assignedNames,installer);
714 // Additionally add ?-postfixed aliases to any boolean methods and properties.
715 if (resultType == boolean.class) {
716 // is_something?, contains_thing?
717 addUnassignedAlias(rubyCasedName+'?',assignedNames,installer);
718 if (rubyPropertyName != null) {
719 // something?
720 addUnassignedAlias(rubyPropertyName+'?',assignedNames,installer);
726 private static void addUnassignedAlias(String name, Map<String, AssignedName> assignedNames,
727 MethodInstaller installer) {
728 if (name != null) {
729 AssignedName assignedName = (AssignedName)assignedNames.get(name);
730 if (assignedName == null) {
731 installer.addAlias(name);
732 assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS));
733 } else if (assignedName.type == AssignedName.ALIAS) {
734 installer.addAlias(name);
735 } else if (assignedName.type > AssignedName.ALIAS) {
736 // TODO: there will be some additional logic in this branch
737 // dealing with conflicting protected fields.
738 installer.addAlias(name);
739 assignedNames.put(name,new AssignedName(name,AssignedName.ALIAS));
744 // old (quasi-deprecated) interface class
745 private void setupInterfaceProxy(final RubyClass proxy) {
746 assert javaClass().isInterface();
747 assert proxyLock.isHeldByCurrentThread();
748 assert this.proxyClass == null;
749 this.proxyClass = proxy;
750 // nothing else to here - the module version will be
751 // included in the class.
754 public void setupInterfaceModule(final RubyModule module) {
755 assert javaClass().isInterface();
756 assert proxyLock.isHeldByCurrentThread();
757 assert this.proxyModule == null;
758 this.unfinishedProxyModule = module;
759 Class<?> javaClass = javaClass();
760 for (ConstantField field: constantFields) {
761 field.install(module);
763 // setup constants for public inner classes
764 Class<?>[] classes = EMPTY_CLASS_ARRAY;
765 try {
766 classes = javaClass.getClasses();
767 } catch (SecurityException e) {
769 for (int i = classes.length; --i >= 0; ) {
770 if (javaClass == classes[i].getDeclaringClass()) {
771 Class<?> clazz = classes[i];
772 String simpleName = getSimpleName(clazz);
773 if (simpleName.length() == 0) continue;
775 // Ignore bad constant named inner classes pending JRUBY-697
776 if (IdUtil.isConstant(simpleName) && module.getConstantAt(simpleName) == null) {
777 module.const_set(getRuntime().newString(simpleName),
778 Java.get_proxy_class(JAVA_UTILITIES,get(getRuntime(),clazz)));
783 this.proxyModule = module;
784 applyProxyExtenders();
787 public void addProxyExtender(final IRubyObject extender) {
788 lockProxy();
789 try {
790 if (!extender.respondsTo("extend_proxy")) {
791 throw getRuntime().newTypeError("proxy extender must have an extend_proxy method");
793 if (proxyModule == null) {
794 if (proxyExtenders == null) {
795 proxyExtenders = new ArrayList<IRubyObject>();
797 proxyExtenders.add(extender);
798 } else {
799 getRuntime().getWarnings().warn(ID.PROXY_EXTENDED_LATE, " proxy extender added after proxy class created for " + this);
800 extendProxy(extender);
802 } finally {
803 unlockProxy();
807 private void applyProxyExtenders() {
808 ArrayList<IRubyObject> extenders;
809 if ((extenders = proxyExtenders) != null) {
810 for (IRubyObject extender : extenders) {
811 extendProxy(extender);
813 proxyExtenders = null;
817 private void extendProxy(IRubyObject extender) {
818 extender.callMethod(getRuntime().getCurrentContext(), "extend_proxy", proxyModule);
821 @JRubyMethod(required = 1)
822 public IRubyObject extend_proxy(IRubyObject extender) {
823 addProxyExtender(extender);
824 return getRuntime().getNil();
827 public static JavaClass get(Ruby runtime, Class<?> klass) {
828 JavaClass javaClass = runtime.getJavaSupport().getJavaClassFromCache(klass);
829 if (javaClass == null) {
830 javaClass = createJavaClass(runtime,klass);
832 return javaClass;
835 public static RubyArray getRubyArray(Ruby runtime, Class<?>[] classes) {
836 IRubyObject[] javaClasses = new IRubyObject[classes.length];
837 for (int i = classes.length; --i >= 0; ) {
838 javaClasses[i] = get(runtime, classes[i]);
840 return runtime.newArrayNoCopy(javaClasses);
843 private static synchronized JavaClass createJavaClass(Ruby runtime, Class<?> klass) {
844 // double-check the cache now that we're synchronized
845 JavaClass javaClass = runtime.getJavaSupport().getJavaClassFromCache(klass);
846 if (javaClass == null) {
847 javaClass = new JavaClass(runtime, klass);
848 runtime.getJavaSupport().putJavaClassIntoCache(javaClass);
850 return javaClass;
853 public static RubyClass createJavaClassClass(Ruby runtime, RubyModule javaModule) {
854 // FIXME: Determine if a real allocator is needed here. Do people want to extend
855 // JavaClass? Do we want them to do that? Can you Class.new(JavaClass)? Should
856 // you be able to?
857 // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't intend for people to monkey with
858 // this type and it can't be marshalled. Confirm. JRUBY-415
859 RubyClass result = javaModule.defineClassUnder("JavaClass", javaModule.fastGetClass("JavaObject"), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
861 result.includeModule(runtime.fastGetModule("Comparable"));
863 result.defineAnnotatedMethods(JavaClass.class);
865 result.getMetaClass().undefineMethod("new");
866 result.getMetaClass().undefineMethod("allocate");
868 return result;
871 public static synchronized JavaClass forNameVerbose(Ruby runtime, String className) {
872 Class<?> klass = runtime.getJavaSupport().loadJavaClassVerbose(className);
873 return JavaClass.get(runtime, klass);
876 public static synchronized JavaClass forNameQuiet(Ruby runtime, String className) {
877 Class klass = runtime.getJavaSupport().loadJavaClassQuiet(className);
878 return JavaClass.get(runtime, klass);
881 @JRubyMethod(name = "for_name", required = 1, meta = true)
882 public static JavaClass for_name(IRubyObject recv, IRubyObject name) {
883 return forNameVerbose(recv.getRuntime(), name.asJavaString());
886 private static final Callback __jsend_method = new Callback() {
887 public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block block) {
888 String name = args[0].asJavaString();
890 DynamicMethod method = self.getMetaClass().searchMethod(name);
891 int v = method.getArity().getValue();
893 IRubyObject[] newArgs = new IRubyObject[args.length - 1];
894 System.arraycopy(args, 1, newArgs, 0, newArgs.length);
896 if(v < 0 || v == (newArgs.length)) {
897 return RuntimeHelpers.invoke(self.getRuntime().getCurrentContext(), self, name, newArgs, block);
898 } else {
899 RubyClass superClass = self.getMetaClass().getSuperClass();
900 return RuntimeHelpers.invokeAs(self.getRuntime().getCurrentContext(), superClass, self, name, newArgs, CallType.SUPER, block);
904 public Arity getArity() {
905 return Arity.optional();
909 @JRubyMethod
910 public RubyModule ruby_class() {
911 // Java.getProxyClass deals with sync issues, so we won't duplicate the logic here
912 return Java.getProxyClass(getRuntime(), this);
915 @JRubyMethod(name = "public?")
916 public RubyBoolean public_p() {
917 return getRuntime().newBoolean(Modifier.isPublic(javaClass().getModifiers()));
920 @JRubyMethod(name = "protected?")
921 public RubyBoolean protected_p() {
922 return getRuntime().newBoolean(Modifier.isProtected(javaClass().getModifiers()));
925 @JRubyMethod(name = "private?")
926 public RubyBoolean private_p() {
927 return getRuntime().newBoolean(Modifier.isPrivate(javaClass().getModifiers()));
930 public Class javaClass() {
931 return (Class) getValue();
934 @JRubyMethod(name = "final?")
935 public RubyBoolean final_p() {
936 return getRuntime().newBoolean(Modifier.isFinal(javaClass().getModifiers()));
939 @JRubyMethod(name = "interface?")
940 public RubyBoolean interface_p() {
941 return getRuntime().newBoolean(javaClass().isInterface());
944 @JRubyMethod(name = "array?")
945 public RubyBoolean array_p() {
946 return getRuntime().newBoolean(javaClass().isArray());
949 @JRubyMethod(name = "enum?")
950 public RubyBoolean enum_p() {
951 return getRuntime().newBoolean(javaClass().isEnum());
954 @JRubyMethod(name = "annotation?")
955 public RubyBoolean annotation_p() {
956 return getRuntime().newBoolean(javaClass().isAnnotation());
959 @JRubyMethod(name = "anonymous_class?")
960 public RubyBoolean anonymous_class_p() {
961 return getRuntime().newBoolean(javaClass().isAnonymousClass());
964 @JRubyMethod(name = "local_class?")
965 public RubyBoolean local_class_p() {
966 return getRuntime().newBoolean(javaClass().isLocalClass());
969 @JRubyMethod(name = "member_class?")
970 public RubyBoolean member_class_p() {
971 return getRuntime().newBoolean(javaClass().isMemberClass());
974 @JRubyMethod(name = "synthetic?")
975 public IRubyObject synthetic_p() {
976 return getRuntime().newBoolean(javaClass().isSynthetic());
979 @JRubyMethod(name = {"name", "to_s"})
980 public RubyString name() {
981 return getRuntime().newString(javaClass().getName());
984 @JRubyMethod
985 public IRubyObject canonical_name() {
986 String canonicalName = javaClass().getCanonicalName();
987 if (canonicalName != null) {
988 return getRuntime().newString(canonicalName);
990 return getRuntime().getNil();
993 @JRubyMethod(name = "package")
994 public IRubyObject get_package() {
995 return Java.getInstance(getRuntime(), javaClass().getPackage());
998 @JRubyMethod
999 public IRubyObject class_loader() {
1000 return Java.getInstance(getRuntime(), javaClass().getClassLoader());
1003 @JRubyMethod
1004 public IRubyObject protection_domain() {
1005 return Java.getInstance(getRuntime(), javaClass().getProtectionDomain());
1008 @JRubyMethod(required = 1)
1009 public IRubyObject resource(IRubyObject name) {
1010 return Java.getInstance(getRuntime(), javaClass().getResource(name.asJavaString()));
1013 @JRubyMethod(required = 1)
1014 public IRubyObject resource_as_stream(IRubyObject name) {
1015 return Java.getInstance(getRuntime(), javaClass().getResourceAsStream(name.asJavaString()));
1018 @JRubyMethod(required = 1)
1019 public IRubyObject resource_as_string(IRubyObject name) {
1020 InputStream in = javaClass().getResourceAsStream(name.asJavaString());
1021 if (in == null) return getRuntime().getNil();
1022 ByteArrayOutputStream out = new ByteArrayOutputStream();
1023 try {
1024 int len;
1025 byte[] buf = new byte[4096];
1026 while ((len = in.read(buf)) >= 0) {
1027 out.write(buf, 0, len);
1029 } catch (IOException e) {
1030 throw getRuntime().newIOErrorFromException(e);
1032 return getRuntime().newString(new ByteList(out.toByteArray(), false));
1035 @SuppressWarnings("unchecked")
1036 @JRubyMethod(required = 1)
1037 public IRubyObject annotation(IRubyObject annoClass) {
1038 if (!(annoClass instanceof JavaClass)) {
1039 throw getRuntime().newTypeError(annoClass, getRuntime().getJavaSupport().getJavaClassClass());
1041 return Java.getInstance(getRuntime(), javaClass().getAnnotation(((JavaClass)annoClass).javaClass()));
1044 @JRubyMethod
1045 public IRubyObject annotations() {
1046 // note: intentionally returning the actual array returned from Java, rather
1047 // than wrapping it in a RubyArray. wave of the future, when java_class will
1048 // return the actual class, rather than a JavaClass wrapper.
1049 return Java.getInstance(getRuntime(), javaClass().getAnnotations());
1052 @JRubyMethod(name = "annotations?")
1053 public RubyBoolean annotations_p() {
1054 return getRuntime().newBoolean(javaClass().getAnnotations().length > 0);
1057 @JRubyMethod
1058 public IRubyObject declared_annotations() {
1059 // see note above re: return type
1060 return Java.getInstance(getRuntime(), javaClass().getDeclaredAnnotations());
1063 @JRubyMethod(name = "declared_annotations?")
1064 public RubyBoolean declared_annotations_p() {
1065 return getRuntime().newBoolean(javaClass().getDeclaredAnnotations().length > 0);
1068 @SuppressWarnings("unchecked")
1069 @JRubyMethod(name = "annotation_present?", required = 1)
1070 public IRubyObject annotation_present_p(IRubyObject annoClass) {
1071 if (!(annoClass instanceof JavaClass)) {
1072 throw getRuntime().newTypeError(annoClass, getRuntime().getJavaSupport().getJavaClassClass());
1074 return getRuntime().newBoolean(javaClass().isAnnotationPresent(((JavaClass)annoClass).javaClass()));
1077 @JRubyMethod
1078 public IRubyObject modifiers() {
1079 return getRuntime().newFixnum(javaClass().getModifiers());
1082 @JRubyMethod
1083 public IRubyObject declaring_class() {
1084 Class<?> clazz = javaClass().getDeclaringClass();
1085 if (clazz != null) {
1086 return JavaClass.get(getRuntime(), clazz);
1088 return getRuntime().getNil();
1091 @JRubyMethod
1092 public IRubyObject enclosing_class() {
1093 return Java.getInstance(getRuntime(), javaClass().getEnclosingClass());
1096 @JRubyMethod
1097 public IRubyObject enclosing_constructor() {
1098 Constructor<?> ctor = javaClass().getEnclosingConstructor();
1099 if (ctor != null) {
1100 return new JavaConstructor(getRuntime(), ctor);
1102 return getRuntime().getNil();
1105 @JRubyMethod
1106 public IRubyObject enclosing_method() {
1107 Method meth = javaClass().getEnclosingMethod();
1108 if (meth != null) {
1109 return new JavaMethod(getRuntime(), meth);
1111 return getRuntime().getNil();
1114 @JRubyMethod
1115 public IRubyObject enum_constants() {
1116 return Java.getInstance(getRuntime(), javaClass().getEnumConstants());
1119 @JRubyMethod
1120 public IRubyObject generic_interfaces() {
1121 return Java.getInstance(getRuntime(), javaClass().getGenericInterfaces());
1124 @JRubyMethod
1125 public IRubyObject generic_superclass() {
1126 return Java.getInstance(getRuntime(), javaClass().getGenericSuperclass());
1129 @JRubyMethod
1130 public IRubyObject type_parameters() {
1131 return Java.getInstance(getRuntime(), javaClass().getTypeParameters());
1134 @JRubyMethod
1135 public IRubyObject signers() {
1136 return Java.getInstance(getRuntime(), javaClass().getSigners());
1139 private static String getSimpleName(Class<?> clazz) {
1140 if (clazz.isArray()) {
1141 return getSimpleName(clazz.getComponentType()) + "[]";
1144 String className = clazz.getName();
1145 int len = className.length();
1146 int i = className.lastIndexOf('$');
1147 if (i != -1) {
1148 do {
1149 i++;
1150 } while (i < len && Character.isDigit(className.charAt(i)));
1151 return className.substring(i);
1154 return className.substring(className.lastIndexOf('.') + 1);
1157 @JRubyMethod
1158 public RubyString simple_name() {
1159 return getRuntime().newString(getSimpleName(javaClass()));
1162 @JRubyMethod
1163 public IRubyObject superclass() {
1164 Class<?> superclass = javaClass().getSuperclass();
1165 if (superclass == null) {
1166 return getRuntime().getNil();
1168 return JavaClass.get(getRuntime(), superclass);
1171 @JRubyMethod(name = "<=>", required = 1)
1172 public RubyFixnum op_cmp(IRubyObject other) {
1173 if (! (other instanceof JavaClass)) {
1174 throw getRuntime().newTypeError("<=> requires JavaClass (" + other.getType() + " given)");
1176 JavaClass otherClass = (JavaClass) other;
1177 if (this.javaClass() == otherClass.javaClass()) {
1178 return getRuntime().newFixnum(0);
1180 if (otherClass.javaClass().isAssignableFrom(this.javaClass())) {
1181 return getRuntime().newFixnum(-1);
1183 return getRuntime().newFixnum(1);
1186 @JRubyMethod
1187 public RubyArray java_instance_methods() {
1188 return java_methods(javaClass().getMethods(), false);
1191 @JRubyMethod
1192 public RubyArray declared_instance_methods() {
1193 return java_methods(javaClass().getDeclaredMethods(), false);
1196 private RubyArray java_methods(Method[] methods, boolean isStatic) {
1197 RubyArray result = getRuntime().newArray(methods.length);
1198 for (int i = 0; i < methods.length; i++) {
1199 Method method = methods[i];
1200 if (isStatic == Modifier.isStatic(method.getModifiers())) {
1201 result.append(JavaMethod.create(getRuntime(), method));
1204 return result;
1207 @JRubyMethod
1208 public RubyArray java_class_methods() {
1209 return java_methods(javaClass().getMethods(), true);
1212 @JRubyMethod
1213 public RubyArray declared_class_methods() {
1214 return java_methods(javaClass().getDeclaredMethods(), true);
1217 @JRubyMethod(required = 1, rest = true)
1218 public JavaMethod java_method(IRubyObject[] args) throws ClassNotFoundException {
1219 String methodName = args[0].asJavaString();
1220 Class<?>[] argumentTypes = buildArgumentTypes(args);
1221 return JavaMethod.create(getRuntime(), javaClass(), methodName, argumentTypes);
1224 @JRubyMethod(required = 1, rest = true)
1225 public JavaMethod declared_method(IRubyObject[] args) throws ClassNotFoundException {
1226 String methodName = args[0].asJavaString();
1227 Class<?>[] argumentTypes = buildArgumentTypes(args);
1228 return JavaMethod.createDeclared(getRuntime(), javaClass(), methodName, argumentTypes);
1231 @JRubyMethod(required = 1, rest = true)
1232 public JavaCallable declared_method_smart(IRubyObject[] args) throws ClassNotFoundException {
1233 String methodName = args[0].asJavaString();
1234 Class<?>[] argumentTypes = buildArgumentTypes(args);
1236 JavaCallable callable = getMatchingCallable(getRuntime(), javaClass(), methodName, argumentTypes);
1238 if (callable != null) return callable;
1240 throw getRuntime().newNameError("undefined method '" + methodName + "' for class '" + javaClass().getName() + "'",
1241 methodName);
1244 public static JavaCallable getMatchingCallable(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
1245 if ("<init>".equals(methodName)) {
1246 return JavaConstructor.getMatchingConstructor(runtime, javaClass, argumentTypes);
1247 } else {
1248 // FIXME: do we really want 'declared' methods? includes private/protected, and does _not_
1249 // include superclass methods
1250 return JavaMethod.getMatchingDeclaredMethod(runtime, javaClass, methodName, argumentTypes);
1254 private Class<?>[] buildArgumentTypes(IRubyObject[] args) throws ClassNotFoundException {
1255 if (args.length < 1) {
1256 throw getRuntime().newArgumentError(args.length, 1);
1258 Class<?>[] argumentTypes = new Class[args.length - 1];
1259 for (int i = 1; i < args.length; i++) {
1260 JavaClass type;
1261 if (args[i] instanceof JavaClass) {
1262 type = (JavaClass)args[i];
1263 } else if (args[i].respondsTo("java_class")) {
1264 type = (JavaClass)args[i].callMethod(getRuntime().getCurrentContext(), "java_class");
1265 } else {
1266 type = for_name(this, args[i]);
1268 argumentTypes[i - 1] = type.javaClass();
1270 return argumentTypes;
1273 @JRubyMethod
1274 public RubyArray constructors() {
1275 RubyArray ctors;
1276 if ((ctors = constructors) != null) return ctors;
1277 return constructors = buildConstructors(javaClass().getConstructors());
1280 @JRubyMethod
1281 public RubyArray classes() {
1282 return JavaClass.getRubyArray(getRuntime(), javaClass().getClasses());
1285 @JRubyMethod
1286 public RubyArray declared_classes() {
1287 Ruby runtime = getRuntime();
1288 RubyArray result = runtime.newArray();
1289 Class<?> javaClass = javaClass();
1290 try {
1291 Class<?>[] classes = javaClass.getDeclaredClasses();
1292 for (int i = 0; i < classes.length; i++) {
1293 if (Modifier.isPublic(classes[i].getModifiers())) {
1294 result.append(get(runtime, classes[i]));
1297 } catch (SecurityException e) {
1298 // restrictive security policy; no matter, we only want public
1299 // classes anyway
1300 try {
1301 Class<?>[] classes = javaClass.getClasses();
1302 for (int i = 0; i < classes.length; i++) {
1303 if (javaClass == classes[i].getDeclaringClass()) {
1304 result.append(get(runtime, classes[i]));
1307 } catch (SecurityException e2) {
1308 // very restrictive policy (disallows Member.PUBLIC)
1309 // we'd never actually get this far in that case
1312 return result;
1315 @JRubyMethod
1316 public RubyArray declared_constructors() {
1317 return buildConstructors(javaClass().getDeclaredConstructors());
1320 private RubyArray buildConstructors(Constructor<?>[] constructors) {
1321 RubyArray result = getRuntime().newArray(constructors.length);
1322 for (int i = 0; i < constructors.length; i++) {
1323 result.append(new JavaConstructor(getRuntime(), constructors[i]));
1325 return result;
1328 @JRubyMethod(rest = true)
1329 public JavaConstructor constructor(IRubyObject[] args) {
1330 try {
1331 Class<?>[] parameterTypes = buildClassArgs(args);
1332 Constructor<?> constructor = javaClass().getConstructor(parameterTypes);
1333 return new JavaConstructor(getRuntime(), constructor);
1334 } catch (NoSuchMethodException nsme) {
1335 throw getRuntime().newNameError("no matching java constructor", null);
1339 @JRubyMethod(rest = true)
1340 public JavaConstructor declared_constructor(IRubyObject[] args) {
1341 try {
1342 Class<?>[] parameterTypes = buildClassArgs(args);
1343 Constructor<?> constructor = javaClass().getDeclaredConstructor (parameterTypes);
1344 return new JavaConstructor(getRuntime(), constructor);
1345 } catch (NoSuchMethodException nsme) {
1346 throw getRuntime().newNameError("no matching java constructor", null);
1350 private Class<?>[] buildClassArgs(IRubyObject[] args) {
1351 JavaSupport javaSupport = getRuntime().getJavaSupport();
1352 Class<?>[] parameterTypes = new Class<?>[args.length];
1353 for (int i = args.length; --i >= 0; ) {
1354 String name = args[i].asJavaString();
1355 parameterTypes[i] = javaSupport.loadJavaClassVerbose(name);
1357 return parameterTypes;
1360 @JRubyMethod
1361 public JavaClass array_class() {
1362 return JavaClass.get(getRuntime(), Array.newInstance(javaClass(), 0).getClass());
1365 @JRubyMethod(required = 1)
1366 public JavaObject new_array(IRubyObject lengthArgument) {
1367 if (lengthArgument instanceof RubyInteger) {
1368 // one-dimensional array
1369 int length = (int) ((RubyInteger) lengthArgument).getLongValue();
1370 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), length));
1371 } else if (lengthArgument instanceof RubyArray) {
1372 // n-dimensional array
1373 List list = ((RubyArray)lengthArgument).getList();
1374 int length = list.size();
1375 if (length == 0) {
1376 throw getRuntime().newArgumentError("empty dimensions specifier for java array");
1378 int[] dimensions = new int[length];
1379 for (int i = length; --i >= 0; ) {
1380 IRubyObject dimensionLength = (IRubyObject)list.get(i);
1381 if ( !(dimensionLength instanceof RubyInteger) ) {
1382 throw getRuntime()
1383 .newTypeError(dimensionLength, getRuntime().getInteger());
1385 dimensions[i] = (int) ((RubyInteger) dimensionLength).getLongValue();
1387 return new JavaArray(getRuntime(), Array.newInstance(javaClass(), dimensions));
1388 } else {
1389 throw getRuntime().newArgumentError(
1390 "invalid length or dimensions specifier for java array" +
1391 " - must be Integer or Array of Integer");
1395 public IRubyObject emptyJavaArray(ThreadContext context) {
1396 JavaArray javaArray = new JavaArray(getRuntime(), Array.newInstance(javaClass(), 0));
1397 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1399 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1400 proxy.dataWrapStruct(javaArray);
1402 return proxy;
1405 public IRubyObject javaArraySubarray(ThreadContext context, JavaArray fromArray, int index, int size) {
1406 int actualLength = Array.getLength(fromArray.getValue());
1407 if (index >= actualLength) {
1408 return context.getRuntime().getNil();
1409 } else {
1410 if (index + size > actualLength) {
1411 size = actualLength - index;
1414 Object newArray = Array.newInstance(javaClass(), size);
1415 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1416 System.arraycopy(fromArray.getValue(), index, newArray, 0, size);
1417 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1419 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1420 proxy.dataWrapStruct(javaArray);
1422 return proxy;
1427 * Contatenate two Java arrays into a new one. The component type of the
1428 * additional array must be assignable to the component type of the
1429 * original array.
1431 * @param context
1432 * @param original
1433 * @param additional
1434 * @return
1436 public IRubyObject concatArrays(ThreadContext context, JavaArray original, JavaArray additional) {
1437 int oldLength = (int)original.length().getLongValue();
1438 int addLength = (int)additional.length().getLongValue();
1439 Object newArray = Array.newInstance(javaClass(), oldLength + addLength);
1440 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1441 System.arraycopy(original.getValue(), 0, newArray, 0, oldLength);
1442 System.arraycopy(additional.getValue(), 0, newArray, oldLength, addLength);
1443 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1445 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1446 proxy.dataWrapStruct(javaArray);
1448 return proxy;
1452 * The slow version for when concatenating a Java array of a different type.
1454 * @param context
1455 * @param original
1456 * @param additional
1457 * @return
1459 public IRubyObject concatArrays(ThreadContext context, JavaArray original, IRubyObject additional) {
1460 int oldLength = (int)original.length().getLongValue();
1461 int addLength = (int)((RubyFixnum)RuntimeHelpers.invoke(context, additional, "length")).getLongValue();
1462 Object newArray = Array.newInstance(javaClass(), oldLength + addLength);
1463 JavaArray javaArray = new JavaArray(getRuntime(), newArray);
1464 System.arraycopy(original.getValue(), 0, newArray, 0, oldLength);
1465 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1466 ArrayJavaProxy proxy = new ArrayJavaProxy(context.getRuntime(), proxyClass);
1467 proxy.dataWrapStruct(javaArray);
1469 Ruby runtime = context.getRuntime();
1470 for (int i = 0; i < addLength; i++) {
1471 RuntimeHelpers.invoke(context, proxy, "[]=", runtime.newFixnum(oldLength + i),
1472 RuntimeHelpers.invoke(context, additional, "[]", runtime.newFixnum(i)));
1475 return proxy;
1478 public IRubyObject javaArrayFromRubyArray(ThreadContext context, IRubyObject fromArray) {
1479 Ruby runtime = context.getRuntime();
1480 if (!(fromArray instanceof RubyArray)) {
1481 throw runtime.newTypeError(fromArray, runtime.getArray());
1483 RubyArray rubyArray = (RubyArray)fromArray;
1484 JavaArray javaArray = new JavaArray(getRuntime(), Array.newInstance(javaClass(), rubyArray.size()));
1485 ArrayJavaAddons.copyDataToJavaArray(context, rubyArray, javaArray);
1486 RubyClass proxyClass = (RubyClass)Java.get_proxy_class(javaArray, array_class());
1488 ArrayJavaProxy proxy = new ArrayJavaProxy(runtime, proxyClass);
1489 proxy.dataWrapStruct(javaArray);
1491 return proxy;
1494 @JRubyMethod
1495 public RubyArray fields() {
1496 return buildFieldResults(javaClass().getFields());
1499 @JRubyMethod
1500 public RubyArray declared_fields() {
1501 return buildFieldResults(javaClass().getDeclaredFields());
1504 private RubyArray buildFieldResults(Field[] fields) {
1505 RubyArray result = getRuntime().newArray(fields.length);
1506 for (int i = 0; i < fields.length; i++) {
1507 result.append(new JavaField(getRuntime(), fields[i]));
1509 return result;
1512 @JRubyMethod(required = 1)
1513 public JavaField field(IRubyObject name) {
1514 String stringName = name.asJavaString();
1515 try {
1516 Field field = javaClass().getField(stringName);
1517 return new JavaField(getRuntime(), field);
1518 } catch (NoSuchFieldException nsfe) {
1519 throw undefinedFieldError(stringName);
1523 @JRubyMethod(required = 1)
1524 public JavaField declared_field(IRubyObject name) {
1525 String stringName = name.asJavaString();
1526 try {
1527 Field field = javaClass().getDeclaredField(stringName);
1528 return new JavaField(getRuntime(), field);
1529 } catch (NoSuchFieldException nsfe) {
1530 throw undefinedFieldError(stringName);
1534 private RaiseException undefinedFieldError(String name) {
1535 return getRuntime().newNameError("undefined field '" + name + "' for class '" + javaClass().getName() + "'", name);
1538 @JRubyMethod
1539 public RubyArray interfaces() {
1540 return JavaClass.getRubyArray(getRuntime(), javaClass().getInterfaces());
1543 @JRubyMethod(name = "primitive?")
1544 public RubyBoolean primitive_p() {
1545 return getRuntime().newBoolean(isPrimitive());
1548 @JRubyMethod(name = "assignable_from?", required = 1)
1549 public RubyBoolean assignable_from_p(IRubyObject other) {
1550 if (! (other instanceof JavaClass)) {
1551 throw getRuntime().newTypeError("assignable_from requires JavaClass (" + other.getType() + " given)");
1554 Class<?> otherClass = ((JavaClass) other).javaClass();
1555 return assignable(javaClass(), otherClass) ? getRuntime().getTrue() : getRuntime().getFalse();
1558 static boolean assignable(Class<?> thisClass, Class<?> otherClass) {
1559 if(!thisClass.isPrimitive() && otherClass == Void.TYPE ||
1560 thisClass.isAssignableFrom(otherClass)) {
1561 return true;
1564 otherClass = JavaUtil.primitiveToWrapper(otherClass);
1565 thisClass = JavaUtil.primitiveToWrapper(thisClass);
1567 if(thisClass.isAssignableFrom(otherClass)) {
1568 return true;
1570 if(Number.class.isAssignableFrom(thisClass)) {
1571 if(Number.class.isAssignableFrom(otherClass)) {
1572 return true;
1574 if(otherClass.equals(Character.class)) {
1575 return true;
1578 if(thisClass.equals(Character.class)) {
1579 if(Number.class.isAssignableFrom(otherClass)) {
1580 return true;
1583 return false;
1586 private boolean isPrimitive() {
1587 return javaClass().isPrimitive();
1590 @JRubyMethod
1591 public JavaClass component_type() {
1592 if (! javaClass().isArray()) {
1593 throw getRuntime().newTypeError("not a java array-class");
1595 return JavaClass.get(getRuntime(), javaClass().getComponentType());