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

org.hibernate.bytecode.enhance.spi.Enhancer Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2012, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.bytecode.enhance.spi;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.StackMapTable;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.stackmap.MapMaker;

import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.EnhancementException;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedComposite;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;

/**
 * Class responsible for performing enhancement.
 *
 * @author Steve Ebersole
 * @author Jason Greene
 */
public class Enhancer {
	private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );

	private final EnhancementContext enhancementContext;

	private final ClassPool classPool;
	private final CtClass managedEntityCtClass;
	private final CtClass managedCompositeCtClass;
	private final CtClass attributeInterceptorCtClass;
	private final CtClass attributeInterceptableCtClass;
	private final CtClass entityEntryCtClass;
	private final CtClass objectCtClass;
	private boolean isComposite;

	/**
	 * Constructs the Enhancer, using the given context.
	 *
	 * @param enhancementContext Describes the context in which enhancement will occur so as to give access
	 * to contextual/environmental information.
	 */
	public Enhancer(EnhancementContext enhancementContext) {
		this.enhancementContext = enhancementContext;
		this.classPool = buildClassPool( enhancementContext );

		try {
			// add ManagedEntity contract
			this.managedEntityCtClass = classPool.makeClass(
					ManagedEntity.class.getClassLoader().getResourceAsStream(
							ManagedEntity.class.getName().replace( '.', '/' ) + ".class"
					)
			);

			// add ManagedComposite contract
			this.managedCompositeCtClass = classPool.makeClass(
					ManagedComposite.class.getClassLoader().getResourceAsStream(
							ManagedComposite.class.getName().replace( '.', '/' ) + ".class"
					)
			);

			// add PersistentAttributeInterceptable contract
			this.attributeInterceptableCtClass = classPool.makeClass(
					PersistentAttributeInterceptable.class.getClassLoader().getResourceAsStream(
							PersistentAttributeInterceptable.class.getName().replace( '.', '/' ) + ".class"
					)
			);

			// add PersistentAttributeInterceptor contract
			this.attributeInterceptorCtClass = classPool.makeClass(
					PersistentAttributeInterceptor.class.getClassLoader().getResourceAsStream(
							PersistentAttributeInterceptor.class.getName().replace( '.', '/' ) + ".class"
					)
			);

			// "add" EntityEntry
			this.entityEntryCtClass = classPool.makeClass( EntityEntry.class.getName() );
		}
		catch (IOException e) {
			throw new EnhancementException( "Could not prepare Javassist ClassPool", e );
		}

		try {
			this.objectCtClass = classPool.getCtClass( Object.class.getName() );
		}
		catch (NotFoundException e) {
			throw new EnhancementException( "Could not prepare Javassist ClassPool", e );
		}
	}

	private ClassPool buildClassPool(EnhancementContext enhancementContext) {
		final ClassPool classPool = new ClassPool( false );
		final ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader();
		if ( loadingClassLoader != null ) {
			classPool.appendClassPath( new LoaderClassPath( loadingClassLoader ) );
		}
		return classPool;
	}

	/**
	 * Performs the enhancement.
	 *
	 * @param className The name of the class whose bytecode is being enhanced.
	 * @param originalBytes The class's original (pre-enhancement) byte code
	 *
	 * @return The enhanced bytecode.  Could be the same as the original bytecode if the original was
	 *         already enhanced or we could not enhance it for some reason.
	 *
	 * @throws EnhancementException Indicates a problem performing the enhancement
	 */
	public byte[] enhance(String className, byte[] originalBytes) throws EnhancementException {
		final CtClass managedCtClass;
		try {
			managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
		}
		catch (IOException e) {
			log.unableToBuildEnhancementMetamodel( className );
			return originalBytes;
		}

		enhance( managedCtClass, false );

		return getByteCode( managedCtClass );
	}

	public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException {
		final CtClass managedCtClass;
		try {
			managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
		}
		catch (IOException e) {
			log.unableToBuildEnhancementMetamodel( className );
			return originalBytes;
		}

		enhance( managedCtClass, true );

		return getByteCode( managedCtClass );
	}

	private byte[] getByteCode(CtClass managedCtClass) {
		final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		final DataOutputStream out;
		try {
			out = new DataOutputStream( byteStream );
			try {
				managedCtClass.toBytecode( out );
				return byteStream.toByteArray();
			}
			finally {
				try {
					out.close();
				}
				catch (IOException e) {
					//swallow
				}
			}
		}
		catch (Exception e) {
			log.unableToTransformClass( e.getMessage() );
			throw new HibernateException( "Unable to transform class: " + e.getMessage() );
		}
	}

	private void enhance(CtClass managedCtClass, boolean isComposite) {
		this.isComposite = isComposite;
		final String className = managedCtClass.getName();
		log.debugf( "Enhancing %s", className );

		// can't effectively enhance interfaces
		if ( managedCtClass.isInterface() ) {
			log.debug( "skipping enhancement : interface" );
			return;
		}

		// skip already enhanced classes
		final String[] interfaceNames = managedCtClass.getClassFile2().getInterfaces();
		for ( String interfaceName : interfaceNames ) {
			if ( ManagedEntity.class.getName().equals( interfaceName )
					|| ManagedComposite.class.getName().equals( interfaceName ) ) {
				log.debug( "skipping enhancement : already enhanced" );
				return;
			}
		}

		if ( !isComposite && enhancementContext.isEntityClass( managedCtClass ) ) {
			enhanceAsEntity( managedCtClass );
		}
		else if ( isComposite || enhancementContext.isCompositeClass( managedCtClass ) ) {
			enhanceAsComposite( managedCtClass );
		}
		else {
			log.debug( "skipping enhancement : not entity or composite" );
		}
	}

	private void enhanceAsEntity(CtClass managedCtClass) {
		// add the ManagedEntity interface
		managedCtClass.addInterface( managedEntityCtClass );

		enhancePersistentAttributes( managedCtClass );

		addEntityInstanceHandling( managedCtClass );
		addEntityEntryHandling( managedCtClass );
		addLinkedPreviousHandling( managedCtClass );
		addLinkedNextHandling( managedCtClass );
	}

	private void enhanceAsComposite(CtClass managedCtClass) {
		enhancePersistentAttributes( managedCtClass );
	}

	private void addEntityInstanceHandling(CtClass managedCtClass) {
		// add the ManagedEntity#$$_hibernate_getEntityInstance method
		try {
			managedCtClass.addMethod(
					CtNewMethod.make(
							objectCtClass,
							EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME,
							new CtClass[0],
							new CtClass[0],
							"{ return this; }",
							managedCtClass
					)
			);
		}
		catch (CannotCompileException e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance entity class [%s] to add EntityEntry getter",
							managedCtClass.getName()
					),
					e
			);
		}
	}

	private void addEntityEntryHandling(CtClass managedCtClass) {
		addFieldWithGetterAndSetter(
				managedCtClass,
				entityEntryCtClass,
				EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
				EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
				EnhancerConstants.ENTITY_ENTRY_SETTER_NAME
		);
	}

	private void addLinkedPreviousHandling(CtClass managedCtClass) {
		addFieldWithGetterAndSetter(
				managedCtClass,
				managedEntityCtClass,
				EnhancerConstants.PREVIOUS_FIELD_NAME,
				EnhancerConstants.PREVIOUS_GETTER_NAME,
				EnhancerConstants.PREVIOUS_SETTER_NAME
		);
	}

	private void addLinkedNextHandling(CtClass managedCtClass) {
		addFieldWithGetterAndSetter(
				managedCtClass,
				managedEntityCtClass,
				EnhancerConstants.NEXT_FIELD_NAME,
				EnhancerConstants.NEXT_GETTER_NAME,
				EnhancerConstants.NEXT_SETTER_NAME
		);
	}

	private AnnotationsAttribute getVisibleAnnotations(FieldInfo fieldInfo) {
		AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag );
		if ( annotationsAttribute == null ) {
			annotationsAttribute = new AnnotationsAttribute(
					fieldInfo.getConstPool(),
					AnnotationsAttribute.visibleTag
			);
			fieldInfo.addAttribute( annotationsAttribute );
		}
		return annotationsAttribute;
	}

	private void enhancePersistentAttributes(CtClass managedCtClass) {
		addInterceptorHandling( managedCtClass );
		if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
			addInLineDirtyHandling( managedCtClass );
		}

		final IdentityHashMap attrDescriptorMap
				= new IdentityHashMap();

		for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) {
			attrDescriptorMap.put(
					persistentField.getName(),
					enhancePersistentAttribute( managedCtClass, persistentField )
			);
		}

		// lastly, find all references to the transformed fields and replace with calls to the added reader/writer
		transformFieldAccessesIntoReadsAndWrites( managedCtClass, attrDescriptorMap );
	}

	private PersistentAttributeDescriptor enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) {
		try {
			final AttributeTypeDescriptor typeDescriptor = resolveAttributeTypeDescriptor( persistentField );
			return new PersistentAttributeDescriptor(
					persistentField,
					generateFieldReader( managedCtClass, persistentField, typeDescriptor ),
					generateFieldWriter( managedCtClass, persistentField, typeDescriptor ),
					typeDescriptor
			);
		}
		catch (Exception e) {
			throw new EnhancementException(
					String.format(
							"Unable to enhance persistent attribute [%s:%s]",
							managedCtClass.getName(),
							persistentField.getName()
					),
					e
			);
		}
	}

	private CtField[] collectPersistentFields(CtClass managedCtClass) {
		// todo : drive this from the Hibernate metamodel instance...

		final List persistentFieldList = new ArrayList();
		for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
			// skip static fields
			if ( Modifier.isStatic( ctField.getModifiers() ) ) {
				continue;
			}
			// skip fields added by enhancement
			if ( ctField.getName().startsWith( "$" ) ) {
				continue;
			}
			if ( enhancementContext.isPersistentField( ctField ) ) {
				persistentFieldList.add( ctField );
			}
		}

		return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()] ) );
	}

	private List collectCollectionFields(CtClass managedCtClass) {

		final List collectionList = new ArrayList();
		try {
			for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
				// skip static fields
				if ( Modifier.isStatic( ctField.getModifiers() ) ) {
					continue;
				}
				// skip fields added by enhancement
				if ( ctField.getName().startsWith( "$" ) ) {
					continue;
				}
				if ( enhancementContext.isPersistentField( ctField ) ) {
					for ( CtClass ctClass : ctField.getType().getInterfaces() ) {
						if ( ctClass.getName().equals( "java.util.Collection" ) ) {
							collectionList.add( ctField );
							break;
						}
					}
				}
			}
		}
		catch (NotFoundException ignored) {
		}

		return collectionList;
	}

	private void addInterceptorHandling(CtClass managedCtClass) {
		// interceptor handling is only needed if either:
		//		a) in-line dirty checking has *not* been requested
		//		b) class has lazy-loadable attributes
		if ( enhancementContext.doDirtyCheckingInline( managedCtClass )
				&& !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
			return;
		}

		log.debug( "Weaving in PersistentAttributeInterceptable implementation" );


		// add in the PersistentAttributeInterceptable contract
		managedCtClass.addInterface( attributeInterceptableCtClass );

		addFieldWithGetterAndSetter(
				managedCtClass,
				attributeInterceptorCtClass,
				EnhancerConstants.INTERCEPTOR_FIELD_NAME,
				EnhancerConstants.INTERCEPTOR_GETTER_NAME,
				EnhancerConstants.INTERCEPTOR_SETTER_NAME
		);
	}

	private boolean isClassAlreadyTrackingDirtyStatus(CtClass managedCtClass) {
		try {
			for ( CtClass ctInterface : managedCtClass.getInterfaces() ) {
				if ( ctInterface.getName().equals( SelfDirtinessTracker.class.getName() ) ) {
					return true;
				}
			}
		}
		catch (NotFoundException e) {
			e.printStackTrace();
		}
		return false;
	}

	private void addInLineDirtyHandling(CtClass managedCtClass) {
		try {

			//create composite methods
			if ( isComposite ) {
				managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.CompositeTracker" ) );
				CtClass compositeCtType = classPool.get( "org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker" );
				addField( managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, true );
				createCompositeTrackerMethod( managedCtClass );
			}
			// "normal" entity
			else {
				managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.SelfDirtinessTracker" ) );
				CtClass trackerCtType = classPool.get( "java.util.Set" );
				addField( managedCtClass, trackerCtType, EnhancerConstants.TRACKER_FIELD_NAME, true );

				CtClass collectionTrackerCtType = classPool.get( "org.hibernate.bytecode.enhance.spi.CollectionTracker" );
				addField( managedCtClass, collectionTrackerCtType, EnhancerConstants.TRACKER_COLLECTION_NAME, true );

				createDirtyTrackerMethods( managedCtClass );
			}


		}
		catch (NotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Create all dirty tracker methods
	 */
	private void createDirtyTrackerMethods(CtClass managedCtClass) {
		try {
			String trackerChangeMethod =
					"public void " + EnhancerConstants.TRACKER_CHANGER_NAME + "(String name) {" +
							"  if(" + EnhancerConstants.TRACKER_FIELD_NAME + " == null) {" +
							"    " + EnhancerConstants.TRACKER_FIELD_NAME + " = new java.util.HashSet();" +
							"  }" +
							"  if(!" + EnhancerConstants.TRACKER_FIELD_NAME + ".contains(name)) {" +
							"    " + EnhancerConstants.TRACKER_FIELD_NAME + ".add(name);" +
							"  }" +
							"}";
			managedCtClass.addMethod( CtNewMethod.make( trackerChangeMethod, managedCtClass ) );

			createCollectionDirtyCheckMethod( managedCtClass );
			createCollectionDirtyCheckGetFieldsMethod( managedCtClass );
			//createCompositeFieldsDirtyCheckMethod(managedCtClass);
			//createGetCompositeDirtyFieldsMethod(managedCtClass);

			createHasDirtyAttributesMethod( managedCtClass );

			createClearDirtyCollectionMethod( managedCtClass );
			createClearDirtyMethod( managedCtClass );

			String trackerGetMethod =
					"public java.util.List " + EnhancerConstants.TRACKER_GET_NAME + "() { " +
							"if(" + EnhancerConstants.TRACKER_FIELD_NAME + " == null) " +
							EnhancerConstants.TRACKER_FIELD_NAME + " = new java.util.HashSet();" +
							EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME + "(" +
							EnhancerConstants.TRACKER_FIELD_NAME + ");" +
							"return " + EnhancerConstants.TRACKER_FIELD_NAME + "; }";
			CtMethod getMethod = CtNewMethod.make( trackerGetMethod, managedCtClass );

			MethodInfo methodInfo = getMethod.getMethodInfo();
			SignatureAttribute signatureAttribute =
					new SignatureAttribute( methodInfo.getConstPool(), "()Ljava/util/Set;" );
			methodInfo.addAttribute( signatureAttribute );
			managedCtClass.addMethod( getMethod );

		}
		catch (CannotCompileException e) {
			e.printStackTrace();
		}
		catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private void createTrackChangeCompositeMethod(CtClass managedCtClass) {
		StringBuilder builder = new StringBuilder();
		builder.append( "public void " )
				.append( EnhancerConstants.TRACKER_CHANGER_NAME )
				.append( "(String name) {" )
				.append( "if (" )
				.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
				.append( " != null) " )
				.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
				.append( ".callOwner(\".\"+name); }" );

		System.out.println( "COMPOSITE METHOD: " + builder.toString() );

		try {
			managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
		}
		catch (CannotCompileException e) {
			// swallow
		}
	}

	private void createCompositeTrackerMethod(CtClass managedCtClass) {
		try {
			StringBuilder builder = new StringBuilder();
			builder.append( "public void " )
					.append( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER )
					.append( "(String name, org.hibernate.engine.spi.CompositeOwner tracker) {" )
					.append( "if(" )
					.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
					.append( " == null) " )
					.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
					.append( " = new org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker();" )
					.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
					.append( ".add(name, tracker); }" );

			managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );

			builder = new StringBuilder();
			builder.append( "public void " )
					.append( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER )
					.append( "(String name) {" )
					.append( " if(" )
					.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
					.append( " != null)" )
					.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
					.append( ".removeOwner(name);}" );

			managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
		}
		catch (CannotCompileException e) {
			e.printStackTrace();
		}
	}

	private void createHasDirtyAttributesMethod(CtClass managedCtClass) throws CannotCompileException {
		String trackerHasChangedMethod =
				"public boolean " + EnhancerConstants.TRACKER_HAS_CHANGED_NAME + "() { return (" +
						EnhancerConstants.TRACKER_FIELD_NAME + " != null && !" +
						EnhancerConstants.TRACKER_FIELD_NAME + ".isEmpty()) || " +
						EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME + "(); } ";

		managedCtClass.addMethod( CtNewMethod.make( trackerHasChangedMethod, managedCtClass ) );
	}

	/**
	 * Creates _clearDirtyAttributes
	 */
	private void createClearDirtyMethod(CtClass managedCtClass) throws CannotCompileException, ClassNotFoundException {
		StringBuilder builder = new StringBuilder();
		builder.append( "public void " )
				.append( EnhancerConstants.TRACKER_CLEAR_NAME )
				.append( "() {" )
				.append( "if (" )
				.append( EnhancerConstants.TRACKER_FIELD_NAME )
				.append( " != null) " )
				.append( EnhancerConstants.TRACKER_FIELD_NAME )
				.append( ".clear(); " )
				.append( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME )
				.append( "(); }" );

		managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
	}

	private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException {
		StringBuilder builder = new StringBuilder();
		builder.append( "private void " )
				.append( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME )
				.append( "() { if(" )
				.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
				.append( " == null)" )
				.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
				.append( " = new org.hibernate.bytecode.enhance.spi.CollectionTracker();" );

		for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
			if ( !enhancementContext.isMappedCollection( ctField ) ) {
				builder.append( "if(" )
						.append( ctField.getName() )
						.append( " != null) " )
						.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
						.append( ".add(\"" )
						.append( ctField.getName() )
						.append( "\", " )
						.append( ctField.getName() )
						.append( ".size());" );
			}
		}

		builder.append( "}" );

		managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
	}

	/**
	 * create _areCollectionFieldsDirty
	 */
	private void createCollectionDirtyCheckMethod(CtClass managedCtClass) throws CannotCompileException {
		StringBuilder builder = new StringBuilder( "private boolean " )
				.append( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME )
				.append( "() { if ($$_hibernate_getInterceptor() == null || " )
				.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
				.append( " == null) return false; " );

		for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
			if ( !enhancementContext.isMappedCollection( ctField ) ) {
				builder.append( "if(" )
						.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
						.append( ".getSize(\"" )
						.append( ctField.getName() )
						.append( "\") != " )
						.append( ctField.getName() )
						.append( ".size()) return true;" );
			}
		}

		builder.append( "return false; }" );

		managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
	}

	/**
	 * create _getCollectionFieldDirtyNames
	 */
	private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) throws CannotCompileException {
		StringBuilder collectionFieldDirtyFieldMethod = new StringBuilder( "private void " )
				.append( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME )
				.append( "(java.util.Set trackerSet) { if(" )
				.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
				.append( " == null) return; else {" );

		for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
			if ( !ctField.getName().startsWith( "$$_hibernate" )
					&& !enhancementContext.isMappedCollection( ctField ) ) {
				collectionFieldDirtyFieldMethod
						.append( "if(" )
						.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
						.append( ".getSize(\"" )
						.append( ctField.getName() )
						.append( "\") != " )
						.append( ctField.getName() )
						.append( ".size()) trackerSet.add(\"" )
						.append( ctField.getName() )
						.append( "\");" );
			}
		}

		collectionFieldDirtyFieldMethod.append( "}}" );

		managedCtClass.addMethod( CtNewMethod.make( collectionFieldDirtyFieldMethod.toString(), managedCtClass ) );
	}

	private void addFieldWithGetterAndSetter(
			CtClass targetClass,
			CtClass fieldType,
			String fieldName,
			String getterName,
			String setterName) {
		final CtField theField = addField( targetClass, fieldType, fieldName, true );
		addGetter( targetClass, theField, getterName );
		addSetter( targetClass, theField, setterName );
	}

	private CtField addField(CtClass targetClass, CtClass fieldType, String fieldName, boolean makeTransient) {
		final ConstPool constPool = targetClass.getClassFile().getConstPool();

		final CtField theField;
		try {
			theField = new CtField( fieldType, fieldName, targetClass );
			targetClass.addField( theField );
		}
		catch (CannotCompileException e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance class [%s] to add field [%s]",
							targetClass.getName(),
							fieldName
					),
					e
			);
		}

		// make that new field (1) private, (2) transient and (3) @Transient
		if ( makeTransient ) {
			theField.setModifiers( theField.getModifiers() | Modifier.TRANSIENT );
		}
		theField.setModifiers( Modifier.setPrivate( theField.getModifiers() ) );

		final AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( theField.getFieldInfo() );
		annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) );
		return theField;
	}

	private void addGetter(CtClass targetClass, CtField theField, String getterName) {
		try {
			targetClass.addMethod( CtNewMethod.getter( getterName, theField ) );
		}
		catch (CannotCompileException e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance entity class [%s] to add getter method [%s]",
							targetClass.getName(),
							getterName
					),
					e
			);
		}
	}

	private void addSetter(CtClass targetClass, CtField theField, String setterName) {
		try {
			targetClass.addMethod( CtNewMethod.setter( setterName, theField ) );
		}
		catch (CannotCompileException e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance entity class [%s] to add setter method [%s]",
							targetClass.getName(),
							setterName
					),
					e
			);
		}
	}

	private CtMethod generateFieldReader(
			CtClass managedCtClass,
			CtField persistentField,
			AttributeTypeDescriptor typeDescriptor)
			throws BadBytecode, CannotCompileException {

		final FieldInfo fieldInfo = persistentField.getFieldInfo();
		final String fieldName = fieldInfo.getName();
		final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;

		// read attempts only have to deal lazy-loading support, not dirty checking; so if the field
		// is not enabled as lazy-loadable return a plain simple getter as the reader
		if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
			// not lazy-loadable...
			// EARLY RETURN!!!
			try {
				final CtMethod reader = CtNewMethod.getter( readerName, persistentField );
				managedCtClass.addMethod( reader );
				return reader;
			}
			catch (CannotCompileException e) {
				throw new EnhancementException(
						String.format(
								"Could not enhance entity class [%s] to add field reader method [%s]",
								managedCtClass.getName(),
								readerName
						),
						e
				);
			}
		}

		// temporary solution...
		final String methodBody = typeDescriptor.buildReadInterceptionBodyFragment( fieldName )
				+ " return this." + fieldName + ";";

		try {
			final CtMethod reader = CtNewMethod.make(
					Modifier.PRIVATE,
					persistentField.getType(),
					readerName,
					null,
					null,
					"{" + methodBody + "}",
					managedCtClass
			);
			managedCtClass.addMethod( reader );
			return reader;
		}
		catch (Exception e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance entity class [%s] to add field reader method [%s]",
							managedCtClass.getName(),
							readerName
					),
					e
			);
		}
	}

	private CtMethod generateFieldWriter(
			CtClass managedCtClass,
			CtField persistentField,
			AttributeTypeDescriptor typeDescriptor) {

		final FieldInfo fieldInfo = persistentField.getFieldInfo();
		final String fieldName = fieldInfo.getName();
		final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;

		final CtMethod writer;

		try {
			if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
				// not lazy-loadable...
				writer = CtNewMethod.setter( writerName, persistentField );
			}
			else {
				final String methodBody = typeDescriptor.buildWriteInterceptionBodyFragment( fieldName );
				writer = CtNewMethod.make(
						Modifier.PRIVATE,
						CtClass.voidType,
						writerName,
						new CtClass[] {persistentField.getType()},
						null,
						"{" + methodBody + "}",
						managedCtClass
				);
			}

			if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) && !isComposite ) {
				writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( persistentField ) );
			}

			if ( isComposite ) {
				StringBuilder builder = new StringBuilder();
				builder.append( " if(  " )
						.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
						.append( " != null) " )
						.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
						.append( ".callOwner(\"." )
						.append( persistentField.getName() )
						.append( "\");" );

				writer.insertBefore( builder.toString() );
			}

			//composite types
			if ( persistentField.getAnnotation( Embedded.class ) != null ) {
				//make sure to add the CompositeOwner interface
				if ( !doClassInheritCompositeOwner( managedCtClass ) ) {
					managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.CompositeOwner" ) );
				}
				//if a composite have a embedded field we need to implement the method as well
				if ( isComposite ) {
					createTrackChangeCompositeMethod( managedCtClass );
				}


				writer.insertBefore( cleanupPreviousOwner( persistentField ) );

				writer.insertAfter( compositeMethodBody( persistentField ) );
			}

			managedCtClass.addMethod( writer );
			return writer;
		}
		catch (Exception e) {
			throw new EnhancementException(
					String.format(
							"Could not enhance entity class [%s] to add field writer method [%s]",
							managedCtClass.getName(),
							writerName
					),
					e
			);
		}
	}

	private boolean doClassInheritCompositeOwner(CtClass managedCtClass) {
		try {
			for ( CtClass ctClass : managedCtClass.getInterfaces() ) {
				if ( ctClass.getName().equals( "org.hibernate.engine.spi.CompositeOwner" ) ) {
					return true;
				}
			}

			return false;
		}
		catch (NotFoundException e) {
			return false;
		}
	}

	private String cleanupPreviousOwner(CtField currentValue) {
		StringBuilder builder = new StringBuilder();
		builder.append( "if (" )
				.append( currentValue.getName() )
				.append( " != null) " )
				.append( "((org.hibernate.engine.spi.CompositeTracker)" )
				.append( currentValue.getName() )
				.append( ")." )
				.append( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER )
				.append( "(\"" )
				.append( currentValue.getName() )
				.append( "\");" );

		return builder.toString();
	}

	private String compositeMethodBody(CtField currentValue) {
		StringBuilder builder = new StringBuilder();
		builder.append( "((org.hibernate.engine.spi.CompositeTracker) " )
				.append( currentValue.getName() )
				.append( ").$$_hibernate_setOwner(\"" )
				.append( currentValue.getName() )
				.append( "\",(org.hibernate.engine.spi.CompositeOwner) this);" )
				.append( EnhancerConstants.TRACKER_CHANGER_NAME + "(\"" ).append( currentValue.getName() ).append(
				"\");"
		);

		return builder.toString();
	}

	private void transformFieldAccessesIntoReadsAndWrites(
			CtClass managedCtClass,
			IdentityHashMap attributeDescriptorMap) {

		final ConstPool constPool = managedCtClass.getClassFile().getConstPool();

		for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
			final MethodInfo methodInfo = (MethodInfo) oMethod;
			final String methodName = methodInfo.getName();

			// skip methods added by enhancement
			if ( methodName.startsWith( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX )
					|| methodName.startsWith( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX )
					|| methodName.equals( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME )
					|| methodName.equals( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME )
					|| methodName.equals( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME )
					|| methodName.equals( EnhancerConstants.PREVIOUS_GETTER_NAME )
					|| methodName.equals( EnhancerConstants.PREVIOUS_SETTER_NAME )
					|| methodName.equals( EnhancerConstants.NEXT_GETTER_NAME )
					|| methodName.equals( EnhancerConstants.NEXT_SETTER_NAME ) ) {
				continue;
			}

			final CodeAttribute codeAttr = methodInfo.getCodeAttribute();
			if ( codeAttr == null ) {
				// would indicate an abstract method, continue to next method
				continue;
			}

			try {
				final CodeIterator itr = codeAttr.iterator();
				while ( itr.hasNext() ) {
					final int index = itr.next();
					final int op = itr.byteAt( index );
					if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
						continue;
					}

					final int constIndex = itr.u16bitAt( index + 1 );

					final String fieldName = constPool.getFieldrefName( constIndex );
					final PersistentAttributeDescriptor attributeDescriptor = attributeDescriptorMap.get( fieldName );

					if ( attributeDescriptor == null ) {
						// its not a field we have enhanced for interception, so skip it
						continue;
					}

					log.tracef(
							"Transforming access to field [%s] from method [%s]",
							fieldName,
							methodName
					);

					if ( op == Opcode.GETFIELD ) {
						final int readMethodIndex = constPool.addMethodrefInfo(
								constPool.getThisClassInfo(),
								attributeDescriptor.getReader().getName(),
								attributeDescriptor.getReader().getSignature()
						);
						itr.writeByte( Opcode.INVOKESPECIAL, index );
						itr.write16bit( readMethodIndex, index + 1 );
					}
					else {
						final int writeMethodIndex = constPool.addMethodrefInfo(
								constPool.getThisClassInfo(),
								attributeDescriptor.getWriter().getName(),
								attributeDescriptor.getWriter().getSignature()
						);
						itr.writeByte( Opcode.INVOKESPECIAL, index );
						itr.write16bit( writeMethodIndex, index + 1 );
					}
				}

				final StackMapTable smt = MapMaker.make( classPool, methodInfo );
				methodInfo.getCodeAttribute().setAttribute( smt );
			}
			catch (BadBytecode e) {
				throw new EnhancementException(
						"Unable to perform field access transformation in method : " + methodName,
						e
				);
			}
		}
	}

	private static class PersistentAttributeDescriptor {
		private final CtField field;
		private final CtMethod reader;
		private final CtMethod writer;
		private final AttributeTypeDescriptor typeDescriptor;

		private PersistentAttributeDescriptor(
				CtField field,
				CtMethod reader,
				CtMethod writer,
				AttributeTypeDescriptor typeDescriptor) {
			this.field = field;
			this.reader = reader;
			this.writer = writer;
			this.typeDescriptor = typeDescriptor;
		}

		public CtField getField() {
			return field;
		}

		public CtMethod getReader() {
			return reader;
		}

		public CtMethod getWriter() {
			return writer;
		}

		@SuppressWarnings("UnusedDeclaration")
		public AttributeTypeDescriptor getTypeDescriptor() {
			return typeDescriptor;
		}
	}

	private static interface AttributeTypeDescriptor {
		public String buildReadInterceptionBodyFragment(String fieldName);

		public String buildWriteInterceptionBodyFragment(String fieldName);

		public String buildInLineDirtyCheckingBodyFragment(CtField currentField);
	}

	private AttributeTypeDescriptor resolveAttributeTypeDescriptor(CtField persistentField) throws NotFoundException {
		// for now cheat... we know we only have Object fields
		if ( persistentField.getType() == CtClass.booleanType ) {
			return BOOLEAN_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.byteType ) {
			return BYTE_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.charType ) {
			return CHAR_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.shortType ) {
			return SHORT_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.intType ) {
			return INT_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.longType ) {
			return LONG_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.doubleType ) {
			return DOUBLE_DESCRIPTOR;
		}
		else if ( persistentField.getType() == CtClass.floatType ) {
			return FLOAT_DESCRIPTOR;
		}
		else {
			return new ObjectAttributeTypeDescriptor( persistentField.getType() );
		}
	}

	private abstract static class AbstractAttributeTypeDescriptor implements AttributeTypeDescriptor {
		@Override
		public String buildInLineDirtyCheckingBodyFragment(CtField currentValue) {
			StringBuilder builder = new StringBuilder();
			try {
				//should ignore primary keys
				for ( Object o : currentValue.getType().getAnnotations() ) {
					if ( o instanceof Id ) {
						return "";
					}
				}

				builder.append( entityMethodBody( currentValue ) );


			}
			catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			catch (NotFoundException e) {
				e.printStackTrace();
			}
			return builder.toString();
		}

		private String entityMethodBody(CtField currentValue) {
			StringBuilder inlineBuilder = new StringBuilder();
			try {
				inlineBuilder.append( "if ( $$_hibernate_getInterceptor() != null " );
				//primitives || enums
				if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
					inlineBuilder.append( "&& " ).append( currentValue.getName() ).append( " != $1)" );
				}
				//simple data types
				else if ( currentValue.getType().getName().startsWith( "java.lang" )
						|| currentValue.getType().getName().startsWith( "java.math.Big" )
						|| currentValue.getType().getName().startsWith( "java.sql.Time" )
						|| currentValue.getType().getName().startsWith( "java.sql.Date" )
						|| currentValue.getType().getName().startsWith( "java.util.Date" )
						|| currentValue.getType().getName().startsWith( "java.util.Calendar" ) ) {
					inlineBuilder.append( "&& ((" )
							.append( currentValue.getName() )
							.append( " == null) || (!" )
							.append( currentValue.getName() )
							.append( ".equals( $1))))" );
				}
				//all other objects
				else {
					//if the field is a collection we return since we handle that in a separate method
					for ( CtClass ctClass : currentValue.getType().getInterfaces() ) {
						if ( ctClass.getName().equals( "java.util.Collection" ) ) {

							//if the collection is not managed we should write it to the tracker
							//todo: should use EnhancementContext.isMappedCollection here instead
							if ( currentValue.getAnnotation( OneToMany.class ) != null ||
									currentValue.getAnnotation( ManyToMany.class ) != null ||
									currentValue.getAnnotation( ElementCollection.class ) != null ) {
								return "";
							}
						}
					}

					//todo: for now just call equals, should probably do something else here
					inlineBuilder.append( "&& ((" )
							.append( currentValue.getName() )
							.append( " == null) || (!" )
							.append( currentValue.getName() )
							.append( ".equals( $1))))" );
				}

				inlineBuilder.append( EnhancerConstants.TRACKER_CHANGER_NAME + "(\"" )
						.append( currentValue.getName() )
						.append( "\");" );
			}
			catch (NotFoundException e) {
				e.printStackTrace();
			}
			catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			return inlineBuilder.toString();
		}
	}

	private static class ObjectAttributeTypeDescriptor extends AbstractAttributeTypeDescriptor {
		private final CtClass concreteType;

		private ObjectAttributeTypeDescriptor(CtClass concreteType) {
			this.concreteType = concreteType;
		}

		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = (%2$s) $$_hibernate_getInterceptor().readObject(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName,
					concreteType.getName()
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"%2$s localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = (%2$s) $$_hibernate_getInterceptor().writeObject(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName,
					concreteType.getName()
			);
		}
	}

	private static final AttributeTypeDescriptor BOOLEAN_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readBoolean(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"boolean localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeBoolean(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor BYTE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readByte(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"byte localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeByte(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor CHAR_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readChar(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"char localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeChar(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor SHORT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readShort(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"short localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeShort(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor INT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readInt(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"int localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeInt(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor LONG_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readLong(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"long localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeLong(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor DOUBLE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readDouble(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"double localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeDouble(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};

	private static final AttributeTypeDescriptor FLOAT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
		@Override
		public String buildReadInterceptionBodyFragment(String fieldName) {
			return String.format(
					"if ( $$_hibernate_getInterceptor() != null ) { " +
							"this.%1$s = $$_hibernate_getInterceptor().readFloat(this, \"%1$s\", this.%1$s); " +
							"}",
					fieldName
			);
		}

		@Override
		public String buildWriteInterceptionBodyFragment(String fieldName) {
			return String.format(
					"float localVar = $1;" +
							"if ( $$_hibernate_getInterceptor() != null ) {" +
							"localVar = $$_hibernate_getInterceptor().writeFloat(this, \"%1$s\", this.%1$s, $1);" +
							"}" +
							"this.%1$s = localVar;",
					fieldName
			);
		}
	};
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy