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

org.hibernate.bytecode.enhance.internal.javassist.PersistentAttributesEnhancer Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.bytecode.enhance.internal.javassist;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.stackmap.MapMaker;

import org.hibernate.Hibernate;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.engine.spi.CompositeOwner;
import org.hibernate.engine.spi.CompositeTracker;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;

/**
 * enhancer for persistent attributes of any type of entity
 *
 * @author Luis Barreiro
 */
public class PersistentAttributesEnhancer extends EnhancerImpl {

	private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesEnhancer.class );

	public PersistentAttributesEnhancer(JavassistEnhancementContext context) {
		super( context );
	}

	public void enhance(CtClass managedCtClass) {
		final IdentityHashMap attrDescriptorMap = new IdentityHashMap();

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

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

		// same thing for direct access to fields of other entities
		if ( this.enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
			extendedEnhancement( managedCtClass );
		}
	}

	private CtField[] collectPersistentFields(CtClass managedCtClass) {
		List persistentFieldList = new ArrayList();
		for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
			// skip static fields and skip fields added by enhancement and  outer reference in inner classes
			if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
				continue;
			}
			if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
				persistentFieldList.add( ctField );
			}
		}
		// HHH-10646 Add fields inherited from @MappedSuperclass
		// HHH-10981 There is no need to do it for @MappedSuperclass
		if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
			persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass ) );
		}

		CtField[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new CtField[0] ) );
		log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) );
		return orderedFields;
	}

	private Collection collectInheritPersistentFields(CtClass managedCtClass) {
		if ( managedCtClass == null || Object.class.getName().equals( managedCtClass.getName() ) ) {
			return Collections.emptyList();
		}
		try {
			CtClass managedCtSuperclass = managedCtClass.getSuperclass();

			if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) {
				return collectInheritPersistentFields( managedCtSuperclass );
			}
			log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass.getName() );
			List persistentFieldList = new ArrayList();

			for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
				if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
					continue;
				}
				if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
					persistentFieldList.add( ctField );
				}
			}
			persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass ) );
			return persistentFieldList;
		}
		catch ( NotFoundException nfe ) {
			log.warnf( "Could not find the superclass of %s", managedCtClass );
			return Collections.emptyList();
		}
	}

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

	protected CtMethod generateFieldReader(
			CtClass managedCtClass,
			CtField persistentField,
			AttributeTypeDescriptor typeDescriptor) {
		String fieldName = persistentField.getName();
		String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
		String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
		CtMethod tmpSuperReader = null;
		CtMethod tmpSuperWriter = null;
		CtMethod reader = null;

		try {
			boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
			String declaredReadFragment = "this." + fieldName + "";
			String superReadFragment = "super." + readerName + "()";

			if ( !declared ) {
				// create a temporary getter on the supper entity to be able to compile our code
				try {
					persistentField.getDeclaringClass().getDeclaredMethod( readerName );
					persistentField.getDeclaringClass().getDeclaredMethod( writerName );
				}
				catch ( NotFoundException nfe ) {
					tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
					tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
				}
			}

			// 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.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
				reader = MethodWriter.write(
						managedCtClass, "public %s %s() {  return %s;%n}",
						persistentField.getType().getName(),
						readerName,
						declared ? declaredReadFragment : superReadFragment
				);
			}
			else {
				reader = MethodWriter.write(
						managedCtClass, "public %s %s() {%n%s%n  return %s;%n}",
						persistentField.getType().getName(),
						readerName,
						typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
						declared ? declaredReadFragment : superReadFragment
				);
			}
			if ( tmpSuperReader != null ) {
				persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
			}
			if ( tmpSuperWriter != null ) {
				persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
			}
			return reader;
		}
		catch (CannotCompileException cce) {
			final String msg = String.format(
					"Could not enhance entity class [%s] to add field reader method [%s]",
					managedCtClass.getName(),
					readerName
			);
			throw new EnhancementException( msg, cce );
		}
		catch (NotFoundException nfe) {
			final String msg = String.format(
					"Could not enhance entity class [%s] to add field reader method [%s]",
					managedCtClass.getName(),
					readerName
			);
			throw new EnhancementException( msg, nfe );
		}
	}

	protected CtMethod generateFieldWriter(
			CtClass managedCtClass,
			CtField persistentField,
			AttributeTypeDescriptor typeDescriptor) {
		String fieldName = persistentField.getName();
		String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
		String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
		CtMethod tmpSuperReader = null;
		CtMethod tmpSuperWriter = null;
		CtMethod writer;

		try {
			boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
			String declaredWriteFragment = "this." + fieldName + "=" + fieldName + ";";
			String superWriteFragment = "super." + writerName + "(" + fieldName + ");";

			if ( !declared ) {
				// create a temporary setter on the supper entity to be able to compile our code
				try {
					persistentField.getDeclaringClass().getDeclaredMethod( readerName );
					persistentField.getDeclaringClass().getDeclaredMethod( writerName );
				}
				catch ( NotFoundException nfe ) {
					tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
					tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
				}
			}

			if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
				writer = MethodWriter.write(
						managedCtClass,
						"public void %s(%s %s) {%n  %s%n}",
						writerName,
						persistentField.getType().getName(),
						fieldName,
						declared ? declaredWriteFragment : superWriteFragment
				);
			}
			else {
				writer = MethodWriter.write(
						managedCtClass,
						"public void %s(%s %s) {%n%s%n}",
						writerName,
						persistentField.getType().getName(),
						fieldName,
						typeDescriptor.buildWriteInterceptionBodyFragment( fieldName )
				);
			}

			if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
				if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
					writer.insertBefore(
							String.format(
									"  if (%1$s != null) { %1$s.callOwner(\"\"); }%n",
									EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
							)
					);
				}
				else {
					writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField ) );
				}

				handleCompositeField( managedCtClass, persistentField, writer );
			}

			if ( enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
				handleBiDirectionalAssociation( managedCtClass, persistentField, writer );
			}

			if ( tmpSuperReader != null ) {
				persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
			}
			if ( tmpSuperWriter != null ) {
				persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
			}
			return writer;
		}
		catch (CannotCompileException cce) {
			final String msg = String.format(
					"Could not enhance entity class [%s] to add field writer method [%s]",
					managedCtClass.getName(),
					writerName
			);
			throw new EnhancementException( msg, cce );
		}
		catch (NotFoundException nfe) {
			final String msg = String.format(
					"Could not enhance entity class [%s] to add field writer method [%s]",
					managedCtClass.getName(),
					writerName
			);
			throw new EnhancementException( msg, nfe );
		}
	}

	private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
			throws NotFoundException, CannotCompileException {
		if ( !PersistentAttributesHelper.isPossibleBiDirectionalAssociation( persistentField ) ) {
			return;
		}
		final CtClass targetEntity = PersistentAttributesHelper.getTargetEntityClass( managedCtClass, persistentField );
		if ( targetEntity == null ) {
			log.infof(
					"Could not find type of bi-directional association for field [%s#%s]",
					managedCtClass.getName(),
					persistentField.getName()
			);
			return;
		}
		final String mappedBy = PersistentAttributesHelper.getMappedBy( persistentField, targetEntity, enhancementContext );
		if ( mappedBy == null || mappedBy.isEmpty() ) {
			log.infof(
					"Could not find bi-directional association for field [%s#%s]",
					managedCtClass.getName(),
					persistentField.getName()
			);
			return;
		}

		// create a temporary getter and setter on the target entity to be able to compile our code
		final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
		final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
		CtMethod getter;
		CtMethod setter;
		boolean tmpTargetMethods = false;
		try {
			getter = targetEntity.getDeclaredMethod( mappedByGetterName );
			setter = targetEntity.getDeclaredMethod( mappedByGetterName );
		}
		catch ( NotFoundException nfe ) {
			getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
			setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
			tmpTargetMethods = true;
		}

		// code fragments to check loaded state. We don't want to trigger lazy loading in association management code
		String currentAssociationLoaded = String.format(
				"%s.isPropertyInitialized(this.%s, \"%s\")",
				Hibernate.class.getName(),
				persistentField.getName(),
				mappedBy
		);
		String targetElementLoaded = String.format(
				"%s.isPropertyInitialized(target, \"%s\")",
				Hibernate.class.getName(),
				mappedBy
		);
		String newAssociationLoaded = String.format(
				"%s.isPropertyInitialized($1, \"%s\")",
				Hibernate.class.getName(),
				mappedBy
		);

		if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ) {
			// only unset when $1 != null to avoid recursion
			fieldWriter.insertBefore(
					String.format(
							"  if (this.%1$s != null && %2$s && $1 != null) { this.%1$s.%3$s(null); }%n",
							persistentField.getName(),
							currentAssociationLoaded,
							mappedBySetterName
					)
			);
			fieldWriter.insertAfter(
					String.format(
							"  if ($1 != null && %s && $1.%s() != this) { $1.%s(this); }%n",
							newAssociationLoaded,
							mappedByGetterName,
							mappedBySetterName
					)
			);
		}
		if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) {
			boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() );
			String toArrayMethod = isMap ? "values().toArray()" : "toArray()";

			// only remove elements not in the new collection or else we would loose those elements
			// don't use iterator to avoid ConcurrentModException
			fieldWriter.insertBefore(
					String.format(
							"  if (this.%3$s != null && %1$s) {%n" +
									"    Object[] array = this.%3$s.%2$s;%n" +
									"    for (int i = 0; i < array.length; i++) {%n" +
									"      %4$s target = (%4$s) array[i];%n" +
									"      if ($1 == null || !$1.contains(target)) { target.%5$s(null); }%n" +
									"    }%n" +
									"  }%n",
							currentAssociationLoaded,
							toArrayMethod,
							persistentField.getName(),
							targetEntity.getName(),
							mappedBySetterName
					)
			);
			fieldWriter.insertAfter(
					String.format(
							"  if ($1 != null && %1$s) {%n" +
									"    Object[] array = $1.%2$s;%n" +
									"    for (int i = 0; i < array.length; i++) {%n" +
									"      %4$s target = (%4$s) array[i];%n" +
									"      if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" +
									"    }%n" +
									"  }%n",
							newAssociationLoaded,
							toArrayMethod,
							targetElementLoaded,
							targetEntity.getName(),
							mappedByGetterName,
							mappedBySetterName
					)
			);
		}
		if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ) {
			fieldWriter.insertBefore(
					String.format(
							"  if (this.%2$s != null && %1$s && this.%2$s.%3$s() != null) { this.%2$s.%3$s().remove(this); }%n",
							currentAssociationLoaded,
							persistentField.getName(),
							mappedByGetterName
					)
			);
			// check .contains(this) to avoid double inserts (but preventing duplicates)
			fieldWriter.insertAfter(
					String.format(
							"  if ($1 != null && %s) {%n" +
									"    java.util.Collection c = $1.%s();%n" +
									"    if (c != null && !c.contains(this)) { c.add(this); }%n" +
									"  }%n",
							newAssociationLoaded,
							mappedByGetterName
					)
			);
		}
		if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) {
			if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) ||
					PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType(), Map.class.getName() ) ) {
				log.infof(
						"Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ",
						managedCtClass.getName(),
						persistentField.getName()
				);
				return;
			}
			fieldWriter.insertBefore(
					String.format(
							"  if (this.%2$s != null && %1$s) {%n" +
									"    Object[] array = this.%2$s.toArray();%n" +
									"    for (int i = 0; i < array.length; i++) {%n" +
									"      %3$s target = (%3$s) array[i];%n" +
									"      if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" +
									"    }%n" +
									"  }%n",
							currentAssociationLoaded,
							persistentField.getName(),
							targetEntity.getName(),
							mappedByGetterName
					)
			);
			fieldWriter.insertAfter(
					String.format(
							"  if ($1 != null && %s) {%n" +
									"    Object[] array = $1.toArray();%n" +
									"    for (int i = 0; i < array.length; i++) {%n" +
									"      %s target = (%s) array[i];%n" +
									"	   if (%s) {%n" +
									"        java.util.Collection c = target.%s();%n" +
									"        if (c != this && c != null) { c.add(this); }%n" +
									"      }%n" +
									"    }%n" +
									"  }%n",
							newAssociationLoaded,
							targetEntity.getName(),
							targetEntity.getName(),
							targetElementLoaded,
							mappedByGetterName
					)
			);
		}
		// implementation note: association management @OneToMany and @ManyToMay works for add() operations but for remove() a snapshot of the collection is needed so we know what associations to break.
		// another approach that could force that behavior would be to return Collections.unmodifiableCollection() ...

		if ( tmpTargetMethods ) {
			targetEntity.removeMethod( getter );
			targetEntity.removeMethod( setter );
		}
	}

	private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
			throws NotFoundException, CannotCompileException {
		if ( !enhancementContext.isCompositeClass( persistentField.getType() ) ||
				!PersistentAttributesHelper.hasAnnotation( persistentField, Embedded.class ) ) {
			return;
		}

		// make sure to add the CompositeOwner interface
		addCompositeOwnerInterface( managedCtClass );

		// cleanup previous owner
		fieldWriter.insertBefore(
				String.format(
						"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
						persistentField.getName(),
						CompositeTracker.class.getName(),
						EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER
				)
		);

		// trigger track changes
		fieldWriter.insertAfter(
				String.format(
						"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this); }%n" +
								"%5$s(\"%1$s\");",
						persistentField.getName(),
						CompositeTracker.class.getName(),
						CompositeOwner.class.getName(),
						EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
						EnhancerConstants.TRACKER_CHANGER_NAME
				)
		);
	}

	private void addCompositeOwnerInterface(CtClass managedCtClass) throws NotFoundException, CannotCompileException {
		CtClass compositeOwnerCtClass = managedCtClass.getClassPool().get( CompositeOwner.class.getName() );

		// HHH-10540 only add the interface once
		for ( CtClass i : managedCtClass.getInterfaces() ) {
			if ( i.subclassOf( compositeOwnerCtClass ) ) {
				return;
			}
		}

		managedCtClass.addInterface( compositeOwnerCtClass );

		if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
			// if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method as well
			MethodWriter.write(
					managedCtClass,
					"public void %1$s(String name) {%n" +
							"  if (%2$s != null) { %2$s.callOwner(\".\" + name); }%n}",
					EnhancerConstants.TRACKER_CHANGER_NAME,
					EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
			);
		}
	}

	protected void enhanceAttributesAccess(
			CtClass managedCtClass,
			IdentityHashMap attributeDescriptorMap) {
		final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
		final ClassPool classPool = managedCtClass.getClassPool();

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

			// skip methods added by enhancement and abstract methods (methods without any code)
			if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
				continue;
			}

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

					// only transform access to fields of the entity being enhanced
					if ( !managedCtClass.getName().equals( constPool.getFieldrefClassName( itr.u16bitAt( index + 1 ) ) ) ) {
						continue;
					}

					final String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
					final PersistentAttributeAccessMethods attributeMethods = attributeDescriptorMap.get( fieldName );

					// its not a field we have enhanced for interception, so skip it
					if ( attributeMethods == null ) {
						continue;
					}
					//System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName, methodName );
					log.debugf( "Transforming access to field [%s] from method [%s]", fieldName, methodName );

					if ( op == Opcode.GETFIELD ) {
						final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getReader() );
						itr.writeByte( Opcode.INVOKEVIRTUAL, index );
						itr.write16bit( methodIndex, index + 1 );
					}
					else {
						final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getWriter() );
						itr.writeByte( Opcode.INVOKEVIRTUAL, index );
						itr.write16bit( methodIndex, index + 1 );
					}
				}
				methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
			}
			catch (BadBytecode bb) {
				final String msg = String.format(
						"Unable to perform field access transformation in method [%s]",
						methodName
				);
				throw new EnhancementException( msg, bb );
			}
		}
	}

	private static class PersistentAttributeAccessMethods {
		private final CtMethod reader;
		private final CtMethod writer;

		private PersistentAttributeAccessMethods(CtMethod reader, CtMethod writer) {
			this.reader = reader;
			this.writer = writer;
		}

		private CtMethod getReader() {
			return reader;
		}

		private CtMethod getWriter() {
			return writer;
		}
	}

	// --- //

	/**
	 * Replace access to fields of entities (for example, entity.field) with a call to the enhanced getter / setter
	 * (in this example, entity.$$_hibernate_read_field()). It's assumed that the target entity is enhanced as well.
	 *
	 * @param aCtClass Class to enhance (not an entity class).
	 */
	public void extendedEnhancement(CtClass aCtClass) {
		final ConstPool constPool = aCtClass.getClassFile().getConstPool();
		final ClassPool classPool = aCtClass.getClassPool();

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

			// skip methods added by enhancement and abstract methods (methods without any code)
			if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
				continue;
			}

			try {
				final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
				while ( itr.hasNext() ) {
					int index = itr.next();
					int op = itr.byteAt( index );
					if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
						continue;
					}
					String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
					String fieldClassName = constPool.getClassInfo( constPool.getFieldrefClass( itr.u16bitAt( index + 1 ) ) );
					CtClass targetCtClass = classPool.getCtClass( fieldClassName );

					if ( !enhancementContext.isEntityClass( targetCtClass ) && !enhancementContext.isCompositeClass( targetCtClass ) ) {
						continue;
					}
					if ( targetCtClass == aCtClass
							|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
							|| PersistentAttributesHelper.hasAnnotation( targetCtClass, fieldName, Id.class )
							|| "this$0".equals( fieldName ) ) {
						continue;
					}

					log.debugf(
							"Extended enhancement: Transforming access to field [%s.%s] from method [%s#%s]",
							fieldClassName,
							fieldName,
							aCtClass.getName(),
							methodName
					);

					if ( op == Opcode.GETFIELD ) {
						int fieldReaderMethodIndex = constPool.addMethodrefInfo(
								constPool.addClassInfo( fieldClassName ),
								EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName,
								"()" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) )
						);
						itr.writeByte( Opcode.INVOKEVIRTUAL, index );
						itr.write16bit( fieldReaderMethodIndex, index + 1 );
					}
					else {
						int fieldWriterMethodIndex = constPool.addMethodrefInfo(
								constPool.addClassInfo( fieldClassName ),
								EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName,
								"(" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) ) + ")V"
						);
						itr.writeByte( Opcode.INVOKEVIRTUAL, index );
						itr.write16bit( fieldWriterMethodIndex, index + 1 );
					}

				}
				methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
			}
			catch (BadBytecode bb) {
				final String msg = String.format(
						"Unable to perform extended enhancement in method [%s]",
						methodName
				);
				throw new EnhancementException( msg, bb );
			}
			catch (NotFoundException nfe) {
				final String msg = String.format(
						"Unable to perform extended enhancement in method [%s]",
						methodName
				);
				throw new EnhancementException( msg, nfe );
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy