0001: /*=============================================================================
0002: * Copyright Texas Instruments 2000-2004. All Rights Reserved.
0003: *
0004: * This program is free software; you can redistribute it and/or
0005: * modify it under the terms of the GNU Lesser General Public
0006: * License as published by the Free Software Foundation; either
0007: * version 2 of the License, or (at your option) any later version.
0008: *
0009: * This program is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public
0015: * License along with this library; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: *
0018: * $ProjectHeader: OSCRIPT 0.155 Fri, 20 Dec 2002 18:34:22 -0800 rclark $
0019: */
0020:
0021: package oscript.data;
0022:
0023: import java.lang.reflect.*;
0024: import java.util.*;
0025:
0026: import oscript.exceptions.*;
0027: import oscript.util.*;
0028: import oscript.classwrap.ClassWrapGen;
0029:
0030: /**
0031: * A wrapper for a java class. Types should be intern'd.
0032: *
0033: * @author Rob Clark (rob@ti.com)
0034: */
0035: public class JavaClassWrapper extends Type {
0036: protected Class javaClass;
0037: private int id = -1;
0038:
0039: transient protected JavaClassWrapperImpl impl;
0040: transient protected JavaClassWrapperImpl wrapperImpl;
0041:
0042: /**
0043: * Table of all java-class-wrappers. A <code>JavaClassWrapper</code>
0044: * instance is intern'd, so there shouldn't be two instances representing
0045: * the same java class.
0046: */
0047: private static Hashtable classWrapperCache = new Hashtable();
0048:
0049: /**
0050: * The type object for an script java type.
0051: */
0052: public final static Value TYPE = BuiltinType
0053: .makeBuiltinType("oscript.data.JavaClassWrapper");
0054: public final static String PARENT_TYPE_NAME = "oscript.data.OObject";
0055: public final static String TYPE_NAME = "JavaClass";
0056: public final static String[] MEMBER_NAMES = new String[] { "isA",
0057: "castToString", "castToJavaObject", "callAsConstructor",
0058: "callAsExtends", "getMember", "getClassLoader", "getName" };
0059:
0060: /*=======================================================================*/
0061: /**
0062: * The class wrapper instances need to be intern'd, so the types work out
0063: * right... otherwise you might have multiple wrappers per java class (ie
0064: * type), which would confuse type checking...
0065: *
0066: * @param javaClass the java class this object is a wrapper for
0067: */
0068: public static synchronized JavaClassWrapper getClassWrapper(
0069: Class javaClass) {
0070: javaClass = ClassWrapGen.getNonWrapperClass(javaClass);
0071:
0072: JavaClassWrapper jcw = (JavaClassWrapper) (classWrapperCache
0073: .get(javaClass));
0074:
0075: if (jcw == null) {
0076: jcw = new JavaClassWrapper(javaClass);
0077: classWrapperCache.put(javaClass, jcw);
0078: }
0079:
0080: return jcw;
0081: }
0082:
0083: public static JavaClassWrapper getClassWrapper(String className)
0084: throws ClassNotFoundException {
0085: return getClassWrapper(forName(className));
0086: }
0087:
0088: public static synchronized Class forName(String className)
0089: throws ClassNotFoundException {
0090: return oscript.compiler.CompilerClassLoader.forName(className,
0091: true, null);
0092: }
0093:
0094: private static ClassNotFoundException CLASS_NOT_FOUND_EXCEPTION = new ClassNotFoundException();
0095:
0096: /*=======================================================================*/
0097: /**
0098: * Class Constructor.
0099: *
0100: * @param javaClass the java class this object is a wrapper for
0101: */
0102: protected JavaClassWrapper(Class javaClass) {
0103: super ();
0104:
0105: this .javaClass = javaClass;
0106: }
0107:
0108: /**
0109: * Initialize this object. Initialization is done on demand because
0110: * <code>impl</code> and <code>wrapperImpl</code> are transient, and
0111: * might not exist if this object gets unserialized...
0112: */
0113: protected synchronized void init() {
0114: if (impl == null) {
0115: this .id = Symbol.getSymbol(javaClass.getName()).getId();
0116:
0117: // note order is important... we want to initialize "impl" last,
0118: // since other places in the code use it to decide whether init()
0119: // should be called
0120:
0121: if (ClassWrapGen.canMakeWrapperClass(javaClass))
0122: wrapperImpl = new JavaClassWrapperImpl(javaClass, true);
0123:
0124: impl = new JavaClassWrapperImpl(javaClass, false);
0125: }
0126: }
0127:
0128: /*=======================================================================*/
0129: /**
0130: * Get the type of this object. The returned type doesn't have to take
0131: * into account the possibility of a script type extending a built-in
0132: * type, since that is handled by {@link #getType}.
0133: *
0134: * @return the object's type
0135: */
0136: protected Value getTypeImpl() {
0137: return TYPE;
0138: }
0139:
0140: /*=======================================================================*/
0141: /**
0142: * Convert this object to a native java <code>String</code> value.
0143: *
0144: * @return a String value
0145: * @throws PackagedScriptObjectException(NoSuchMethodException)
0146: */
0147: public String getName() {
0148: return javaClass.getName();
0149: }
0150:
0151: /*=======================================================================*/
0152: /**
0153: * If this object is a type, determine if an instance of this type is
0154: * an instance of the specified type, ie. if this is <code>type</code>,
0155: * or a subclass.
0156: *
0157: * @param type the type to compare this type to
0158: * @return <code>true</code> or <code>false</code>
0159: * @throws PackagedScriptObjectException(NoSuchMemberException)
0160: */
0161: public boolean isA(Value type) {
0162: type = type.unhand();
0163:
0164: Class c;
0165: if (super .isA(type))
0166: return true;
0167:
0168: if (((c = javaClass.getSuperclass()) != null)
0169: && getClassWrapper(c).isA(type))
0170: return true;
0171:
0172: Class[] interfaces = javaClass.getInterfaces();
0173:
0174: for (int i = 0; i < interfaces.length; i++)
0175: if (getClassWrapper(interfaces[i]).isA(type))
0176: return true;
0177:
0178: return false;
0179: }
0180:
0181: /*=======================================================================*/
0182: /**
0183: * Convert this object to a native java <code>String</code> value.
0184: *
0185: * @return a String value
0186: * @throws PackagedScriptObjectException(NoSuchMethodException)
0187: */
0188: public String castToString() throws PackagedScriptObjectException {
0189: return getName();
0190: }
0191:
0192: /*=======================================================================*/
0193: /**
0194: * Convert this object to a native java <code>Object</code> value.
0195: *
0196: * @return a java object
0197: * @throws PackagedScriptObjectException(NoSuchMethodException)
0198: */
0199: public Object castToJavaObject()
0200: throws PackagedScriptObjectException {
0201: return javaClass;
0202: }
0203:
0204: /*=======================================================================*/
0205: /**
0206: * Call this object as a constructor.
0207: *
0208: * @param sf the current stack frame
0209: * @param args the arguments to the function, or <code>null</code> if none
0210: * @return the newly constructed object
0211: * @throws PackagedScriptObjectException
0212: * @see Function
0213: */
0214: public Value callAsConstructor(StackFrame sf, MemberTable args)
0215: throws PackagedScriptObjectException {
0216: return JavaBridge.convertToScriptObject(doConstruct(sf, args,
0217: false));
0218: }
0219:
0220: /*=======================================================================*/
0221: /**
0222: * Call this object as a parent class constructor.
0223: *
0224: * @param sf the current stack frame
0225: * @param scope the object
0226: * @param args the arguments to the function, or <code>null</code> if none
0227: * @return the value returned by the function
0228: * @throws PackagedScriptObjectException
0229: * @see Function
0230: */
0231: public Value callAsExtends(StackFrame sf, Scope scope,
0232: MemberTable args) throws PackagedScriptObjectException {
0233: if (impl == null)
0234: init();
0235:
0236: if (wrapperImpl == null)
0237: throw PackagedScriptObjectException
0238: .makeExceptionWrapper(new OUnsupportedOperationException(
0239: "java class is final; can't call as constructor"));
0240:
0241: /* XXX this is kinda a hack. We need what we link to to be the ScriptObject,
0242: * and not the ForkScope
0243: */
0244: Scope scriptObject = scope;
0245: while (scriptObject instanceof ForkScope)
0246: scriptObject = scriptObject.getPreviousScope();
0247:
0248: ClassWrapGen.linkObjects(doConstruct(sf, args, true),
0249: scriptObject);
0250:
0251: return scope;
0252: }
0253:
0254: // we have to leave this here, because it gets overloaded...
0255: protected Object doConstruct(StackFrame sf, MemberTable args,
0256: boolean isWrapper) {
0257: if (impl == null)
0258: init();
0259:
0260: if (isWrapper)
0261: return wrapperImpl.doConstruct(this , sf, args);
0262: else
0263: return impl.doConstruct(this , sf, args);
0264: }
0265:
0266: /*=======================================================================*/
0267: /**
0268: * Get a member of this object.
0269: *
0270: * @param id the id of the symbol that maps to the member
0271: * @param exception whether an exception should be thrown if the
0272: * member object is not resolved
0273: * @return a reference to the member
0274: * @throws PackagedScriptObjectException(NoSuchMethodException)
0275: * @throws PackagedScriptObjectException(NoSuchMemberException)
0276: */
0277: public Value getMember(int id, boolean exception)
0278: throws PackagedScriptObjectException {
0279: if (impl == null)
0280: init();
0281:
0282: Value val = impl.getMemberImpl(id);
0283:
0284: // this should handle looking for inner-classes...
0285: if (val == null) {
0286: try {
0287: val = JavaClassWrapper
0288: .getClassWrapper(forName(getInnerClassName(
0289: javaClass, id)));
0290: } catch (ClassNotFoundException e) {
0291: }
0292: }
0293:
0294: if (val != null)
0295: return val;
0296: else
0297: return super .getMember(id, exception);
0298: }
0299:
0300: /*=======================================================================*/
0301: /**
0302: * Get a member of this type. This is used to interface to the java
0303: * method of having members be attributes of a type. Regular object-
0304: * script object's members are attributes of the object, but in the
0305: * case of java types (including built-in types), the members are
0306: * attributes of the type.
0307: *
0308: * @param obj an object of this type
0309: * @param id the id of the symbol that maps to the member
0310: * @return a reference to the member, or null
0311: */
0312: protected Value getTypeMember(Value obj, int id) {
0313: if (impl == null)
0314: init();
0315:
0316: obj = obj.unhand();
0317:
0318: Object javaObj;
0319:
0320: /* XXX hack: (needed because ExactNumber, InexactNumber, etc. cast to
0321: * Long, Double, etc)
0322: */
0323: if ((this instanceof BuiltinType)
0324: && !(obj instanceof ScriptObject))
0325: javaObj = obj;
0326: else
0327: javaObj = obj.castToJavaObject();
0328:
0329: Value val = getTypeMemberImpl(javaObj, id);
0330:
0331: /* XXX hack: (kinda) if we don't find the method, perhaps it is provided
0332: * by Value... this is sorta like multiple inheritance, but we need to
0333: * ensure that even java objects implement certain basic methods... if
0334: * an object is a ScriptObject, this is handled by the ScriptObject
0335: * class... except getMember() won't return the method... so it will work
0336: * for stuff like (foo instanceof Foo), but not foo.bopInstanceOf(Foo)
0337: */
0338: if (val == null) {
0339: if (obj instanceof ScriptObject)
0340: val = ScriptObject.TYPE.getTypeMemberImpl(obj, Symbol
0341: .getSymbol(
0342: "_"
0343: + Symbol.getSymbol(id)
0344: .castToString())
0345: .getId());
0346: else
0347: val = Value.TYPE.getTypeMemberImpl(obj, id);
0348: }
0349:
0350: /* If we still haven't found it, check if it is a static
0351: * member of the class:
0352: */
0353: if (val == null)
0354: val = impl.getMemberImpl(id);
0355:
0356: return val;
0357: }
0358:
0359: protected Value getTypeMemberImpl(Object javaObj, int id) {
0360: if (impl == null)
0361: init();
0362:
0363: CacheEntry ce = null;
0364: if (ClassWrapGen.isWrapperInstance(javaObj))
0365: ce = wrapperImpl.getTypeMemberCacheEntry(id);
0366:
0367: // XXX will this work to resolve fields??
0368: if (ce == null)
0369: ce = impl.getTypeMemberCacheEntry(id);
0370:
0371: if (ce == null)
0372: return null;
0373:
0374: return ce.getMember(id, javaObj);
0375: }
0376:
0377: /*=======================================================================*/
0378: /**
0379: * Get the {@link ClassLoader} object for the java class this class is
0380: * a wrapper for.
0381: *
0382: * @return the {@link ClassLoader} of the java class
0383: */
0384: public ClassLoader getClassLoader() {
0385: return javaClass.getClassLoader();
0386: }
0387:
0388: /*=======================================================================*/
0389: /**
0390: * Implements the reflection stuff... this is broken out into it's own
0391: * class because a <code>JavaClassWrapper</code> might have two of these
0392: * (which get initialized at different times), one for the regular class
0393: * and one for the wrapper class. The wrapper class is only used when
0394: * script code extends a java type. Also, since we need to create a
0395: * new instance of <code>JavaInnerClassWrapper</code> for each access,
0396: * we cache the impl's for inner classes, to avoid having to go thru
0397: * the expensive init() process multiple times for the same java class.
0398: */
0399: protected static class JavaClassWrapperImpl {
0400: Class javaClass;
0401:
0402: private Constructor[] constructors;
0403:
0404: /**
0405: * Cache of static fields, maps name (OString) -> Field or Method[]
0406: */
0407: private SymbolMap classMemberCache;
0408:
0409: /**
0410: * Cache of instance field, maps name (OString) -> Field or Method[]
0411: */
0412: private SymbolMap instanceMemberCache;
0413:
0414: private boolean isWrapper;
0415: private boolean initialized = false;
0416:
0417: /**
0418: * Class Constructor
0419: */
0420: JavaClassWrapperImpl(Class javaClass, boolean isWrapper) {
0421: this .javaClass = javaClass;
0422: this .isWrapper = isWrapper;
0423: }
0424:
0425: synchronized void init() {
0426: /* Yes, we check this twice... the reasoning is that by checking it
0427: * before we call init(), we can avoid a method call (to a synchronized
0428: * method, no less). There is a slight race condition there, so we
0429: * have to check it a second time within the protection of the held
0430: * monitor.
0431: */
0432: if (!initialized) {
0433: if (isWrapper)
0434: javaClass = ClassWrapGen
0435: .makeWrapperClass(javaClass);
0436:
0437: constructors = javaClass.getDeclaredConstructors();
0438:
0439: /* the handling of unaccessible classes could be a little more optimized,
0440: * but this will do for now...
0441: */
0442: boolean isNotPublic = !Modifier.isPublic(javaClass
0443: .getModifiers());
0444:
0445: instanceMemberCache = new SymbolMap();
0446: classMemberCache = new SymbolMap();
0447:
0448: if (isWrapper) {
0449: Method[] methods = javaClass.getMethods();
0450: SymbolMap methodMap = new SymbolMap();
0451:
0452: // build up the method table
0453: for (int i = 0; i < methods.length; i++) {
0454: if (!Modifier.isStatic(methods[i]
0455: .getModifiers())) {
0456: addMethodToCache(methodMap, Symbol
0457: .getSymbol(methods[i].getName())
0458: .getId(), methods[i]);
0459: }
0460: }
0461:
0462: for (Iterator itr = methodMap.keys(); itr.hasNext();) {
0463: int id = ((Integer) (itr.next())).intValue();
0464:
0465: Object obj = methodMap.get(Symbol.getSymbol(
0466: ClassWrapGen.getOrigMethodName(Symbol
0467: .getSymbol(id).castToString()))
0468: .getId());
0469:
0470: // if there is an entry with the corresponding __orig_ name, use
0471: // that... if we use the regular version of the method instead,
0472: // it will lookup and resolve to itself, and we'll endup in an
0473: // infinite loop!
0474: if (obj != null)
0475: instanceMemberCache.put(id, obj);
0476: }
0477: } else {
0478: Method[] methods = javaClass.getMethods();
0479: Field[] fields = javaClass.getFields(); //getDeclaredFields();
0480:
0481: for (int i = 0; i < fields.length; i++) {
0482: int id = Symbol.getSymbol(fields[i].getName())
0483: .getId();
0484:
0485: if (Modifier.isStatic(fields[i].getModifiers()))
0486: addFieldToCache(classMemberCache, id,
0487: fields[i]);
0488: else
0489: addFieldToCache(instanceMemberCache, id,
0490: fields[i]);
0491: }
0492:
0493: for (int i = 0; i < methods.length; i++) {
0494: Method method = methods[i];
0495: String methodName = method.getName();
0496:
0497: int id = Symbol.getSymbol(methodName).getId();
0498:
0499: if (isNotPublic)
0500: method = searchForAccessibleMethod(
0501: methodName, javaClass, method
0502: .getParameterTypes());
0503:
0504: if (method != null) {
0505: if (Modifier
0506: .isStatic(method.getModifiers()))
0507: addMethodToCache(classMemberCache, id,
0508: method);
0509: else
0510: addMethodToCache(instanceMemberCache,
0511: id, method);
0512: }
0513: }
0514: }
0515:
0516: initialized = true;
0517: }
0518: }
0519:
0520: /**
0521: * basically we decide on the constructor with the closest matching args,
0522: * and call it to create a new instance.
0523: */
0524: Object doConstruct(JavaClassWrapper jcw, StackFrame sf,
0525: MemberTable args) {
0526: if (!initialized)
0527: init();
0528:
0529: return JavaBridge.call(constructorAccessor, jcw.id, null,
0530: constructors, sf, args);
0531: }
0532:
0533: // returns <code>null</code> if not found. Does not throw exception
0534: // probably should be updated to return a CacheEntry for consistency
0535: Value getMemberImpl(int id) {
0536: if (!initialized)
0537: init();
0538:
0539: CacheEntry ce = (CacheEntry) (classMemberCache.get(id));
0540:
0541: if (ce == null)
0542: return null;
0543:
0544: return ce.getMember(id, null);
0545: }
0546:
0547: CacheEntry getTypeMemberCacheEntry(int id) {
0548: if (!initialized)
0549: init();
0550:
0551: Object ce = instanceMemberCache.get(id);
0552:
0553: if (ce == null) {
0554: ce = getInnerClass(id);
0555:
0556: if (ce == null)
0557: ce = getBeanAccessor(id);
0558:
0559: if (ce == null)
0560: ce = Boolean.FALSE;
0561:
0562: instanceMemberCache.put(id, ce);
0563: }
0564:
0565: if (ce == Boolean.FALSE)
0566: ce = null;
0567:
0568: return (CacheEntry) ce;
0569: }
0570:
0571: private CacheEntry getInnerClass(int id) {
0572: try {
0573: final Class innerClass = forName(getInnerClassName(
0574: javaClass, id));
0575:
0576: return new CacheEntry() {
0577:
0578: private JavaClassWrapperImpl[] impls;
0579:
0580: public Value getMember(int id, Object javaObj) {
0581: Value obj = JavaBridge
0582: .convertToScriptObject(javaObj);
0583:
0584: /* not synchronized on innerClassImplCache, to avoid deadlock when
0585: * calling forName() triggers loading a class that does something
0586: * that causes methods of this object to be called again
0587: */
0588: synchronized (javaClass) // XXX is this ok to sync on?
0589: {
0590: JavaInnerClassWrapper jicw = null;
0591:
0592: if (impls == null) {
0593: jicw = new JavaInnerClassWrapper(obj,
0594: innerClass);
0595: jicw.init();
0596:
0597: impls = new JavaClassWrapperImpl[2];
0598: impls[0] = jicw.impl;
0599: impls[1] = jicw.wrapperImpl;
0600: }
0601:
0602: if (jicw == null) {
0603: jicw = new JavaInnerClassWrapper(obj,
0604: impls[0].javaClass);
0605:
0606: jicw.impl = impls[0];
0607: jicw.wrapperImpl = impls[1];
0608: }
0609:
0610: return jicw;
0611: }
0612: }
0613:
0614: };
0615: } catch (ClassNotFoundException e) {
0616: return null;
0617: }
0618: }
0619:
0620: /**
0621: * Check for "getter" and "setter" methods corresponding to <code>id</code>,
0622: * and if present return a bean-accessor, to allow access to properties of
0623: * java beans.
0624: */
0625: private CacheEntry getBeanAccessor(int id) {
0626: String name = Symbol.getSymbol(id).castToString();
0627: char l = name.charAt(0);
0628: if (!Character.isLowerCase(l))
0629: return null;
0630:
0631: final String cname = name.substring(0, 1).toUpperCase()
0632: + ((name.length() > 1) ? name.substring(1) : "");
0633:
0634: Object obj = instanceMemberCache.get(Symbol.getSymbol(
0635: "get" + cname).getId());
0636: final CacheEntry getterCE = (obj == Boolean.FALSE) ? null
0637: : (CacheEntry) obj;
0638:
0639: if (getterCE == null)
0640: return null;
0641:
0642: return new CacheEntry() {
0643:
0644: public Value getMember(final int id,
0645: final Object javaObj) {
0646: /* work-around to deal with the case of this getting called while a
0647: * script class that extends a java class is being constructed (but
0648: * is not yet linked to the java object). Since no script types
0649: * have bean properties to access, this is the easy work-around:
0650: * <p>
0651: * XXX Note work-around for work-around... I need to clean this up!!
0652: */
0653: if ((javaObj instanceof Value)
0654: && !(javaObj instanceof RegExpResult))
0655: return null;
0656:
0657: return new AbstractReference() {
0658:
0659: private Value setter;
0660: private Value getter;
0661:
0662: public void opAssign(Value val)
0663: throws PackagedScriptObjectException {
0664: if (setter == null) {
0665: String setterName = "set" + cname;
0666: Object obj = instanceMemberCache
0667: .get(Symbol.getSymbol(
0668: setterName).getId());
0669: CacheEntry setterCE = (obj == Boolean.FALSE) ? null
0670: : (CacheEntry) obj;
0671:
0672: if (setterCE != null)
0673: setter = setterCE.getMember(id,
0674: javaObj);
0675:
0676: if (setter == null)
0677: throw noSuchMember(setterName);
0678: }
0679:
0680: setter.callAsFunction(new Value[] { val });
0681: }
0682:
0683: protected Value get() {
0684: if (getter == null)
0685: getter = getterCE
0686: .getMember(id, javaObj);
0687:
0688: return getter.callAsFunction(new Value[0]);
0689: }
0690:
0691: };
0692: }
0693: };
0694: }
0695:
0696: void populateTypeMemberSet(Set s) {
0697: for (Iterator itr = instanceMemberCache.keys(); itr
0698: .hasNext();) {
0699: int id = ((Integer) (itr.next())).intValue();
0700: if (instanceMemberCache.get(id) != Boolean.FALSE)
0701: s.add(Symbol.getSymbol(id));
0702: }
0703: }
0704:
0705: void populateMemberSet(Set s) {
0706: for (Iterator itr = classMemberCache.keys(); itr.hasNext();)
0707: s.add(Symbol.getSymbol(((Integer) (itr.next()))
0708: .intValue()));
0709: }
0710: }
0711:
0712: private static final JavaBridge.JavaCallableAccessor constructorAccessor = new JavaBridge.JavaCallableAccessor() {
0713:
0714: public Class[] getParameterTypes(Object javaCallable) {
0715: return ((Constructor) javaCallable).getParameterTypes();
0716: }
0717:
0718: public Object call(Object javaCallable, Object javaObject,
0719: Object[] args) throws InvocationTargetException,
0720: InstantiationException, IllegalAccessException {
0721: return ((Constructor) javaCallable).newInstance(args);
0722: }
0723: };
0724:
0725: private static String getInnerClassName(Class javaClass, int id) {
0726: return javaClass.getName() + "$"
0727: + Symbol.getSymbol(id).castToString();
0728: }
0729:
0730: // XXX not a good place for this, but...
0731: private static final String _arrayToString(Object[] objs) {
0732: String str = "[";
0733: for (int i = 0; i < objs.length; i++)
0734: str += objs[i] + ",";
0735: return str + "]";
0736: }
0737:
0738: /**
0739: */
0740: private interface CacheEntry {
0741: Value getMember(int id, Object obj);
0742: }
0743:
0744: /**
0745: * member-cache entry for fields
0746: */
0747: private static class FieldCacheEntry implements CacheEntry {
0748: private Field field;
0749:
0750: FieldCacheEntry() {
0751: this .field = field;
0752: }
0753:
0754: void add(Field field) {
0755: // unlike methods, we only keep track of one method.. but we need to pick
0756: // the one that isn't eclipsed by a field in a derived classe
0757: if ((this .field == null)
0758: || this .field.getDeclaringClass().isAssignableFrom(
0759: field.getDeclaringClass()))
0760: this .field = field;
0761: }
0762:
0763: public Value getMember(int id, final Object obj) {
0764: return new AbstractReference() {
0765:
0766: public void opAssign(Value val)
0767: throws PackagedScriptObjectException {
0768: try {
0769: field.set(obj, val.castToJavaObject());
0770: } catch (IllegalAccessException e) {
0771: throw OJavaException
0772: .makeJavaExceptionWrapper(e);
0773: }
0774: }
0775:
0776: protected Value get() {
0777: try {
0778: return JavaBridge.convertToScriptObject(field
0779: .get(obj));
0780: } catch (IllegalAccessException e) {
0781: throw OJavaException
0782: .makeJavaExceptionWrapper(e);
0783: }
0784: }
0785:
0786: };
0787: }
0788: }
0789:
0790: /**
0791: * member-cache entry for methods
0792: */
0793: private static class MethodCacheEntry implements CacheEntry {
0794: private Vector v;
0795: private Method[] methods;
0796:
0797: public void add(Method method) {
0798: if (v == null) {
0799: v = new Vector();
0800: }
0801:
0802: if (methods != null) {
0803: for (int i = 0; i < methods.length; i++)
0804: v.add(methods[i]);
0805: methods = null;
0806: }
0807:
0808: /* I'm not sure if we are supposed to be seeing duplicate methods, or not,
0809: * but I am (at least under JDK v1.3.1 macosx... I haven't tested others),
0810: * so try to deal with this in as sane a mannar as possibly by ignoring
0811: * overriden methods:
0812: */
0813: Class[] parameterTypes = method.getParameterTypes();
0814: for (int i = 0; i < v.size(); i++) {
0815: if (parameterTypesMatch(((Method) (v.elementAt(i)))
0816: .getParameterTypes(), parameterTypes)) {
0817: if (((Method) (v.elementAt(i))).getDeclaringClass()
0818: .isAssignableFrom(
0819: method.getDeclaringClass()))
0820: v.set(i, method);
0821: return;
0822: }
0823: }
0824:
0825: v.add(method);
0826: }
0827:
0828: public synchronized Value getMember(int id, Object obj) {
0829: if (methods == null) {
0830: methods = new Method[v.size()];
0831: v.copyInto(methods);
0832: v = null;
0833: }
0834: return new JavaMethodWrapper(id, obj, methods);
0835: }
0836: }
0837:
0838: /**
0839: * get a member from the specified cache
0840: */
0841: /*
0842: private static final Value getMemberFromCache( SymbolMap memberCache, int id, Object obj )
0843: {
0844: CacheEntry ce = (CacheEntry)(memberCache.get(id));
0845:
0846: if( ce != null )
0847: return ce.getMember( id, obj );
0848:
0849: return null;
0850: }
0851: */
0852:
0853: /**
0854: * add a field member to the specified cache
0855: */
0856: private static final void addFieldToCache(SymbolMap memberCache,
0857: int id, Field field) {
0858: FieldCacheEntry fce = (FieldCacheEntry) (memberCache.get(id));
0859: if (fce == null)
0860: memberCache.put(id, fce = new FieldCacheEntry());
0861: fce.add(field);
0862: }
0863:
0864: /**
0865: * add a method member to the specified cache, or if a member already exists
0866: * for a method with the same name, append this method to the existing member
0867: */
0868: private static final void addMethodToCache(SymbolMap memberCache,
0869: int id, Method method) {
0870: Object obj = memberCache.get(id);
0871:
0872: if ((obj == null) || !(obj instanceof MethodCacheEntry))
0873: memberCache.put(id, obj = new MethodCacheEntry());
0874:
0875: MethodCacheEntry mce = (MethodCacheEntry) obj;
0876:
0877: mce.add(method);
0878: }
0879:
0880: private static final boolean parameterTypesMatch(Class[] p1,
0881: Class[] p2) {
0882: if (p1.length == p2.length) {
0883: for (int i = 0; i < p1.length; i++)
0884: if (!p1[i].equals(p2[i]))
0885: return false;
0886:
0887: return true;
0888: }
0889:
0890: return false;
0891: }
0892:
0893: /*=======================================================================*/
0894: /**
0895: * Utility to search for accessible methods with the specified name and
0896: * parameters.
0897: */
0898: static final Method searchForAccessibleMethod(String name,
0899: Class javaClass, Class[] paramTypes) {
0900: if (Modifier.isPublic(javaClass.getModifiers())) {
0901: try {
0902: Method method = javaClass.getDeclaredMethod(name,
0903: paramTypes);
0904: return method;
0905: } catch (NoSuchMethodException e) {
0906: // ignore
0907: }
0908: }
0909:
0910: // we got here, so what we are looking for isn't in this class... so
0911: // recursively check parent classes and interfaces:
0912:
0913: // check interfaces:
0914: Class[] interfaceClasses = javaClass.getInterfaces();
0915: for (int i = 0; i < interfaceClasses.length; i++) {
0916: Method method = searchForAccessibleMethod(name,
0917: interfaceClasses[i], paramTypes);
0918:
0919: if (method != null)
0920: return method;
0921: }
0922:
0923: // if not an interface, there are superclasses to check:
0924: Class super Class = javaClass.getSuperclass();
0925: if (super Class != null)
0926: return searchForAccessibleMethod(name, super Class,
0927: paramTypes);
0928:
0929: // if we get here, no match was found:
0930: return null;
0931: }
0932:
0933: /*=======================================================================*/
0934: /**
0935: * maintains unique-ness of a JavaClassWrapper when stuff gets serialized or
0936: * un-serialized
0937: */
0938: Object readResolve() throws java.io.ObjectStreamException {
0939: Object obj;
0940:
0941: synchronized (JavaClassWrapper.class) {
0942: obj = classWrapperCache.get(javaClass);
0943:
0944: if (obj == null) {
0945: obj = this ;
0946: classWrapperCache.put(javaClass, obj);
0947: }
0948: }
0949:
0950: return obj;
0951: }
0952:
0953: /*=======================================================================*/
0954: /**
0955: * Derived classes that implement {@link #getMember} should also
0956: * implement this.
0957: *
0958: * @param s the set to populate
0959: * @param debugger <code>true</code> if being used by debugger, in
0960: * which case both public and private/protected field names should
0961: * be returned
0962: * @see #getMember
0963: */
0964: protected void populateMemberSet(Set s, boolean debugger) {
0965: if (impl == null)
0966: init();
0967: impl.populateMemberSet(s);
0968: }
0969:
0970: /*=======================================================================*/
0971: /**
0972: * Derived classes that implement {@link #getTypeMember} should also
0973: * implement this.
0974: *
0975: * @param s the set to populate
0976: * @param debugger <code>true</code> if being used by debugger, in
0977: * which case both public and private/protected field names should
0978: * be returned
0979: * @see #getTypeMember
0980: */
0981: protected void populateTypeMemberSet(Set s, boolean debugger) {
0982: if (impl == null)
0983: init();
0984: impl.populateTypeMemberSet(s);
0985: }
0986:
0987: /*=======================================================================*/
0988: /**
0989: * used by Debugger#getMemberAccessor, allows access to non-public members
0990: */
0991: Debugger.MemberAccessor _getTypeMemberAccessor(Value obj, Value name) {
0992: if (impl == null)
0993: init();
0994:
0995: Value val = getTypeMember(obj, name);
0996:
0997: if (val != null)
0998: return new JavaClassWrapperMemberAccessor(val);
0999: else
1000: return null;
1001: }
1002:
1003: private static class JavaClassWrapperMemberAccessor implements
1004: Debugger.MemberAccessor {
1005: private Value val;
1006:
1007: /**
1008: * It is safe to use val instead of name, because we never replace an
1009: * entry in the member table.
1010: */
1011: JavaClassWrapperMemberAccessor(Value val) {
1012: this .val = val;
1013: }
1014:
1015: public int getAttr() {
1016: return Reference.ATTR_PUBLIC;
1017: }
1018:
1019: public Value getValue() {
1020: return val;
1021: }
1022: }
1023:
1024: /*=======================================================================*/
1025: /**
1026: * For use by test suite...
1027: */
1028: public static class Base {
1029: public static final int ID = 1;
1030:
1031: public int[] getFoo() {
1032: System.err.println("getFoo");
1033: return new int[] { 1, 2, 3 };
1034: }
1035: }
1036:
1037: public static class Derived extends Base {
1038: public static final int ID = 2;
1039: }
1040: }
1041:
1042: /*
1043: * Local Variables:
1044: * tab-width: 2
1045: * indent-tabs-mode: nil
1046: * mode: java
1047: * c-indentation-style: java
1048: * c-basic-offset: 2
1049: * eval: (c-set-offset 'substatement-open '0)
1050: * eval: (c-set-offset 'case-label '+)
1051: * eval: (c-set-offset 'inclass '+)
1052: * eval: (c-set-offset 'inline-open '0)
1053: * End:
1054: */
|