All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bsh.ClassGeneratorUtil Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * The AIBench Shell Plugin
 * %%
 * Copyright (C) 2006 - 2017 Daniel Glez-Peña and Florentino Fdez-Riverola
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
/*****************************************************************************
 *                                                                           *
 *  This file is part of the BeanShell Java Scripting distribution.          *
 *  Documentation and updates may be found at http://www.beanshell.org/      *
 *                                                                           *
 *  Sun Public License Notice:                                               *
 *                                                                           *
 *  The contents of this file are subject to the Sun Public License Version  *
 *  1.0 (the "License"); you may not use this file except in compliance with *
 *  the License. A copy of the License is available at http://www.sun.com    * 
 *                                                                           *
 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 *  (C) 2000.  All Rights Reserved.                                          *
 *                                                                           *
 *  GNU Public License Notice:                                               *
 *                                                                           *
 *  Alternatively, the contents of this file may be used under the terms of  *
 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 *  provisions of LGPL are applicable instead of those above. If you wish to *
 *  allow use of your version of this file only under the  terms of the LGPL *
 *  and not to allow others to use your version of this file under the SPL,  *
 *  indicate your decision by deleting the provisions above and replace      *
 *  them with the notice and other provisions required by the LGPL.  If you  *
 *  do not delete the provisions above, a recipient may use your version of  *
 *  this file under either the SPL or the LGPL.                              *
 *                                                                           *
 *  Patrick Niemeyer ([email protected])                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/

package bsh;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import bsh.org.objectweb.asm.ClassWriter;
import bsh.org.objectweb.asm.CodeVisitor;
import bsh.org.objectweb.asm.Constants;
import bsh.org.objectweb.asm.Label;
import bsh.org.objectweb.asm.Type;

/**
 * ClassGeneratorUtil utilizes the ASM (www.objectweb.org) bytecode generator by
 * Eric Bruneton in order to generate class "stubs" for BeanShell at runtime.
 * 

* * Stub classes contain all of the fields of a BeanShell scripted class as well * as two "callback" references to BeanShell namespaces: one for static methods * and one for instance methods. Methods of the class are delegators which * invoke corresponding methods on either the static or instance bsh object and * then unpack and return the results. The static namespace utilizes a static * import to delegate variable access to the class' static fields. The instance * namespace utilizes a dynamic import (i.e. mixin) to delegate variable access * to the class' instance variables. *

* * Constructors for the class delegate to the static initInstance() method of * ClassGeneratorUtil to initialize new instances of the object. initInstance() * invokes the instance intializer code (init vars and instance blocks) and then * delegates to the corresponding scripted constructor method in the instance * namespace. Constructors contain special switch logic which allows the * BeanShell to control the calling of alternate constructors (this() or super() * references) at runtime. *

* * Specially named superclass delegator methods are also generated in order to * allow BeanShell to access overridden methods of the superclass (which * reflection does not normally allow). *

* * @author Pat Niemeyer */ /* * Notes: It would not be hard to eliminate the use of org.objectweb.asm.Type * from this class, making the distribution a tiny bit smaller. */ public class ClassGeneratorUtil implements Constants { /** * The name of the static field holding the reference to the bsh static This * (the callback namespace for static methods) */ static final String BSHSTATIC = "_bshStatic"; /** * The name of the instance field holding the reference to the bsh instance * This (the callback namespace for instance methods) */ static final String BSHTHIS = "_bshThis"; /** * The prefix for the name of the super delegate methods. e.g. * _bshSuperfoo() is equivalent to super.foo() */ static final String BSHSUPER = "_bshSuper"; /** The bsh static namespace variable name of the instance initializer */ static final String BSHINIT = "_bshInstanceInitializer"; /** The bsh static namespace variable that holds the constructor methods */ static final String BSHCONSTRUCTORS = "_bshConstructors"; /** * The switch branch number for the default constructor. The value -1 will * cause the default branch to be taken. */ static final int DEFAULTCONSTRUCTOR = -1; static final String OBJECT = "Ljava/lang/Object;"; String className; /** fully qualified class name (with package) e.g. foo/bar/Blah */ String fqClassName; Class superClass; String superClassName; Class[] interfaces; Variable[] vars; Constructor[] superConstructors; DelayedEvalBshMethod[] constructors; DelayedEvalBshMethod[] methods; NameSpace classStaticNameSpace; Modifiers classModifiers; boolean isInterface; public ClassGeneratorUtil( Modifiers classModifiers, String className, String packageName, Class superClass, Class[] interfaces, Variable[] vars, DelayedEvalBshMethod[] bshmethods, NameSpace classStaticNameSpace, boolean isInterface ) { this.classModifiers = classModifiers; this.className = className; if (packageName != null) this.fqClassName = packageName.replace('.', '/') + "/" + className; else this.fqClassName = className; if (superClass == null) superClass = Object.class; this.superClass = superClass; this.superClassName = Type.getInternalName(superClass); if (interfaces == null) interfaces = new Class[0]; this.interfaces = interfaces; this.vars = vars; this.classStaticNameSpace = classStaticNameSpace; this.superConstructors = superClass.getDeclaredConstructors(); // Split the methods into constructors and regular method lists List consl = new ArrayList(); List methodsl = new ArrayList(); String classBaseName = getBaseName(className); // for inner // classes for (int i = 0; i < bshmethods.length; i++) if (bshmethods[i].getName().equals(classBaseName)) consl.add(bshmethods[i]); else methodsl.add(bshmethods[i]); this.constructors = (DelayedEvalBshMethod[]) consl.toArray(new DelayedEvalBshMethod[0]); this.methods = (DelayedEvalBshMethod[]) methodsl.toArray(new DelayedEvalBshMethod[0]); try { classStaticNameSpace.setLocalVariable(BSHCONSTRUCTORS, constructors, false/* strict */); } catch (UtilEvalError e) { throw new InterpreterError("can't set cons var"); } this.isInterface = isInterface; } /** * Generates the class bytecode for this class. * * @return the class bytecode for this class. */ public byte[] generateClass() { // Force the class public for now... int classMods = getASMModifiers(classModifiers) | ACC_PUBLIC; if (isInterface) classMods |= ACC_INTERFACE; String[] interfaceNames = new String[interfaces.length]; for (int i = 0; i < interfaces.length; i++) interfaceNames[i] = Type.getInternalName(interfaces[i]); String sourceFile = "BeanShell Generated via ASM (www.objectweb.org)"; ClassWriter cw = new ClassWriter(false); cw.visit(classMods, fqClassName, superClassName, interfaceNames, sourceFile); if (!isInterface) { // Generate the bsh instance 'This' reference holder // field generateField(BSHTHIS + className, "Lbsh/This;", ACC_PUBLIC, cw); // Generate the static bsh static reference holder field generateField(BSHSTATIC + className, "Lbsh/This;", ACC_PUBLIC + ACC_STATIC, cw); } // Generate the fields for (int i = 0; i < vars.length; i++) { String type = vars[i].getTypeDescriptor(); // Don't generate private or loosely typed fields // Note: loose types aren't currently parsed anyway... if (vars[i].hasModifier("private") || type == null) continue; int modifiers; if (isInterface) modifiers = ACC_PUBLIC | ACC_STATIC | ACC_FINAL; else modifiers = getASMModifiers(vars[i].getModifiers()); generateField(vars[i].getName(), type, modifiers, cw); } // Generate the constructors boolean hasConstructor = false; for (int i = 0; i < constructors.length; i++) { // Don't generate private constructors if (constructors[i].hasModifier("private")) continue; int modifiers = getASMModifiers(constructors[i].getModifiers()); generateConstructor(i, constructors[i].getParamTypeDescriptors(), modifiers, cw); hasConstructor = true; } // If no other constructors, generate a default constructor if (!isInterface && !hasConstructor) generateConstructor(DEFAULTCONSTRUCTOR/* index */, new String[0], ACC_PUBLIC, cw); // Generate the delegate methods for (int i = 0; i < methods.length; i++) { String returnType = methods[i].getReturnTypeDescriptor(); // Don't generate private /*or loosely return typed */ // methods if (methods[i].hasModifier("private") /* * || returnType == null */) continue; int modifiers = getASMModifiers(methods[i].getModifiers()); if (isInterface) modifiers |= (ACC_PUBLIC | ACC_ABSTRACT); generateMethod(className, fqClassName, methods[i].getName(), returnType, methods[i].getParamTypeDescriptors(), modifiers, cw); boolean isStatic = (modifiers & ACC_STATIC) > 0; boolean overridden = classContainsMethod(superClass, methods[i].getName(), methods[i].getParamTypeDescriptors()); if (!isStatic && overridden) generateSuperDelegateMethod(superClassName, methods[i].getName(), returnType, methods[i].getParamTypeDescriptors(), modifiers, cw); } return cw.toByteArray(); } /** * Translate bsh.Modifiers into ASM modifier bitflags. */ static int getASMModifiers(Modifiers modifiers) { int mods = 0; if (modifiers == null) return mods; if (modifiers.hasModifier("public")) mods += ACC_PUBLIC; if (modifiers.hasModifier("protected")) mods += ACC_PROTECTED; if (modifiers.hasModifier("static")) mods += ACC_STATIC; if (modifiers.hasModifier("synchronized")) mods += ACC_SYNCHRONIZED; if (modifiers.hasModifier("abstract")) mods += ACC_ABSTRACT; return mods; } /** * Generate a field - static or instance. */ static void generateField(String fieldName, String type, int modifiers, ClassWriter cw) { cw.visitField(modifiers, fieldName, type, null/* value */); } /** * Generate a delegate method - static or instance. The generated code packs * the method arguments into an object array (wrapping primitive types in * bsh.Primitive), invokes the static or instance namespace invokeMethod() * method, and then unwraps / returns the result. */ static void generateMethod(String className, String fqClassName, String methodName, String returnType, String[] paramTypes, int modifiers, ClassWriter cw) { String[] exceptions = null; boolean isStatic = (modifiers & ACC_STATIC) != 0; if (returnType == null) // map loose return type to Object returnType = OBJECT; String methodDescriptor = getMethodDescriptor(returnType, paramTypes); // Generate method body CodeVisitor cv = cw.visitMethod(modifiers, methodName, methodDescriptor, exceptions); if ((modifiers & ACC_ABSTRACT) != 0) return; // Generate code to push the BSHTHIS or BSHSTATIC field if (isStatic) { cv.visitFieldInsn(GETSTATIC, fqClassName, BSHSTATIC + className, "Lbsh/This;"); } else { // Push 'this' cv.visitVarInsn(ALOAD, 0); // Get the instance field cv.visitFieldInsn(GETFIELD, fqClassName, BSHTHIS + className, "Lbsh/This;"); } // Push the name of the method as a constant cv.visitLdcInsn(methodName); // Generate code to push arguments as an object array generateParameterReifierCode(paramTypes, isStatic, cv); // Push nulls for various args of invokeMethod cv.visitInsn(ACONST_NULL); // interpreter cv.visitInsn(ACONST_NULL); // callstack cv.visitInsn(ACONST_NULL); // callerinfo // Push the boolean constant 'true' (for declaredOnly) cv.visitInsn(ICONST_1); // Invoke the method This.invokeMethod( name, Class [] sig, // boolean ) cv.visitMethodInsn( INVOKEVIRTUAL, "bsh/This", "invokeMethod", Type.getMethodDescriptor( Type.getType(Object.class), new Type[] { Type.getType(String.class), Type.getType(Object[].class), Type.getType(Interpreter.class), Type.getType(CallStack.class), Type.getType(SimpleNode.class), Type.getType(Boolean.TYPE) } ) ); // Generate code to unwrap bsh Primitive types cv.visitMethodInsn(INVOKESTATIC, "bsh/Primitive", "unwrap", "(Ljava/lang/Object;)Ljava/lang/Object;"); // Generate code to return the value generateReturnCode(returnType, cv); // Need to calculate this... just fudging here for now. cv.visitMaxs(20, 20); } /** * Generate a constructor. */ void generateConstructor(int index, String[] paramTypes, int modifiers, ClassWriter cw) { /** offset after params of the args object [] var */ final int argsVar = paramTypes.length + 1; /** offset after params of the ConstructorArgs var */ final int consArgsVar = paramTypes.length + 2; String[] exceptions = null; String methodDescriptor = getMethodDescriptor("V", paramTypes); // Create this constructor method CodeVisitor cv = cw.visitMethod(modifiers, "", methodDescriptor, exceptions); // Generate code to push arguments as an object array generateParameterReifierCode(paramTypes, false/* isStatic */, cv); cv.visitVarInsn(ASTORE, argsVar); // Generate the code implementing the alternate constructor // switch generateConstructorSwitch(index, argsVar, consArgsVar, cv); // Generate code to invoke the ClassGeneratorUtil initInstance() // method // push 'this' cv.visitVarInsn(ALOAD, 0); // Push the class/constructor name as a constant cv.visitLdcInsn(className); // Push arguments as an object array cv.visitVarInsn(ALOAD, argsVar); // invoke the initInstance() method cv.visitMethodInsn(INVOKESTATIC, "bsh/ClassGeneratorUtil", "initInstance", "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V"); cv.visitInsn(RETURN); // Need to calculate this... just fudging here for now. cv.visitMaxs(20, 20); } /** * Generate a switch with a branch for each possible alternate constructor. * This includes all superclass constructors and all constructors of this * class. The default branch of this switch is the default superclass * constructor. *

* This method also generates the code to call the static ClassGeneratorUtil * getConstructorArgs() method which inspects the scripted constructor to * find the alternate constructor signature (if any) and evalute the * arguments at runtime. The getConstructorArgs() method returns the actual * arguments as well as the index of the constructor to call. */ void generateConstructorSwitch(int consIndex, int argsVar, int consArgsVar, CodeVisitor cv) { Label defaultLabel = new Label(); Label endLabel = new Label(); int cases = superConstructors.length + constructors.length; Label[] labels = new Label[cases]; for (int i = 0; i < cases; i++) labels[i] = new Label(); // Generate code to call ClassGeneratorUtil to get our switch // index // and give us args... // push super class name cv.visitLdcInsn(superClass.getName()); // use superClassName // var? // push class static This object cv.visitFieldInsn(GETSTATIC, fqClassName, BSHSTATIC + className, "Lbsh/This;"); // push args cv.visitVarInsn(ALOAD, argsVar); // push this constructor index number onto stack cv.visitIntInsn(BIPUSH, consIndex); // invoke the ClassGeneratorUtil getConstructorsArgs() method cv.visitMethodInsn( INVOKESTATIC, "bsh/ClassGeneratorUtil", "getConstructorArgs", "(Ljava/lang/String;Lbsh/This;[Ljava/lang/Object;I)" + "Lbsh/ClassGeneratorUtil$ConstructorArgs;" ); // store ConstructorArgs in consArgsVar cv.visitVarInsn(ASTORE, consArgsVar); // Get the ConstructorArgs selector field from ConstructorArgs // push ConstructorArgs cv.visitVarInsn(ALOAD, consArgsVar); cv.visitFieldInsn(GETFIELD, "bsh/ClassGeneratorUtil$ConstructorArgs", "selector", "I"); // start switch cv.visitTableSwitchInsn(0/* min */, cases - 1/* max */, defaultLabel, labels); // generate switch body int index = 0; for (int i = 0; i < superConstructors.length; i++, index++) doSwitchBranch(index, superClassName, getTypeDescriptors(superConstructors[i].getParameterTypes()), endLabel, labels, consArgsVar, cv); for (int i = 0; i < constructors.length; i++, index++) doSwitchBranch(index, fqClassName, constructors[i].getParamTypeDescriptors(), endLabel, labels, consArgsVar, cv); // generate the default branch of switch cv.visitLabel(defaultLabel); // default branch always invokes no args super cv.visitVarInsn(ALOAD, 0); // push 'this' cv.visitMethodInsn(INVOKESPECIAL, superClassName, "", "()V"); // done with switch cv.visitLabel(endLabel); } /* * Generate a branch of the constructor switch. This method is called by * generateConstructorSwitch. The code generated by this method assumes that * the argument array is on the stack. */ static void doSwitchBranch(int index, String targetClassName, String[] paramTypes, Label endLabel, Label[] labels, int consArgsVar, CodeVisitor cv) { cv.visitLabel(labels[index]); // cv.visitLineNumber( index, labels[index] ); cv.visitVarInsn(ALOAD, 0); // push this before args // Unload the arguments from the ConstructorArgs object for (int i = 0; i < paramTypes.length; i++) { String type = paramTypes[i]; String method = null; if (type.equals("Z")) method = "getBoolean"; else if (type.equals("B")) method = "getByte"; else if (type.equals("C")) method = "getChar"; else if (type.equals("S")) method = "getShort"; else if (type.equals("I")) method = "getInt"; else if (type.equals("J")) method = "getLong"; else if (type.equals("D")) method = "getDouble"; else if (type.equals("F")) method = "getFloat"; else method = "getObject"; // invoke the iterator method on the ConstructorArgs cv.visitVarInsn(ALOAD, consArgsVar); // push the // ConstructorArgs String className = "bsh/ClassGeneratorUtil$ConstructorArgs"; String retType; if (method.equals("getObject")) retType = OBJECT; else retType = type; cv.visitMethodInsn(INVOKEVIRTUAL, className, method, "()" + retType); // if it's an object type we must do a check cast if (method.equals("getObject")) cv.visitTypeInsn(CHECKCAST, descriptorToClassName(type)); } // invoke the constructor for this branch String descriptor = getMethodDescriptor("V", paramTypes); cv.visitMethodInsn(INVOKESPECIAL, targetClassName, "", descriptor); cv.visitJumpInsn(GOTO, endLabel); } static String getMethodDescriptor(String returnType, String[] paramTypes) { StringBuffer sb = new StringBuffer("("); for (int i = 0; i < paramTypes.length; i++) sb.append(paramTypes[i]); sb.append(")" + returnType); return sb.toString(); } /** * Generate a superclass method delegate accessor method. These methods are * specially named methods which allow access to overridden methods of the * superclass (which the Java reflection API normally does not allow). */ // Maybe combine this with generateMethod() static void generateSuperDelegateMethod(String superClassName, String methodName, String returnType, String[] paramTypes, int modifiers, ClassWriter cw) { String[] exceptions = null; if (returnType == null) // map loose return to Object returnType = OBJECT; String methodDescriptor = getMethodDescriptor(returnType, paramTypes); // Add method body CodeVisitor cv = cw.visitMethod(modifiers, "_bshSuper" + methodName, methodDescriptor, exceptions); cv.visitVarInsn(ALOAD, 0); // Push vars int localVarIndex = 1; for (int i = 0; i < paramTypes.length; ++i) { if (isPrimitive(paramTypes[i])) cv.visitVarInsn(ILOAD, localVarIndex); else cv.visitVarInsn(ALOAD, localVarIndex); localVarIndex += ((paramTypes[i].equals("D") || paramTypes[i].equals("J")) ? 2 : 1); } cv.visitMethodInsn(INVOKESPECIAL, superClassName, methodName, methodDescriptor); generatePlainReturnCode(returnType, cv); // Need to calculate this... just fudging here for now. cv.visitMaxs(20, 20); } boolean classContainsMethod(Class clas, String methodName, String[] paramTypes) { while (clas != null) { Method[] methods = clas.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(methodName)) { String[] methodParamTypes = getTypeDescriptors(methods[i].getParameterTypes()); boolean found = true; for (int j = 0; j < methodParamTypes.length; j++) { if (!paramTypes[j].equals(methodParamTypes[j])) { found = false; break; } } if (found) return true; } } clas = clas.getSuperclass(); } return false; } /** * Generate return code for a normal bytecode */ static void generatePlainReturnCode(String returnType, CodeVisitor cv) { if (returnType.equals("V")) cv.visitInsn(RETURN); else if (isPrimitive(returnType)) { int opcode = IRETURN; if (returnType.equals("D")) opcode = DRETURN; else if (returnType.equals("F")) opcode = FRETURN; else if (returnType.equals("J")) // long opcode = LRETURN; cv.visitInsn(opcode); } else { cv.visitTypeInsn(CHECKCAST, descriptorToClassName(returnType)); cv.visitInsn(ARETURN); } } /** * Generates the code to reify the arguments of the given method. For a * method "int m (int i, String s)", this code is the bytecode corresponding * to the "new Object[] { new bsh.Primitive(i), s }" expression. * * @author Eric Bruneton * @author Pat Niemeyer * @param cv * the code visitor to be used to generate the bytecode. * @param isStatic * the enclosing methods is static. * @param paramTypes * the types of the parameters. */ public static void generateParameterReifierCode(String[] paramTypes, boolean isStatic, final CodeVisitor cv) { cv.visitIntInsn(SIPUSH, paramTypes.length); cv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); int localVarIndex = isStatic ? 0 : 1; for (int i = 0; i < paramTypes.length; ++i) { String param = paramTypes[i]; cv.visitInsn(DUP); cv.visitIntInsn(SIPUSH, i); if (isPrimitive(param)) { int opcode; if (param.equals("F")) { opcode = FLOAD; } else if (param.equals("D")) { opcode = DLOAD; } else if (param.equals("J")) { opcode = LLOAD; } else { opcode = ILOAD; } String type = "bsh/Primitive"; cv.visitTypeInsn(NEW, type); cv.visitInsn(DUP); cv.visitVarInsn(opcode, localVarIndex); String desc = param; // ok? cv.visitMethodInsn(INVOKESPECIAL, type, "", "(" + desc + ")V"); } else { // Technically incorrect here - we need to wrap // null values // as bsh.Primitive.NULL. However the // This.invokeMethod() // will do that much for us. // We need to generate a conditional here to // test for null // and return Primitive.NULL cv.visitVarInsn(ALOAD, localVarIndex); } cv.visitInsn(AASTORE); localVarIndex += ((param.equals("D") || param.equals("J")) ? 2 : 1); } } /** * Generates the code to unreify the result of the given method. For a * method "int m (int i, String s)", this code is the bytecode corresponding * to the "((Integer)...).intValue()" expression. * * @param returnType * the return type. * @param cv * the code visitor to be used to generate the bytecode. * @author Eric Bruneton * @author Pat Niemeyer */ public static void generateReturnCode(String returnType, CodeVisitor cv) { if (returnType.equals("V")) { cv.visitInsn(POP); cv.visitInsn(RETURN); } else if (isPrimitive(returnType)) { int opcode = IRETURN; String type; String meth; if (returnType.equals("B")) { type = "java/lang/Byte"; meth = "byteValue"; } else if (returnType.equals("I")) { type = "java/lang/Integer"; meth = "intValue"; } else if (returnType.equals("Z")) { type = "java/lang/Boolean"; meth = "booleanValue"; } else if (returnType.equals("D")) { opcode = DRETURN; type = "java/lang/Double"; meth = "doubleValue"; } else if (returnType.equals("F")) { opcode = FRETURN; type = "java/lang/Float"; meth = "floatValue"; } else if (returnType.equals("J")) { opcode = LRETURN; type = "java/lang/Long"; meth = "longValue"; } else if (returnType.equals("C")) { type = "java/lang/Character"; meth = "charValue"; } else /* if (returnType.equals("S") ) */ { type = "java/lang/Short"; meth = "shortValue"; } String desc = returnType; cv.visitTypeInsn(CHECKCAST, type); // type is // correct here cv.visitMethodInsn(INVOKEVIRTUAL, type, meth, "()" + desc); cv.visitInsn(opcode); } else { cv.visitTypeInsn(CHECKCAST, descriptorToClassName(returnType)); cv.visitInsn(ARETURN); } } /** * Evaluate the arguments (if any) for the constructor specified by the * constructor index. Return the ConstructorArgs object which contains the * actual arguments to the alternate constructor and also the index of that * constructor for the constructor switch. * * @param superClassName * the class name of the super class. * @param classStaticThis * a static this reference. * @param consArgs * the arguments to the constructor. These are necessary in the * evaluation of the alt constructor args. e.g. Foo(a) { * super(a); } * @param index * the index of the constructor. * @return the ConstructorArgs object containing a constructor selector and * evaluated arguments for the alternate constructor */ public static ConstructorArgs getConstructorArgs(String superClassName, This classStaticThis, Object[] consArgs, int index) { DelayedEvalBshMethod[] constructors; try { constructors = (DelayedEvalBshMethod[]) classStaticThis.getNameSpace().getVariable(BSHCONSTRUCTORS); } catch (Exception e) { throw new InterpreterError("unable to get instance initializer: " + e); } if (index == DEFAULTCONSTRUCTOR) // auto-gen default // constructor return ConstructorArgs.DEFAULT; // use default super // constructor DelayedEvalBshMethod constructor = constructors[index]; if (constructor.methodBody.jjtGetNumChildren() == 0) return ConstructorArgs.DEFAULT; // use default super // constructor // Determine if the constructor calls this() or super() String altConstructor = null; BSHArguments argsNode = null; SimpleNode firstStatement = (SimpleNode) constructor.methodBody.jjtGetChild(0); if (firstStatement instanceof BSHPrimaryExpression) firstStatement = (SimpleNode) firstStatement.jjtGetChild(0); if (firstStatement instanceof BSHMethodInvocation) { BSHMethodInvocation methodNode = (BSHMethodInvocation) firstStatement; BSHAmbiguousName methodName = methodNode.getNameNode(); if (methodName.text.equals("super") || methodName.text.equals("this")) { altConstructor = methodName.text; argsNode = methodNode.getArgsNode(); } } if (altConstructor == null) return ConstructorArgs.DEFAULT; // use default super // constructor // Make a tmp namespace to hold the original constructor args // for // use in eval of the parameters node NameSpace consArgsNameSpace = new NameSpace(classStaticThis.getNameSpace(), "consArgs"); String[] consArgNames = constructor.getParameterNames(); Class[] consArgTypes = constructor.getParameterTypes(); for (int i = 0; i < consArgs.length; i++) { try { consArgsNameSpace.setTypedVariable(consArgNames[i], consArgTypes[i], consArgs[i], null/* modifiers */); } catch (UtilEvalError e) { throw new InterpreterError("err setting local cons arg:" + e); } } // evaluate the args CallStack callstack = new CallStack(); callstack.push(consArgsNameSpace); Object[] args = null; Interpreter interpreter = classStaticThis.declaringInterpreter; try { args = argsNode.getArguments(callstack, interpreter); } catch (EvalError e) { throw new InterpreterError("Error evaluating constructor args: " + e); } Class[] argTypes = Types.getTypes(args); args = Primitive.unwrap(args); Class superClass = interpreter.getClassManager().classForName(superClassName); if (superClass == null) throw new InterpreterError("can't find superclass: " + superClassName); Constructor[] superCons = superClass.getDeclaredConstructors(); // find the matching super() constructor for the args if (altConstructor.equals("super")) { int i = Reflect.findMostSpecificConstructorIndex(argTypes, superCons); if (i == -1) throw new InterpreterError("can't find constructor for args!"); return new ConstructorArgs(i, args); } // find the matching this() constructor for the args Class[][] candidates = new Class[constructors.length][]; for (int i = 0; i < candidates.length; i++) candidates[i] = constructors[i].getParameterTypes(); int i = Reflect.findMostSpecificSignature(argTypes, candidates); if (i == -1) throw new InterpreterError("can't find constructor for args 2!"); // this() constructors come after super constructors in the // table int selector = i + superCons.length; int ourSelector = index + superCons.length; // Are we choosing ourselves recursively through a this() // reference? if (selector == ourSelector) throw new InterpreterError("Recusive constructor call."); return new ConstructorArgs(selector, args); } /** * Initialize an instance of the class. This method is called from the * generated class constructor to evaluate the instance initializer and * scripted constructor in the instance namespace. * * @param instance * the instance to be initialized. * @param className * the class name of the instance. * @param args * the arguments used in the initialization. */ public static void initInstance(Object instance, String className, Object[] args) { Class[] sig = Types.getTypes(args); CallStack callstack = new CallStack(); Interpreter interpreter; NameSpace instanceNameSpace; // check to see if the instance has already been initialized // (the case if using a this() alternate constuctor) This instanceThis = getClassInstanceThis(instance, className); // XXX clean up this conditional if (instanceThis == null) { // Create the instance 'This' namespace, set it on the // object // instance and invoke the instance initializer // Get the static This reference from the proto-instance This classStaticThis = getClassStaticThis(instance.getClass(), className); interpreter = classStaticThis.declaringInterpreter; // Get the instance initializer block from the static // This BSHBlock instanceInitBlock; try { instanceInitBlock = (BSHBlock) classStaticThis.getNameSpace().getVariable(BSHINIT); } catch (Exception e) { throw new InterpreterError("unable to get instance initializer: " + e); } // Create the instance namespace instanceNameSpace = new NameSpace(classStaticThis.getNameSpace(), className); instanceNameSpace.isClass = true; // Set the instance This reference on the instance instanceThis = instanceNameSpace.getThis(interpreter); try { LHS lhs = Reflect.getLHSObjectField(instance, BSHTHIS + className); lhs.assign(instanceThis, false/* strict */); } catch (Exception e) { throw new InterpreterError("Error in class gen setup: " + e); } // Give the instance space its object import instanceNameSpace.setClassInstance(instance); // should use try/finally here to pop ns callstack.push(instanceNameSpace); // evaluate the instance portion of the block in it try { // Evaluate the initializer block instanceInitBlock.evalBlock(callstack, interpreter, true/* override */, ClassGeneratorImpl.ClassNodeFilter.CLASSINSTANCE); } catch (Exception e) { throw new InterpreterError("Error in class initialization: " + e); } callstack.pop(); } else { // The object instance has already been initialzed by // another // constructor. Fall through to invoke the constructor // body below. interpreter = instanceThis.declaringInterpreter; instanceNameSpace = instanceThis.getNameSpace(); } // invoke the constructor method from the instanceThis String constructorName = getBaseName(className); try { // Find the constructor (now in the instance namespace) BshMethod constructor = instanceNameSpace.getMethod(constructorName, sig, true/* declaredOnly */); // if args, we must have constructor if (args.length > 0 && constructor == null) throw new InterpreterError("Can't find constructor: " + className); // Evaluate the constructor if (constructor != null) constructor.invoke(args, interpreter, callstack, null/* callerInfo */, false/* overrideNameSpace */); } catch (Exception e) { if (e instanceof TargetError) e = (Exception) ((TargetError) e).getTarget(); if (e instanceof InvocationTargetException) e = (Exception) ((InvocationTargetException) e).getTargetException(); e.printStackTrace(System.err); throw new InterpreterError("Error in class initialization: " + e); } } /** * Get the static bsh namespace field from the class. * * @param className * may be the name of clas itself or a superclass of clas. */ static This getClassStaticThis(Class clas, String className) { try { return (This) Reflect.getStaticFieldValue(clas, BSHSTATIC + className); } catch (Exception e) { throw new InterpreterError("Unable to get class static space: " + e); } } /** * Get the instance bsh namespace field from the object instance. * * @return the class instance This object or null if the object has not been * initialized. */ static This getClassInstanceThis(Object instance, String className) { try { Object o = Reflect.getObjectFieldValue(instance, BSHTHIS + className); return (This) Primitive.unwrap(o); // unwrap // Primitive.Null // to null } catch (Exception e) { throw new InterpreterError("Generated class: Error getting This" + e); } } /** * Does the type descriptor string describe a primitive type? */ private static boolean isPrimitive(String typeDescriptor) { return typeDescriptor.length() == 1; // right? } static String[] getTypeDescriptors(Class[] cparams) { String[] sa = new String[cparams.length]; for (int i = 0; i < sa.length; i++) sa[i] = BSHType.getTypeDescriptor(cparams[i]); return sa; } /** * If a non-array object type, remove the prefix "L" and suffix ";". */ // Can this be factored out...? // Should be be adding the L...; here instead? private static String descriptorToClassName(String s) { if (s.startsWith("[") || !s.startsWith("L")) return s; return s.substring(1, s.length() - 1); } private static String getBaseName(String className) { int i = className.indexOf("$"); if (i == -1) return className; return className.substring(i + 1); } /** * A ConstructorArgs object holds evaluated arguments for a constructor call * as well as the index of a possible alternate selector to invoke. This * object is used by the constructor switch. * * @see #generateConstructor( int , String [] , int , ClassWriter ) */ public static class ConstructorArgs { /** A ConstructorArgs which calls the default constructor */ public static ConstructorArgs DEFAULT = new ConstructorArgs(); public int selector = DEFAULTCONSTRUCTOR; Object[] args; int arg = 0; /** * The index of the constructor to call. */ ConstructorArgs() {} ConstructorArgs(int selector, Object[] args) { this.selector = selector; this.args = args; } Object next() { return args[arg++]; } public boolean getBoolean() { return ((Boolean) next()).booleanValue(); } public byte getByte() { return ((Byte) next()).byteValue(); } public char getChar() { return ((Character) next()).charValue(); } public short getShort() { return ((Short) next()).shortValue(); } public int getInt() { return ((Integer) next()).intValue(); } public long getLong() { return ((Long) next()).longValue(); } public double getDouble() { return ((Double) next()).doubleValue(); } public float getFloat() { return ((Float) next()).floatValue(); } public Object getObject() { return next(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy