alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*****************************************************************************
 *                                                                           *
 *  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 (pat@pat.net)                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/

package bsh;

import org.objectweb.asm.Constants;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
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.  
	

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; /** @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( 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; } /** Generate the class bytecode for this class. @param className should be a path style name, e.g. "TestClass" or "mypackage/TestClass" */ 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 0 ; boolean overridden = classContainsMethod( superClass, methods[i].getName(), methods[i].getParamTypeDescriptors() ) ; if ( !isStatic && overridden ) generateSuperDelegateMethod( superClass, 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", "()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", descriptor ); cv.visitJumpInsn( GOTO, endLabel ); } static String getMethodDescriptor( String returnType, String [] paramTypes ) { StringBuffer sb = new StringBuffer("("); for(int i=0; i", "(" + 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 m a method object. @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 args 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 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.getStaticField( 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.getObjectField( 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

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.