/*****************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one                *
 * or more contributor license agreements.  See the NOTICE file              *
 * distributed with this work for additional information                     *
 * regarding copyright ownership.  The ASF licenses this file                *
 * to you under the Apache License, Version 2.0 (the                         *
 * "License"); you may not use this file except in compliance                *
 * with the License.  You may obtain a copy of the License at                *
 *                                                                           *
 *     http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                           *
 * Unless required by applicable law or agreed to in writing,                *
 * software distributed under the License is distributed on an               *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                    *
 * KIND, either express or implied.  See the License for the                 *
 * specific language governing permissions and limitations                   *
 * under the License.                                                        *
 *                                                                           *
 * This file is part of the BeanShell Java Scripting distribution.           *
 * Documentation and updates may be found at http://www.beanshell.org/       *
 * Patrick Niemeyer (pat@pat.net)                                            *
 * Author of Learning Java, O'Reilly & Associates                            *
 *                                                                           *
 *****************************************************************************/

package bsh;

import bsh.org.objectweb.asm.*;
import bsh.org.objectweb.asm.Type;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassGeneratorUtil utilizes the ASM (www.objectweb.org) bytecode generator
 * by Eric Bruneton in order to generate class "stubs" for BeanShell at
 * runtime.
 * <p/>
 * <p/>
 * 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.
 * <p/>
 * <p/>
 * 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.
 * <p/>
 * <p/>
 * 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).
 * <p/>
 *
 * @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)
	 */
	private 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
	 */
	private static final String BSHCONSTRUCTORS = "_bshConstructors";

	/**
	 * The switch branch number for the default constructor.
	 * The value -1 will cause the default branch to be taken.
	 */
	private static final int DEFAULTCONSTRUCTOR = -1;

	private static final String OBJECT = "Ljava/lang/Object;";

	private final String className;
	/**
	 * fully qualified class name (with package) e.g. foo/bar/Blah
	 */
	private final String fqClassName;
	private final Class superClass;
	private final String superClassName;
	private final Class[] interfaces;
	private final Variable[] vars;
	private final Constructor[] superConstructors;
	private final DelayedEvalBshMethod[] constructors;
	private final DelayedEvalBshMethod[] methods;
	private final NameSpace classStaticNameSpace;
	private final Modifiers classModifiers;
	private boolean isInterface;


	/**
	 * @param packageName e.g. "com.foo.bar"
	 */
	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 (DelayedEvalBshMethod bshmethod : bshmethods) {
			if (bshmethod.getName().equals(classBaseName)) {
				consl.add(bshmethod);
                if ((packageName == null) && !Capabilities.haveAccessibility()) {
                    bshmethod.makePublic();
                }
			} else {
				methodsl.add(bshmethod);
			}
		}

		this.constructors = (DelayedEvalBshMethod[]) consl.toArray(new DelayedEvalBshMethod[consl.size()]);
		this.methods = (DelayedEvalBshMethod[]) methodsl.toArray(new DelayedEvalBshMethod[methodsl.size()]);

		try {
			classStaticNameSpace.setLocalVariable(BSHCONSTRUCTORS, constructors, false/*strict*/);
		} catch (UtilEvalError e) {
			throw new InterpreterError("can't set cons var");
		}

		this.isInterface = isInterface;
	}


	/**
	 * Generate 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 + (isInterface ? 0 : 1)]; // one more interface for instance init callback
		for (int i = 0; i < interfaces.length; i++) {
			interfaceNames[i] = Type.getInternalName(interfaces[i]);
		}
		if ( ! isInterface) {
			interfaceNames[interfaces.length] = Type.getInternalName(GeneratedClass.class);
		}

		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 (Variable var : vars) {
			String type = var.getTypeDescriptor();

			// Don't generate private or loosely typed fields
			// Note: loose types aren't currently parsed anyway...
			if (var.hasModifier("private") || type == null) {
				continue;
			}

			int modifiers;
			if (isInterface) {
				modifiers = ACC_PUBLIC | ACC_STATIC | ACC_FINAL;
			} else {
				modifiers = getASMModifiers(var.getModifiers());
			}

			generateField(var.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 (DelayedEvalBshMethod method : methods) {
			String returnType = method.getReturnTypeDescriptor();

			// Don't generate private /*or loosely return typed */ methods
			if (method.hasModifier("private") /*|| returnType == null*/) {
				continue;
			}

			int modifiers = getASMModifiers(method.getModifiers());
			if (isInterface) {
				modifiers |= (ACC_PUBLIC | ACC_ABSTRACT);
			}

			generateMethod(className, fqClassName, method.getName(), returnType, method.getParamTypeDescriptors(), modifiers, cw);

			boolean isStatic = (modifiers & ACC_STATIC) > 0;
			boolean overridden = classContainsMethod(superClass, method.getName(), method.getParamTypeDescriptors());
			if (!isStatic && overridden) {
				generateSuperDelegateMethod(superClassName, method.getName(), returnType, method.getParamTypeDescriptors(), modifiers, cw);
			}
		}

		return cw.toByteArray();
	}


	/**
	 * Translate bsh.Modifiers into ASM modifier bitflags.
	 */
	private 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.
	 */
	private 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.
	 */
	private 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, "<init>", 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", "(L" + GeneratedClass.class.getName().replace('.', '/') + ";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.
	 * <p/>
	 * 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, "<init>", "()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.
		 */


	private 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 (String type : paramTypes) {
			final String method;
			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, "<init>", descriptor);
		cv.visitJumpInsn(GOTO, endLabel);
	}


	private static String getMethodDescriptor(String returnType, String[] paramTypes) {
		StringBuilder sb = new StringBuilder("(");
		for (String paramType : paramTypes) {
			sb.append(paramType);
		}
		sb.append(')').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()
	private 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 (String paramType : paramTypes) {
			if (isPrimitive(paramType)) {
				cv.visitVarInsn(ILOAD, localVarIndex);
			} else {
				cv.visitVarInsn(ALOAD, localVarIndex);
			}
			localVarIndex += ((paramType.equals("D") || paramType.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 (Method method : methods) {
				if (method.getName().equals(methodName)) {
					String[] methodParamTypes = getTypeDescriptors(method.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
	 */
	private 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.
	 *
	 * @param cv	   the code visitor to be used to generate the bytecode.
	 * @param isStatic the enclosing methods is static
	 * @author Eric Bruneton
	 * @author Pat Niemeyer
	 */
	private 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, "<init>", "(" + 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 cv the code visitor to be used to generate the bytecode.
	 * @author Eric Bruneton
	 * @author Pat Niemeyer
	 */
	private 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 consArgs the arguments to the constructor.  These are necessary in
	 *                 the evaluation of the alt constructor args.  e.g. Foo(a) { super(a); }
	 * @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;
		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);
	}


	private static final ThreadLocal<NameSpace> CONTEXT_NAMESPACE = new ThreadLocal<NameSpace>();
	private static final ThreadLocal<Interpreter> CONTEXT_INTERPRETER = new ThreadLocal<Interpreter>();


	/**
	 * Register actual context, used by generated class constructor, which calls
	 * {@link  #initInstance(GeneratedClass, String, Object[])}.
	 */
	static void registerConstructorContext(CallStack callstack, Interpreter interpreter) {
		if (callstack != null) {
			CONTEXT_NAMESPACE.set(callstack.top());
		} else {
			CONTEXT_NAMESPACE.remove();
		}
		if (interpreter != null) {
			CONTEXT_INTERPRETER.set(interpreter);
		} else {
			CONTEXT_INTERPRETER.remove();
		}
	}


	/**
	 * 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.
	 */
	public static void initInstance(GeneratedClass 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)
		// todo PeJoBo70 write test for this
		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 = CONTEXT_INTERPRETER.get();
			if (interpreter == null) {
				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
			if (CONTEXT_NAMESPACE.get() != null) {
				instanceNameSpace = classStaticThis.getNameSpace().copy();
				instanceNameSpace.setParent(CONTEXT_NAMESPACE.get());
			} else {
				instanceNameSpace = new NameSpace(classStaticThis.getNameSpace(), className); // todo: old code
			}
			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*/, ClassGenerator.ClassNodeFilter.CLASSINSTANCE);
			} catch (Exception e) {
				throw new InterpreterError("Error in class initialization: " + e, 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();
			}
			throw new InterpreterError("Error in class initialization: " + e, e);
		}
	}


	/**
	 * Get the static bsh namespace field from the class.
	 *
	 * @param className may be the name of clas itself or a superclass of clas.
	 */
	private 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?
	}


	private 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 bsh.ClassGeneratorUtil#generateConstructor(int, String[], int, bsh.org.objectweb.asm.ClassWriter)
	 */
	public static class ConstructorArgs {

		/**
		 * A ConstructorArgs which calls the default constructor
		 */
		public static final ConstructorArgs DEFAULT = new ConstructorArgs();

		public int selector = DEFAULTCONSTRUCTOR;
		Object[] args;
		int arg;


		/**
		 * 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();
		}


		public byte getByte() {
			return (Byte) next();
		}


		public char getChar() {
			return (Character) next();
		}


		public short getShort() {
			return (Short) next();
		}


		public int getInt() {
			return (Integer) next();
		}


		public long getLong() {
			return (Long) next();
		}


		public double getDouble() {
			return (Double) next();
		}


		public float getFloat() {
			return (Float) next();
		}


		public Object getObject() {
			return next();
		}
	}


}
