FileDocCategorySizeDatePackage
FieldTransformer.javaAPI DocHibernate 3.2.520941Thu Aug 03 15:35:26 BST 2006org.hibernate.bytecode.javassist

FieldTransformer

public class FieldTransformer extends Object
The thing that handles actual class enhancement in regards to intercepting field accesses.
author
Muga Nishizawa

Fields Summary
private static final String
EACH_READ_METHOD_PREFIX
private static final String
EACH_WRITE_METHOD_PREFIX
private static final String
FIELD_HANDLED_TYPE_NAME
private static final String
HANDLER_FIELD_NAME
private static final String
FIELD_HANDLER_TYPE_NAME
private static final String
HANDLER_FIELD_DESCRIPTOR
private static final String
GETFIELDHANDLER_METHOD_NAME
private static final String
SETFIELDHANDLER_METHOD_NAME
private static final String
GETFIELDHANDLER_METHOD_DESCRIPTOR
private static final String
SETFIELDHANDLER_METHOD_DESCRIPTOR
private org.hibernate.bytecode.javassist.FieldFilter
filter
private HashMap
readableFields
private HashMap
writableFields
Constructors Summary
public FieldTransformer()


	  
		this(null);
	
public FieldTransformer(org.hibernate.bytecode.javassist.FieldFilter f)

		filter = f;
		readableFields = new HashMap();
		writableFields = new HashMap();
	
Methods Summary
private voidaddFieldHandledInterface(javassist.bytecode.ClassFile classfile)

		String[] interfaceNames = classfile.getInterfaces();
		String[] newInterfaceNames = new String[interfaceNames.length + 1];
		System.arraycopy(interfaceNames, 0, newInterfaceNames, 0,
		                 interfaceNames.length);
		newInterfaceNames[newInterfaceNames.length - 1] = FIELD_HANDLED_TYPE_NAME;
		classfile.setInterfaces(newInterfaceNames);
	
private voidaddFieldHandlerField(javassist.bytecode.ClassFile classfile)

		ConstPool cp = classfile.getConstPool();
		FieldInfo finfo = new FieldInfo(cp, HANDLER_FIELD_NAME,
		                                HANDLER_FIELD_DESCRIPTOR);
		finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.TRANSIENT);
		classfile.addField(finfo);
	
private voidaddGetFieldHandlerMethod(javassist.bytecode.ClassFile classfile)

		ConstPool cp = classfile.getConstPool();
		int this_class_index = cp.getThisClassInfo();
		MethodInfo minfo = new MethodInfo(cp, GETFIELDHANDLER_METHOD_NAME,
		                                  GETFIELDHANDLER_METHOD_DESCRIPTOR);
		/* local variable | this | */
		Bytecode code = new Bytecode(cp, 2, 1);
		// aload_0 // load this
		code.addAload(0);
		// getfield // get field "$JAVASSIST_CALLBACK" defined already
		code.addOpcode(Opcode.GETFIELD);
		int field_index = cp.addFieldrefInfo(this_class_index,
		                                     HANDLER_FIELD_NAME, HANDLER_FIELD_DESCRIPTOR);
		code.addIndex(field_index);
		// areturn // return the value of the field
		code.addOpcode(Opcode.ARETURN);
		minfo.setCodeAttribute(code.toCodeAttribute());
		minfo.setAccessFlags(AccessFlag.PUBLIC);
		classfile.addMethod(minfo);
	
private static voidaddInvokeFieldHandlerMethod(javassist.bytecode.ClassFile classfile, javassist.bytecode.Bytecode code, java.lang.String typeName, boolean isReadMethod)

		ConstPool cp = classfile.getConstPool();
		// invokeinterface
		int callback_type_index = cp.addClassInfo(FIELD_HANDLER_TYPE_NAME);
		if ((typeName.charAt(0) == 'L")
		    && (typeName.charAt(typeName.length() - 1) == ';")
		    || (typeName.charAt(0) == '[")) {
			// reference type
			int indexOfL = typeName.indexOf('L");
			String type;
			if (indexOfL == 0) {
				// not array
				type = typeName.substring(1, typeName.length() - 1);
				type = type.replace('/", '.");
			} else if (indexOfL == -1) {
				// array of primitive type
				// do nothing
				type = typeName;
			} else {
				// array of reference type
				type = typeName.replace('/", '.");
			}
			if (isReadMethod) {
				code
						.addInvokeinterface(
								callback_type_index,
								"readObject",
								"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;",
								4);
				// checkcast
				code.addCheckcast(type);
			} else {
				code
						.addInvokeinterface(
								callback_type_index,
								"writeObject",
								"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
								5);
				// checkcast
				code.addCheckcast(type);
			}
		} else if (typeName.equals("Z")) {
			// boolean
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readBoolean",
				                        "(Ljava/lang/Object;Ljava/lang/String;Z)Z", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeBoolean",
				                        "(Ljava/lang/Object;Ljava/lang/String;ZZ)Z", 5);
			}
		} else if (typeName.equals("B")) {
			// byte
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readByte",
				                        "(Ljava/lang/Object;Ljava/lang/String;B)B", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeByte",
				                        "(Ljava/lang/Object;Ljava/lang/String;BB)B", 5);
			}
		} else if (typeName.equals("C")) {
			// char
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readChar",
				                        "(Ljava/lang/Object;Ljava/lang/String;C)C", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeChar",
				                        "(Ljava/lang/Object;Ljava/lang/String;CC)C", 5);
			}
		} else if (typeName.equals("I")) {
			// int
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readInt",
				                        "(Ljava/lang/Object;Ljava/lang/String;I)I", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeInt",
				                        "(Ljava/lang/Object;Ljava/lang/String;II)I", 5);
			}
		} else if (typeName.equals("S")) {
			// short
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readShort",
				                        "(Ljava/lang/Object;Ljava/lang/String;S)S", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeShort",
				                        "(Ljava/lang/Object;Ljava/lang/String;SS)S", 5);
			}
		} else if (typeName.equals("D")) {
			// double
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readDouble",
				                        "(Ljava/lang/Object;Ljava/lang/String;D)D", 5);
			} else {
				code.addInvokeinterface(callback_type_index, "writeDouble",
				                        "(Ljava/lang/Object;Ljava/lang/String;DD)D", 7);
			}
		} else if (typeName.equals("F")) {
			// float
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readFloat",
				                        "(Ljava/lang/Object;Ljava/lang/String;F)F", 4);
			} else {
				code.addInvokeinterface(callback_type_index, "writeFloat",
				                        "(Ljava/lang/Object;Ljava/lang/String;FF)F", 5);
			}
		} else if (typeName.equals("J")) {
			// long
			if (isReadMethod) {
				code.addInvokeinterface(callback_type_index, "readLong",
				                        "(Ljava/lang/Object;Ljava/lang/String;J)J", 5);
			} else {
				code.addInvokeinterface(callback_type_index, "writeLong",
				                        "(Ljava/lang/Object;Ljava/lang/String;JJ)J", 7);
			}
		} else {
			// bad type
			throw new RuntimeException("bad type: " + typeName);
		}
	
private voidaddReadMethod(javassist.bytecode.ClassFile classfile, javassist.bytecode.FieldInfo finfo)

		ConstPool cp = classfile.getConstPool();
		int this_class_index = cp.getThisClassInfo();
		String desc = "()" + finfo.getDescriptor();
		MethodInfo minfo = new MethodInfo(cp, EACH_READ_METHOD_PREFIX
		                                      + finfo.getName(), desc);
		/* local variables | target obj | each oldvalue | */
		Bytecode code = new Bytecode(cp, 5, 3);
		// aload_0
		code.addAload(0);
		// getfield // get each field
		code.addOpcode(Opcode.GETFIELD);
		int base_field_index = cp.addFieldrefInfo(this_class_index, finfo
				.getName(), finfo.getDescriptor());
		code.addIndex(base_field_index);
		// aload_0
		code.addAload(0);
		// invokeinterface // invoke Enabled.getInterceptFieldCallback()
		int enabled_class_index = cp.addClassInfo(FIELD_HANDLED_TYPE_NAME);
		code.addInvokeinterface(enabled_class_index,
		                        GETFIELDHANDLER_METHOD_NAME, GETFIELDHANDLER_METHOD_DESCRIPTOR,
		                        1);
		// ifnonnull
		code.addOpcode(Opcode.IFNONNULL);
		code.addIndex(4);
		// *return // each type
		addTypeDependDataReturn(code, finfo.getDescriptor());
		// *store_1 // each type
		addTypeDependDataStore(code, finfo.getDescriptor(), 1);
		// aload_0
		code.addAload(0);
		// invokeinterface // invoke Enabled.getInterceptFieldCallback()
		code.addInvokeinterface(enabled_class_index,
		                        GETFIELDHANDLER_METHOD_NAME, GETFIELDHANDLER_METHOD_DESCRIPTOR,
		                        1);
		// aload_0
		code.addAload(0);
		// ldc // name of the field
		code.addLdc(finfo.getName());
		// *load_1 // each type
		addTypeDependDataLoad(code, finfo.getDescriptor(), 1);
		// invokeinterface // invoke Callback.read*() // each type
		addInvokeFieldHandlerMethod(classfile, code, finfo.getDescriptor(),
		                            true);
		// *return // each type
		addTypeDependDataReturn(code, finfo.getDescriptor());

		minfo.setCodeAttribute(code.toCodeAttribute());
		minfo.setAccessFlags(AccessFlag.PUBLIC);
		classfile.addMethod(minfo);
	
private voidaddReadWriteMethods(javassist.bytecode.ClassFile classfile)

		List fields = classfile.getFields();
		for (Iterator field_iter = fields.iterator(); field_iter.hasNext();) {
			FieldInfo finfo = (FieldInfo) field_iter.next();
			if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0
			    && (!finfo.getName().equals(HANDLER_FIELD_NAME))) {
				// case of non-static field
				if (filter.handleRead(finfo.getDescriptor(), finfo
						.getName())) {
					addReadMethod(classfile, finfo);
					readableFields.put(finfo.getName(), finfo
							.getDescriptor());
				}
				if (filter.handleWrite(finfo.getDescriptor(), finfo
						.getName())) {
					addWriteMethod(classfile, finfo);
					writableFields.put(finfo.getName(), finfo
							.getDescriptor());
				}
			}
		}
	
private voidaddSetFieldHandlerMethod(javassist.bytecode.ClassFile classfile)

		ConstPool cp = classfile.getConstPool();
		int this_class_index = cp.getThisClassInfo();
		MethodInfo minfo = new MethodInfo(cp, SETFIELDHANDLER_METHOD_NAME,
		                                  SETFIELDHANDLER_METHOD_DESCRIPTOR);
		/* local variables | this | callback | */
		Bytecode code = new Bytecode(cp, 3, 3);
		// aload_0 // load this
		code.addAload(0);
		// aload_1 // load callback
		code.addAload(1);
		// putfield // put field "$JAVASSIST_CALLBACK" defined already
		code.addOpcode(Opcode.PUTFIELD);
		int field_index = cp.addFieldrefInfo(this_class_index,
		                                     HANDLER_FIELD_NAME, HANDLER_FIELD_DESCRIPTOR);
		code.addIndex(field_index);
		// return
		code.addOpcode(Opcode.RETURN);
		minfo.setCodeAttribute(code.toCodeAttribute());
		minfo.setAccessFlags(AccessFlag.PUBLIC);
		classfile.addMethod(minfo);
	
private static voidaddTypeDependDataLoad(javassist.bytecode.Bytecode code, java.lang.String typeName, int i)

		if ((typeName.charAt(0) == 'L")
		    && (typeName.charAt(typeName.length() - 1) == ';")
		    || (typeName.charAt(0) == '[")) {
			// reference type
			code.addAload(i);
		} else if (typeName.equals("Z") || typeName.equals("B")
		           || typeName.equals("C") || typeName.equals("I")
		           || typeName.equals("S")) {
			// boolean, byte, char, int, short
			code.addIload(i);
		} else if (typeName.equals("D")) {
			// double
			code.addDload(i);
		} else if (typeName.equals("F")) {
			// float
			code.addFload(i);
		} else if (typeName.equals("J")) {
			// long
			code.addLload(i);
		} else {
			// bad type
			throw new RuntimeException("bad type: " + typeName);
		}
	
private static voidaddTypeDependDataReturn(javassist.bytecode.Bytecode code, java.lang.String typeName)

		if ((typeName.charAt(0) == 'L")
		    && (typeName.charAt(typeName.length() - 1) == ';")
		    || (typeName.charAt(0) == '[")) {
			// reference type
			code.addOpcode(Opcode.ARETURN);
		} else if (typeName.equals("Z") || typeName.equals("B")
		           || typeName.equals("C") || typeName.equals("I")
		           || typeName.equals("S")) {
			// boolean, byte, char, int, short
			code.addOpcode(Opcode.IRETURN);
		} else if (typeName.equals("D")) {
			// double
			code.addOpcode(Opcode.DRETURN);
		} else if (typeName.equals("F")) {
			// float
			code.addOpcode(Opcode.FRETURN);
		} else if (typeName.equals("J")) {
			// long
			code.addOpcode(Opcode.LRETURN);
		} else {
			// bad type
			throw new RuntimeException("bad type: " + typeName);
		}
	
private static voidaddTypeDependDataStore(javassist.bytecode.Bytecode code, java.lang.String typeName, int i)

		if ((typeName.charAt(0) == 'L")
		    && (typeName.charAt(typeName.length() - 1) == ';")
		    || (typeName.charAt(0) == '[")) {
			// reference type
			code.addAstore(i);
		} else if (typeName.equals("Z") || typeName.equals("B")
		           || typeName.equals("C") || typeName.equals("I")
		           || typeName.equals("S")) {
			// boolean, byte, char, int, short
			code.addIstore(i);
		} else if (typeName.equals("D")) {
			// double
			code.addDstore(i);
		} else if (typeName.equals("F")) {
			// float
			code.addFstore(i);
		} else if (typeName.equals("J")) {
			// long
			code.addLstore(i);
		} else {
			// bad type
			throw new RuntimeException("bad type: " + typeName);
		}
	
private voidaddWriteMethod(javassist.bytecode.ClassFile classfile, javassist.bytecode.FieldInfo finfo)

		ConstPool cp = classfile.getConstPool();
		int this_class_index = cp.getThisClassInfo();
		String desc = "(" + finfo.getDescriptor() + ")V";
		MethodInfo minfo = new MethodInfo(cp, EACH_WRITE_METHOD_PREFIX
		                                      + finfo.getName(), desc);
		/* local variables | target obj | each oldvalue | */
		Bytecode code = new Bytecode(cp, 6, 3);
		// aload_0
		code.addAload(0);
		// invokeinterface // enabled.getInterceptFieldCallback()
		int enabled_class_index = cp.addClassInfo(FIELD_HANDLED_TYPE_NAME);
		code.addInvokeinterface(enabled_class_index,
		                        GETFIELDHANDLER_METHOD_NAME, GETFIELDHANDLER_METHOD_DESCRIPTOR,
		                        1);
		// ifnonnull (label1)
		code.addOpcode(Opcode.IFNONNULL);
		code.addIndex(9);
		// aload_0
		code.addAload(0);
		// *load_1
		addTypeDependDataLoad(code, finfo.getDescriptor(), 1);
		// putfield
		code.addOpcode(Opcode.PUTFIELD);
		int base_field_index = cp.addFieldrefInfo(this_class_index, finfo
				.getName(), finfo.getDescriptor());
		code.addIndex(base_field_index);
		code.growStack(-Descriptor.dataSize(finfo.getDescriptor()));
		// return ;
		code.addOpcode(Opcode.RETURN);
		// aload_0
		code.addAload(0);
		// dup
		code.addOpcode(Opcode.DUP);
		// invokeinterface // enabled.getInterceptFieldCallback()
		code.addInvokeinterface(enabled_class_index,
		                        GETFIELDHANDLER_METHOD_NAME, GETFIELDHANDLER_METHOD_DESCRIPTOR,
		                        1);
		// aload_0
		code.addAload(0);
		// ldc // field name
		code.addLdc(finfo.getName());
		// aload_0
		code.addAload(0);
		// getfield // old value of the field
		code.addOpcode(Opcode.GETFIELD);
		code.addIndex(base_field_index);
		code.growStack(Descriptor.dataSize(finfo.getDescriptor()) - 1);
		// *load_1
		addTypeDependDataLoad(code, finfo.getDescriptor(), 1);
		// invokeinterface // callback.write*(..)
		addInvokeFieldHandlerMethod(classfile, code, finfo.getDescriptor(),
		                            false);
		// putfield // new value of the field
		code.addOpcode(Opcode.PUTFIELD);
		code.addIndex(base_field_index);
		code.growStack(-Descriptor.dataSize(finfo.getDescriptor()));
		// return
		code.addOpcode(Opcode.RETURN);

		minfo.setCodeAttribute(code.toCodeAttribute());
		minfo.setAccessFlags(AccessFlag.PUBLIC);
		classfile.addMethod(minfo);
	
public voidsetFieldFilter(org.hibernate.bytecode.javassist.FieldFilter f)

		filter = f;
	
public voidtransform(java.io.File file)

		DataInputStream in = new DataInputStream(new FileInputStream(file));
		ClassFile classfile = new ClassFile(in);
		transform(classfile);
		DataOutputStream out = new DataOutputStream(new FileOutputStream(file));
		try {
			classfile.write(out);
		} finally {
			out.close();
		}
	
public voidtransform(javassist.bytecode.ClassFile classfile)

		if (classfile.isInterface()) {
			return;
		}
		try {
			addFieldHandlerField(classfile);
			addGetFieldHandlerMethod(classfile);
			addSetFieldHandlerMethod(classfile);
			addFieldHandledInterface(classfile);
			addReadWriteMethods(classfile);
			transformInvokevirtualsIntoPutAndGetfields(classfile);
		} catch (CannotCompileException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	
private inttransformInvokevirtualsIntoGetfields(javassist.bytecode.ClassFile classfile, javassist.bytecode.CodeIterator iter, int pos)

		ConstPool cp = classfile.getConstPool();
		int c = iter.byteAt(pos);
		if (c != Opcode.GETFIELD) {
			return pos;
		}
		int index = iter.u16bitAt(pos + 1);
		String fieldName = cp.getFieldrefName(index);
		String className = cp.getFieldrefClassName(index);
		if ((!classfile.getName().equals(className))
		    || (!readableFields.containsKey(fieldName))) {
			return pos;
		}
		String desc = "()" + (String) readableFields.get(fieldName);
		int read_method_index = cp.addMethodrefInfo(cp.getThisClassInfo(),
		                                            EACH_READ_METHOD_PREFIX + fieldName, desc);
		iter.writeByte(Opcode.INVOKEVIRTUAL, pos);
		iter.write16bit(read_method_index, pos + 1);
		return pos;
	
private voidtransformInvokevirtualsIntoPutAndGetfields(javassist.bytecode.ClassFile classfile)

		List methods = classfile.getMethods();
		for (Iterator method_iter = methods.iterator(); method_iter.hasNext();) {
			MethodInfo minfo = (MethodInfo) method_iter.next();
			String methodName = minfo.getName();
			if (methodName.startsWith(EACH_READ_METHOD_PREFIX)
			    || methodName.startsWith(EACH_WRITE_METHOD_PREFIX)
			    || methodName.equals(GETFIELDHANDLER_METHOD_NAME)
			    || methodName.equals(SETFIELDHANDLER_METHOD_NAME)) {
				continue;
			}
			CodeAttribute codeAttr = minfo.getCodeAttribute();
			if (codeAttr == null) {
				return;
			}
			CodeIterator iter = codeAttr.iterator();
			while (iter.hasNext()) {
				try {
					int pos = iter.next();
					pos = transformInvokevirtualsIntoGetfields(classfile, iter,
					                                           pos);
					pos = transformInvokevirtualsIntoPutfields(classfile, iter,
					                                           pos);

				} catch (BadBytecode e) {
					throw new CannotCompileException(e);
				}
			}
		}
	
private inttransformInvokevirtualsIntoPutfields(javassist.bytecode.ClassFile classfile, javassist.bytecode.CodeIterator iter, int pos)

		ConstPool cp = classfile.getConstPool();
		int c = iter.byteAt(pos);
		if (c != Opcode.PUTFIELD) {
			return pos;
		}
		int index = iter.u16bitAt(pos + 1);
		String fieldName = cp.getFieldrefName(index);
		String className = cp.getFieldrefClassName(index);
		if ((!classfile.getName().equals(className))
		    || (!writableFields.containsKey(fieldName))) {
			return pos;
		}
		String desc = "(" + (String) writableFields.get(fieldName) + ")V";
		int write_method_index = cp.addMethodrefInfo(cp.getThisClassInfo(),
		                                             EACH_WRITE_METHOD_PREFIX + fieldName, desc);
		iter.writeByte(Opcode.INVOKEVIRTUAL, pos);
		iter.write16bit(write_method_index, pos + 1);
		return pos;