0001: /*=============================================================================
0002: * Copyright Texas Instruments 2000. 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.classwrap;
0022:
0023: import oscript.data.*;
0024: import oscript.exceptions.*;
0025: import oscript.compiler.*;
0026:
0027: // The Bytecode Engineerign Library
0028: import org.apache.bcel.generic.*;
0029: import org.apache.bcel.Constants;
0030:
0031: import java.lang.reflect.*;
0032: import java.util.*;
0033: import java.util.Map.Entry;
0034: import java.io.*;
0035: import java.security.*;
0036:
0037: /**
0038: * The <code>classwrap</code> package is used to generate a "wrapper" for
0039: * a java class. A wrapper is just a subclass of a given java class, where
0040: * for each public non-final, non-static method, a wrapper method and an
0041: * "orig" method are generated. The wrapper method looks up a property
0042: * with the same name in the script object, and if it exists, and is a
0043: * function that takes a compatible number of arguments, calls it,
0044: * otherwise it calls the same method in the parent (original) java class.
0045: * The orig method simply calls the same method in the parent class.
0046: * <p>
0047: * The "wrapper" class is used any place where a script objects extends
0048: * a java class (ie. java class, java interface, or builtin-type). The
0049: * purpose is to allow the script object to override methods in a java
0050: * class, or implement methods in a java interface.
0051: * <p>
0052: * The "wrapper" class is generated using the Byte Code Engineering Library
0053: * (BCEL.jar).
0054: *
0055: * @author Rob Clark (rob@ti.com)
0056: * <!--$Format: " * @version $Revision$"$-->
0057: * @version 1.34
0058: */
0059: public class ClassWrapGen {
0060: private static java.util.Hashtable wrapperClassTable = new java.util.Hashtable();
0061:
0062: /**
0063: * The class that is being built.
0064: */
0065: private ClassGen cg;
0066:
0067: /**
0068: * The constant-pool of the class that is being built.
0069: */
0070: private ConstantPoolGen cp;
0071:
0072: /**
0073: */
0074: private Class origClass;
0075: private String origClassName;
0076: private String className;
0077:
0078: /**
0079: * Table mapping symbol name to static member idx
0080: */
0081: private Hashtable symbolTable = new Hashtable();
0082:
0083: /**
0084: * Note, conflict between org.apache.bcel.generic.Type and oscript.data.Type.
0085: */
0086: private static final org.apache.bcel.generic.Type SCOPE_TYPE = new ObjectType(
0087: "oscript.data.Scope");
0088: private static final ObjectType EXCEPTION_TYPE = new ObjectType(
0089: "oscript.exceptions.PackagedScriptObjectException");
0090:
0091: /**
0092: * Used to load a class from a <code>JavaClass</code>.
0093: */
0094: private static final CompilerClassLoader loader = CompilerClassLoader
0095: .getCompilerClassLoader();
0096:
0097: /*=======================================================================*/
0098: /**
0099: * Check if we can make a wrapper class for the specified class... this
0100: * is needed by JavaClassWrapper, which needs to know if it can construct
0101: * a wrapper class... but it wants to defer the act of actually making
0102: * the wrapper class.
0103: *
0104: * @param origClass the original class
0105: * @return <code>true</code> if we can make a wrapper (ie. class isn't
0106: * final, etc)
0107: */
0108: public static boolean canMakeWrapperClass(Class origClass) {
0109: int m = origClass.getModifiers();
0110: if (origClass.isPrimitive() || origClass.isArray())
0111: throw new ProgrammingErrorException(origClass
0112: + " is primitive or array");
0113: else if (origClass.getName().endsWith("_wrapper"))
0114: throw new ProgrammingErrorException(origClass
0115: + " is already a wrapper class");
0116: else if (Modifier.isFinal(m) || !Modifier.isPublic(m))
0117: return false;
0118: else
0119: return true;
0120: }
0121:
0122: /*=======================================================================*/
0123: /**
0124: * Make the wrapper class. Wrap all public non-final instance methods.
0125: *
0126: * @param origClass the original class
0127: * @return the auto-generated wrapper class, or if a wrapper class cannot
0128: * be generated, the <code>origClass</code>. This should never return
0129: * <code>null</code>.
0130: */
0131: public static synchronized Class makeWrapperClass(Class origClass) {
0132: Class tmp;
0133:
0134: if (!canMakeWrapperClass(origClass)) {
0135: return origClass;
0136: } else if ((tmp = (Class) (wrapperClassTable.get(origClass))) != null) {
0137: /* this is ok, because of how JavaInnerClassWrapper works...
0138: * there will be a new JavaInnerClassWrapper instance for
0139: * each reference to an inner class (because it needs to
0140: * store "this" (the outer class instance))... since it
0141: * subclasses JavaClassWrapper, the net result is multiple
0142: * requests for the same wrapper class!
0143: */
0144: return tmp;
0145: } else {
0146: String className = "wrap"
0147: + oscript.OscriptInterpreter.CACHE_VERSION + "."
0148: + origClass.getName() + "_wrapper";
0149:
0150: try {
0151: tmp = CompilerClassLoader.forName(className, false,
0152: null);
0153: } catch (ClassNotFoundException e) {
0154: tmp = null;
0155: }
0156:
0157: // if we can result the class, check to see if the serialVersionUID still
0158: // matches the wrapper classes' wrappedSerialVersionUID
0159: if (tmp != null) {
0160: try {
0161: if (computeInterfaceVersionUID(origClass) != ((Long) (tmp
0162: .getDeclaredField("__interfaceVersionUID")
0163: .get(null))).longValue())
0164: tmp = null;
0165: } catch (Throwable t) {
0166: tmp = null;
0167: }
0168: }
0169:
0170: if (tmp == null) {
0171: tmp = (new ClassWrapGen(origClass, className))
0172: .makeWrapperClassImpl();
0173: }
0174:
0175: if (tmp != null) {
0176: wrapperClassTable.put(origClass, tmp);
0177: }
0178:
0179: return tmp;
0180: }
0181: }
0182:
0183: /*=======================================================================*/
0184: /**
0185: * Compute the interface version uid... this works in a manner similar
0186: * to the <code>serialVersionUID</code> that java's serialization uses,
0187: * but since the code to compute the <code>serialVersionUID</code> isn't
0188: * available to us, we have to recalculate it ourselves. Since we have
0189: * to calculate it anyways, we simplify a little by only taking into
0190: * account things that would change the wrapper class itself (ie. public
0191: * constructors and public methods), and thus reducing the number of
0192: * false negatives w.r.t. changes to the original java class.
0193: * <p>
0194: * NOTE: synchronized under the umbrella of {@link #makeWrapperClass}
0195: */
0196: private static final long computeInterfaceVersionUID(Class c) {
0197: try {
0198: MessageDigest md = MessageDigest.getInstance("SHA");
0199: DataOutputStream dos = new DataOutputStream(
0200: new DigestOutputStream(new ByteArrayOutputStream(
0201: 512), md));
0202:
0203: //////////// Add public constructors:
0204: Constructor[] constructors = c.getConstructors();
0205:
0206: // sort the constructors, so a change in order doesn't change
0207: // the interface-UID:
0208: Arrays.sort(constructors, new Comparator() {
0209:
0210: public int compare(Object o1, Object o2) {
0211: int rc = ((Constructor) o1).getName().compareTo(
0212: ((Constructor) o2).getName());
0213:
0214: if (rc == 0)
0215: rc = getSignature((Constructor) o1).compareTo(
0216: getSignature((Constructor) o2));
0217:
0218: return rc;
0219: }
0220:
0221: });
0222:
0223: for (int i = 0; i < constructors.length; i++) {
0224: dos.writeUTF("<init>");
0225: dos.writeUTF(getSignature(constructors[i]));
0226: }
0227:
0228: //////////// Add public methods:
0229: Method[] methods = c.getMethods();
0230:
0231: // sort the methods, so a change in order doesn't change
0232: // the interface-UID:
0233: Arrays.sort(methods, new Comparator() {
0234:
0235: public int compare(Object o1, Object o2) {
0236: int rc = ((Method) o1).getName().compareTo(
0237: ((Method) o2).getName());
0238:
0239: if (rc == 0)
0240: rc = getSignature((Method) o1).compareTo(
0241: getSignature((Method) o2));
0242:
0243: return rc;
0244: }
0245:
0246: });
0247:
0248: for (int i = 0; i < methods.length; i++) {
0249: int mod = methods[i].getModifiers();
0250: if (!(Modifier.isStatic(mod) || Modifier.isFinal(mod))) {
0251: dos.writeUTF(methods[i].getName());
0252: dos.writeUTF(getSignature(methods[i]));
0253: }
0254: }
0255:
0256: dos.flush();
0257: byte[] hash = md.digest();
0258:
0259: // the UID is the first 64 bits of the hash:
0260: long uid = 0;
0261: for (int i = 0; i < Math.min(8, hash.length); i++)
0262: uid += (long) (hash[i] & 0xff) << (i * 8);
0263:
0264: return uid;
0265: } catch (IOException e) {
0266: throw new ProgrammingErrorException("shouldn't happen: "
0267: + e.getMessage());
0268: } catch (NoSuchAlgorithmException e) {
0269: throw new ProgrammingErrorException("shouldn't happen: "
0270: + e.getMessage());
0271: }
0272: }
0273:
0274: private static final Hashtable constructorSignatureCache = new Hashtable();
0275:
0276: private static final String getSignature(Constructor constructor) {
0277: String str = (String) (constructorSignatureCache
0278: .get(constructor));
0279: if (str == null) {
0280: StringBuffer sb = new StringBuffer();
0281:
0282: sb.append('(');
0283:
0284: Class[] params = constructor.getParameterTypes();
0285:
0286: for (int i = 0; i < params.length; i++)
0287: getSignature(params[i], sb);
0288:
0289: sb.append(")V");
0290:
0291: str = sb.toString();
0292:
0293: constructorSignatureCache.put(constructor, str);
0294: }
0295: return str;
0296: }
0297:
0298: private static final Hashtable methodSignatureCache = new Hashtable();
0299:
0300: private static final String getSignature(Method method) {
0301: String str = (String) (methodSignatureCache.get(method));
0302: if (str == null) {
0303: StringBuffer sb = new StringBuffer();
0304:
0305: sb.append('(');
0306:
0307: Class[] params = method.getParameterTypes();
0308:
0309: for (int i = 0; i < params.length; i++)
0310: getSignature(params[i], sb);
0311:
0312: sb.append(")V");
0313:
0314: str = sb.toString();
0315:
0316: methodSignatureCache.put(method, str);
0317: }
0318: return str;
0319: }
0320:
0321: // no point in caching class signatures:
0322: private static final void getSignature(Class c, StringBuffer sb) {
0323: if (c.isArray()) {
0324: sb.append('[');
0325: getSignature(c.getComponentType(), sb);
0326: } else if (c.isPrimitive()) {
0327: if (c == Integer.TYPE)
0328: sb.append('I');
0329: else if (c == Byte.TYPE)
0330: sb.append('B');
0331: else if (c == Long.TYPE)
0332: sb.append('J');
0333: else if (c == Float.TYPE)
0334: sb.append('F');
0335: else if (c == Double.TYPE)
0336: sb.append('D');
0337: else if (c == Short.TYPE)
0338: sb.append('S');
0339: else if (c == Character.TYPE)
0340: sb.append('C');
0341: else if (c == Boolean.TYPE)
0342: sb.append('Z');
0343: else if (c == Void.TYPE)
0344: sb.append('V');
0345: else
0346: throw new ProgrammingErrorException(
0347: "hmm, I don't know what " + c + " is");
0348: } else {
0349: sb.append('L');
0350: sb.append(c.getName().replace('.', '/'));
0351: sb.append(';');
0352: }
0353: }
0354:
0355: /*=======================================================================*/
0356: /**
0357: * Because the wrapper method looks to the script object first, before
0358: * calling the wrapped method, there are times when we want to call the
0359: * original method directly. To do this, you call the "orig" method.
0360: * To hide the naming convention, other code should use this method
0361: * to get the name of the "orig" method for the named method.
0362: *
0363: * @param javaObj the java object
0364: * @param methodName the name of the method in the parent class
0365: * @return the mangled name of the method that calls the requested
0366: * method in the parent class, ie. the "orig" method.
0367: */
0368: public static String getOrigMethodName(Object javaObj,
0369: String methodName) {
0370: // the java class may have been final, in which case javaObj is just
0371: // a plain 'ol object:
0372: if ((javaObj == null/*XXX*/)
0373: || (javaObj instanceof WrappedClass))
0374: return "__orig_" + methodName;
0375: else
0376: return methodName;
0377: }
0378:
0379: public static String getOrigMethodName(String methodName) {
0380: return getOrigMethodName(null, methodName);
0381: }
0382:
0383: /*=======================================================================*/
0384: /**
0385: * Is the specified object an instance of a wrapper class?
0386: *
0387: * @param javaObj the java object to test
0388: * @return <code>true</code> if instance of auto-generated wrapper class
0389: */
0390: public static boolean isWrapperInstance(Object javaObj) {
0391: return (javaObj instanceof WrappedClass)
0392: || (javaObj instanceof WrappedInterface);
0393: }
0394:
0395: /*=======================================================================*/
0396: /**
0397: * Link the specified java object and script object. The java object
0398: * should be an instance of a wrapper class generated by the
0399: * <code>makeWrapperClass</code> method.
0400: *
0401: * @param javaObj the java object
0402: * @param scriptObj the script object
0403: */
0404: public static void linkObjects(Object javaObj, Scope scriptObj) {
0405: ((Scope) scriptObj).__setJavaObject(javaObj);
0406:
0407: // the java class may have been final, in which case javaObj is just
0408: // a plain 'ol object:
0409: if (javaObj instanceof WrappedClass)
0410: ((WrappedClass) javaObj).__setScriptObject(scriptObj);
0411: else if (javaObj instanceof WrappedInterface)
0412: ((WrappedInterface) javaObj).__setScriptObject(scriptObj);
0413: else if (Value.DEBUG)
0414: System.err.println("could not link: javaObj=" + javaObj
0415: + " (" + javaObj.getClass() + "), scriptObj="
0416: + scriptObj + " (" + scriptObj.getType() + ")");
0417: }
0418:
0419: /*=======================================================================*/
0420: /**
0421: * Given a java object, which may be linked to a script object, return
0422: * the linked script object.
0423: *
0424: * @param javaObj the java object
0425: * @return the script object, or <code>null</code>
0426: */
0427: public static Value getScriptObject(Object javaObj) {
0428: // the java class may have been final, in which case javaObj is just
0429: // a plain 'ol object:
0430: if (javaObj instanceof WrappedClass)
0431: return ((WrappedClass) javaObj).__getScriptObject();
0432: else if (javaObj instanceof WrappedInterface)
0433: return ((WrappedInterface) javaObj).__getScriptObject();
0434:
0435: return null;
0436: }
0437:
0438: /*=======================================================================*/
0439: /**
0440: * Given a java class that may or may not be a wrapper class, return a
0441: * java class that is the closest super-class that is not a wrapper
0442: * class.
0443: *
0444: * @param javaClass a java class that might be a wrapper class
0445: * @return a java class that is not a wrapper class
0446: */
0447: public static final Class getNonWrapperClass(Class javaClass) {
0448: Class tmp;
0449:
0450: if (((tmp = javaClass.getSuperclass()) != null)
0451: && ((tmp = (Class) (wrapperClassTable.get(tmp))) != null)
0452: && (tmp == javaClass)) {
0453: return javaClass.getSuperclass();
0454: } else {
0455: return javaClass;
0456: }
0457: }
0458:
0459: /*=======================================================================*/
0460: /**
0461: */
0462: private ClassWrapGen(Class origClass, String className) {
0463: this .origClass = origClass;
0464:
0465: this .origClassName = origClass.getName();
0466: this .className = loader.makeClassName(className, true);
0467: }
0468:
0469: /*=======================================================================*/
0470: /**
0471: */
0472: private Class makeWrapperClassImpl() {
0473: /* NOTE: BCEL is not thread safe, so synchronize use of the library on
0474: * the ClassGen class... we do the same thing in the script
0475: * compiler
0476: */
0477: synchronized (ClassGen.class) {
0478: try {
0479: String super ClassName;
0480: String[] interfaceNames;
0481:
0482: if (origClass.isInterface()) {
0483: super ClassName = "java.lang.Object";
0484: interfaceNames = new String[] {
0485: origClass.getName(),
0486: "oscript.classwrap.WrappedInterface" };
0487: } else {
0488: super ClassName = origClassName;
0489: interfaceNames = new String[] { "oscript.classwrap.WrappedClass" };
0490: }
0491:
0492: cg = new ClassGen(className, super ClassName,
0493: "<generated>", Constants.ACC_PUBLIC
0494: | Constants.ACC_SUPER, interfaceNames);
0495:
0496: cp = cg.getConstantPool();
0497:
0498: Constructor[] constructors = origClass
0499: .getConstructors();
0500: if (constructors.length > 0)
0501: for (int i = 0; i < constructors.length; i++)
0502: addConstructor(constructors[i]);
0503: else
0504: addEmptyConstructor();
0505:
0506: Method[] methods = origClass.getMethods();
0507: for (int i = 0; i < methods.length; i++) {
0508: int mod = methods[i].getModifiers();
0509: if (!(Modifier.isStatic(mod) || Modifier
0510: .isFinal(mod)))
0511: addMethod(methods[i]);
0512: }
0513:
0514: addCommonJunk();
0515:
0516: if (Value.DEBUG && false) {
0517: try {
0518: org.apache.bcel.util.Class2HTML c2html = new org.apache.bcel.util.Class2HTML(
0519: cg.getJavaClass(), "../");
0520: } catch (java.io.IOException e) {
0521: e.printStackTrace();
0522: }
0523: }
0524:
0525: return loader.makeClass(className, cg.getJavaClass());
0526: } catch (LinkageError e) {
0527: // this means we hit a bug of the compiler:
0528: e.printStackTrace();
0529: return null;
0530: }
0531: }
0532: }
0533:
0534: private void addCommonJunk() {
0535: // add the __interfaceVersionUID:
0536: {
0537: FieldGen fg = new FieldGen(Constants.ACC_PUBLIC
0538: | Constants.ACC_FINAL | Constants.ACC_STATIC,
0539: org.apache.bcel.generic.Type.LONG,
0540: "__interfaceVersionUID", cp);
0541: cg.addField(fg.getField());
0542: }
0543:
0544: // add <clinit> to initialize static finals
0545: {
0546: CompilerInstructionList il = new CompilerInstructionList();
0547:
0548: il.append(new PUSH(cp,
0549: computeInterfaceVersionUID(origClass)));
0550: il.append(new PUTSTATIC(cp.addFieldref(className,
0551: "__interfaceVersionUID", "J")));
0552:
0553: for (Iterator itr = symbolTable.entrySet().iterator(); itr
0554: .hasNext();) {
0555: Entry e = (Entry) (itr.next());
0556: String name = (String) (e.getKey());
0557: Integer iidx = (Integer) (e.getValue());
0558: il.append(new PUSH(cp, name));
0559: il.append(new INVOKESTATIC(cp.addMethodref(
0560: "oscript.data.Symbol", "getSymbol",
0561: "(Ljava/lang/String;)Loscript/data/Symbol;")));
0562: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0563: "oscript.data.Symbol", "getId", "()I")));
0564: il.append(new PUTSTATIC(iidx.intValue()));
0565: }
0566:
0567: il.append(InstructionConstants.RETURN);
0568:
0569: MethodGen mg = new MethodGen(Constants.ACC_STATIC
0570: | Constants.ACC_PUBLIC | Constants.ACC_FINAL,
0571: org.apache.bcel.generic.Type.VOID,
0572: new org.apache.bcel.generic.Type[] {},
0573: new String[] {}, "<clinit>", className, il, cp);
0574:
0575: mg.setMaxStack();
0576: cg.addMethod(mg.getMethod());
0577: }
0578:
0579: // add the __scriptObject field:
0580: {
0581: FieldGen fg = new FieldGen(Constants.ACC_PRIVATE,
0582: SCOPE_TYPE, "__scriptObject", cp);
0583:
0584: cg.addField(fg.getField());
0585: }
0586:
0587: // add the __setScriptObject method:
0588: {
0589: CompilerInstructionList il = new CompilerInstructionList();
0590: il.append(InstructionConstants.ALOAD_0);
0591: il.append(InstructionConstants.ALOAD_1);
0592: // il.append( new CHECKCAST( cp.addClass("oscript.data.Value") ) ); // XXX do we need this?
0593: il.append(new PUTFIELD(cp.addFieldref(className,
0594: "__scriptObject", "Loscript/data/Scope;")));
0595: il.append(InstructionConstants.RETURN);
0596: MethodGen mg = new MethodGen(Constants.ACC_PUBLIC
0597: | Constants.ACC_FINAL,
0598: org.apache.bcel.generic.Type.VOID,
0599: new org.apache.bcel.generic.Type[] { SCOPE_TYPE },
0600: new String[] { "val" }, "__setScriptObject",
0601: className, il, cp);
0602:
0603: mg.setMaxStack();
0604: cg.addMethod(mg.getMethod());
0605: }
0606:
0607: // add the __getScriptObject method:
0608: {
0609: CompilerInstructionList il = new CompilerInstructionList();
0610: il.append(InstructionConstants.ALOAD_0);
0611: il.append(new GETFIELD(cp.addFieldref(className,
0612: "__scriptObject", "Loscript/data/Scope;")));
0613: il.append(InstructionConstants.ARETURN);
0614: MethodGen mg = new MethodGen(Constants.ACC_PUBLIC
0615: | Constants.ACC_FINAL, SCOPE_TYPE,
0616: new org.apache.bcel.generic.Type[] {},
0617: new String[] {}, "__getScriptObject", className,
0618: il, cp);
0619:
0620: mg.setMaxStack();
0621: cg.addMethod(mg.getMethod());
0622: }
0623: }
0624:
0625: private void addConstructor(Constructor constructor) {
0626: Class[] paramTypes = constructor.getParameterTypes();
0627:
0628: // now generate code:
0629: CompilerInstructionList il = new CompilerInstructionList();
0630: insertReturnCallSuper(il, "<init>", Void.TYPE, paramTypes);
0631:
0632: MethodGen mg = new MethodGen(getAccessFlags(constructor
0633: .getModifiers()), org.apache.bcel.generic.Type.VOID,
0634: getParamTypes(paramTypes), getParamNames(paramTypes),
0635: "<init>", className, il, cp);
0636:
0637: Class[] exceptionTypes = constructor.getExceptionTypes();
0638: for (int i = 0; i < exceptionTypes.length; i++) {
0639: mg.addException(exceptionTypes[i].getName());
0640: }
0641:
0642: mg.setMaxStack();
0643: cg.addMethod(mg.getMethod());
0644: }
0645:
0646: private void addEmptyConstructor() {
0647: Class[] paramTypes = new Class[0];
0648:
0649: // now generate code:
0650: CompilerInstructionList il = new CompilerInstructionList();
0651:
0652: insertReturnCallSuper(il, "<init>", Void.TYPE, paramTypes);
0653:
0654: MethodGen mg = new MethodGen(Constants.ACC_PUBLIC,
0655: org.apache.bcel.generic.Type.VOID,
0656: getParamTypes(paramTypes), getParamNames(paramTypes),
0657: "<init>", className, il, cp);
0658:
0659: mg.setMaxStack();
0660: cg.addMethod(mg.getMethod());
0661: }
0662:
0663: private Hashtable methodTable = new Hashtable(); // XXX quick hack to avoid generating duplicate methods!
0664:
0665: private void addMethod(Method method) {
0666: Class[] paramTypes = method.getParameterTypes();
0667: Class retType = method.getReturnType();
0668: boolean isAbstract = origClass.isInterface()
0669: || Modifier.isAbstract(method.getModifiers()); // XXX is an interface method always abstract?
0670:
0671: /* This is sort of a hack, to work around what (I think) is a bug that I've seen
0672: * under JDK v1.4.1 for windoze (but not JDK v1.3.x on other platforms). The
0673: * problem is this, given:
0674: *
0675: * interface I1
0676: * interface I2 extends I1
0677: * abstract class AC1 implements I1
0678: * abstract class AC2 extends A1 implements I2
0679: *
0680: * methods declared in I1 are showing up twice, resulting in us trying to generate
0681: * a class with duplcate methods, which results in a LinkageError when we try to
0682: * load the class.
0683: */
0684: {
0685: String methodSig = method.getName() + "#"
0686: + makeMethodSignature(retType, paramTypes);
0687: if (methodTable.get(methodSig) != null)
0688: return;
0689: methodTable.put(methodSig, method);
0690: }
0691:
0692: // generate wrapper method:
0693: {
0694: CompilerInstructionList il = new CompilerInstructionList();
0695:
0696: il.append(InstructionConstants.ALOAD_0);
0697: il.append(new GETFIELD(cp.addFieldref(className,
0698: "__scriptObject", "Loscript/data/Scope;")));
0699:
0700: il.append(InstructionConstants.DUP);
0701: IFNULL ifnull1 = new IFNULL(null);
0702: il.append(ifnull1);
0703:
0704: pushSymbol(il, method.getName());
0705: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0706: "oscript.data.Scope", "__getInstanceMember",
0707: "(I)Loscript/data/Value;")));
0708:
0709: il.append(InstructionConstants.DUP);
0710: IFNULL ifnull2 = new IFNULL(null);
0711: il.append(ifnull2);
0712:
0713: // if calling getMember() didn't throw a NoSuchMember exception, then
0714: // build up the argument array:
0715: il.append(new PUSH(cp, paramTypes.length));
0716: il.append(new ANEWARRAY(cp.addClass("oscript.data.Value")));
0717:
0718: for (int i = 0, idx = 1; i < paramTypes.length; i++) {
0719: il.append(InstructionConstants.DUP);
0720: il.append(new PUSH(cp, i));
0721:
0722: idx += insertLoad(il, paramTypes[i], idx);
0723:
0724: insertConvertToScriptValue(il, paramTypes[i]);
0725:
0726: il.append(InstructionConstants.AASTORE);
0727: }
0728:
0729: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0730: "oscript.data.Value", "callAsFunction",
0731: "([Loscript/data/Value;)Loscript/data/Value;")));
0732:
0733: // handle return-type conversion:
0734: if (retType.isPrimitive()) {
0735: if (retType == Void.TYPE) {
0736: il.append(InstructionConstants.RETURN);
0737: } else if (retType == Double.TYPE) {
0738: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0739: "oscript.data.Value",
0740: "castToInexactNumber", "()D")));
0741: il.append(InstructionConstants.DRETURN);
0742: } else if (retType == Float.TYPE) {
0743: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0744: "oscript.data.Value",
0745: "castToInexactNumber", "()D")));
0746: il.append(InstructionConstants.D2F);
0747: il.append(InstructionConstants.FRETURN);
0748: } else if (retType == Boolean.TYPE) {
0749: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0750: "oscript.data.Value", "castToBoolean",
0751: "()Z")));
0752: il.append(InstructionConstants.IRETURN);
0753: } else if (retType == Long.TYPE) {
0754: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0755: "oscript.data.Value", "castToExactNumber",
0756: "()J")));
0757: il.append(InstructionConstants.LRETURN);
0758: } else {
0759: il.append(new INVOKEVIRTUAL(cp.addMethodref(
0760: "oscript.data.Value", "castToExactNumber",
0761: "()J")));
0762: il.append(InstructionConstants.L2I);
0763: il.append(InstructionConstants.IRETURN);
0764: }
0765: } else if (retType == Value.class) // XXX or subclass ???
0766: {
0767: il.append(InstructionConstants.ARETURN);
0768: } else {
0769: il.append(new PUSH(cp, retType.getName()));
0770: il
0771: .append(new INVOKESTATIC(
0772: cp
0773: .addMethodref(
0774: "oscript.data.JavaBridge",
0775: "convertToJavaObject",
0776: "(Loscript/data/Value;Ljava/lang/String;)Ljava/lang/Object;")));
0777: il
0778: .append(new CHECKCAST(cp.addClass(retType
0779: .getName())));
0780: il.append(InstructionConstants.ARETURN);
0781: }
0782:
0783: InstructionHandle target = il
0784: .append(InstructionConstants.POP);
0785: ifnull2.setTarget(target);
0786: ifnull1.setTarget(target);
0787:
0788: if (!isAbstract) {
0789: insertReturnCallSuper(il, method.getName(), retType,
0790: paramTypes);
0791: } else {
0792: il
0793: .append(new NEW(
0794: cp
0795: .addClass("oscript.data.ONoSuchMemberException")));
0796: il.append(InstructionConstants.DUP);
0797: il.append(new PUSH(cp, "[class " + origClass.getName()
0798: + "]: " + method.getName()));
0799: il.append(new INVOKESPECIAL(cp.addMethodref(
0800: "oscript.data.ONoSuchMemberException",
0801: "<init>", "(Ljava/lang/String;)V")));
0802: il
0803: .append(new INVOKESTATIC(
0804: cp
0805: .addMethodref(
0806: "oscript.exceptions.PackagedScriptObjectException",
0807: "makeExceptionWrapper",
0808: "(Loscript/data/Value;)Loscript/exceptions/PackagedScriptObjectException;")));
0809: il.append(InstructionConstants.ATHROW);
0810: }
0811:
0812: MethodGen mg = new MethodGen(getAccessFlags(method
0813: .getModifiers()), getParamType(retType),
0814: getParamTypes(paramTypes),
0815: getParamNames(paramTypes), method.getName(),
0816: className, il, cp);
0817:
0818: Class[] exceptionTypes = method.getExceptionTypes();
0819: for (int i = 0; i < exceptionTypes.length; i++) {
0820: mg.addException(exceptionTypes[i].getName());
0821: }
0822:
0823: mg.setMaxStack();
0824: cg.addMethod(mg.getMethod());
0825: }
0826:
0827: // generate "orig" method:
0828: if (!isAbstract) {
0829: CompilerInstructionList il = new CompilerInstructionList();
0830: insertReturnCallSuper(il, method.getName(), retType,
0831: paramTypes);
0832:
0833: MethodGen mg = new MethodGen(getAccessFlags(method
0834: .getModifiers()), getParamType(retType),
0835: getParamTypes(paramTypes),
0836: getParamNames(paramTypes), getOrigMethodName(method
0837: .getName()), className, il, cp);
0838:
0839: Class[] exceptionTypes = method.getExceptionTypes();
0840: for (int i = 0; i < exceptionTypes.length; i++)
0841: mg.addException(exceptionTypes[i].getName());
0842:
0843: mg.setMaxStack();
0844: cg.addMethod(mg.getMethod());
0845: }
0846: }
0847:
0848: private int identifierCnt = 0;
0849:
0850: private void pushSymbol(CompilerInstructionList il, String name) {
0851: Integer iidx = (Integer) (symbolTable.get(name));
0852: if (iidx == null) {
0853: String fieldName = "_sym_" + (identifierCnt++) + name;
0854: FieldGen fg = new FieldGen(Constants.ACC_PRIVATE
0855: | Constants.ACC_STATIC,
0856: org.apache.bcel.generic.Type.INT, fieldName, cp);
0857: cg.addField(fg.getField());
0858: iidx = new Integer(cp.addFieldref(className, fieldName,
0859: org.apache.bcel.generic.Type.INT.getSignature()));
0860: symbolTable.put(name, iidx);
0861: }
0862: il.append(new GETSTATIC(iidx.intValue()));
0863: }
0864:
0865: private void insertReturnCallSuper(CompilerInstructionList il,
0866: String name, Class retType, Class[] paramTypes) {
0867: String className;
0868:
0869: if (origClass.isInterface())
0870: className = "java.lang.Object";
0871: else
0872: className = origClassName;
0873:
0874: il.append(InstructionConstants.ALOAD_0); // this
0875:
0876: for (int i = 0, idx = 1; i < paramTypes.length; i++)
0877: idx += insertLoad(il, paramTypes[i], idx);
0878:
0879: il.append(new INVOKESPECIAL(cp.addMethodref(className, name,
0880: makeMethodSignature(retType, paramTypes))));
0881:
0882: if (retType.isPrimitive()) {
0883: if (retType == Void.TYPE)
0884: il.append(InstructionConstants.RETURN);
0885: else if (retType == Double.TYPE)
0886: il.append(InstructionConstants.DRETURN);
0887: else if (retType == Float.TYPE)
0888: il.append(InstructionConstants.FRETURN);
0889: else if (retType == Long.TYPE)
0890: il.append(InstructionConstants.LRETURN);
0891: else
0892: il.append(InstructionConstants.IRETURN);
0893: } else {
0894: il.append(InstructionConstants.ARETURN);
0895: }
0896: }
0897:
0898: private int insertLoad(CompilerInstructionList il, Class paramType,
0899: int idx) {
0900: if (paramType.isPrimitive()) {
0901: if (paramType == Double.TYPE) {
0902: il.append(new DLOAD(idx));
0903: return 2;
0904: } else if (paramType == Float.TYPE) {
0905: il.append(new FLOAD(idx));
0906: return 1;
0907: } else if (paramType == Long.TYPE) {
0908: il.append(new LLOAD(idx));
0909: return 2;
0910: } else {
0911: il.append(new ILOAD(idx));
0912: return 1;
0913: }
0914: } else {
0915: il.append(new ALOAD(idx));
0916: return 1;
0917: }
0918: }
0919:
0920: private void insertConvertToScriptValue(CompilerInstructionList il,
0921: Class paramType) {
0922: String signature;
0923:
0924: // XXX note this has to be kept in sync with JavaBridge... uhhg!
0925: if (paramType.isPrimitive()) {
0926: if (paramType == Double.TYPE) {
0927: signature = "(D)Loscript/data/Value;";
0928: } else if (paramType == Float.TYPE) {
0929: il.append(InstructionConstants.F2D);
0930: signature = "(D)Loscript/data/Value;";
0931: } else if (paramType == Long.TYPE) {
0932: signature = "(J)Loscript/data/Value;";
0933: } else if (paramType == Integer.TYPE) {
0934: il.append(InstructionConstants.I2L);
0935: signature = "(J)Loscript/data/Value;";
0936: } else if (paramType == Short.TYPE) {
0937: il.append(InstructionConstants.I2L);
0938: signature = "(J)Loscript/data/Value;";
0939: } else if (paramType == Byte.TYPE) {
0940: il.append(InstructionConstants.I2L);
0941: signature = "(J)Loscript/data/Value;";
0942: } else if (paramType == Boolean.TYPE) {
0943: signature = "(Z)Loscript/data/Value;";
0944: } else if (paramType == Character.TYPE) {
0945: il.append(new INVOKESTATIC(cp.addMethodref(
0946: "java.lang.String", "valueOf",
0947: "(C)Ljava/lang/String;")));
0948: signature = "(Ljava/lang/String;)Loscript/data/Value;";
0949: } else {
0950: throw new ProgrammingErrorException(
0951: "unknown primitive type: " + paramType);
0952: }
0953: } else if (paramType == String.class) {
0954: signature = "(Ljava/lang/String;)Loscript/data/Value;";
0955: } else {
0956: signature = "(Ljava/lang/Object;)Loscript/data/Value;";
0957: }
0958:
0959: il.append(new INVOKESTATIC(cp.addMethodref(
0960: "oscript.data.JavaBridge", "convertToScriptObject",
0961: signature)));
0962: }
0963:
0964: private int getAccessFlags(int mod) {
0965: int acc = 0;
0966:
0967: if (Modifier.isPublic(mod)) {
0968: acc |= Constants.ACC_PUBLIC;
0969: }
0970:
0971: // XXX
0972:
0973: return acc;
0974: }
0975:
0976: private org.apache.bcel.generic.Type[] getParamTypes(
0977: Class[] paramTypes) {
0978: org.apache.bcel.generic.Type[] types = new org.apache.bcel.generic.Type[paramTypes.length];
0979:
0980: for (int i = 0; i < types.length; i++) {
0981: types[i] = getParamType(paramTypes[i]);
0982: }
0983:
0984: return types;
0985: }
0986:
0987: private org.apache.bcel.generic.Type getParamType(Class paramType) {
0988: if (paramType.isPrimitive()) {
0989: if (paramType == Boolean.TYPE)
0990: return org.apache.bcel.generic.Type.BOOLEAN;
0991: else if (paramType == Character.TYPE)
0992: return org.apache.bcel.generic.Type.CHAR;
0993: else if (paramType == Double.TYPE)
0994: return org.apache.bcel.generic.Type.DOUBLE;
0995: else if (paramType == Float.TYPE)
0996: return org.apache.bcel.generic.Type.FLOAT;
0997: else if (paramType == Integer.TYPE)
0998: return org.apache.bcel.generic.Type.INT;
0999: else if (paramType == Long.TYPE)
1000: return org.apache.bcel.generic.Type.LONG;
1001: else if (paramType == Short.TYPE)
1002: return org.apache.bcel.generic.Type.SHORT;
1003: else if (paramType == Byte.TYPE)
1004: return org.apache.bcel.generic.Type.BYTE;
1005: else if (paramType == Void.TYPE)
1006: return org.apache.bcel.generic.Type.VOID;
1007: else
1008: throw new ProgrammingErrorException(
1009: "unknown primitive: " + paramType);
1010: } else if (paramType.isArray()) {
1011: return new ArrayType(getParamType(paramType
1012: .getComponentType()), 1);
1013: } else {
1014: return new ObjectType(paramType.getName());
1015: }
1016: }
1017:
1018: private String[] getParamNames(Class[] paramTypes) {
1019: String[] names = new String[paramTypes.length];
1020:
1021: for (int i = 0; i < names.length; i++) {
1022: names[i] = "arg" + i;
1023: }
1024:
1025: return names;
1026: }
1027:
1028: private String makeMethodSignature(Class retType, Class[] paramTypes) {
1029: String signature = org.apache.bcel.generic.Type
1030: .getMethodSignature(getParamType(retType),
1031: getParamTypes(paramTypes));
1032:
1033: return signature;
1034: }
1035:
1036: }
1037:
1038: /*
1039: * Local Variables:
1040: * tab-width: 2
1041: * indent-tabs-mode: nil
1042: * mode: java
1043: * c-indentation-style: java
1044: * c-basic-offset: 2
1045: * eval: (c-set-offset 'substatement-open '0)
1046: * eval: (c-set-offset 'case-label '+)
1047: * eval: (c-set-offset 'inclass '+)
1048: * eval: (c-set-offset 'inline-open '0)
1049: * End:
1050: */
|