/*
* Copyright 2008 JBoss Inc
*
* Licensed 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.
*/
package org.drools.factmodel;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.drools.RuntimeDroolsException;
import org.mvel2.asm.AnnotationVisitor;
import org.mvel2.asm.ClassVisitor;
import org.mvel2.asm.ClassWriter;
import org.mvel2.asm.FieldVisitor;
import org.mvel2.asm.Label;
import org.mvel2.asm.MethodVisitor;
import org.mvel2.asm.Opcodes;
import org.mvel2.asm.Type;
/**
* A builder to dynamically build simple Javabean(TM) classes
*/
public class ClassBuilder {
private boolean debug = false;
public ClassBuilder() {
this( "true".equalsIgnoreCase( System.getProperty( "org.drools.classbuilder.debug" ) ) );
}
public ClassBuilder(final boolean debug) {
this.debug = debug;
}
/**
* Dynamically builds, defines and loads a class based on the given class definition
*
* @param classDef the class definition object structure
*
* @return the Class instance for the given class definition
*
* @throws IOException
* @throws IntrospectionException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws IllegalArgumentException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws InstantiationException
*/
public byte[] buildClass(ClassDefinition classDef) throws IOException,
IntrospectionException,
SecurityException,
IllegalArgumentException,
ClassNotFoundException,
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException,
InstantiationException,
NoSuchFieldException {
ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS );
//ClassVisitor cw = new CheckClassAdapter(cwr);
this.buildClassHeader( cw,
classDef );
// Building fields
for ( FieldDefinition fieldDef : classDef.getFieldsDefinitions() ) {
if (! fieldDef.isInherited())
this.buildField( cw,
fieldDef );
}
// Building default constructor
this.buildDefaultConstructor( cw,
classDef );
// Building constructor with all fields
if (classDef.getFieldsDefinitions().size() > 0) {
this.buildConstructorWithFields( cw,
classDef,
classDef.getFieldsDefinitions() );
}
// Building constructor with key fields only
List<FieldDefinition> keys = new LinkedList<FieldDefinition>();
for ( FieldDefinition fieldDef : classDef.getFieldsDefinitions() ) {
if ( fieldDef.isKey() ) {
keys.add( fieldDef );
}
}
if ( !keys.isEmpty() && keys.size() != classDef.getFieldsDefinitions().size() ) {
this.buildConstructorWithFields( cw,
classDef,
keys );
}
// Building methods
for ( FieldDefinition fieldDef : classDef.getFieldsDefinitions() ) {
if (! fieldDef.isInherited()) {
this.buildGetMethod( cw,
classDef,
fieldDef );
this.buildSetMethod( cw,
classDef,
fieldDef );
}
}
this.buildEquals( cw,
classDef );
this.buildHashCode( cw,
classDef );
this.buildToString( cw,
classDef );
cw.visitEnd();
byte[] serializedClass = cw.toByteArray();
return serializedClass;
}
/**
* Defines the class header for the given class definition
*
* @param cw
* @param classDef
*/
private void buildClassHeader(ClassVisitor cw,
ClassDefinition classDef) {
String[] original = classDef.getInterfaces();
String[] interfaces = new String[original.length];
for ( int i = 0; i < original.length; i++ ) {
interfaces[i] = getInternalType( original[i] );
}
// Building class header
cw.visit( Opcodes.V1_5,
Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER,
getInternalType( classDef.getClassName() ),
null,
getInternalType( classDef.getSuperClass() ),
interfaces );
buildClassAnnotations(classDef, cw);
cw.visitSource( classDef.getClassName() + ".java",
null );
}
/**
* Creates the field defined by the given FieldDefinition
*
* @param cw
* @param fieldDef
*/
private void buildField(ClassVisitor cw,
FieldDefinition fieldDef) {
FieldVisitor fv;
fv = cw.visitField( Opcodes.ACC_PRIVATE,
fieldDef.getName(),
getTypeDescriptor( fieldDef.getTypeName() ),
null,
null );
buildFieldAnnotations(fieldDef, fv);
fv.visitEnd();
}
/**
* Creates a default constructor for the class
*
* @param cw
*/
private void buildDefaultConstructor(ClassVisitor cw,
ClassDefinition classDef) {
MethodVisitor mv;
// Building default constructor
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor( Type.VOID_TYPE,
new Type[]{} ),
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
mv.visitVarInsn( Opcodes.ALOAD,
0 );
String sup = "";
try {
sup = Type.getInternalName(Class.forName(classDef.getSuperClass()));
} catch (ClassNotFoundException e) {
sup = getInternalType( classDef.getSuperClass() );
}
mv.visitMethodInsn( Opcodes.INVOKESPECIAL,
sup,
"<init>",
Type.getMethodDescriptor( Type.VOID_TYPE,
new Type[]{} ) );
for (FieldDefinition field : classDef.getFieldsDefinitions()) {
if (! field.isInherited()) {
Object val = getDefaultValue(field);
if (val != null) {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn(val);
if (isBoxed(field.getTypeName())) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
getInternalType(field.getTypeName()),
"valueOf",
"("+unBox(field.getTypeName())+")"+getTypeDescriptor(field.getTypeName()));
}
mv.visitFieldInsn( Opcodes.PUTFIELD,
getInternalType( classDef.getClassName() ),
field.getName(),
getTypeDescriptor( field.getTypeName() ) );
}
}
}
mv.visitInsn(Opcodes.RETURN);
Label l1 = null;
if ( this.debug ) {
l1 = new Label();
mv.visitLabel( l1 );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
l1,
0 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
/**
* Creates a constructor that takes and assigns values to all
* fields in the order they are declared.
*
* @param cw
* @param classDef
*/
private void buildConstructorWithFields(ClassVisitor cw,
ClassDefinition classDef,
Collection<FieldDefinition> fieldDefs) {
MethodVisitor mv;
// Building constructor
{
Type[] params = new Type[fieldDefs.size()];
int index = 0;
for ( FieldDefinition field : fieldDefs ) {
params[index++] = Type.getType( getTypeDescriptor( field.getTypeName() ) );
}
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor( Type.VOID_TYPE,
params ),
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
mv.visitVarInsn( Opcodes.ALOAD,
0 );
String sup = "";
try {
sup = Type.getInternalName(Class.forName(classDef.getSuperClass()));
} catch (ClassNotFoundException e) {
sup = getInternalType( classDef.getSuperClass() );
}
mv.visitMethodInsn( Opcodes.INVOKESPECIAL,
sup,
"<init>",
Type.getMethodDescriptor( Type.VOID_TYPE,
new Type[]{} ) );
index = 1; // local vars start at 1, as 0 is "this"
for ( FieldDefinition field : fieldDefs ) {
if ( this.debug ) {
Label l11 = new Label();
mv.visitLabel( l11 );
}
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitVarInsn( Type.getType( getTypeDescriptor( field.getTypeName() ) ).getOpcode( Opcodes.ILOAD ),
index++ );
if ( field.getTypeName().equals( "long" ) || field.getTypeName().equals( "double" ) ) {
// long and double variables use 2 words on the variables table
index++;
}
if (! field.isInherited()) {
mv.visitFieldInsn( Opcodes.PUTFIELD,
getInternalType( classDef.getClassName() ),
field.getName(),
getTypeDescriptor( field.getTypeName() ) );
} else {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
getInternalType(classDef.getClassName()),
field.getWriteMethod(),
Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[]{Type.getType(getTypeDescriptor(field.getTypeName()))}
));
}
}
mv.visitInsn( Opcodes.RETURN );
Label l1 = null;
if ( this.debug ) {
l1 = new Label();
mv.visitLabel( l1 );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
l1,
0 );
for ( FieldDefinition field : classDef.getFieldsDefinitions() ) {
Label l11 = new Label();
mv.visitLabel( l11 );
mv.visitLocalVariable( field.getName(),
getTypeDescriptor( field.getTypeName() ),
null,
l0,
l1,
0 );
}
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
/**
* Creates the set method for the given field definition
*
* @param cw
* @param classDef
* @param fieldDef
*/
private void buildSetMethod(ClassVisitor cw,
ClassDefinition classDef,
FieldDefinition fieldDef) {
MethodVisitor mv;
// set method
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
fieldDef.getWriteMethod(),
Type.getMethodDescriptor( Type.VOID_TYPE,
new Type[]{Type.getType( getTypeDescriptor( fieldDef.getTypeName() ) )} ),
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitVarInsn( Type.getType( getTypeDescriptor( fieldDef.getTypeName() ) ).getOpcode( Opcodes.ILOAD ),
1 );
mv.visitFieldInsn( Opcodes.PUTFIELD,
getInternalType( classDef.getClassName() ),
fieldDef.getName(),
getTypeDescriptor( fieldDef.getTypeName() ) );
mv.visitInsn( Opcodes.RETURN );
Label l1 = null;
if ( this.debug ) {
l1 = new Label();
mv.visitLabel( l1 );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
l1,
0 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
/**
* Creates the get method for the given field definition
*
* @param cw
* @param classDef
* @param fieldDef
*/
private void buildGetMethod(ClassVisitor cw,
ClassDefinition classDef,
FieldDefinition fieldDef) {
MethodVisitor mv;
// Get method
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
fieldDef.getReadMethod(),
Type.getMethodDescriptor( Type.getType( getTypeDescriptor( fieldDef.getTypeName() ) ),
new Type[]{} ),
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitFieldInsn( Opcodes.GETFIELD,
getInternalType( classDef.getClassName() ),
fieldDef.getName(),
getTypeDescriptor( fieldDef.getTypeName() ) );
mv.visitInsn( Type.getType( getTypeDescriptor( fieldDef.getTypeName() ) ).getOpcode( Opcodes.IRETURN ) );
Label l1 = null;
if ( this.debug ) {
l1 = new Label();
mv.visitLabel( l1 );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
l1,
0 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
private void buildEquals(ClassVisitor cw,
ClassDefinition classDef) {
MethodVisitor mv;
// Building equals method
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
"equals",
"(Ljava/lang/Object;)Z",
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
// if ( this == obj ) return true;
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitVarInsn( Opcodes.ALOAD,
1 );
Label l1 = new Label();
mv.visitJumpInsn( Opcodes.IF_ACMPNE,
l1 );
mv.visitInsn( Opcodes.ICONST_1 );
mv.visitInsn( Opcodes.IRETURN );
// if ( obj == null ) return false;
mv.visitLabel( l1 );
mv.visitVarInsn( Opcodes.ALOAD,
1 );
Label l2 = new Label();
mv.visitJumpInsn( Opcodes.IFNONNULL,
l2 );
mv.visitInsn( Opcodes.ICONST_0 );
mv.visitInsn( Opcodes.IRETURN );
// if ( getClass() != obj.getClass() ) return false;
mv.visitLabel( l2 );
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( Object.class ),
"getClass",
Type.getMethodDescriptor( Type.getType( Class.class ),
new Type[]{} ) );
mv.visitVarInsn( Opcodes.ALOAD,
1 );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( Object.class ),
"getClass",
Type.getMethodDescriptor( Type.getType( Class.class ),
new Type[]{} ) );
Label l3 = new Label();
mv.visitJumpInsn( Opcodes.IF_ACMPEQ,
l3 );
mv.visitInsn( Opcodes.ICONST_0 );
mv.visitInsn( Opcodes.IRETURN );
// final <classname> other = (<classname>) obj;
mv.visitLabel( l3 );
mv.visitVarInsn( Opcodes.ALOAD,
1 );
mv.visitTypeInsn( Opcodes.CHECKCAST,
getInternalType( classDef.getClassName() ) );
mv.visitVarInsn( Opcodes.ASTORE,
2 );
// for each key field
int count = 0;
for ( FieldDefinition field : classDef.getFieldsDefinitions() ) {
if ( field.isKey() ) {
count++;
Label goNext = new Label();
if ( isPrimitive( field.getTypeName() ) ) {
// if attr is primitive
// if ( this.<attr> != other.<booleanAttr> ) return false;
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
mv.visitVarInsn(Opcodes.ALOAD,
2);
visitFieldOrGetter(mv, classDef, field);
if ( field.getTypeName().equals( "long" ) ) {
mv.visitInsn( Opcodes.LCMP );
mv.visitJumpInsn( Opcodes.IFEQ,
goNext );
} else if ( field.getTypeName().equals( "double" ) ) {
mv.visitInsn( Opcodes.DCMPL );
mv.visitJumpInsn( Opcodes.IFEQ,
goNext );
} else if ( field.getTypeName().equals( "float" ) ) {
mv.visitInsn( Opcodes.FCMPL );
mv.visitJumpInsn( Opcodes.IFEQ,
goNext );
} else {
// boolean, byte, char, short, int
mv.visitJumpInsn( Opcodes.IF_ICMPEQ,
goNext );
}
mv.visitInsn( Opcodes.ICONST_0 );
mv.visitInsn( Opcodes.IRETURN );
} else {
// if attr is not a primitive
// if ( this.<attr> == null && other.<attr> != null ||
// this.<attr> != null && ! this.<attr>.equals( other.<attr> ) ) return false;
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
Label secondIfPart = new Label();
mv.visitJumpInsn( Opcodes.IFNONNULL,
secondIfPart );
// if ( other.objAttr != null ) return false;
mv.visitVarInsn( Opcodes.ALOAD,
2 );
visitFieldOrGetter(mv, classDef, field);
Label returnFalse = new Label();
mv.visitJumpInsn( Opcodes.IFNONNULL,
returnFalse );
mv.visitLabel( secondIfPart );
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
mv.visitJumpInsn( Opcodes.IFNULL,
goNext );
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
mv.visitVarInsn( Opcodes.ALOAD,
2 );
visitFieldOrGetter(mv, classDef, field);
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
getInternalType( field.getTypeName() ),
"equals",
"(Ljava/lang/Object;)Z" );
mv.visitJumpInsn( Opcodes.IFNE,
goNext );
mv.visitLabel( returnFalse );
mv.visitInsn( Opcodes.ICONST_0 );
mv.visitInsn( Opcodes.IRETURN );
}
mv.visitLabel( goNext );
}
}
if ( count > 0 ) {
mv.visitInsn( Opcodes.ICONST_1 );
} else {
mv.visitInsn( Opcodes.ICONST_0 );
}
mv.visitInsn( Opcodes.IRETURN );
Label lastLabel = null;
if ( this.debug ) {
lastLabel = new Label();
mv.visitLabel( lastLabel );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
lastLabel,
0 );
mv.visitLocalVariable( "obj",
Type.getDescriptor( Object.class ),
null,
l0,
lastLabel,
1 );
mv.visitLocalVariable( "other",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
lastLabel,
2 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
private void buildHashCode(ClassVisitor cw,
ClassDefinition classDef) {
MethodVisitor mv;
// Building hashCode() method
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
"hashCode",
"()I",
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
// int result = 1;
mv.visitInsn( Opcodes.ICONST_1 );
mv.visitVarInsn( Opcodes.ISTORE,
1 );
// for each key field
for ( FieldDefinition field : classDef.getFieldsDefinitions() ) {
if ( field.isKey() ) {
// result = result * 31 + <attr_hash>
mv.visitVarInsn( Opcodes.ILOAD,
1 );
mv.visitIntInsn( Opcodes.BIPUSH,
31 );
mv.visitVarInsn( Opcodes.ILOAD,
1 );
mv.visitInsn( Opcodes.IMUL );
mv.visitVarInsn(Opcodes.ALOAD,
0);
visitFieldOrGetter(mv, classDef, field);
if ( "boolean".equals( field.getTypeName() ) ) {
// attr_hash ::== <boolean_attr> ? 1231 : 1237;
Label blabel1 = new Label();
mv.visitJumpInsn( Opcodes.IFEQ,
blabel1 );
mv.visitIntInsn( Opcodes.SIPUSH,
1231 );
Label blabel2 = new Label();
mv.visitJumpInsn( Opcodes.GOTO,
blabel2 );
mv.visitLabel( blabel1 );
mv.visitIntInsn( Opcodes.SIPUSH,
1237 );
mv.visitLabel( blabel2 );
} else if ( "long".equals( field.getTypeName() ) ) {
// attr_hash ::== (int) (longAttr ^ (longAttr >>> 32))
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
mv.visitIntInsn( Opcodes.BIPUSH,
32 );
mv.visitInsn( Opcodes.LUSHR );
mv.visitInsn( Opcodes.LXOR );
mv.visitInsn( Opcodes.L2I );
} else if ( "float".equals( field.getTypeName() ) ) {
// attr_hash ::== Float.floatToIntBits( floatAttr );
mv.visitMethodInsn( Opcodes.INVOKESTATIC,
Type.getInternalName( Float.class ),
"floatToIntBits",
"(F)I" );
} else if ( "double".equals( field.getTypeName() ) ) {
// attr_hash ::== (int) (Double.doubleToLongBits( doubleAttr ) ^ (Double.doubleToLongBits( doubleAttr ) >>> 32));
mv.visitMethodInsn( Opcodes.INVOKESTATIC,
Type.getInternalName( Double.class ),
"doubleToLongBits",
"(D)J" );
mv.visitInsn( Opcodes.DUP2 );
mv.visitIntInsn( Opcodes.BIPUSH,
32 );
mv.visitInsn( Opcodes.LUSHR );
mv.visitInsn( Opcodes.LXOR );
mv.visitInsn( Opcodes.L2I );
} else if ( !isPrimitive( field.getTypeName() ) ) {
// attr_hash ::== ((objAttr == null) ? 0 : objAttr.hashCode());
Label olabel1 = new Label();
mv.visitJumpInsn( Opcodes.IFNONNULL,
olabel1 );
mv.visitInsn( Opcodes.ICONST_0 );
Label olabel2 = new Label();
mv.visitJumpInsn( Opcodes.GOTO,
olabel2 );
mv.visitLabel( olabel1 );
mv.visitVarInsn( Opcodes.ALOAD,
0 );
visitFieldOrGetter(mv, classDef, field);
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
getInternalType( field.getTypeName() ),
"hashCode",
"()I" );
mv.visitLabel( olabel2 );
}
mv.visitInsn( Opcodes.IADD );
mv.visitVarInsn( Opcodes.ISTORE,
1 );
}
}
mv.visitVarInsn( Opcodes.ILOAD,
1 );
mv.visitInsn( Opcodes.IRETURN );
Label lastLabel = null;
if ( this.debug ) {
lastLabel = new Label();
mv.visitLabel( lastLabel );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
lastLabel,
0 );
mv.visitLocalVariable( "hash",
Type.getDescriptor( int.class ),
null,
l0,
lastLabel,
1 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
private void buildToString(ClassVisitor cw,
ClassDefinition classDef) {
MethodVisitor mv;
{
mv = cw.visitMethod( Opcodes.ACC_PUBLIC,
"toString",
"()Ljava/lang/String;",
null,
null );
mv.visitCode();
Label l0 = null;
if ( this.debug ) {
l0 = new Label();
mv.visitLabel( l0 );
}
// StringBuilder buf = new StringBuilder();
mv.visitTypeInsn( Opcodes.NEW,
Type.getInternalName( StringBuilder.class ) );
mv.visitInsn( Opcodes.DUP );
mv.visitMethodInsn( Opcodes.INVOKESPECIAL,
Type.getInternalName( StringBuilder.class ),
"<init>",
"()V" );
mv.visitVarInsn( Opcodes.ASTORE,
1 );
// buf.append(this.getClass().getSimpleName())
mv.visitVarInsn( Opcodes.ALOAD,
1 );
mv.visitVarInsn( Opcodes.ALOAD,
0 );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
getInternalType( classDef.getClassName() ),
"getClass",
"()Ljava/lang/Class;" );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( Class.class ),
"getSimpleName",
"()Ljava/lang/String;" );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
// buf.append("( ");
mv.visitLdcInsn( "( " );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
boolean previous = false;
for ( FieldDefinition field : classDef.getFieldsDefinitions() ) {
if ( previous ) {
// buf.append(", ");
mv.visitLdcInsn( ", " );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
}
// buf.append(attrName)
mv.visitLdcInsn( field.getName() );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
// buf.append("=");
mv.visitLdcInsn( "=" );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
// buf.append(attrValue)
mv.visitVarInsn(Opcodes.ALOAD,
0);
visitFieldOrGetter(mv,classDef,field);
if ( isPrimitive( field.getTypeName() ) ) {
String type = field.getTypeName().matches( "(byte|short)" ) ? "int" : field.getTypeName();
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
Type.getMethodDescriptor( Type.getType( StringBuilder.class ),
new Type[]{Type.getType( getTypeDescriptor( type ) )} ) );
} else {
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
Type.getMethodDescriptor( Type.getType( StringBuilder.class ),
new Type[]{Type.getType( Object.class )} ) );
}
previous = true;
}
mv.visitLdcInsn( " )" );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;" );
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
Type.getInternalName( StringBuilder.class ),
"toString",
"()Ljava/lang/String;" );
mv.visitInsn( Opcodes.ARETURN );
Label lastLabel = null;
if ( this.debug ) {
lastLabel = new Label();
mv.visitLabel( lastLabel );
mv.visitLocalVariable( "this",
getTypeDescriptor( classDef.getClassName() ),
null,
l0,
lastLabel,
0 );
mv.visitLocalVariable( "buf",
Type.getDescriptor( StringBuilder.class ),
null,
l0,
lastLabel,
1 );
}
mv.visitMaxs( 0,
0 );
mv.visitEnd();
}
}
private void buildClassAnnotations(ClassDefinition classDef, ClassVisitor cw) {
if (classDef.getAnnotations() != null) {
for (AnnotationDefinition ad : classDef.getAnnotations()) {
AnnotationVisitor av = cw.visitAnnotation("L"+getInternalType(ad.getName())+";", true);
for (String key : ad.getValues().keySet()) {
AnnotationDefinition.AnnotationPropertyVal apv = ad.getValues().get(key);
switch (apv.getValType()) {
case STRINGARRAY:
AnnotationVisitor subAv = av.visitArray(apv.getProperty());
Object[] array = (Object[]) apv.getValue();
for (Object o : array) {
subAv.visit(null,o);
}
subAv.visitEnd();
break;
case PRIMARRAY:
av.visit(apv.getProperty(),apv.getValue());
break;
case ENUMARRAY:
AnnotationVisitor subEnav = av.visitArray(apv.getProperty());
Enum[] enArray = (Enum[]) apv.getValue();
String aenumType = "L" + getInternalType(enArray[0].getClass().getName()) + ";";
for (Enum enumer : enArray) {
subEnav.visitEnum(null,aenumType,enumer.name());
}
subEnav.visitEnd();
break;
case CLASSARRAY:
AnnotationVisitor subKlav = av.visitArray(apv.getProperty());
Class[] klarray = (Class[]) apv.getValue();
for (Class klass : klarray) {
subKlav.visit(null,Type.getType("L"+getInternalType(klass.getName())+";"));
}
subKlav.visitEnd();
break;
case ENUMERATION:
String enumType = "L" + getInternalType(apv.getType().getName()) + ";";
av.visitEnum(apv.getProperty(),enumType,((Enum) apv.getValue()).name());
break;
case KLASS:
String klassName = getInternalType(((Class) apv.getValue()).getName());
av.visit(apv.getProperty(),Type.getType("L"+klassName+";"));
break;
case PRIMITIVE:
av.visit(apv.getProperty(),apv.getValue());
break;
case STRING:
av.visit(apv.getProperty(),apv.getValue());
break;
}
}
av.visitEnd();
}
}
}
private void buildFieldAnnotations(FieldDefinition fieldDef, FieldVisitor fv) {
if (fieldDef.getAnnotations() != null) {
for (AnnotationDefinition ad : fieldDef.getAnnotations()) {
AnnotationVisitor av = fv.visitAnnotation("L"+getInternalType(ad.getName())+";", true);
for (String key : ad.getValues().keySet()) {
AnnotationDefinition.AnnotationPropertyVal apv = ad.getValues().get(key);
switch (apv.getValType()) {
case STRINGARRAY:
AnnotationVisitor subAv = av.visitArray(apv.getProperty());
Object[] array = (Object[]) apv.getValue();
for (Object o : array) {
subAv.visit(null,o);
}
subAv.visitEnd();
break;
case PRIMARRAY:
av.visit(apv.getProperty(),apv.getValue());
break;
case ENUMARRAY:
AnnotationVisitor subEnav = av.visitArray(apv.getProperty());
Enum[] enArray = (Enum[]) apv.getValue();
String aenumType = "L" + getInternalType(enArray[0].getClass().getName()) + ";";
for (Enum enumer : enArray) {
subEnav.visitEnum(null,aenumType,enumer.name());
}
subEnav.visitEnd();
break;
case CLASSARRAY:
AnnotationVisitor subKlav = av.visitArray(apv.getProperty());
Class[] klarray = (Class[]) apv.getValue();
for (Class klass : klarray) {
subKlav.visit(null,Type.getType("L"+getInternalType(klass.getName())+";"));
}
subKlav.visitEnd();
break;
case ENUMERATION:
String enumType = "L" + getInternalType(apv.getType().getName()) + ";";
av.visitEnum(apv.getProperty(),enumType,((Enum) apv.getValue()).name());
break;
case KLASS:
String klassName = getInternalType(((Class) apv.getValue()).getName());
av.visit(apv.getProperty(),Type.getType("L"+klassName+";"));
break;
case PRIMITIVE:
av.visit(apv.getProperty(),apv.getValue());
break;
case STRING:
av.visit(apv.getProperty(),apv.getValue());
break;
}
}
av.visitEnd();
}
}
}
/**
* Returns the corresponding internal type representation for the
* given type.
*
* I decided to not use the ASM Type class methods because they require
* resolving the actual type into a Class instance and at this point,
* I think it is best to delay type resolution until it is really needed.
*
* @param type
* @return
*/
private String getInternalType(String type) {
String internalType = null;
if ( "byte".equals( type ) ) {
internalType = "B";
} else if ( "char".equals( type ) ) {
internalType = "C";
} else if ( "double".equals( type ) ) {
internalType = "D";
} else if ( "float".equals( type ) ) {
internalType = "F";
} else if ( "int".equals( type ) ) {
internalType = "I";
} else if ( "long".equals( type ) ) {
internalType = "J";
} else if ( "short".equals( type ) ) {
internalType = "S";
} else if ( "boolean".equals( type ) ) {
internalType = "Z";
} else if ( "void".equals( type ) ) {
internalType = "V";
} else if ( type != null ) {
// I think this will fail for inner classes, but we don't really
// support inner class generation at the moment
internalType = type.replace( '.',
'/' );
}
return internalType;
}
/**
* Returns the corresponding type descriptor for the
* given type.
*
* I decided to not use the ASM Type class methods because they require
* resolving the actual type into a Class instance and at this point,
* I think it is best to delay type resolution until it is really needed.
*
* @param type
* @return
*/
private String getTypeDescriptor(String type) {
String internalType = null;
if ( "byte".equals( type ) ) {
internalType = "B";
} else if ( "char".equals( type ) ) {
internalType = "C";
} else if ( "double".equals( type ) ) {
internalType = "D";
} else if ( "float".equals( type ) ) {
internalType = "F";
} else if ( "int".equals( type ) ) {
internalType = "I";
} else if ( "long".equals( type ) ) {
internalType = "J";
} else if ( "short".equals( type ) ) {
internalType = "S";
} else if ( "boolean".equals( type ) ) {
internalType = "Z";
} else if ( "void".equals( type ) ) {
internalType = "V";
} else if ( type != null ) {
// I think this will fail for inner classes, but we don't really
// support inner class generation at the moment
internalType = "L" + type.replace( '.',
'/' ) + ";";
}
return internalType;
}
/**
* Returns true if the provided type is a primitive type
*
* @param type
* @return
*/
private boolean isPrimitive(String type) {
boolean isPrimitive = false;
if ( "byte".equals( type ) || "char".equals( type ) || "double".equals( type ) || "float".equals( type ) || "int".equals( type ) || "long".equals( type ) || "short".equals( type ) || "boolean".equals( type ) || "void".equals( type ) ) {
isPrimitive = true;
}
return isPrimitive;
}
private void visitFieldOrGetter(MethodVisitor mv, ClassDefinition classDef, FieldDefinition field) {
if (! field.isInherited()) {
mv.visitFieldInsn( Opcodes.GETFIELD,
getInternalType( classDef.getClassName() ),
field.getName(),
getTypeDescriptor( field.getTypeName() ) );
} else {
mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
getInternalType( classDef.getClassName()),
field.getReadMethod(),
Type.getMethodDescriptor( Type.getType( getTypeDescriptor( field.getTypeName() ) ),
new Type[]{} )
);
}
}
private Object getDefaultValue(FieldDefinition fld) {
String type = fld.getTypeName();
if ( "byte".equals( type ) ) {
return fld.getDefaultValueAs_byte();
} else if ( "char".equals( type ) ) {
return fld.getDefaultValueAs_char();
} else if ( "double".equals( type ) ) {
return fld.getDefaultValueAs_double();
} else if ( "float".equals( type ) ) {
return fld.getDefaultValueAs_float();
} else if ( "int".equals( type ) ) {
return fld.getDefaultValueAs_int();
} else if ( "long".equals( type ) ) {
return fld.getDefaultValueAs_long();
} else if ( "short".equals( type ) ) {
return fld.getDefaultValueAs_short();
} else if ( "boolean".equals( type ) ) {
return fld.getDefaultValueAs_boolean();
} else if ( "java.lang.String".equals( type ) ) {
return fld.getDefaultValueAsString();
} else if ( "java.lang.Byte".equals( type ) || "Byte".equals( type )) {
return fld.getDefaultValueAsByte();
} else if ( "java.lang.Character".equals( type ) || "Character".equals( type ) ) {
return fld.getDefaultValueAsChar();
} else if ( "java.lang.Double".equals( type ) || "Double".equals( type )) {
return fld.getDefaultValueAsDouble();
} else if ( "java.lang.Float".equals( type ) || "Float".equals( type )) {
return fld.getDefaultValueAsFloat();
} else if ( "java.lang.Integer".equals( type ) || "Integer".equals( type )) {
return fld.getDefaultValueAsInt();
} else if ( "java.lang.Long".equals( type ) || "Long".equals( type )) {
return fld.getDefaultValueAsLong();
} else if ( "java.lang.Short".equals( type ) || "Short".equals( type )) {
return fld.getDefaultValueAsShort();
} else if ( "java.lang.Boolean".equals( type ) || "Boolean".equals( type )) {
return fld.getDefaultValueAsBoolean();
}
return null;
}
private boolean isBoxed(String type) {
if (type == null) return false;
return "java.lang.Short".equals(type)
|| "java.lang.Byte".equals(type)
|| "java.lang.Character".equals(type)
|| "java.lang.Double".equals(type)
|| "java.lang.Float".equals(type)
|| "java.lang.Integer".equals(type)
|| "java.lang.Boolean".equals(type)
|| "java.lang.Long".equals(type) ;
}
private String unBox(String type) {
if ( "java.lang.Byte".equals( type ) || "Byte".equals( type )) {
return getInternalType("byte");
} else if ( "java.lang.Character".equals( type ) || "Character".equals( type ) ) {
return getInternalType("char");
} else if ( "java.lang.Double".equals( type ) || "Double".equals( type )) {
return getInternalType("double");
} else if ( "java.lang.Float".equals( type ) || "Float".equals( type )) {
return getInternalType("float");
} else if ( "java.lang.Integer".equals( type ) || "Integer".equals( type )) {
return getInternalType("int");
} else if ( "java.lang.Long".equals( type ) || "Long".equals( type )) {
return getInternalType("long");
} else if ( "java.lang.Short".equals( type ) || "Short".equals( type )) {
return getInternalType("short");
} else if ( "java.lang.Boolean".equals( type ) || "Boolean".equals( type )) {
return getInternalType("boolean");
} else {
throw new RuntimeDroolsException("Unable to recognize boxed primitive type " + type);
}
}
}