0001: /***** BEGIN LICENSE BLOCK *****
0002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
0003: *
0004: * The contents of this file are subject to the Common Public
0005: * License Version 1.0 (the "License"); you may not use this file
0006: * except in compliance with the License. You may obtain a copy of
0007: * the License at http://www.eclipse.org/legal/cpl-v10.html
0008: *
0009: * Software distributed under the License is distributed on an "AS
0010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
0011: * implied. See the License for the specific language governing
0012: * rights and limitations under the License.
0013: *
0014: * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
0015: * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
0016: * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
0017: * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
0018: * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
0019: * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
0020: * Copyright (C) 2006 Kresten Krab Thorup <krab@gnu.org>
0021: * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
0022: * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
0023: *
0024: * Alternatively, the contents of this file may be used under the terms of
0025: * either of the GNU General Public License Version 2 or later (the "GPL"),
0026: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
0027: * in which case the provisions of the GPL or the LGPL are applicable instead
0028: * of those above. If you wish to allow use of your version of this file only
0029: * under the terms of either the GPL or the LGPL, and not to allow others to
0030: * use your version of this file under the terms of the CPL, indicate your
0031: * decision by deleting the provisions above and replace them with the notice
0032: * and other provisions required by the GPL or the LGPL. If you do not delete
0033: * the provisions above, a recipient may use your version of this file under
0034: * the terms of any one of the CPL, the GPL or the LGPL.
0035: ***** END LICENSE BLOCK *****/package org.jruby.javasupport;
0036:
0037: import java.beans.BeanInfo;
0038: import java.beans.IntrospectionException;
0039: import java.beans.Introspector;
0040: import java.beans.PropertyDescriptor;
0041: import java.lang.reflect.Array;
0042: import java.lang.reflect.Constructor;
0043: import java.lang.reflect.Field;
0044: import java.lang.reflect.Method;
0045: import java.lang.reflect.Modifier;
0046: import java.util.ArrayList;
0047: import java.util.HashMap;
0048: import java.util.HashSet;
0049: import java.util.Iterator;
0050: import java.util.List;
0051: import java.util.Map;
0052: import java.util.Set;
0053: import java.util.regex.Matcher;
0054: import java.util.regex.Pattern;
0055:
0056: import org.jruby.Ruby;
0057: import org.jruby.RubyArray;
0058: import org.jruby.RubyBoolean;
0059: import org.jruby.RubyClass;
0060: import org.jruby.RubyFixnum;
0061: import org.jruby.RubyInteger;
0062: import org.jruby.RubyMethod;
0063: import org.jruby.RubyModule;
0064: import org.jruby.RubyNumeric;
0065: import org.jruby.RubyString;
0066: import org.jruby.exceptions.RaiseException;
0067: import org.jruby.runtime.Arity;
0068: import org.jruby.runtime.Block;
0069: import org.jruby.runtime.CallType;
0070: import org.jruby.runtime.CallbackFactory;
0071: import org.jruby.runtime.ObjectAllocator;
0072: import org.jruby.runtime.Visibility;
0073: import org.jruby.runtime.builtin.IRubyObject;
0074: import org.jruby.runtime.callback.Callback;
0075: import org.jruby.util.IdUtil;
0076: import org.jruby.util.collections.IntHashMap;
0077:
0078: public class JavaClass extends JavaObject {
0079:
0080: private static boolean DEBUG = false;
0081:
0082: private static class AssignedName {
0083: // to override an assigned name, the type must be less than
0084: // or equal to the assigned type. so a field name in a subclass
0085: // will override an alias in a superclass, but not a method.
0086: static final int RESERVED = 0;
0087: static final int METHOD = 1;
0088: static final int FIELD = 2;
0089: static final int PROTECTED_METHOD = 3;
0090: static final int WEAKLY_RESERVED = 4; // we'll be peeved, but not devastated, if you override
0091: static final int ALIAS = 5;
0092: // yes, protected fields are weaker than aliases. many conflicts
0093: // in the old AWT code, for example, where you really want 'size'
0094: // to mean the public method getSize, not the protected field 'size'.
0095: static final int PROTECTED_FIELD = 6;
0096: String name;
0097: int type;
0098:
0099: AssignedName() {
0100: }
0101:
0102: AssignedName(String name, int type) {
0103: this .name = name;
0104: this .type = type;
0105: }
0106: }
0107:
0108: // TODO: other reserved names?
0109: private static final Map RESERVED_NAMES = new HashMap();
0110: static {
0111: RESERVED_NAMES.put("__id__", new AssignedName("__id__",
0112: AssignedName.RESERVED));
0113: RESERVED_NAMES.put("__send__", new AssignedName("__send__",
0114: AssignedName.RESERVED));
0115: RESERVED_NAMES.put("class", new AssignedName("class",
0116: AssignedName.RESERVED));
0117: RESERVED_NAMES.put("initialize", new AssignedName("initialize",
0118: AssignedName.RESERVED));
0119: RESERVED_NAMES.put("object_id", new AssignedName("object_id",
0120: AssignedName.RESERVED));
0121: RESERVED_NAMES.put("private", new AssignedName("private",
0122: AssignedName.RESERVED));
0123: RESERVED_NAMES.put("protected", new AssignedName("protected",
0124: AssignedName.RESERVED));
0125: RESERVED_NAMES.put("public", new AssignedName("public",
0126: AssignedName.RESERVED));
0127:
0128: // weakly reserved names
0129: RESERVED_NAMES.put("id", new AssignedName("id",
0130: AssignedName.WEAKLY_RESERVED));
0131: }
0132: private static final Map STATIC_RESERVED_NAMES = new HashMap(
0133: RESERVED_NAMES);
0134: static {
0135: STATIC_RESERVED_NAMES.put("new", new AssignedName("new",
0136: AssignedName.RESERVED));
0137: }
0138: private static final Map INSTANCE_RESERVED_NAMES = new HashMap(
0139: RESERVED_NAMES);
0140:
0141: private static abstract class NamedCallback implements Callback {
0142: static final int STATIC_FIELD = 1;
0143: static final int STATIC_METHOD = 2;
0144: static final int INSTANCE_FIELD = 3;
0145: static final int INSTANCE_METHOD = 4;
0146: String name;
0147: int type;
0148: Visibility visibility = Visibility.PUBLIC;
0149: boolean isProtected;
0150:
0151: NamedCallback() {
0152: }
0153:
0154: NamedCallback(String name, int type) {
0155: this .name = name;
0156: this .type = type;
0157: }
0158:
0159: abstract void install(RubyClass proxy);
0160:
0161: // small hack to save a cast later on
0162: boolean hasLocalMethod() {
0163: return true;
0164: }
0165:
0166: boolean isPublic() {
0167: return visibility == Visibility.PUBLIC;
0168: }
0169:
0170: boolean isProtected() {
0171: return visibility == Visibility.PROTECTED;
0172: }
0173:
0174: void logMessage(IRubyObject self, IRubyObject[] args) {
0175: if (!DEBUG) {
0176: return;
0177: }
0178: String type;
0179: switch (this .type) {
0180: case STATIC_FIELD:
0181: type = "static field";
0182: break;
0183: case STATIC_METHOD:
0184: type = "static method";
0185: break;
0186: case INSTANCE_FIELD:
0187: type = "instance field";
0188: break;
0189: case INSTANCE_METHOD:
0190: type = "instance method";
0191: break;
0192: default:
0193: type = "?";
0194: break;
0195: }
0196: StringBuffer b = new StringBuffer(type).append(" => '")
0197: .append(name).append("'; args.length = ").append(
0198: args.length);
0199: for (int i = 0; i < args.length; i++) {
0200: b.append("\n arg[").append(i).append("] = ").append(
0201: args[i]);
0202: }
0203: System.out.println(b);
0204: }
0205: }
0206:
0207: private static abstract class FieldCallback extends NamedCallback {
0208: Field field;
0209: JavaField javaField;
0210:
0211: FieldCallback() {
0212: }
0213:
0214: FieldCallback(String name, int type, Field field) {
0215: super (name, type);
0216: this .field = field;
0217: // if (Modifier.isProtected(field.getModifiers())) {
0218: // field.setAccessible(true);
0219: // this.visibility = Visibility.PROTECTED;
0220: // }
0221: }
0222: }
0223:
0224: private class StaticFieldGetter extends FieldCallback {
0225: StaticFieldGetter() {
0226: }
0227:
0228: StaticFieldGetter(String name, Field field) {
0229: super (name, STATIC_FIELD, field);
0230: }
0231:
0232: void install(RubyClass proxy) {
0233: proxy.getSingletonClass().defineFastMethod(this .name, this ,
0234: this .visibility);
0235: }
0236:
0237: public IRubyObject execute(IRubyObject self,
0238: IRubyObject[] args, Block block) {
0239: logMessage(self, args);
0240: if (javaField == null) {
0241: javaField = new JavaField(getRuntime(), field);
0242: }
0243: return Java.java_to_ruby(self, javaField.static_value(),
0244: Block.NULL_BLOCK);
0245: }
0246:
0247: public Arity getArity() {
0248: return Arity.NO_ARGUMENTS;
0249: }
0250: }
0251:
0252: private class StaticFieldSetter extends FieldCallback {
0253: StaticFieldSetter() {
0254: }
0255:
0256: StaticFieldSetter(String name, Field field) {
0257: super (name, STATIC_FIELD, field);
0258: }
0259:
0260: void install(RubyClass proxy) {
0261: proxy.getSingletonClass().defineFastMethod(this .name, this ,
0262: this .visibility);
0263: }
0264:
0265: public IRubyObject execute(IRubyObject self,
0266: IRubyObject[] args, Block block) {
0267: logMessage(self, args);
0268: if (javaField == null) {
0269: javaField = new JavaField(getRuntime(), field);
0270: }
0271: return Java.java_to_ruby(self, javaField
0272: .set_static_value(Java.ruby_to_java(self, args[0],
0273: Block.NULL_BLOCK)), Block.NULL_BLOCK);
0274: }
0275:
0276: public Arity getArity() {
0277: return Arity.ONE_ARGUMENT;
0278: }
0279: }
0280:
0281: private class InstanceFieldGetter extends FieldCallback {
0282: InstanceFieldGetter() {
0283: }
0284:
0285: InstanceFieldGetter(String name, Field field) {
0286: super (name, INSTANCE_FIELD, field);
0287: }
0288:
0289: void install(RubyClass proxy) {
0290: proxy.defineFastMethod(this .name, this , this .visibility);
0291: }
0292:
0293: public IRubyObject execute(IRubyObject self,
0294: IRubyObject[] args, Block block) {
0295: logMessage(self, args);
0296: if (javaField == null) {
0297: javaField = new JavaField(getRuntime(), field);
0298: }
0299: return Java.java_to_ruby(self, javaField.value(self
0300: .getInstanceVariable("@java_object")),
0301: Block.NULL_BLOCK);
0302: }
0303:
0304: public Arity getArity() {
0305: return Arity.NO_ARGUMENTS;
0306: }
0307: }
0308:
0309: private class InstanceFieldSetter extends FieldCallback {
0310: InstanceFieldSetter() {
0311: }
0312:
0313: InstanceFieldSetter(String name, Field field) {
0314: super (name, INSTANCE_FIELD, field);
0315: }
0316:
0317: void install(RubyClass proxy) {
0318: proxy.defineFastMethod(this .name, this , this .visibility);
0319: }
0320:
0321: public IRubyObject execute(IRubyObject self,
0322: IRubyObject[] args, Block block) {
0323: logMessage(self, args);
0324: if (javaField == null) {
0325: javaField = new JavaField(getRuntime(), field);
0326: }
0327: return Java.java_to_ruby(self, javaField.set_value(self
0328: .getInstanceVariable("@java_object"), Java
0329: .ruby_to_java(self, args[0], Block.NULL_BLOCK)),
0330: Block.NULL_BLOCK);
0331: }
0332:
0333: public Arity getArity() {
0334: return Arity.ONE_ARGUMENT;
0335: }
0336: }
0337:
0338: private static abstract class MethodCallback extends NamedCallback {
0339: boolean haveLocalMethod;
0340: List methods;
0341: List aliases;
0342: IntHashMap javaMethods;
0343: IntHashMap matchingMethods;
0344: JavaMethod javaMethod;
0345:
0346: MethodCallback() {
0347: }
0348:
0349: MethodCallback(String name, int type) {
0350: super (name, type);
0351: }
0352:
0353: void addMethod(Method method, Class javaClass) {
0354: if (methods == null) {
0355: methods = new ArrayList();
0356: }
0357: methods.add(method);
0358: // if (Modifier.isProtected(method.getModifiers())) {
0359: // visibility = Visibility.PROTECTED;
0360: // }
0361: haveLocalMethod |= javaClass == method.getDeclaringClass();
0362: }
0363:
0364: void addAlias(String alias) {
0365: if (aliases == null) {
0366: aliases = new ArrayList();
0367: }
0368: if (!aliases.contains(alias))
0369: aliases.add(alias);
0370: }
0371:
0372: boolean hasLocalMethod() {
0373: return haveLocalMethod;
0374: }
0375:
0376: // TODO: varargs?
0377: // TODO: rework Java.matching_methods_internal and
0378: // ProxyData.method_cache, since we really don't need to be passing
0379: // around RubyArray objects anymore.
0380: void createJavaMethods(Ruby runtime) {
0381: if (methods != null) {
0382: if (methods.size() == 1) {
0383: javaMethod = JavaMethod.create(runtime,
0384: (Method) methods.get(0));
0385: } else {
0386: javaMethods = new IntHashMap();
0387: matchingMethods = new IntHashMap();
0388: for (Iterator iter = methods.iterator(); iter
0389: .hasNext();) {
0390: Method method = (Method) iter.next();
0391: // TODO: deal with varargs
0392: //int arity = method.isVarArgs() ? -1 : method.getParameterTypes().length;
0393: int arity = method.getParameterTypes().length;
0394: RubyArray methodsForArity = (RubyArray) javaMethods
0395: .get(arity);
0396: if (methodsForArity == null) {
0397: methodsForArity = RubyArray
0398: .newArrayLight(runtime);
0399: javaMethods.put(arity, methodsForArity);
0400: }
0401: methodsForArity.append(JavaMethod.create(
0402: runtime, method));
0403: }
0404: }
0405: methods = null;
0406: }
0407: }
0408:
0409: void raiseNoMatchingMethodError(IRubyObject proxy,
0410: IRubyObject[] args, int start) {
0411: int len = args.length;
0412: List argTypes = new ArrayList(len - start);
0413: for (int i = start; i < len; i++) {
0414: argTypes.add(((JavaClass) ((JavaObject) args[i])
0415: .java_class()).getValue());
0416: }
0417: throw proxy.getRuntime().newNameError(
0418: "no "
0419: + this .name
0420: + " with arguments matching "
0421: + argTypes
0422: + " on object "
0423: + proxy.callMethod(proxy.getRuntime()
0424: .getCurrentContext(), "inspect"),
0425: null);
0426: }
0427: }
0428:
0429: private class StaticMethodInvoker extends MethodCallback {
0430: StaticMethodInvoker() {
0431: }
0432:
0433: StaticMethodInvoker(String name) {
0434: super (name, STATIC_METHOD);
0435: }
0436:
0437: void install(RubyClass proxy) {
0438: if (haveLocalMethod) {
0439: RubyClass singleton = proxy.getSingletonClass();
0440: singleton.defineFastMethod(this .name, this ,
0441: this .visibility);
0442: if (aliases != null && isPublic()) {
0443: for (Iterator iter = aliases.iterator(); iter
0444: .hasNext();) {
0445: singleton.defineAlias((String) iter.next(),
0446: this .name);
0447: }
0448: aliases = null;
0449: }
0450: }
0451: }
0452:
0453: public IRubyObject execute(IRubyObject self,
0454: IRubyObject[] args, Block block) {
0455: logMessage(self, args);
0456: if (javaMethod == null && javaMethods == null) {
0457: createJavaMethods(self.getRuntime());
0458: }
0459: // TODO: ok to convert args in place, rather than new array?
0460: int len = args.length;
0461: IRubyObject[] convertedArgs = new IRubyObject[len];
0462: for (int i = len; --i >= 0;) {
0463: convertedArgs[i] = Java.ruby_to_java(self, args[i],
0464: Block.NULL_BLOCK);
0465: }
0466: if (javaMethods == null) {
0467: return Java
0468: .java_to_ruby(self, javaMethod
0469: .invoke_static(convertedArgs),
0470: Block.NULL_BLOCK);
0471: } else {
0472: int argsTypeHash = 0;
0473: for (int i = len; --i >= 0;) {
0474: argsTypeHash += 3 * args[i].getMetaClass().id;
0475: }
0476: IRubyObject match = (IRubyObject) matchingMethods
0477: .get(argsTypeHash);
0478: if (match == null) {
0479: // TODO: varargs?
0480: RubyArray methods = (RubyArray) javaMethods
0481: .get(len);
0482: if (methods == null) {
0483: raiseNoMatchingMethodError(self, convertedArgs,
0484: 0);
0485: }
0486: match = Java.matching_method_internal(
0487: JAVA_UTILITIES, methods, convertedArgs, 0,
0488: len);
0489: }
0490: return Java
0491: .java_to_ruby(self, ((JavaMethod) match)
0492: .invoke_static(convertedArgs),
0493: Block.NULL_BLOCK);
0494: }
0495: }
0496:
0497: public Arity getArity() {
0498: return Arity.OPTIONAL;
0499: }
0500: }
0501:
0502: private class InstanceMethodInvoker extends MethodCallback {
0503: InstanceMethodInvoker() {
0504: }
0505:
0506: InstanceMethodInvoker(String name) {
0507: super (name, INSTANCE_METHOD);
0508: }
0509:
0510: void install(RubyClass proxy) {
0511: if (haveLocalMethod) {
0512: proxy
0513: .defineFastMethod(this .name, this ,
0514: this .visibility);
0515: if (aliases != null && isPublic()) {
0516: for (Iterator iter = aliases.iterator(); iter
0517: .hasNext();) {
0518: proxy.defineAlias((String) iter.next(),
0519: this .name);
0520: }
0521: aliases = null;
0522: }
0523: }
0524: }
0525:
0526: public IRubyObject execute(IRubyObject self,
0527: IRubyObject[] args, Block block) {
0528: logMessage(self, args);
0529: if (javaMethod == null && javaMethods == null) {
0530: createJavaMethods(self.getRuntime());
0531: }
0532: // TODO: ok to convert args in place, rather than new array?
0533: int len = args.length;
0534: IRubyObject[] convertedArgs = new IRubyObject[len + 1];
0535: convertedArgs[0] = self.getInstanceVariable("@java_object");
0536: for (int i = len; --i >= 0;) {
0537: convertedArgs[i + 1] = Java.ruby_to_java(self, args[i],
0538: Block.NULL_BLOCK);
0539: }
0540: if (javaMethods == null) {
0541: return Java.java_to_ruby(self, javaMethod
0542: .invoke(convertedArgs), Block.NULL_BLOCK);
0543: } else {
0544: int argsTypeHash = 0;
0545: for (int i = len; --i >= 0;) {
0546: argsTypeHash += 3 * args[i].getMetaClass().id;
0547: }
0548: IRubyObject match = (IRubyObject) matchingMethods
0549: .get(argsTypeHash);
0550: if (match == null) {
0551: // TODO: varargs?
0552: RubyArray methods = (RubyArray) javaMethods
0553: .get(len);
0554: if (methods == null) {
0555: raiseNoMatchingMethodError(self, convertedArgs,
0556: 1);
0557: }
0558: match = Java.matching_method_internal(
0559: JAVA_UTILITIES, methods, convertedArgs, 1,
0560: len);
0561: matchingMethods.put(argsTypeHash, match);
0562: }
0563: return Java.java_to_ruby(self, ((JavaMethod) match)
0564: .invoke(convertedArgs), Block.NULL_BLOCK);
0565: }
0566: }
0567:
0568: public Arity getArity() {
0569: return Arity.OPTIONAL;
0570: }
0571: }
0572:
0573: private static class ConstantField {
0574: static final int CONSTANT = Modifier.FINAL | Modifier.PUBLIC
0575: | Modifier.STATIC;
0576: Field field;
0577:
0578: ConstantField(Field field) {
0579: this .field = field;
0580: }
0581:
0582: void install(RubyModule proxy) {
0583: if (proxy.getConstantAt(field.getName()) == null) {
0584: JavaField javaField = new JavaField(proxy.getRuntime(),
0585: field);
0586: RubyString name = javaField.name();
0587: proxy.const_set(name, Java.java_to_ruby(proxy,
0588: javaField.static_value(), Block.NULL_BLOCK));
0589: }
0590: }
0591:
0592: static boolean isConstant(Field field) {
0593: return (field.getModifiers() & CONSTANT) == CONSTANT
0594: && Character.isUpperCase(field.getName().charAt(0));
0595: }
0596: }
0597:
0598: private final RubyModule JAVA_UTILITIES = getRuntime()
0599: .getJavaSupport().getJavaUtilitiesModule();
0600:
0601: private Map staticAssignedNames;
0602: private Map instanceAssignedNames;
0603: private Map staticCallbacks;
0604: private Map instanceCallbacks;
0605: private List constantFields;
0606: // caching constructors, as they're accessed for each new instance
0607: private RubyArray constructors;
0608:
0609: protected Map getStaticAssignedNames() {
0610: return staticAssignedNames;
0611: }
0612:
0613: protected Map getInstanceAssignedNames() {
0614: return instanceAssignedNames;
0615: }
0616:
0617: private JavaClass(Ruby runtime, Class javaClass) {
0618: super (runtime, (RubyClass) runtime.getJavaSupport()
0619: .getJavaClassClass(), javaClass);
0620: if (javaClass.isInterface()) {
0621: initializeInterface(javaClass);
0622: } else if (!(javaClass.isArray() || javaClass.isPrimitive())) {
0623: // TODO: public only?
0624: initializeClass(javaClass);
0625: }
0626: }
0627:
0628: private void initializeInterface(Class javaClass) {
0629: Map staticNames = new HashMap(STATIC_RESERVED_NAMES);
0630: List constantFields = new ArrayList();
0631: Field[] fields;
0632: try {
0633: fields = javaClass.getDeclaredFields();
0634: } catch (SecurityException e) {
0635: fields = javaClass.getFields();
0636: }
0637: for (int i = fields.length; --i >= 0;) {
0638: Field field = fields[i];
0639: if (javaClass != field.getDeclaringClass())
0640: continue;
0641: if (ConstantField.isConstant(field)) {
0642: constantFields.add(new ConstantField(field));
0643: }
0644: }
0645: this .staticAssignedNames = staticNames;
0646: this .constantFields = constantFields;
0647: }
0648:
0649: private void initializeClass(Class javaClass) {
0650: Class super class = javaClass.getSuperclass();
0651: Map staticNames;
0652: Map instanceNames;
0653: if (super class == null) {
0654: staticNames = new HashMap();
0655: instanceNames = new HashMap();
0656: } else {
0657: JavaClass super JavaClass = get(getRuntime(), super class);
0658: staticNames = new HashMap(super JavaClass
0659: .getStaticAssignedNames());
0660: instanceNames = new HashMap(super JavaClass
0661: .getInstanceAssignedNames());
0662: }
0663: staticNames.putAll(STATIC_RESERVED_NAMES);
0664: instanceNames.putAll(INSTANCE_RESERVED_NAMES);
0665: Map staticCallbacks = new HashMap();
0666: Map instanceCallbacks = new HashMap();
0667: List constantFields = new ArrayList();
0668: Field[] fields = javaClass.getFields();
0669: for (int i = fields.length; --i >= 0;) {
0670: Field field = fields[i];
0671: if (javaClass != field.getDeclaringClass())
0672: continue;
0673:
0674: if (ConstantField.isConstant(field)) {
0675: constantFields.add(new ConstantField(field));
0676: continue;
0677: }
0678: String name = field.getName();
0679: int modifiers = field.getModifiers();
0680: if (Modifier.isStatic(modifiers)) {
0681: AssignedName assignedName = (AssignedName) staticNames
0682: .get(name);
0683: if (assignedName != null
0684: && assignedName.type < AssignedName.FIELD)
0685: continue;
0686: staticNames.put(name, new AssignedName(name,
0687: AssignedName.FIELD));
0688: staticCallbacks.put(name, new StaticFieldGetter(name,
0689: field));
0690: if (!Modifier.isFinal(modifiers)) {
0691: String setName = name + '=';
0692: staticCallbacks.put(setName, new StaticFieldSetter(
0693: setName, field));
0694: }
0695: } else {
0696: AssignedName assignedName = (AssignedName) instanceNames
0697: .get(name);
0698: if (assignedName != null
0699: && assignedName.type < AssignedName.FIELD)
0700: continue;
0701: instanceNames.put(name, new AssignedName(name,
0702: AssignedName.FIELD));
0703: instanceCallbacks.put(name, new InstanceFieldGetter(
0704: name, field));
0705: if (!Modifier.isFinal(modifiers)) {
0706: String setName = name + '=';
0707: instanceCallbacks.put(setName,
0708: new InstanceFieldSetter(setName, field));
0709: }
0710: }
0711: }
0712: // TODO: protected methods. this is going to require a rework
0713: // of some of the mechanism.
0714: Method[] methods = javaClass.getMethods();
0715: for (int i = methods.length; --i >= 0;) {
0716: // we need to collect all methods, though we'll only
0717: // install the ones that are named in this class
0718: Method method = methods[i];
0719: String name = method.getName();
0720: if (Modifier.isStatic(method.getModifiers())) {
0721: AssignedName assignedName = (AssignedName) staticNames
0722: .get(name);
0723: if (assignedName == null) {
0724: staticNames.put(name, new AssignedName(name,
0725: AssignedName.METHOD));
0726: } else {
0727: if (assignedName.type < AssignedName.METHOD)
0728: continue;
0729: if (assignedName.type != AssignedName.METHOD) {
0730: staticCallbacks.remove(name);
0731: staticCallbacks.remove(name + '=');
0732: staticNames.put(name, new AssignedName(name,
0733: AssignedName.METHOD));
0734: }
0735: }
0736: StaticMethodInvoker invoker = (StaticMethodInvoker) staticCallbacks
0737: .get(name);
0738: if (invoker == null) {
0739: invoker = new StaticMethodInvoker(name);
0740: staticCallbacks.put(name, invoker);
0741: }
0742: invoker.addMethod(method, javaClass);
0743: } else {
0744: AssignedName assignedName = (AssignedName) instanceNames
0745: .get(name);
0746: if (assignedName == null) {
0747: instanceNames.put(name, new AssignedName(name,
0748: AssignedName.METHOD));
0749: } else {
0750: if (assignedName.type < AssignedName.METHOD)
0751: continue;
0752: if (assignedName.type != AssignedName.METHOD) {
0753: instanceCallbacks.remove(name);
0754: instanceCallbacks.remove(name + '=');
0755: instanceNames.put(name, new AssignedName(name,
0756: AssignedName.METHOD));
0757: }
0758: }
0759: InstanceMethodInvoker invoker = (InstanceMethodInvoker) instanceCallbacks
0760: .get(name);
0761: if (invoker == null) {
0762: invoker = new InstanceMethodInvoker(name);
0763: instanceCallbacks.put(name, invoker);
0764: }
0765: invoker.addMethod(method, javaClass);
0766: }
0767: }
0768: this .staticAssignedNames = staticNames;
0769: this .instanceAssignedNames = instanceNames;
0770: this .staticCallbacks = staticCallbacks;
0771: this .instanceCallbacks = instanceCallbacks;
0772: this .constantFields = constantFields;
0773: }
0774:
0775: public void setupProxy(RubyClass proxy) {
0776: proxy.defineFastMethod("__jsend!", __jsend_method);
0777: Class javaClass = javaClass();
0778: if (javaClass.isInterface()) {
0779: setupInterfaceProxy(proxy);
0780: return;
0781: } else if (javaClass.isArray() || javaClass.isPrimitive()) {
0782: return;
0783: }
0784: for (Iterator iter = constantFields.iterator(); iter.hasNext();) {
0785: ((ConstantField) iter.next()).install(proxy);
0786: }
0787: for (Iterator iter = staticCallbacks.values().iterator(); iter
0788: .hasNext();) {
0789: NamedCallback callback = (NamedCallback) iter.next();
0790: if (callback.type == NamedCallback.STATIC_METHOD
0791: && callback.hasLocalMethod()) {
0792: assignAliases((MethodCallback) callback,
0793: staticAssignedNames);
0794: }
0795: callback.install(proxy);
0796: }
0797: for (Iterator iter = instanceCallbacks.values().iterator(); iter
0798: .hasNext();) {
0799: NamedCallback callback = (NamedCallback) iter.next();
0800: if (callback.type == NamedCallback.INSTANCE_METHOD
0801: && callback.hasLocalMethod()) {
0802: assignAliases((MethodCallback) callback,
0803: instanceAssignedNames);
0804: }
0805: callback.install(proxy);
0806: }
0807: // setup constants for public inner classes
0808: Class[] classes = javaClass.getClasses();
0809: for (int i = classes.length; --i >= 0;) {
0810: if (javaClass == classes[i].getDeclaringClass()) {
0811: Class clazz = classes[i];
0812: String simpleName = getSimpleName(clazz);
0813:
0814: if (simpleName.length() == 0)
0815: continue;
0816:
0817: // Ignore bad constant named inner classes pending JRUBY-697
0818: if (IdUtil.isConstant(simpleName)
0819: && proxy.getConstantAt(simpleName) == null) {
0820: proxy.const_set(getRuntime().newString(simpleName),
0821: Java.get_proxy_class(JAVA_UTILITIES, get(
0822: getRuntime(), clazz)));
0823: }
0824: }
0825: }
0826: // TODO: we can probably release our references to the constantFields
0827: // array and static/instance callback hashes at this point. I don't see
0828: // a case where the proxy would be GC'd and we'd have to reinitialize.
0829: // I suppose we could keep a reference to the proxy just to be sure...
0830: }
0831:
0832: private static void assignAliases(MethodCallback callback,
0833: Map assignedNames) {
0834: String name = callback.name;
0835: addUnassignedAlias(getRubyCasedName(name), assignedNames,
0836: callback);
0837: // logic adapted from java.beans.Introspector
0838: if (!(name.length() > 3 || name.startsWith("is")))
0839: return;
0840:
0841: String javaPropertyName = getJavaPropertyName(name);
0842: if (javaPropertyName == null)
0843: return; // not a Java property name, done with this method
0844:
0845: for (Iterator iter = callback.methods.iterator(); iter
0846: .hasNext();) {
0847: Method method = (Method) iter.next();
0848: Class[] argTypes = method.getParameterTypes();
0849: Class resultType = method.getReturnType();
0850: int argCount = argTypes.length;
0851: if (argCount == 0) {
0852: if (name.startsWith("get")) {
0853: addUnassignedAlias(
0854: getRubyCasedName(javaPropertyName),
0855: assignedNames, callback);
0856: addUnassignedAlias(javaPropertyName, assignedNames,
0857: callback);
0858: } else if (resultType == boolean.class
0859: && name.startsWith("is")) {
0860: String rubyName = getRubyCasedName(name
0861: .substring(2));
0862: if (rubyName != null) {
0863: addUnassignedAlias(rubyName, assignedNames,
0864: callback);
0865: addUnassignedAlias(rubyName + '?',
0866: assignedNames, callback);
0867: }
0868: if (!javaPropertyName.equals(rubyName)) {
0869: addUnassignedAlias(javaPropertyName,
0870: assignedNames, callback);
0871: addUnassignedAlias(javaPropertyName + '?',
0872: assignedNames, callback);
0873: }
0874: }
0875: } else if (argCount == 1) {
0876: // indexed get
0877: if (argTypes[0] == int.class && name.startsWith("get")) {
0878: addUnassignedAlias(getRubyCasedName(name
0879: .substring(3)), assignedNames, callback);
0880: addUnassignedAlias(javaPropertyName, assignedNames,
0881: callback);
0882: } else if (resultType == void.class
0883: && name.startsWith("set")) {
0884: String rubyName = getRubyCasedName(name
0885: .substring(3));
0886: if (rubyName != null) {
0887: addUnassignedAlias(rubyName + '=',
0888: assignedNames, callback);
0889: }
0890: if (!javaPropertyName.equals(rubyName)) {
0891: addUnassignedAlias(javaPropertyName + '=',
0892: assignedNames, callback);
0893: }
0894: }
0895: }
0896: }
0897: }
0898:
0899: private static void addUnassignedAlias(String name,
0900: Map assignedNames, MethodCallback callback) {
0901: if (name != null) {
0902: AssignedName assignedName = (AssignedName) assignedNames
0903: .get(name);
0904: if (assignedName == null) {
0905: callback.addAlias(name);
0906: assignedNames.put(name, new AssignedName(name,
0907: AssignedName.ALIAS));
0908: } else if (assignedName.type == AssignedName.ALIAS) {
0909: callback.addAlias(name);
0910: } else if (assignedName.type > AssignedName.ALIAS) {
0911: // TODO: there will be some additional logic in this branch
0912: // dealing with conflicting protected fields.
0913: callback.addAlias(name);
0914: assignedNames.put(name, new AssignedName(name,
0915: AssignedName.ALIAS));
0916: }
0917: }
0918: }
0919:
0920: private static final Pattern JAVA_PROPERTY_CHOPPER = Pattern
0921: .compile("(get|set|is)([A-Z0-9])(.*)");
0922:
0923: public static String getJavaPropertyName(String beanMethodName) {
0924: Matcher m = JAVA_PROPERTY_CHOPPER.matcher(beanMethodName);
0925:
0926: if (!m.find())
0927: return null;
0928: String javaPropertyName = m.group(2).toLowerCase() + m.group(3);
0929: return javaPropertyName;
0930: }
0931:
0932: private static final Pattern CAMEL_CASE_SPLITTER = Pattern
0933: .compile("([a-z])([A-Z])");
0934:
0935: public static String getRubyCasedName(String javaCasedName) {
0936: Matcher m = CAMEL_CASE_SPLITTER.matcher(javaCasedName);
0937: String rubyCasedName = m.replaceAll("$1_$2").toLowerCase();
0938: if (rubyCasedName.equals(javaCasedName)) {
0939: return null;
0940: }
0941: return rubyCasedName;
0942: }
0943:
0944: public void setupInterfaceProxy(RubyClass proxy) {
0945: Class javaClass = javaClass();
0946: for (Iterator iter = constantFields.iterator(); iter.hasNext();) {
0947: ((ConstantField) iter.next()).install(proxy);
0948: }
0949: // setup constants for public inner classes
0950: Class[] classes = javaClass.getClasses();
0951: for (int i = classes.length; --i >= 0;) {
0952: if (javaClass == classes[i].getDeclaringClass()) {
0953: Class clazz = classes[i];
0954: String simpleName = getSimpleName(clazz);
0955: if (simpleName.length() == 0)
0956: continue;
0957:
0958: // Ignore bad constant named inner classes pending JRUBY-697
0959: if (IdUtil.isConstant(simpleName)
0960: && proxy.getConstantAt(simpleName) == null) {
0961: proxy.const_set(getRuntime().newString(simpleName),
0962: Java.get_proxy_class(JAVA_UTILITIES, get(
0963: getRuntime(), clazz)));
0964: }
0965: }
0966: }
0967: }
0968:
0969: public void setupInterfaceModule(RubyModule module) {
0970: Class javaClass = javaClass();
0971: for (Iterator iter = constantFields.iterator(); iter.hasNext();) {
0972: ((ConstantField) iter.next()).install(module);
0973: }
0974: // setup constants for public inner classes
0975: Class[] classes = javaClass.getClasses();
0976: for (int i = classes.length; --i >= 0;) {
0977: if (javaClass == classes[i].getDeclaringClass()) {
0978: Class clazz = classes[i];
0979: String simpleName = getSimpleName(clazz);
0980: if (simpleName.length() == 0)
0981: continue;
0982:
0983: // Ignore bad constant named inner classes pending JRUBY-697
0984: if (IdUtil.isConstant(simpleName)
0985: && module.getConstantAt(simpleName) == null) {
0986: module.const_set(
0987: getRuntime().newString(simpleName), Java
0988: .get_proxy_class(JAVA_UTILITIES,
0989: get(getRuntime(), clazz)));
0990: }
0991: }
0992: }
0993: }
0994:
0995: // unsynchronized, so create won't hold up get by other threads
0996: public static JavaClass get(Ruby runtime, Class klass) {
0997: JavaClass javaClass = runtime.getJavaSupport()
0998: .getJavaClassFromCache(klass);
0999: if (javaClass == null) {
1000: javaClass = createJavaClass(runtime, klass);
1001: }
1002: return javaClass;
1003: }
1004:
1005: private static synchronized JavaClass createJavaClass(Ruby runtime,
1006: Class klass) {
1007: // double-check the cache now that we're synchronized
1008: JavaClass javaClass = runtime.getJavaSupport()
1009: .getJavaClassFromCache(klass);
1010: if (javaClass == null) {
1011: javaClass = new JavaClass(runtime, klass);
1012: runtime.getJavaSupport().putJavaClassIntoCache(javaClass);
1013: }
1014: return javaClass;
1015: }
1016:
1017: public static RubyClass createJavaClassClass(Ruby runtime,
1018: RubyModule javaModule) {
1019: // FIXME: Determine if a real allocator is needed here. Do people want to extend
1020: // JavaClass? Do we want them to do that? Can you Class.new(JavaClass)? Should
1021: // you be able to?
1022: // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here, since we don't intend for people to monkey with
1023: // this type and it can't be marshalled. Confirm. JRUBY-415
1024: RubyClass result = javaModule.defineClassUnder("JavaClass",
1025: javaModule.getClass("JavaObject"),
1026: ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
1027:
1028: CallbackFactory callbackFactory = runtime
1029: .callbackFactory(JavaClass.class);
1030:
1031: result.includeModule(runtime.getModule("Comparable"));
1032:
1033: JavaObject.registerRubyMethods(runtime, result);
1034:
1035: result.getMetaClass().defineFastMethod(
1036: "for_name",
1037: callbackFactory.getFastSingletonMethod("for_name",
1038: IRubyObject.class));
1039: result.defineFastMethod("public?", callbackFactory
1040: .getFastMethod("public_p"));
1041: result.defineFastMethod("protected?", callbackFactory
1042: .getFastMethod("protected_p"));
1043: result.defineFastMethod("private?", callbackFactory
1044: .getFastMethod("private_p"));
1045: result.defineFastMethod("final?", callbackFactory
1046: .getFastMethod("final_p"));
1047: result.defineFastMethod("interface?", callbackFactory
1048: .getFastMethod("interface_p"));
1049: result.defineFastMethod("array?", callbackFactory
1050: .getFastMethod("array_p"));
1051: result.defineFastMethod("name", callbackFactory
1052: .getFastMethod("name"));
1053: result.defineFastMethod("simple_name", callbackFactory
1054: .getFastMethod("simple_name"));
1055: result.defineFastMethod("to_s", callbackFactory
1056: .getFastMethod("name"));
1057: result.defineFastMethod("superclass", callbackFactory
1058: .getFastMethod("superclass"));
1059: result.defineFastMethod("<=>", callbackFactory.getFastMethod(
1060: "op_cmp", IRubyObject.class));
1061: result.defineFastMethod("java_instance_methods",
1062: callbackFactory.getFastMethod("java_instance_methods"));
1063: result.defineFastMethod("java_class_methods", callbackFactory
1064: .getFastMethod("java_class_methods"));
1065: result.defineFastMethod("java_method", callbackFactory
1066: .getFastOptMethod("java_method"));
1067: result.defineFastMethod("constructors", callbackFactory
1068: .getFastMethod("constructors"));
1069: result.defineFastMethod("constructor", callbackFactory
1070: .getFastOptMethod("constructor"));
1071: result.defineFastMethod("array_class", callbackFactory
1072: .getFastMethod("array_class"));
1073: result.defineFastMethod("new_array", callbackFactory
1074: .getFastMethod("new_array", IRubyObject.class));
1075: result.defineFastMethod("fields", callbackFactory
1076: .getFastMethod("fields"));
1077: result.defineFastMethod("field", callbackFactory.getFastMethod(
1078: "field", IRubyObject.class));
1079: result.defineFastMethod("interfaces", callbackFactory
1080: .getFastMethod("interfaces"));
1081: result.defineFastMethod("primitive?", callbackFactory
1082: .getFastMethod("primitive_p"));
1083: result.defineFastMethod("assignable_from?", callbackFactory
1084: .getFastMethod("assignable_from_p", IRubyObject.class));
1085: result.defineFastMethod("component_type", callbackFactory
1086: .getFastMethod("component_type"));
1087: result.defineFastMethod("declared_instance_methods",
1088: callbackFactory
1089: .getFastMethod("declared_instance_methods"));
1090: result
1091: .defineFastMethod(
1092: "declared_class_methods",
1093: callbackFactory
1094: .getFastMethod("declared_class_methods"));
1095: result.defineFastMethod("declared_fields", callbackFactory
1096: .getFastMethod("declared_fields"));
1097: result.defineFastMethod("declared_field", callbackFactory
1098: .getFastMethod("declared_field", IRubyObject.class));
1099: result.defineFastMethod("declared_constructors",
1100: callbackFactory.getFastMethod("declared_constructors"));
1101: result.defineFastMethod("declared_constructor", callbackFactory
1102: .getFastOptMethod("declared_constructor"));
1103: result.defineFastMethod("declared_classes", callbackFactory
1104: .getFastMethod("declared_classes"));
1105: result.defineFastMethod("declared_method", callbackFactory
1106: .getFastOptMethod("declared_method"));
1107: result.defineFastMethod("define_instance_methods_for_proxy",
1108: callbackFactory.getFastMethod(
1109: "define_instance_methods_for_proxy",
1110: IRubyObject.class));
1111:
1112: result.getMetaClass().undefineMethod("new");
1113: result.getMetaClass().undefineMethod("allocate");
1114:
1115: return result;
1116: }
1117:
1118: public static synchronized JavaClass forName(Ruby runtime,
1119: String className) {
1120: Class klass = runtime.getJavaSupport().loadJavaClass(className);
1121: return JavaClass.get(runtime, klass);
1122: }
1123:
1124: public static JavaClass for_name(IRubyObject recv, IRubyObject name) {
1125: return forName(recv.getRuntime(), name.asSymbol());
1126: }
1127:
1128: // TODO: part of interim solution, can be removed soon
1129: private Set getPublicFieldNames(boolean isStatic) {
1130: // we're not checking for final here; since the purpose is to prevent
1131: // shortcut property names from being created that conflict with
1132: // field names, it won't make sense to allow the property setter (name=)
1133: // to be created but not the getter.
1134: int mask = Modifier.PUBLIC | Modifier.STATIC;
1135: int want = isStatic ? Modifier.PUBLIC | Modifier.STATIC
1136: : Modifier.PUBLIC;
1137: Set names = new HashSet();
1138: names.add("class");
1139: Field[] fields = ((Class) getValue()).getFields();
1140: for (int i = fields.length; --i >= 0;) {
1141: if ((fields[i].getModifiers() & mask) == want) {
1142: names.add(fields[i].getName());
1143: }
1144: }
1145: return names;
1146: }
1147:
1148: /**
1149: * Get all methods grouped by name (e.g. 'new => {new(), new(int), new(int, int)}, ...')
1150: * @param isStatic determines whether you want static or instance methods from the class
1151: */
1152: private Map getMethodsClumped(boolean isStatic, Set assignedNames) {
1153: System.out.println("JC.gmc");
1154: Map map = new HashMap();
1155: if (((Class) getValue()).isInterface()) {
1156: return map;
1157: }
1158:
1159: Method methods[] = javaClass().getMethods();
1160:
1161: for (int i = 0; i < methods.length; i++) {
1162: if (isStatic != Modifier
1163: .isStatic(methods[i].getModifiers())) {
1164: continue;
1165: }
1166:
1167: String key = methods[i].getName();
1168: RubyArray methodsWithName = (RubyArray) map.get(key);
1169:
1170: if (methodsWithName == null) {
1171: methodsWithName = RubyArray.newArrayLight(getRuntime());
1172: map.put(key, methodsWithName);
1173: assignedNames.add(key);
1174: }
1175:
1176: methodsWithName.append(JavaMethod.create(getRuntime(),
1177: methods[i]));
1178: }
1179:
1180: return map;
1181: }
1182:
1183: private Map getPropertysClumped(Set assignedNames) {
1184: System.out.println("JC.gpc");
1185: Map map = new HashMap();
1186: BeanInfo info;
1187:
1188: try {
1189: info = Introspector.getBeanInfo(javaClass());
1190: } catch (IntrospectionException e) {
1191: return map;
1192: }
1193:
1194: PropertyDescriptor[] descriptors = info
1195: .getPropertyDescriptors();
1196: System.out.println("got bean info for class "
1197: + javaClass().getName() + ": " + descriptors.length);
1198: for (int i = 0; i < descriptors.length; i++) {
1199: Method readMethod = descriptors[i].getReadMethod();
1200:
1201: if (readMethod != null) {
1202: String key = readMethod.getName();
1203: List aliases = (List) map.get(key);
1204:
1205: if (aliases == null) {
1206: aliases = new ArrayList();
1207:
1208: map.put(key, aliases);
1209: }
1210:
1211: if (readMethod.getReturnType() == Boolean.class
1212: || readMethod.getReturnType() == boolean.class) {
1213: aliases.add(descriptors[i].getName() + "?");
1214: }
1215: if (!assignedNames.contains(descriptors[i].getName())) {
1216: aliases.add(descriptors[i].getName());
1217: }
1218: }
1219:
1220: Method writeMethod = descriptors[i].getWriteMethod();
1221:
1222: if (writeMethod != null) {
1223: String key = writeMethod.getName();
1224: List aliases = (List) map.get(key);
1225:
1226: if (aliases == null) {
1227: aliases = new ArrayList();
1228: map.put(key, aliases);
1229: }
1230:
1231: if (!assignedNames.contains(descriptors[i].getName())) {
1232: aliases.add(descriptors[i].getName() + "=");
1233: }
1234: }
1235: }
1236:
1237: return map;
1238: }
1239:
1240: private void define_instance_method_for_proxy(
1241: final RubyClass proxy, List names, final RubyArray methods) {
1242: final RubyModule javaUtilities = JAVA_UTILITIES;
1243: Callback method;
1244: if (methods.size() > 1) {
1245: method = new Callback() {
1246: private IntHashMap matchingMethods = new IntHashMap();
1247:
1248: public IRubyObject execute(IRubyObject self,
1249: IRubyObject[] args, Block block) {
1250: int len = args.length;
1251: IRubyObject[] argsArray = new IRubyObject[len + 1];
1252:
1253: argsArray[0] = self
1254: .getInstanceVariable("@java_object");
1255:
1256: int argsTypeHash = 0;
1257: for (int j = 0; j < len; j++) {
1258: argsArray[j + 1] = Java.ruby_to_java(proxy,
1259: args[j], Block.NULL_BLOCK);
1260: argsTypeHash += 3 * args[j].getMetaClass().id;
1261: }
1262:
1263: IRubyObject match = (IRubyObject) matchingMethods
1264: .get(argsTypeHash);
1265: if (match == null) {
1266: match = Java.matching_method_internal(
1267: javaUtilities, methods, argsArray, 1,
1268: len);
1269: matchingMethods.put(argsTypeHash, match);
1270: }
1271:
1272: return Java.java_to_ruby(self, ((JavaMethod) match)
1273: .invoke(argsArray), Block.NULL_BLOCK);
1274: }
1275:
1276: public Arity getArity() {
1277: return Arity.optional();
1278: }
1279: };
1280: } else {
1281: final JavaMethod METHOD = (JavaMethod) methods
1282: .eltInternal(0);
1283: method = new Callback() {
1284: public IRubyObject execute(IRubyObject self,
1285: IRubyObject[] args, Block block) {
1286: int len = args.length;
1287: IRubyObject[] argsArray = new IRubyObject[len + 1];
1288: argsArray[0] = self
1289: .getInstanceVariable("@java_object");
1290: for (int j = 0; j < len; j++) {
1291: argsArray[j + 1] = Java.ruby_to_java(proxy,
1292: args[j], Block.NULL_BLOCK);
1293: }
1294: return Java.java_to_ruby(self, METHOD
1295: .invoke(argsArray), Block.NULL_BLOCK);
1296: }
1297:
1298: public Arity getArity() {
1299: return Arity.optional();
1300: }
1301: };
1302: }
1303:
1304: for (Iterator iter = names.iterator(); iter.hasNext();) {
1305: String methodName = (String) iter.next();
1306:
1307: // We do not override class since it is too important to be overridden by getClass
1308: // short name.
1309: if (!methodName.equals("class")) {
1310: proxy.defineFastMethod(methodName, method);
1311:
1312: String rubyCasedName = getRubyCasedName(methodName);
1313: if (rubyCasedName != null) {
1314: proxy.defineAlias(rubyCasedName, methodName);
1315: }
1316: }
1317: }
1318: }
1319:
1320: private static final Callback __jsend_method = new Callback() {
1321: public IRubyObject execute(IRubyObject self,
1322: IRubyObject[] args, Block block) {
1323: String name = args[0].asSymbol();
1324:
1325: // FIXME: why newMethod ?
1326: RubyMethod method = (org.jruby.RubyMethod) self
1327: .getMetaClass().newMethod(self, name, true);
1328: int v = RubyNumeric.fix2int(method.arity());
1329:
1330: IRubyObject[] newArgs = new IRubyObject[args.length - 1];
1331: System.arraycopy(args, 1, newArgs, 0, newArgs.length);
1332: if (DEBUG)
1333: System.out.println("__jsend method => '" + name
1334: + "'; arity = " + v + ", args.length = "
1335: + newArgs.length);
1336:
1337: if (v < 0 || v == (newArgs.length)) {
1338: if (DEBUG)
1339: System.out.println(" calling self");
1340: return self.callMethod(self.getRuntime()
1341: .getCurrentContext(), name, newArgs,
1342: CallType.FUNCTIONAL, block);
1343: } else {
1344: if (DEBUG)
1345: System.out.println(" calling super");
1346: return self.callMethod(self.getRuntime()
1347: .getCurrentContext(), self.getMetaClass()
1348: .getSuperClass(), name, newArgs,
1349: CallType.SUPER, block);
1350: }
1351: }
1352:
1353: public Arity getArity() {
1354: return Arity.optional();
1355: }
1356: };
1357:
1358: public IRubyObject define_instance_methods_for_proxy(IRubyObject arg) {
1359: assert arg instanceof RubyClass;
1360: // shouldn't be getting called any more
1361: System.out.println("JC.define_instance_methods_for_proxy");
1362: Set assignedNames = getPublicFieldNames(false);
1363: Map methodsClump = getMethodsClumped(false, assignedNames);
1364: Map aliasesClump = getPropertysClumped(assignedNames);
1365: RubyClass proxy = (RubyClass) arg;
1366:
1367: proxy.defineFastMethod("__jsend!", __jsend_method);
1368:
1369: for (Iterator iter = methodsClump.keySet().iterator(); iter
1370: .hasNext();) {
1371: String name = (String) iter.next();
1372: RubyArray methods = (RubyArray) methodsClump.get(name);
1373: List aliases = (List) aliasesClump.get(name);
1374:
1375: if (aliases == null) {
1376: aliases = new ArrayList();
1377: }
1378:
1379: aliases.add(name);
1380:
1381: define_instance_method_for_proxy(proxy, aliases, methods);
1382: }
1383:
1384: return getRuntime().getNil();
1385: }
1386:
1387: public RubyBoolean public_p() {
1388: return getRuntime().newBoolean(
1389: Modifier.isPublic(javaClass().getModifiers()));
1390: }
1391:
1392: public RubyBoolean protected_p() {
1393: return getRuntime().newBoolean(
1394: Modifier.isProtected(javaClass().getModifiers()));
1395: }
1396:
1397: public RubyBoolean private_p() {
1398: return getRuntime().newBoolean(
1399: Modifier.isPrivate(javaClass().getModifiers()));
1400: }
1401:
1402: public Class javaClass() {
1403: return (Class) getValue();
1404: }
1405:
1406: public RubyBoolean final_p() {
1407: return getRuntime().newBoolean(
1408: Modifier.isFinal(javaClass().getModifiers()));
1409: }
1410:
1411: public RubyBoolean interface_p() {
1412: return getRuntime().newBoolean(javaClass().isInterface());
1413: }
1414:
1415: public RubyBoolean array_p() {
1416: return getRuntime().newBoolean(javaClass().isArray());
1417: }
1418:
1419: public RubyString name() {
1420: return getRuntime().newString(javaClass().getName());
1421: }
1422:
1423: private static String getSimpleName(Class class_) {
1424: if (class_.isArray()) {
1425: return getSimpleName(class_.getComponentType()) + "[]";
1426: }
1427:
1428: String className = class_.getName();
1429:
1430: int i = className.lastIndexOf('$');
1431: if (i != -1) {
1432: do {
1433: i++;
1434: } while (i < className.length()
1435: && Character.isDigit(className.charAt(i)));
1436: return className.substring(i);
1437: }
1438:
1439: return className.substring(className.lastIndexOf('.') + 1);
1440: }
1441:
1442: public RubyString simple_name() {
1443: return getRuntime().newString(getSimpleName(javaClass()));
1444: }
1445:
1446: public IRubyObject super class() {
1447: Class super class = javaClass().getSuperclass();
1448: if (super class == null) {
1449: return getRuntime().getNil();
1450: }
1451: return JavaClass.get(getRuntime(), super class);
1452: }
1453:
1454: public RubyFixnum op_cmp(IRubyObject other) {
1455: if (!(other instanceof JavaClass)) {
1456: throw getRuntime().newTypeError(
1457: "<=> requires JavaClass (" + other.getType()
1458: + " given)");
1459: }
1460: JavaClass otherClass = (JavaClass) other;
1461: if (this .javaClass() == otherClass.javaClass()) {
1462: return getRuntime().newFixnum(0);
1463: }
1464: if (otherClass.javaClass().isAssignableFrom(this .javaClass())) {
1465: return getRuntime().newFixnum(-1);
1466: }
1467: return getRuntime().newFixnum(1);
1468: }
1469:
1470: public RubyArray java_instance_methods() {
1471: return java_methods(javaClass().getMethods(), false);
1472: }
1473:
1474: public RubyArray declared_instance_methods() {
1475: return java_methods(javaClass().getDeclaredMethods(), false);
1476: }
1477:
1478: private RubyArray java_methods(Method[] methods, boolean isStatic) {
1479: RubyArray result = getRuntime().newArray(methods.length);
1480: for (int i = 0; i < methods.length; i++) {
1481: Method method = methods[i];
1482: if (isStatic == Modifier.isStatic(method.getModifiers())) {
1483: result.append(JavaMethod.create(getRuntime(), method));
1484: }
1485: }
1486: return result;
1487: }
1488:
1489: public RubyArray java_class_methods() {
1490: return java_methods(javaClass().getMethods(), true);
1491: }
1492:
1493: public RubyArray declared_class_methods() {
1494: return java_methods(javaClass().getDeclaredMethods(), true);
1495: }
1496:
1497: public JavaMethod java_method(IRubyObject[] args)
1498: throws ClassNotFoundException {
1499: String methodName = args[0].asSymbol();
1500: Class[] argumentTypes = buildArgumentTypes(args);
1501: return JavaMethod.create(getRuntime(), javaClass(), methodName,
1502: argumentTypes);
1503: }
1504:
1505: public JavaMethod declared_method(IRubyObject[] args)
1506: throws ClassNotFoundException {
1507: String methodName = args[0].asSymbol();
1508: Class[] argumentTypes = buildArgumentTypes(args);
1509: return JavaMethod.createDeclared(getRuntime(), javaClass(),
1510: methodName, argumentTypes);
1511: }
1512:
1513: private Class[] buildArgumentTypes(IRubyObject[] args)
1514: throws ClassNotFoundException {
1515: if (args.length < 1) {
1516: throw getRuntime().newArgumentError(args.length, 1);
1517: }
1518: Class[] argumentTypes = new Class[args.length - 1];
1519: for (int i = 1; i < args.length; i++) {
1520: JavaClass type = for_name(this , args[i]);
1521: argumentTypes[i - 1] = type.javaClass();
1522: }
1523: return argumentTypes;
1524: }
1525:
1526: public RubyArray constructors() {
1527: if (constructors == null) {
1528: constructors = buildConstructors(javaClass()
1529: .getConstructors());
1530: }
1531: return constructors;
1532: }
1533:
1534: public RubyArray declared_classes() {
1535: // TODO: should be able to partially work around this by calling
1536: // javaClass().getClasses, which will return any public inner classes.
1537: if (Ruby.isSecurityRestricted()) // Can't even get inner classes?
1538: return getRuntime().newArray(0);
1539: Class[] classes = javaClass().getDeclaredClasses();
1540: List accessibleClasses = new ArrayList();
1541: for (int i = 0; i < classes.length; i++) {
1542: if (Modifier.isPublic(classes[i].getModifiers())) {
1543: accessibleClasses.add(classes[i]);
1544: }
1545: }
1546: return buildClasses((Class[]) accessibleClasses
1547: .toArray(new Class[accessibleClasses.size()]));
1548: }
1549:
1550: private RubyArray buildClasses(Class[] classes) {
1551: RubyArray result = getRuntime().newArray(classes.length);
1552: for (int i = 0; i < classes.length; i++) {
1553: result.append(new JavaClass(getRuntime(), classes[i]));
1554: }
1555: return result;
1556: }
1557:
1558: public RubyArray declared_constructors() {
1559: return buildConstructors(javaClass().getDeclaredConstructors());
1560: }
1561:
1562: private RubyArray buildConstructors(Constructor[] constructors) {
1563: RubyArray result = getRuntime().newArray(constructors.length);
1564: for (int i = 0; i < constructors.length; i++) {
1565: result.append(new JavaConstructor(getRuntime(),
1566: constructors[i]));
1567: }
1568: return result;
1569: }
1570:
1571: public JavaConstructor constructor(IRubyObject[] args) {
1572: try {
1573: Class[] parameterTypes = buildClassArgs(args);
1574: Constructor constructor;
1575: constructor = javaClass().getConstructor(parameterTypes);
1576: return new JavaConstructor(getRuntime(), constructor);
1577: } catch (NoSuchMethodException nsme) {
1578: throw getRuntime().newNameError(
1579: "no matching java constructor", null);
1580: }
1581: }
1582:
1583: public JavaConstructor declared_constructor(IRubyObject[] args) {
1584: try {
1585: Class[] parameterTypes = buildClassArgs(args);
1586: Constructor constructor;
1587: constructor = javaClass().getDeclaredConstructor(
1588: parameterTypes);
1589: return new JavaConstructor(getRuntime(), constructor);
1590: } catch (NoSuchMethodException nsme) {
1591: throw getRuntime().newNameError(
1592: "no matching java constructor", null);
1593: }
1594: }
1595:
1596: private Class[] buildClassArgs(IRubyObject[] args) {
1597: Class[] parameterTypes = new Class[args.length];
1598: for (int i = 0; i < args.length; i++) {
1599: String name = args[i].asSymbol();
1600: parameterTypes[i] = getRuntime().getJavaSupport()
1601: .loadJavaClass(name);
1602: }
1603: return parameterTypes;
1604: }
1605:
1606: public JavaClass array_class() {
1607: return JavaClass.get(getRuntime(), Array.newInstance(
1608: javaClass(), 0).getClass());
1609: }
1610:
1611: public JavaObject new_array(IRubyObject lengthArgument) {
1612: if (lengthArgument instanceof RubyInteger) {
1613: // one-dimensional array
1614: int length = (int) ((RubyInteger) lengthArgument)
1615: .getLongValue();
1616: return new JavaArray(getRuntime(), Array.newInstance(
1617: javaClass(), length));
1618: } else if (lengthArgument instanceof RubyArray) {
1619: // n-dimensional array
1620: List list = ((RubyArray) lengthArgument).getList();
1621: int length = list.size();
1622: if (length == 0) {
1623: throw getRuntime().newArgumentError(
1624: "empty dimensions specifier for java array");
1625: }
1626: int[] dimensions = new int[length];
1627: for (int i = length; --i >= 0;) {
1628: IRubyObject dimensionLength = (IRubyObject) list.get(i);
1629: if (!(dimensionLength instanceof RubyInteger)) {
1630: throw getRuntime().newTypeError(dimensionLength,
1631: getRuntime().getClass("Integer"));
1632: }
1633: dimensions[i] = (int) ((RubyInteger) dimensionLength)
1634: .getLongValue();
1635: }
1636: return new JavaArray(getRuntime(), Array.newInstance(
1637: javaClass(), dimensions));
1638: } else {
1639: throw getRuntime().newArgumentError(
1640: "invalid length or dimensions specifier for java array"
1641: + " - must be Integer or Array of Integer");
1642: }
1643: }
1644:
1645: public RubyArray fields() {
1646: return buildFieldResults(javaClass().getFields());
1647: }
1648:
1649: public RubyArray declared_fields() {
1650: return buildFieldResults(javaClass().getDeclaredFields());
1651: }
1652:
1653: private RubyArray buildFieldResults(Field[] fields) {
1654: RubyArray result = getRuntime().newArray(fields.length);
1655: for (int i = 0; i < fields.length; i++) {
1656: result.append(new JavaField(getRuntime(), fields[i]));
1657: }
1658: return result;
1659: }
1660:
1661: public JavaField field(IRubyObject name) {
1662: String stringName = name.asSymbol();
1663: try {
1664: Field field = javaClass().getField(stringName);
1665: return new JavaField(getRuntime(), field);
1666: } catch (NoSuchFieldException nsfe) {
1667: throw undefinedFieldError(stringName);
1668: }
1669: }
1670:
1671: public JavaField declared_field(IRubyObject name) {
1672: String stringName = name.asSymbol();
1673: try {
1674: Field field = javaClass().getDeclaredField(stringName);
1675: return new JavaField(getRuntime(), field);
1676: } catch (NoSuchFieldException nsfe) {
1677: throw undefinedFieldError(stringName);
1678: }
1679: }
1680:
1681: private RaiseException undefinedFieldError(String name) {
1682: return getRuntime().newNameError(
1683: "undefined field '" + name + "' for class '"
1684: + javaClass().getName() + "'", name);
1685: }
1686:
1687: public RubyArray interfaces() {
1688: Class[] interfaces = javaClass().getInterfaces();
1689: RubyArray result = getRuntime().newArray(interfaces.length);
1690: for (int i = 0; i < interfaces.length; i++) {
1691: result.append(JavaClass.get(getRuntime(), interfaces[i]));
1692: }
1693: return result;
1694: }
1695:
1696: public RubyBoolean primitive_p() {
1697: return getRuntime().newBoolean(isPrimitive());
1698: }
1699:
1700: public RubyBoolean assignable_from_p(IRubyObject other) {
1701: if (!(other instanceof JavaClass)) {
1702: throw getRuntime().newTypeError(
1703: "assignable_from requires JavaClass ("
1704: + other.getType() + " given)");
1705: }
1706:
1707: Class otherClass = ((JavaClass) other).javaClass();
1708: return assignable(javaClass(), otherClass) ? getRuntime()
1709: .getTrue() : getRuntime().getFalse();
1710: }
1711:
1712: static boolean assignable(Class this Class, Class otherClass) {
1713: if (!this Class.isPrimitive() && otherClass == Void.TYPE
1714: || this Class.isAssignableFrom(otherClass)) {
1715: return true;
1716: }
1717:
1718: otherClass = JavaUtil.primitiveToWrapper(otherClass);
1719: this Class = JavaUtil.primitiveToWrapper(this Class);
1720:
1721: if (this Class.isAssignableFrom(otherClass)) {
1722: return true;
1723: }
1724: if (Number.class.isAssignableFrom(this Class)) {
1725: if (Number.class.isAssignableFrom(otherClass)) {
1726: return true;
1727: }
1728: if (otherClass.equals(Character.class)) {
1729: return true;
1730: }
1731: }
1732: if (this Class.equals(Character.class)) {
1733: if (Number.class.isAssignableFrom(otherClass)) {
1734: return true;
1735: }
1736: }
1737: return false;
1738: }
1739:
1740: private boolean isPrimitive() {
1741: return javaClass().isPrimitive();
1742: }
1743:
1744: public JavaClass component_type() {
1745: if (!javaClass().isArray()) {
1746: throw getRuntime().newTypeError("not a java array-class");
1747: }
1748: return JavaClass.get(getRuntime(), javaClass()
1749: .getComponentType());
1750: }
1751: }
|