0001: /*****************************************************************************
0002: * *
0003: * This file is part of the BeanShell Java Scripting distribution. *
0004: * Documentation and updates may be found at http://www.beanshell.org/ *
0005: * *
0006: * Sun Public License Notice: *
0007: * *
0008: * The contents of this file are subject to the Sun Public License Version *
0009: * 1.0 (the "License"); you may not use this file except in compliance with *
0010: * the License. A copy of the License is available at http://www.sun.com *
0011: * *
0012: * The Original Code is BeanShell. The Initial Developer of the Original *
0013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
0014: * (C) 2000. All Rights Reserved. *
0015: * *
0016: * GNU Public License Notice: *
0017: * *
0018: * Alternatively, the contents of this file may be used under the terms of *
0019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
0020: * provisions of LGPL are applicable instead of those above. If you wish to *
0021: * allow use of your version of this file only under the terms of the LGPL *
0022: * and not to allow others to use your version of this file under the SPL, *
0023: * indicate your decision by deleting the provisions above and replace *
0024: * them with the notice and other provisions required by the LGPL. If you *
0025: * do not delete the provisions above, a recipient may use your version of *
0026: * this file under either the SPL or the LGPL. *
0027: * *
0028: * Patrick Niemeyer (pat@pat.net) *
0029: * Author of Learning Java, O'Reilly & Associates *
0030: * http://www.pat.net/~pat/ *
0031: * *
0032: *****************************************************************************/package bsh;
0033:
0034: import bsh.org.objectweb.asm.*;
0035: import bsh.org.objectweb.asm.Type;
0036:
0037: import java.lang.reflect.*;
0038: import java.util.ArrayList;
0039: import java.util.List;
0040: import java.io.*;
0041:
0042: /**
0043: ClassGeneratorUtil utilizes the ASM (www.objectweb.org) bytecode generator
0044: by Eric Bruneton in order to generate class "stubs" for BeanShell at
0045: runtime.
0046: <p>
0047:
0048: Stub classes contain all of the fields of a BeanShell scripted class
0049: as well as two "callback" references to BeanShell namespaces: one for
0050: static methods and one for instance methods. Methods of the class are
0051: delegators which invoke corresponding methods on either the static or
0052: instance bsh object and then unpack and return the results. The static
0053: namespace utilizes a static import to delegate variable access to the
0054: class' static fields. The instance namespace utilizes a dynamic import
0055: (i.e. mixin) to delegate variable access to the class' instance variables.
0056: <p>
0057:
0058: Constructors for the class delegate to the static initInstance() method of
0059: ClassGeneratorUtil to initialize new instances of the object. initInstance()
0060: invokes the instance intializer code (init vars and instance blocks) and
0061: then delegates to the corresponding scripted constructor method in the
0062: instance namespace. Constructors contain special switch logic which allows
0063: the BeanShell to control the calling of alternate constructors (this() or
0064: super() references) at runtime.
0065: <p>
0066:
0067: Specially named superclass delegator methods are also generated in order to
0068: allow BeanShell to access overridden methods of the superclass (which
0069: reflection does not normally allow).
0070: <p>
0071:
0072: TODO: We have hooks for generating static initializer code, now used
0073: to save persistent class stubs. This must be extended to accomodate
0074: general static initializer blocks.
0075:
0076: @author Pat Niemeyer
0077: */
0078: /*
0079: Notes:
0080: It would not be hard to eliminate the use of org.objectweb.asm.Type from
0081: this class, making the distribution a tiny bit smaller.
0082: */
0083: public class ClassGeneratorUtil implements Constants {
0084: /** The name of the static field holding the reference to the bsh
0085: static This (the callback namespace for static methods) */
0086: static final String BSHSTATIC = "_bshStatic";
0087:
0088: /** The name of the instance field holding the reference to the bsh
0089: instance This (the callback namespace for instance methods) */
0090: static final String BSHTHIS = "_bshThis";
0091:
0092: /** The prefix for the name of the super delegate methods. e.g.
0093: _bshSuperfoo() is equivalent to super.foo() */
0094: static final String BSHSUPER = "_bshSuper";
0095:
0096: /** The name of the instance initializer in the bsh static class namespace.
0097: * The instance initializer contains instance blocks and loose code, etc.
0098: * */
0099: static final String BSHINIT = "_bshInstanceInitializer";
0100:
0101: /** The bsh static namespace variable that holds the constructor methods */
0102: static final String BSHCONSTRUCTORS = "_bshConstructors";
0103:
0104: /** The switch branch number for the default constructor.
0105: The value -1 will cause the default branch to be taken. */
0106: static final int DEFAULTCONSTRUCTOR = -1;
0107:
0108: static final String OBJECT = "Ljava/lang/Object;";
0109:
0110: String className;
0111: /** fully qualified class name (with package) e.g. foo/bar/Blah */
0112: String fqClassName;
0113: Class super Class;
0114: String super ClassName;
0115: Class[] interfaces;
0116: Variable[] vars;
0117: Constructor[] super Constructors;
0118: DelayedEvalBshMethod[] constructors;
0119: DelayedEvalBshMethod[] methods;
0120: //NameSpace classStaticNameSpace;
0121: Modifiers classModifiers;
0122: boolean isInterface;
0123:
0124: /**
0125: @param packageName e.g. "com.foo.bar"
0126: */
0127: public ClassGeneratorUtil(Modifiers classModifiers,
0128: String className, String packageName, Class super Class,
0129: Class[] interfaces, Variable[] vars,
0130: DelayedEvalBshMethod[] bshmethods, boolean isInterface) {
0131: this .classModifiers = classModifiers;
0132: this .className = className;
0133: if (packageName != null)
0134: this .fqClassName = packageName.replace('.', '/') + "/"
0135: + className;
0136: else
0137: this .fqClassName = className;
0138: if (super Class == null)
0139: super Class = Object.class;
0140: this .super Class = super Class;
0141: this .super ClassName = Type.getInternalName(super Class);
0142: if (interfaces == null)
0143: interfaces = new Class[0];
0144: this .interfaces = interfaces;
0145: this .vars = vars;
0146: //this.classStaticNameSpace = classStaticNameSpace;
0147: this .super Constructors = super Class.getDeclaredConstructors();
0148: this .isInterface = isInterface;
0149:
0150: splitMethodsAndConstructors(className, bshmethods);
0151: }
0152:
0153: /**
0154: This method provides a hook for the class generator implementation to
0155: store additional information in the class's bsh static namespace.
0156: Currently this is used to store an array of consructors corresponding
0157: to the constructor switch in the generated class.
0158:
0159: This method must be called to initialize the static space even if we
0160: are using a previously generated class.
0161: */
0162: public void initStaticNameSpace(NameSpace classStaticNameSpace,
0163: BSHBlock instanceInitBlock) {
0164: try {
0165: classStaticNameSpace.setLocalVariable(BSHCONSTRUCTORS,
0166: constructors, false/*strict*/);
0167: classStaticNameSpace.setLocalVariable(BSHINIT,
0168: instanceInitBlock, false/*strict*/);
0169: } catch (UtilEvalError e) {
0170: throw new InterpreterError(
0171: "Unable to init class static block: " + e);
0172: }
0173: }
0174:
0175: private void splitMethodsAndConstructors(String className,
0176: DelayedEvalBshMethod[] bshmethods) {
0177: // Split the methods into constructors and regular method lists
0178: List consl = new ArrayList();
0179: List methodsl = new ArrayList();
0180: String classBaseName = getBaseName(className); // for inner classes
0181: for (int i = 0; i < bshmethods.length; i++) {
0182: BshMethod bm = bshmethods[i];
0183: if (bm.getName().equals(classBaseName)
0184: && bm.getReturnType() == null)
0185: consl.add(bm);
0186: else
0187: methodsl.add(bm);
0188: }
0189:
0190: this .constructors = (DelayedEvalBshMethod[]) consl
0191: .toArray(new DelayedEvalBshMethod[0]);
0192: this .methods = (DelayedEvalBshMethod[]) methodsl
0193: .toArray(new DelayedEvalBshMethod[0]);
0194: }
0195:
0196: /**
0197: Generate the class bytecode for this class.
0198: */
0199: public byte[] generateClass(boolean generateInitCode) {
0200: // Force the class public for now...
0201: int classMods = getASMModifiers(classModifiers) | ACC_PUBLIC;
0202: if (isInterface)
0203: classMods |= ACC_INTERFACE;
0204:
0205: String[] interfaceNames = new String[interfaces.length];
0206: for (int i = 0; i < interfaces.length; i++)
0207: interfaceNames[i] = Type.getInternalName(interfaces[i]);
0208:
0209: String sourceFile = "BeanShell Generated via ASM (www.objectweb.org)";
0210: ClassWriter cw = new ClassWriter(true);
0211: cw.visit(classMods, fqClassName, super ClassName,
0212: interfaceNames, sourceFile);
0213:
0214: if (!isInterface) {
0215: // Generate the bsh instance 'This' reference holder field
0216: generateField(BSHTHIS + className, "Lbsh/This;",
0217: ACC_PUBLIC, cw);
0218:
0219: // Generate the static bsh static reference holder field
0220: generateField(BSHSTATIC + className, "Lbsh/This;",
0221: ACC_PUBLIC + ACC_STATIC, cw);
0222: }
0223:
0224: // Generate the fields
0225: for (int i = 0; i < vars.length; i++) {
0226: String type = vars[i].getTypeDescriptor();
0227:
0228: // Don't generate private or loosely typed fields
0229: // Note: loose types aren't currently parsed anyway...
0230: if (vars[i].hasModifier("private") || type == null)
0231: continue;
0232:
0233: int modifiers;
0234: if (isInterface)
0235: modifiers = ACC_PUBLIC | ACC_STATIC | ACC_FINAL;
0236: else
0237: modifiers = getASMModifiers(vars[i].getModifiers());
0238:
0239: generateField(vars[i].getName(), type, modifiers, cw);
0240: }
0241:
0242: // Generate the portion of the static initializer that bootstraps
0243: // the interpreter for a cold class.
0244: if (generateInitCode)
0245: generateStaticInitializer(cw);
0246:
0247: // Generate the constructors
0248: boolean hasConstructor = false;
0249: for (int i = 0; i < constructors.length; i++) {
0250: // Don't generate private constructors
0251: if (constructors[i].hasModifier("private"))
0252: continue;
0253:
0254: int modifiers = getASMModifiers(constructors[i]
0255: .getModifiers());
0256: generateConstructor(i, constructors[i]
0257: .getParamTypeDescriptors(), modifiers, cw);
0258: hasConstructor = true;
0259: }
0260:
0261: // If no other constructors, generate a default constructor
0262: if (!isInterface && !hasConstructor)
0263: generateConstructor(DEFAULTCONSTRUCTOR/*index*/,
0264: new String[0], ACC_PUBLIC, cw);
0265:
0266: // Generate the delegate methods
0267: for (int i = 0; i < methods.length; i++) {
0268: String returnType = methods[i].getReturnTypeDescriptor();
0269:
0270: // Don't generate private /*or loosely return typed */ methods
0271: if (methods[i].hasModifier("private") /*|| returnType == null*/)
0272: continue;
0273:
0274: int modifiers = getASMModifiers(methods[i].getModifiers());
0275: if (isInterface)
0276: modifiers |= (ACC_PUBLIC | ACC_ABSTRACT);
0277:
0278: generateMethod(className, fqClassName,
0279: methods[i].getName(), returnType, methods[i]
0280: .getParamTypeDescriptors(), modifiers, cw);
0281:
0282: boolean isStatic = (modifiers & ACC_STATIC) > 0;
0283: boolean overridden = classContainsMethod(super Class,
0284: methods[i].getName(), methods[i]
0285: .getParamTypeDescriptors());
0286: if (!isStatic && overridden)
0287: generateSuperDelegateMethod(super ClassName, methods[i]
0288: .getName(), returnType, methods[i]
0289: .getParamTypeDescriptors(), modifiers, cw);
0290: }
0291:
0292: return cw.toByteArray();
0293: }
0294:
0295: /**
0296: Translate bsh.Modifiers into ASM modifier bitflags.
0297: */
0298: static int getASMModifiers(Modifiers modifiers) {
0299: int mods = 0;
0300: if (modifiers == null)
0301: return mods;
0302:
0303: if (modifiers.hasModifier("public"))
0304: mods += ACC_PUBLIC;
0305: if (modifiers.hasModifier("protected"))
0306: mods += ACC_PROTECTED;
0307: if (modifiers.hasModifier("static"))
0308: mods += ACC_STATIC;
0309: if (modifiers.hasModifier("synchronized"))
0310: mods += ACC_SYNCHRONIZED;
0311: if (modifiers.hasModifier("abstract"))
0312: mods += ACC_ABSTRACT;
0313:
0314: return mods;
0315: }
0316:
0317: /**
0318: Generate a field - static or instance.
0319: */
0320: static void generateField(String fieldName, String type,
0321: int modifiers, ClassWriter cw) {
0322: cw.visitField(modifiers, fieldName, type, null/*value*/);
0323: }
0324:
0325: /**
0326: Generate a delegate method - static or instance.
0327: The generated code packs the method arguments into an object array
0328: (wrapping primitive types in bsh.Primitive), invokes the static or
0329: instance namespace invokeMethod() method, and then unwraps / returns
0330: the result.
0331: */
0332: static void generateMethod(String className, String fqClassName,
0333: String methodName, String returnType, String[] paramTypes,
0334: int modifiers, ClassWriter cw) {
0335: String[] exceptions = null;
0336: boolean isStatic = (modifiers & ACC_STATIC) != 0;
0337:
0338: if (returnType == null) // map loose return type to Object
0339: returnType = OBJECT;
0340:
0341: String methodDescriptor = getMethodDescriptor(returnType,
0342: paramTypes);
0343:
0344: // Generate method body
0345: CodeVisitor cv = cw.visitMethod(modifiers, methodName,
0346: methodDescriptor, exceptions);
0347:
0348: if ((modifiers & ACC_ABSTRACT) != 0)
0349: return;
0350:
0351: // Generate code to push the BSHTHIS or BSHSTATIC field
0352: if (isStatic)
0353: pushBshStatic(fqClassName, className, cv);
0354: else {
0355: // Push 'this'
0356: cv.visitVarInsn(ALOAD, 0);
0357:
0358: // Get the instance field
0359: cv.visitFieldInsn(GETFIELD, fqClassName, BSHTHIS
0360: + className, "Lbsh/This;");
0361: }
0362:
0363: // Push the name of the method as a constant
0364: cv.visitLdcInsn(methodName);
0365:
0366: // Generate code to push arguments as an object array
0367: generateParameterReifierCode(paramTypes, isStatic, cv);
0368:
0369: // Push nulls for various args of invokeMethod
0370: cv.visitInsn(ACONST_NULL); // interpreter
0371: cv.visitInsn(ACONST_NULL); // callstack
0372: cv.visitInsn(ACONST_NULL); // callerinfo
0373:
0374: // Push the boolean constant 'true' (for declaredOnly)
0375: cv.visitInsn(ICONST_1);
0376:
0377: // Invoke the method This.invokeMethod( name, Class [] sig, boolean )
0378: cv.visitMethodInsn(INVOKEVIRTUAL, "bsh/This", "invokeMethod",
0379: Type.getMethodDescriptor(Type.getType(Object.class),
0380: new Type[] { Type.getType(String.class),
0381: Type.getType(Object[].class),
0382: Type.getType(Interpreter.class),
0383: Type.getType(CallStack.class),
0384: Type.getType(SimpleNode.class),
0385: Type.getType(Boolean.TYPE) }));
0386:
0387: // Generate code to unwrap bsh Primitive types
0388: cv.visitMethodInsn(INVOKESTATIC, "bsh/Primitive", "unwrap",
0389: "(Ljava/lang/Object;)Ljava/lang/Object;");
0390:
0391: // Generate code to return the value
0392: generateReturnCode(returnType, cv);
0393:
0394: // values here are ignored, computed automatically by ClassWriter
0395: cv.visitMaxs(0, 0);
0396: }
0397:
0398: /**
0399: Generate a constructor.
0400: */
0401: void generateConstructor(int index, String[] paramTypes,
0402: int modifiers, ClassWriter cw) {
0403: /** offset after params of the args object [] var */
0404: final int argsVar = paramTypes.length + 1;
0405: /** offset after params of the ConstructorArgs var */
0406: final int consArgsVar = paramTypes.length + 2;
0407:
0408: String[] exceptions = null;
0409: String methodDescriptor = getMethodDescriptor("V", paramTypes);
0410:
0411: // Create this constructor method
0412: CodeVisitor cv = cw.visitMethod(modifiers, "<init>",
0413: methodDescriptor, exceptions);
0414:
0415: // Generate code to push arguments as an object array
0416: generateParameterReifierCode(paramTypes, false/*isStatic*/, cv);
0417: cv.visitVarInsn(ASTORE, argsVar);
0418:
0419: // Generate the code implementing the alternate constructor switch
0420: generateConstructorSwitch(index, argsVar, consArgsVar, cv);
0421:
0422: // Generate code to invoke the ClassGeneratorUtil initInstance() method
0423:
0424: // push 'this'
0425: cv.visitVarInsn(ALOAD, 0);
0426:
0427: // Push the class/constructor name as a constant
0428: cv.visitLdcInsn(className);
0429:
0430: // Push arguments as an object array
0431: cv.visitVarInsn(ALOAD, argsVar);
0432:
0433: // invoke the initInstance() method
0434: cv
0435: .visitMethodInsn(INVOKESTATIC,
0436: "bsh/ClassGeneratorUtil", "initInstance",
0437: "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V");
0438:
0439: cv.visitInsn(RETURN);
0440:
0441: // values here are ignored, computed automatically by ClassWriter
0442: cv.visitMaxs(0, 0);
0443: }
0444:
0445: /**
0446: Generate the static initializer for the class
0447: */
0448: void generateStaticInitializer(ClassWriter cw) {
0449: CodeVisitor cv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V",
0450: null/*exceptions*/);
0451:
0452: // Generate code to invoke the ClassGeneratorUtil initStatic() method
0453:
0454: // Push the class name as a constant
0455: cv.visitLdcInsn(fqClassName);
0456:
0457: // Invoke Class.forName() to get our class.
0458: // We do this here, as opposed to in the bsh static init helper method
0459: // in order to be sure to capture the correct classloader.
0460: cv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
0461: "(Ljava/lang/String;)Ljava/lang/Class;");
0462:
0463: // invoke the initStatic() method
0464: cv.visitMethodInsn(INVOKESTATIC, "bsh/ClassGeneratorUtil",
0465: "initStatic", "(Ljava/lang/Class;)V");
0466:
0467: cv.visitInsn(RETURN);
0468:
0469: // values here are ignored, computed automatically by ClassWriter
0470: cv.visitMaxs(0, 0);
0471: }
0472:
0473: /**
0474: Generate a switch with a branch for each possible alternate
0475: constructor. This includes all superclass constructors and all
0476: constructors of this class. The default branch of this switch is the
0477: default superclass constructor.
0478: <p>
0479: This method also generates the code to call the static
0480: ClassGeneratorUtil
0481: getConstructorArgs() method which inspects the scripted constructor to
0482: find the alternate constructor signature (if any) and evalute the
0483: arguments at runtime. The getConstructorArgs() method returns the
0484: actual arguments as well as the index of the constructor to call.
0485: */
0486: void generateConstructorSwitch(int consIndex, int argsVar,
0487: int consArgsVar, CodeVisitor cv) {
0488: Label defaultLabel = new Label();
0489: Label endLabel = new Label();
0490: int cases = super Constructors.length + constructors.length;
0491:
0492: Label[] labels = new Label[cases];
0493: for (int i = 0; i < cases; i++)
0494: labels[i] = new Label();
0495:
0496: // Generate code to call ClassGeneratorUtil to get our switch index
0497: // and give us args...
0498:
0499: // push super class name
0500: cv.visitLdcInsn(super Class.getName()); // use superClassName var?
0501:
0502: // Push the bsh static namespace field
0503: pushBshStatic(fqClassName, className, cv);
0504:
0505: // push args
0506: cv.visitVarInsn(ALOAD, argsVar);
0507:
0508: // push this constructor index number onto stack
0509: cv.visitIntInsn(BIPUSH, consIndex);
0510:
0511: // invoke the ClassGeneratorUtil getConstructorsArgs() method
0512: cv.visitMethodInsn(INVOKESTATIC, "bsh/ClassGeneratorUtil",
0513: "getConstructorArgs",
0514: "(Ljava/lang/String;Lbsh/This;[Ljava/lang/Object;I)"
0515: + "Lbsh/ClassGeneratorUtil$ConstructorArgs;");
0516:
0517: // store ConstructorArgs in consArgsVar
0518: cv.visitVarInsn(ASTORE, consArgsVar);
0519:
0520: // Get the ConstructorArgs selector field from ConstructorArgs
0521:
0522: // push ConstructorArgs
0523: cv.visitVarInsn(ALOAD, consArgsVar);
0524: cv.visitFieldInsn(GETFIELD,
0525: "bsh/ClassGeneratorUtil$ConstructorArgs", "selector",
0526: "I");
0527:
0528: // start switch
0529: cv.visitTableSwitchInsn(0/*min*/, cases - 1/*max*/,
0530: defaultLabel, labels);
0531:
0532: // generate switch body
0533: int index = 0;
0534: for (int i = 0; i < super Constructors.length; i++, index++)
0535: doSwitchBranch(index, super ClassName,
0536: getTypeDescriptors(super Constructors[i]
0537: .getParameterTypes()), endLabel, labels,
0538: consArgsVar, cv);
0539: for (int i = 0; i < constructors.length; i++, index++)
0540: doSwitchBranch(index, fqClassName, constructors[i]
0541: .getParamTypeDescriptors(), endLabel, labels,
0542: consArgsVar, cv);
0543:
0544: // generate the default branch of switch
0545: cv.visitLabel(defaultLabel);
0546: // default branch always invokes no args super
0547: cv.visitVarInsn(ALOAD, 0); // push 'this'
0548: cv.visitMethodInsn(INVOKESPECIAL, super ClassName, "<init>",
0549: "()V");
0550:
0551: // done with switch
0552: cv.visitLabel(endLabel);
0553: }
0554:
0555: // push the class static This object
0556: private static void pushBshStatic(String fqClassName,
0557: String className, CodeVisitor cv) {
0558: cv.visitFieldInsn(GETSTATIC, fqClassName,
0559: BSHSTATIC + className, "Lbsh/This;");
0560: }
0561:
0562: /*
0563: Generate a branch of the constructor switch. This method is called by
0564: generateConstructorSwitch.
0565: The code generated by this method assumes that the argument array is
0566: on the stack.
0567: */
0568: static void doSwitchBranch(int index, String targetClassName,
0569: String[] paramTypes, Label endLabel, Label[] labels,
0570: int consArgsVar, CodeVisitor cv) {
0571: cv.visitLabel(labels[index]);
0572: //cv.visitLineNumber( index, labels[index] );
0573: cv.visitVarInsn(ALOAD, 0); // push this before args
0574:
0575: // Unload the arguments from the ConstructorArgs object
0576: for (int i = 0; i < paramTypes.length; i++) {
0577: String type = paramTypes[i];
0578: String method = null;
0579: if (type.equals("Z"))
0580: method = "getBoolean";
0581: else if (type.equals("B"))
0582: method = "getByte";
0583: else if (type.equals("C"))
0584: method = "getChar";
0585: else if (type.equals("S"))
0586: method = "getShort";
0587: else if (type.equals("I"))
0588: method = "getInt";
0589: else if (type.equals("J"))
0590: method = "getLong";
0591: else if (type.equals("D"))
0592: method = "getDouble";
0593: else if (type.equals("F"))
0594: method = "getFloat";
0595: else
0596: method = "getObject";
0597:
0598: // invoke the iterator method on the ConstructorArgs
0599: cv.visitVarInsn(ALOAD, consArgsVar); // push the ConstructorArgs
0600: String className = "bsh/ClassGeneratorUtil$ConstructorArgs";
0601: String retType;
0602: if (method.equals("getObject"))
0603: retType = OBJECT;
0604: else
0605: retType = type;
0606: cv.visitMethodInsn(INVOKEVIRTUAL, className, method, "()"
0607: + retType);
0608: // if it's an object type we must do a check cast
0609: if (method.equals("getObject"))
0610: cv
0611: .visitTypeInsn(CHECKCAST,
0612: descriptorToClassName(type));
0613: }
0614:
0615: // invoke the constructor for this branch
0616: String descriptor = getMethodDescriptor("V", paramTypes);
0617: cv.visitMethodInsn(INVOKESPECIAL, targetClassName, "<init>",
0618: descriptor);
0619: cv.visitJumpInsn(GOTO, endLabel);
0620: }
0621:
0622: static String getMethodDescriptor(String returnType,
0623: String[] paramTypes) {
0624: StringBuffer sb = new StringBuffer("(");
0625: for (int i = 0; i < paramTypes.length; i++)
0626: sb.append(paramTypes[i]);
0627: sb.append(")" + returnType);
0628: return sb.toString();
0629: }
0630:
0631: /**
0632: Generate a superclass method delegate accessor method.
0633: These methods are specially named methods which allow access to
0634: overridden methods of the superclass (which the Java reflection API
0635: normally does not allow).
0636: */
0637: // Maybe combine this with generateMethod()
0638: static void generateSuperDelegateMethod(String super ClassName,
0639: String methodName, String returnType, String[] paramTypes,
0640: int modifiers, ClassWriter cw) {
0641: String[] exceptions = null;
0642:
0643: if (returnType == null) // map loose return to Object
0644: returnType = OBJECT;
0645:
0646: String methodDescriptor = getMethodDescriptor(returnType,
0647: paramTypes);
0648:
0649: // Add method body
0650: CodeVisitor cv = cw.visitMethod(modifiers, "_bshSuper"
0651: + methodName, methodDescriptor, exceptions);
0652:
0653: cv.visitVarInsn(ALOAD, 0);
0654: // Push vars
0655: int localVarIndex = 1;
0656: for (int i = 0; i < paramTypes.length; ++i) {
0657: if (isPrimitive(paramTypes[i]))
0658: cv.visitVarInsn(ILOAD, localVarIndex);
0659: else
0660: cv.visitVarInsn(ALOAD, localVarIndex);
0661: localVarIndex += ((paramTypes[i].equals("D") || paramTypes[i]
0662: .equals("J")) ? 2 : 1);
0663: }
0664:
0665: cv.visitMethodInsn(INVOKESPECIAL, super ClassName, methodName,
0666: methodDescriptor);
0667:
0668: generatePlainReturnCode(returnType, cv);
0669:
0670: // Need to calculate this... just fudging here for now.
0671: cv.visitMaxs(20, 20);
0672: }
0673:
0674: static boolean classContainsMethod(Class clas, String methodName,
0675: String[] paramTypes) {
0676: while (clas != null) {
0677: Method[] methods = clas.getDeclaredMethods();
0678: for (int i = 0; i < methods.length; i++) {
0679: if (methods[i].getName().equals(methodName)) {
0680: String[] methodParamTypes = getTypeDescriptors(methods[i]
0681: .getParameterTypes());
0682: boolean found = true;
0683: for (int j = 0; j < methodParamTypes.length; j++) {
0684: if (!paramTypes[j].equals(methodParamTypes[j])) {
0685: found = false;
0686: break;
0687: }
0688: }
0689: if (found)
0690: return true;
0691: }
0692: }
0693:
0694: clas = clas.getSuperclass();
0695: }
0696:
0697: return false;
0698: }
0699:
0700: /**
0701: Generate return code for a normal bytecode
0702: */
0703: static void generatePlainReturnCode(String returnType,
0704: CodeVisitor cv) {
0705: if (returnType.equals("V"))
0706: cv.visitInsn(RETURN);
0707: else if (isPrimitive(returnType)) {
0708: int opcode = IRETURN;
0709: if (returnType.equals("D"))
0710: opcode = DRETURN;
0711: else if (returnType.equals("F"))
0712: opcode = FRETURN;
0713: else if (returnType.equals("J")) //long
0714: opcode = LRETURN;
0715:
0716: cv.visitInsn(opcode);
0717: } else {
0718: cv.visitTypeInsn(CHECKCAST,
0719: descriptorToClassName(returnType));
0720: cv.visitInsn(ARETURN);
0721: }
0722: }
0723:
0724: /**
0725: Generates the code to reify the arguments of the given method.
0726: For a method "int m (int i, String s)", this code is the bytecode
0727: corresponding to the "new Object[] { new bsh.Primitive(i), s }"
0728: expression.
0729:
0730: @author Eric Bruneton
0731: @author Pat Niemeyer
0732: @param cv the code visitor to be used to generate the bytecode.
0733: @param isStatic the enclosing methods is static
0734: */
0735: public static void generateParameterReifierCode(
0736: String[] paramTypes, boolean isStatic, final CodeVisitor cv) {
0737: cv.visitIntInsn(SIPUSH, paramTypes.length);
0738: cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
0739: int localVarIndex = isStatic ? 0 : 1;
0740: for (int i = 0; i < paramTypes.length; ++i) {
0741: String param = paramTypes[i];
0742: cv.visitInsn(DUP);
0743: cv.visitIntInsn(SIPUSH, i);
0744: if (isPrimitive(param)) {
0745: int opcode;
0746: if (param.equals("F")) {
0747: opcode = FLOAD;
0748: } else if (param.equals("D")) {
0749: opcode = DLOAD;
0750: } else if (param.equals("J")) {
0751: opcode = LLOAD;
0752: } else {
0753: opcode = ILOAD;
0754: }
0755:
0756: String type = "bsh/Primitive";
0757: cv.visitTypeInsn(NEW, type);
0758: cv.visitInsn(DUP);
0759: cv.visitVarInsn(opcode, localVarIndex);
0760: String desc = param; // ok?
0761: cv.visitMethodInsn(INVOKESPECIAL, type, "<init>", "("
0762: + desc + ")V");
0763: } else {
0764: // Technically incorrect here - we need to wrap null values
0765: // as bsh.Primitive.NULL. However the This.invokeMethod()
0766: // will do that much for us.
0767: // We need to generate a conditional here to test for null
0768: // and return Primitive.NULL
0769: cv.visitVarInsn(ALOAD, localVarIndex);
0770: }
0771: cv.visitInsn(AASTORE);
0772: localVarIndex += ((param.equals("D") || param.equals("J")) ? 2
0773: : 1);
0774: }
0775: }
0776:
0777: /**
0778: Generates the code to unreify the result of the given method. For a
0779: method "int m (int i, String s)", this code is the bytecode
0780: corresponding to the "((Integer)...).intValue()" expression.
0781:
0782: @param cv the code visitor to be used to generate the bytecode.
0783: @author Eric Bruneton
0784: @author Pat Niemeyer
0785: */
0786: public static void generateReturnCode(String returnType,
0787: CodeVisitor cv) {
0788: if (returnType.equals("V")) {
0789: cv.visitInsn(POP);
0790: cv.visitInsn(RETURN);
0791: } else if (isPrimitive(returnType)) {
0792: int opcode = IRETURN;
0793: String type;
0794: String meth;
0795: if (returnType.equals("B")) {
0796: type = "java/lang/Byte";
0797: meth = "byteValue";
0798: } else if (returnType.equals("I")) {
0799: type = "java/lang/Integer";
0800: meth = "intValue";
0801: } else if (returnType.equals("Z")) {
0802: type = "java/lang/Boolean";
0803: meth = "booleanValue";
0804: } else if (returnType.equals("D")) {
0805: opcode = DRETURN;
0806: type = "java/lang/Double";
0807: meth = "doubleValue";
0808: } else if (returnType.equals("F")) {
0809: opcode = FRETURN;
0810: type = "java/lang/Float";
0811: meth = "floatValue";
0812: } else if (returnType.equals("J")) {
0813: opcode = LRETURN;
0814: type = "java/lang/Long";
0815: meth = "longValue";
0816: } else if (returnType.equals("C")) {
0817: type = "java/lang/Character";
0818: meth = "charValue";
0819: } else /*if (returnType.equals("S") )*/{
0820: type = "java/lang/Short";
0821: meth = "shortValue";
0822: }
0823:
0824: String desc = returnType;
0825: cv.visitTypeInsn(CHECKCAST, type); // type is correct here
0826: cv.visitMethodInsn(INVOKEVIRTUAL, type, meth, "()" + desc);
0827: cv.visitInsn(opcode);
0828: } else {
0829: cv.visitTypeInsn(CHECKCAST,
0830: descriptorToClassName(returnType));
0831: cv.visitInsn(ARETURN);
0832: }
0833: }
0834:
0835: /**
0836: This method is called by the **generated class** during construction.
0837:
0838: Evaluate the arguments (if any) for the constructor specified by
0839: the constructor index. Return the ConstructorArgs object which
0840: contains the actual arguments to the alternate constructor and also the
0841: index of that constructor for the constructor switch.
0842:
0843: @param consArgs the arguments to the constructor. These are necessary in
0844: the evaluation of the alt constructor args. e.g. Foo(a) { super(a); }
0845: @return the ConstructorArgs object containing a constructor selector
0846: and evaluated arguments for the alternate constructor
0847: */
0848: public static ConstructorArgs getConstructorArgs(
0849: String super ClassName, This classStaticThis,
0850: Object[] consArgs, int index) {
0851: if (classStaticThis == null)
0852: throw new InterpreterError("Unititialized class: no static");
0853:
0854: DelayedEvalBshMethod[] constructors;
0855: try {
0856: Object cons = classStaticThis.getNameSpace().getVariable(
0857: BSHCONSTRUCTORS);
0858: if (cons == Primitive.VOID)
0859: throw new InterpreterError(
0860: "Unable to find constructors array in class");
0861: constructors = (DelayedEvalBshMethod[]) cons;
0862: } catch (Exception e) {
0863: throw new InterpreterError(
0864: "Unable to get instance initializers: " + e);
0865: }
0866:
0867: if (index == DEFAULTCONSTRUCTOR) // auto-gen default constructor
0868: return ConstructorArgs.DEFAULT; // use default super constructor
0869:
0870: DelayedEvalBshMethod constructor = constructors[index];
0871:
0872: if (constructor.methodBody.jjtGetNumChildren() == 0)
0873: return ConstructorArgs.DEFAULT; // use default super constructor
0874:
0875: // Determine if the constructor calls this() or super()
0876: String altConstructor = null;
0877: BSHArguments argsNode = null;
0878: SimpleNode firstStatement = (SimpleNode) constructor.methodBody
0879: .jjtGetChild(0);
0880: if (firstStatement instanceof BSHPrimaryExpression)
0881: firstStatement = (SimpleNode) firstStatement.jjtGetChild(0);
0882: if (firstStatement instanceof BSHMethodInvocation) {
0883: BSHMethodInvocation methodNode = (BSHMethodInvocation) firstStatement;
0884: BSHAmbiguousName methodName = methodNode.getNameNode();
0885: if (methodName.text.equals("super")
0886: || methodName.text.equals("this")) {
0887: altConstructor = methodName.text;
0888: argsNode = methodNode.getArgsNode();
0889: }
0890: }
0891:
0892: if (altConstructor == null)
0893: return ConstructorArgs.DEFAULT; // use default super constructor
0894:
0895: // Make a tmp namespace to hold the original constructor args for
0896: // use in eval of the parameters node
0897: NameSpace consArgsNameSpace = new NameSpace(classStaticThis
0898: .getNameSpace(), "consArgs");
0899: String[] consArgNames = constructor.getParameterNames();
0900: Class[] consArgTypes = constructor.getParameterTypes();
0901: for (int i = 0; i < consArgs.length; i++) {
0902: try {
0903: consArgsNameSpace
0904: .setTypedVariable(consArgNames[i],
0905: consArgTypes[i], consArgs[i], null/*modifiers*/);
0906: } catch (UtilEvalError e) {
0907: throw new InterpreterError(
0908: "err setting local cons arg:" + e);
0909: }
0910: }
0911:
0912: // evaluate the args
0913:
0914: CallStack callstack = new CallStack();
0915: callstack.push(consArgsNameSpace);
0916: Object[] args = null;
0917: Interpreter interpreter = classStaticThis.declaringInterpreter;
0918:
0919: try {
0920: args = argsNode.getArguments(callstack, interpreter);
0921: } catch (EvalError e) {
0922: throw new InterpreterError(
0923: "Error evaluating constructor args: " + e);
0924: }
0925:
0926: Class[] argTypes = Types.getTypes(args);
0927: args = Primitive.unwrap(args);
0928: Class super Class = interpreter.getClassManager().classForName(
0929: super ClassName);
0930: if (super Class == null)
0931: throw new InterpreterError("can't find superclass: "
0932: + super ClassName);
0933: Constructor[] super Cons = super Class.getDeclaredConstructors();
0934:
0935: // find the matching super() constructor for the args
0936: if (altConstructor.equals("super")) {
0937: int i = Reflect.findMostSpecificConstructorIndex(argTypes,
0938: super Cons);
0939: if (i == -1)
0940: throw new InterpreterError(
0941: "can't find constructor for args!");
0942: return new ConstructorArgs(i, args);
0943: }
0944:
0945: // find the matching this() constructor for the args
0946: Class[][] candidates = new Class[constructors.length][];
0947: for (int i = 0; i < candidates.length; i++)
0948: candidates[i] = constructors[i].getParameterTypes();
0949: int i = Reflect.findMostSpecificSignature(argTypes, candidates);
0950: if (i == -1)
0951: throw new InterpreterError(
0952: "can't find constructor for args 2!");
0953: // this() constructors come after super constructors in the table
0954:
0955: int selector = i + super Cons.length;
0956: int ourSelector = index + super Cons.length;
0957:
0958: // Are we choosing ourselves recursively through a this() reference?
0959: if (selector == ourSelector)
0960: throw new InterpreterError("Recusive constructor call.");
0961:
0962: return new ConstructorArgs(selector, args);
0963: }
0964:
0965: /**
0966: This method is called from the **generated class** constructor to
0967: evaluate the instance initializer (instance blocks and loosely typed
0968: statements) and then the scripted constructor,
0969: in the instance namespace. These activities happen in the bsh script
0970: but have side effects in the generated stub class (imported instance
0971: and static variables may be initialized).
0972: */
0973: // TODO: Refactor this method... too long and ungainly.
0974: // Why both instance and className here? There must have been a reason.
0975: public static void initInstance(Object instance, String className,
0976: Object[] args) {
0977: Class[] sig = Types.getTypes(args);
0978: CallStack callstack = new CallStack();
0979: Interpreter interpreter;
0980: NameSpace instanceNameSpace;
0981:
0982: // check to see if the instance has already been initialized
0983: // (the case if using a this() alternate constuctor)
0984: This instanceThis = getClassInstanceThis(instance, className);
0985:
0986: // TODO: clean up this conditional
0987: if (instanceThis == null) {
0988: // Create the instance 'This' namespace, set it on the object
0989: // instance and invoke the instance initializer
0990:
0991: // Get the static This reference from the proto-instance
0992: This classStaticThis = getClassStaticThis(instance
0993: .getClass(), className);
0994:
0995: if (classStaticThis == null)
0996: throw new InterpreterError("Failed to init class: "
0997: + className);
0998:
0999: interpreter = classStaticThis.declaringInterpreter;
1000:
1001: // Get the instance initializer block from the static This
1002: BSHBlock instanceInitBlock;
1003: try {
1004: instanceInitBlock = (BSHBlock) classStaticThis
1005: .getNameSpace().getVariable(BSHINIT);
1006: } catch (Exception e) {
1007: throw new InterpreterError(
1008: "unable to get instance initializer: " + e);
1009: }
1010:
1011: // Create the instance namespace
1012: instanceNameSpace = new NameSpace(classStaticThis
1013: .getNameSpace(), className);
1014: instanceNameSpace.isClass = true;
1015:
1016: // Set the instance This reference on the instance
1017: instanceThis = instanceNameSpace.getThis(interpreter);
1018: try {
1019: LHS lhs = Reflect.getLHSObjectField(instance, BSHTHIS
1020: + className);
1021: lhs.assign(instanceThis, false/*strict*/);
1022: } catch (Exception e) {
1023: throw new InterpreterError("Error in class gen setup: "
1024: + e);
1025: }
1026:
1027: // Give the instance space its object import
1028: instanceNameSpace.setClassInstance(instance);
1029:
1030: // should use try/finally here to pop ns
1031: callstack.push(instanceNameSpace);
1032:
1033: // evaluate the instance portion of the block in it
1034: try { // Evaluate the initializer block
1035: instanceInitBlock
1036: .evalBlock(
1037: callstack,
1038: interpreter,
1039: true/*override*/,
1040: ClassGeneratorImpl.ClassNodeFilter.CLASSINSTANCE);
1041: } catch (Exception e) {
1042: throw new InterpreterError(
1043: "Error in class initialization: " + e);
1044: }
1045:
1046: callstack.pop();
1047:
1048: } else {
1049: // The object instance has already been initialzed by another
1050: // constructor. Fall through to invoke the constructor body below.
1051: interpreter = instanceThis.declaringInterpreter;
1052: instanceNameSpace = instanceThis.getNameSpace();
1053: }
1054:
1055: // invoke the constructor method from the instanceThis
1056:
1057: String constructorName = getBaseName(className);
1058: try {
1059: // Find the constructor (now in the instance namespace)
1060: BshMethod constructor = instanceNameSpace.getMethod(
1061: constructorName, sig, true/*declaredOnly*/);
1062:
1063: // differentiate a constructor from a badly named method
1064: if (constructor != null
1065: && constructor.getReturnType() != null)
1066: constructor = null;
1067:
1068: // if args, we must have constructor
1069: if (args.length > 0 && constructor == null)
1070: throw new InterpreterError("Can't find constructor: "
1071: + className);
1072:
1073: // Evaluate the constructor
1074: if (constructor != null)
1075: constructor
1076: .invoke(args, interpreter, callstack,
1077: null/*callerInfo*/, false/*overrideNameSpace*/);
1078: } catch (Exception e) {
1079: if (Interpreter.DEBUG)
1080: e.printStackTrace();
1081: if (e instanceof TargetError)
1082: e = (Exception) ((TargetError) e).getTarget();
1083: if (e instanceof InvocationTargetException)
1084: e = (Exception) ((InvocationTargetException) e)
1085: .getTargetException();
1086: throw new InterpreterError(
1087: "Error in class initialization: " + e);
1088: }
1089: }
1090:
1091: /**
1092: The class is "cold" (detached with no live interpreter static
1093: This reference) try to start a new interpreter and source the
1094: script backing it.
1095:
1096: We pass in both the fq class name and the static This ref here just
1097: to minimize the generated code. All we really do here is a simple
1098: if condition for now.
1099: */
1100: public static void initStatic(Class genClass) {
1101: startInterpreterForClass(genClass);
1102: }
1103:
1104: /**
1105: Get the static bsh namespace field from the class.
1106: @param className may be the name of clas itself or a superclass of clas.
1107: */
1108: static This getClassStaticThis(Class clas, String className) {
1109: try {
1110: return (This) Reflect.getStaticFieldValue(clas, BSHSTATIC
1111: + className);
1112: } catch (Exception e) {
1113: throw new InterpreterError(
1114: "Unable to get class static space: " + e);
1115: }
1116: }
1117:
1118: /**
1119: Get the instance bsh namespace field from the object instance.
1120: @return the class instance This object or null if the object has not
1121: been initialized.
1122: */
1123: static This getClassInstanceThis(Object instance, String className) {
1124: try {
1125: Object o = Reflect.getObjectFieldValue(instance, BSHTHIS
1126: + className);
1127: return (This) Primitive.unwrap(o); // unwrap Primitive.Null to null
1128: } catch (Exception e) {
1129: throw new InterpreterError(
1130: "Generated class: Error getting This" + e);
1131: }
1132: }
1133:
1134: /**
1135: Does the type descriptor string describe a primitive type?
1136: */
1137: private static boolean isPrimitive(String typeDescriptor) {
1138: return typeDescriptor.length() == 1; // right?
1139: }
1140:
1141: static String[] getTypeDescriptors(Class[] cparams) {
1142: String[] sa = new String[cparams.length];
1143: for (int i = 0; i < sa.length; i++)
1144: sa[i] = BSHType.getTypeDescriptor(cparams[i]);
1145: return sa;
1146: }
1147:
1148: /**
1149: If a non-array object type, remove the prefix "L" and suffix ";".
1150: */
1151: // Can this be factored out...?
1152: // Should be be adding the L...; here instead?
1153: private static String descriptorToClassName(String s) {
1154: if (s.startsWith("[") || !s.startsWith("L"))
1155: return s;
1156: return s.substring(1, s.length() - 1);
1157: }
1158:
1159: /**
1160: * This should live in utilities somewhere.
1161: */
1162: private static String getBaseName(String className) {
1163: int i = className.indexOf("$");
1164: if (i == -1)
1165: return className;
1166:
1167: return className.substring(i + 1);
1168: }
1169:
1170: /**
1171: A ConstructorArgs object holds evaluated arguments for a constructor
1172: call as well as the index of a possible alternate selector to invoke.
1173: This object is used by the constructor switch.
1174: @see #generateConstructor( int , String [] , int , ClassWriter )
1175: */
1176: public static class ConstructorArgs {
1177: /** A ConstructorArgs which calls the default constructor */
1178: public static ConstructorArgs DEFAULT = new ConstructorArgs();
1179:
1180: public int selector = DEFAULTCONSTRUCTOR;
1181: Object[] args;
1182: int arg = 0;
1183:
1184: /**
1185: The index of the constructor to call.
1186: */
1187:
1188: ConstructorArgs() {
1189: }
1190:
1191: ConstructorArgs(int selector, Object[] args) {
1192: this .selector = selector;
1193: this .args = args;
1194: }
1195:
1196: Object next() {
1197: return args[arg++];
1198: }
1199:
1200: public boolean getBoolean() {
1201: return ((Boolean) next()).booleanValue();
1202: }
1203:
1204: public byte getByte() {
1205: return ((Byte) next()).byteValue();
1206: }
1207:
1208: public char getChar() {
1209: return ((Character) next()).charValue();
1210: }
1211:
1212: public short getShort() {
1213: return ((Short) next()).shortValue();
1214: }
1215:
1216: public int getInt() {
1217: return ((Integer) next()).intValue();
1218: }
1219:
1220: public long getLong() {
1221: return ((Long) next()).longValue();
1222: }
1223:
1224: public double getDouble() {
1225: return ((Double) next()).doubleValue();
1226: }
1227:
1228: public float getFloat() {
1229: return ((Float) next()).floatValue();
1230: }
1231:
1232: public Object getObject() {
1233: return next();
1234: }
1235: }
1236:
1237: /**
1238: Attempt to load a script named for the class: e.g. Foo.class Foo.bsh.
1239: The script is expected to (at minimum) initialize the class body.
1240: That is, it should contain the scripted class definition.
1241:
1242: This method relies on the fact that the ClassGenerator generateClass()
1243: method will detect that the generated class already exists and
1244: initialize it rather than recreating it.
1245:
1246: The only interact that this method has with the process is to initially
1247: cache the correct class in the class manager for the interpreter to
1248: insure that it is found and associated with the scripted body.
1249: */
1250: public static void startInterpreterForClass(Class genClass) {
1251: String fqClassName = genClass.getName();
1252: String baseName = Name.suffix(fqClassName, 1);
1253: String resName = baseName + ".bsh";
1254:
1255: InputStream in = genClass.getResourceAsStream(resName);
1256: if (in == null)
1257: throw new InterpreterError("Script (" + resName
1258: + ") for BeanShell generated class: " + genClass
1259: + " not found.");
1260:
1261: Reader reader = new InputStreamReader(genClass
1262: .getResourceAsStream(resName));
1263:
1264: // Set up the interpreter
1265: Interpreter bsh = new Interpreter();
1266: NameSpace globalNS = bsh.getNameSpace();
1267: globalNS.setName("class_" + baseName + "_global");
1268: globalNS.getClassManager().associateClass(genClass);
1269:
1270: // Source the script
1271: try {
1272: bsh.eval(reader, bsh.getNameSpace(), resName);
1273: } catch (TargetError e) {
1274: System.out.println("Script threw exception: " + e);
1275: if (e.inNativeCode())
1276: e.printStackTrace(System.err);
1277: } catch (EvalError e) {
1278: System.out.println("Evaluation Error: " + e);
1279: }
1280: }
1281: }
|