diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java new file mode 100644 index 0000000000..a75f95abd6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java @@ -0,0 +1,207 @@ +/* + * 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.internal; + +import javassist.CtClass; +import javassist.CtField; +import javassist.NotFoundException; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; + +import javax.persistence.Id; +import java.util.Collection; + +/** + * utility class to generate interceptor methods + * @see org.hibernate.engine.spi.PersistentAttributeInterceptor + * + * @author Luis Barreiro + */ +public abstract class AttributeTypeDescriptor { + + public abstract String buildReadInterceptionBodyFragment(String fieldName); + + public abstract String buildWriteInterceptionBodyFragment(String fieldName); + + public String buildInLineDirtyCheckingBodyFragment(EnhancementContext context, CtField currentValue) { + final StringBuilder builder = new StringBuilder(); + try { + // should ignore primary keys + for ( Object o : currentValue.getType().getAnnotations() ) { + if ( o instanceof Id) { + return ""; + } + } + builder.append( String.format( "if (%s() != null", EnhancerConstants.INTERCEPTOR_GETTER_NAME ) ); + + // primitives || enums + if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) { + builder.append( String.format( " && %s != $1)", currentValue.getName()) ); + } + // 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" ) ) { + builder.append( String.format( "&& ((%s == null) || (!% primitiveType) { + if ( !primitiveType.isPrimitive() ) { + throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" ); + } + // capitalize first letter + this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase() + primitiveType.getSimpleName().substring( 1 ); + } + + public String buildReadInterceptionBodyFragment(String fieldName) { + return String.format( "" + + "if (%3$s() != null ) {%n" + + " this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s);%n" + + "}", + fieldName, + type, + EnhancerConstants.INTERCEPTOR_GETTER_NAME ); + } + + public String buildWriteInterceptionBodyFragment(String fieldName) { + return String.format( "" + + "%2$s localVar = $1;%n" + + "if ( %4$s() != null ) {%n" + + " localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1);%n" + + "}%n" + + "this.%1$s = localVar;", + fieldName, + type.toLowerCase(), + type, + EnhancerConstants.INTERCEPTOR_GETTER_NAME + ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/CompositeEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/CompositeEnhancer.java new file mode 100644 index 0000000000..2cc3473ac5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/CompositeEnhancer.java @@ -0,0 +1,97 @@ +/* + * 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.internal; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.NotFoundException; +import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.engine.spi.CompositeOwner; +import org.hibernate.engine.spi.CompositeTracker; + +/** + * enhancer for composite (embeddable) entities + * + * @author Luis Barreiro + */ +public class CompositeEnhancer extends Enhancer { + + public CompositeEnhancer(EnhancementContext context) { + super( context ); + } + + public void enhance(CtClass managedCtClass) { + addInterceptorHandling( managedCtClass ); + + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + addInLineDirtyHandling( managedCtClass ); + } + + new PersistentAttributesEnhancer( enhancementContext ).enhance( managedCtClass ); + } + + /* --- */ + + private void addInLineDirtyHandling(CtClass managedCtClass) { + try { + managedCtClass.addInterface( classPool.get( CompositeTracker.class.getName() ) ); + + final CtClass compositeCtType = classPool.get( CompositeOwnerTracker.class.getName() ); + FieldWriter.addField( managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME ); + + createCompositeTrackerMethod( managedCtClass ); + } + catch (NotFoundException nfe) { + nfe.printStackTrace(); + } + } + + private void createCompositeTrackerMethod(CtClass managedCtClass) { + try { + MethodWriter.write( managedCtClass, "" + + "public void %1$s(String name, %3$s tracker) {%n" + + " if (%2$s == null) { %2$s = new %4$s(); }%n" + + " %2$s.add(name, tracker);%n" + + "}", + EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, + EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, + CompositeOwner.class.getName(), + CompositeOwnerTracker.class.getName() ); + + MethodWriter.write( managedCtClass, "" + + "public void %1$s(String name) {%n" + + " if (%2$s != null) { %2$s.removeOwner(name); }%n" + + "}", + EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, + EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME ); + } + catch (CannotCompileException cce) { + cce.printStackTrace(); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/EntityEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/EntityEnhancer.java new file mode 100644 index 0000000000..5f78e9fc6f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/EntityEnhancer.java @@ -0,0 +1,292 @@ +/* + * 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.internal; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtField; +import javassist.Modifier; +import javassist.NotFoundException; +import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker; +import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.engine.spi.SelfDirtinessTracker; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * enhancer for regular entities + * + * @author Luis Barreiro + */ +public class EntityEnhancer extends Enhancer { + + public EntityEnhancer(EnhancementContext context) { + super( context ); + } + + // for very small sizes SimpleDirtyTracker implementation ends up being faster + private static final String TRACKER_IMPL = SimpleDirtyTracker.class.getName(); + + public void enhance(CtClass managedCtClass) { + // add the ManagedEntity interface + managedCtClass.addInterface( managedEntityCtClass ); + + addEntityInstanceHandling( managedCtClass ); + addEntityEntryHandling( managedCtClass ); + addLinkedPreviousHandling( managedCtClass ); + addLinkedNextHandling( managedCtClass ); + addInterceptorHandling( managedCtClass ); + + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + addInLineDirtyHandling( managedCtClass ); + } + + new PersistentAttributesEnhancer( enhancementContext ).enhance( managedCtClass ); + } + + /* -- */ + + private void addEntityInstanceHandling(CtClass managedCtClass) { + try { + MethodWriter.write( managedCtClass, "public Object %s() { return this; }", EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME ); + } + catch (CannotCompileException cce) { + final String msg = String.format( "Could not enhance entity class [%s] to add EntityEntry getter", managedCtClass.getName() ); + throw new EnhancementException(msg, cce); + } + } + + /* -- */ + + private void addEntityEntryHandling(CtClass managedCtClass) { + FieldWriter.addFieldWithGetterAndSetter( managedCtClass, entityEntryCtClass, + EnhancerConstants.ENTITY_ENTRY_FIELD_NAME, + EnhancerConstants.ENTITY_ENTRY_GETTER_NAME, + EnhancerConstants.ENTITY_ENTRY_SETTER_NAME ); + } + + private void addLinkedPreviousHandling(CtClass managedCtClass) { + FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass, + EnhancerConstants.PREVIOUS_FIELD_NAME, + EnhancerConstants.PREVIOUS_GETTER_NAME, + EnhancerConstants.PREVIOUS_SETTER_NAME ); + } + + private void addLinkedNextHandling(CtClass managedCtClass) { + FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass, + EnhancerConstants.NEXT_FIELD_NAME, + EnhancerConstants.NEXT_GETTER_NAME, + EnhancerConstants.NEXT_SETTER_NAME ); + } + + /* --- */ + + private void addInLineDirtyHandling(CtClass managedCtClass) { + try { + managedCtClass.addInterface( classPool.get( SelfDirtinessTracker.class.getName() ) ); + + FieldWriter.addField( managedCtClass, classPool.get( TRACKER_IMPL ), EnhancerConstants.TRACKER_FIELD_NAME ); + FieldWriter.addField( managedCtClass, classPool.get( CollectionTracker.class.getName() ), EnhancerConstants.TRACKER_COLLECTION_NAME ); + + createDirtyTrackerMethods( managedCtClass ); + } + catch (NotFoundException nfe) { + nfe.printStackTrace(); + } + } + + private void createDirtyTrackerMethods(CtClass managedCtClass) { + try { + MethodWriter.write( managedCtClass, "" + + "public void %1$s(String name) {%n" + + " if (%2$s == null) { %2$s = new %3$s(); }%n" + + " %2$s.add(name);%n" + + "}", + EnhancerConstants.TRACKER_CHANGER_NAME, + EnhancerConstants.TRACKER_FIELD_NAME, + TRACKER_IMPL ); + + /* --- */ + + createCollectionDirtyCheckMethod( managedCtClass ); + createCollectionDirtyCheckGetFieldsMethod( managedCtClass ); + createClearDirtyCollectionMethod( managedCtClass ); + + /* --- */ + + MethodWriter.write( managedCtClass, "" + + "public java.util.Set %1$s() {%n" + + " if (%2$s == null) { %2$s = new %4$s(); }%n" + + " %3$s(%2$s);%n" + + " return %2$s.asSet();%n" + + "}", + EnhancerConstants.TRACKER_GET_NAME, + EnhancerConstants.TRACKER_FIELD_NAME, + EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, + TRACKER_IMPL ); + + MethodWriter.write( managedCtClass, "" + + "public boolean %1$s() {%n" + + " return (%2$s != null && !%2$s.isEmpty()) || %3$s();%n" + + "}", + EnhancerConstants.TRACKER_HAS_CHANGED_NAME, + EnhancerConstants.TRACKER_FIELD_NAME, + EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME ); + + MethodWriter.write( managedCtClass, "" + + "public void %1$s() {%n" + + " if (%2$s != null) { %2$s.clear(); }%n" + + " %3$s();%n" + + "}", + EnhancerConstants.TRACKER_CLEAR_NAME, + EnhancerConstants.TRACKER_FIELD_NAME, + EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME ); + } + catch (CannotCompileException cce) { + cce.printStackTrace(); + } + } + + /* -- */ + + private List collectCollectionFields(CtClass managedCtClass) { + final List collectionList = new LinkedList(); + try { + for ( CtField ctField : managedCtClass.getDeclaredFields() ) { + // skip static fields and skip fields added by enhancement + if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { + continue; + } + if ( enhancementContext.isPersistentField( ctField ) ) { + for ( CtClass ctClass : ctField.getType().getInterfaces() ) { + if ( ctClass.getName().equals( Collection.class.getName() ) ) { + collectionList.add( ctField ); + break; + } + } + } + } + } + catch (NotFoundException ignored) { + } + return collectionList; + } + + private void createCollectionDirtyCheckMethod(CtClass managedCtClass) { + try { + final StringBuilder body = new StringBuilder(); + + body.append( String.format( "" + + "private boolean %1$s() {%n" + + " if (%2$s() == null || %3$s == null) { return false; }%n", + EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, + EnhancerConstants.INTERCEPTOR_GETTER_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + + for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { + if ( !enhancementContext.isMappedCollection( ctField )) { + body.append( String.format( "" + + " // collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n"+ + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + } + } + body.append( " return false;%n}" ); + + MethodWriter.write( managedCtClass, body.toString() ); + } + catch (CannotCompileException cce) { + cce.printStackTrace(); + } + } + + private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) { + try { + final StringBuilder body = new StringBuilder(); + + body.append( String.format( "" + + "private void %1$s(%3$s tracker) {%n" + + " if (%2$s == null) { return; }%n", + EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME, + TRACKER_IMPL ) ); + + for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { + if ( !enhancementContext.isMappedCollection( ctField )) { + body.append( String.format( "" + + " // Collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n"+ + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + } + } + body.append( "}" ); + + MethodWriter.write( managedCtClass, body.toString() ); + } + catch (CannotCompileException cce) { + cce.printStackTrace(); + } + } + + private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException { + try { + final StringBuilder body = new StringBuilder(); + + body.append( String.format( "" + + "private void %1$s() {%n" + + " if (%2$s == null) { %2$s = new %3$s(); }%n", + EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME, + CollectionTracker.class.getName()) ); + + for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { + if ( !enhancementContext.isMappedCollection( ctField ) ) { + body.append( String.format( "" + + " // Collection field [%1$s]%n" + + " if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n"+ + " else { %2$s.add(\"%1$s\", %1$s.size()); }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME) ); + } + } + body.append( "}" ); + + MethodWriter.write( managedCtClass, body.toString() ); + } + catch (CannotCompileException cce) { + cce.printStackTrace(); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/FieldWriter.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/FieldWriter.java new file mode 100644 index 0000000000..c96c819f10 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/FieldWriter.java @@ -0,0 +1,99 @@ +/* + * 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.internal; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtField; +import javassist.Modifier; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.annotation.Annotation; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; + +import javax.persistence.Transient; + +/** + * @author Luis Barreiro + */ +public class FieldWriter { + + private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldWriter.class ); + + private FieldWriter() { } + + /* --- */ + + /** + * Add enhancement field + */ + public static void addField(CtClass target, CtClass type, String field) { + addPrivateTransient( target, type, field ); + } + + /** + * Add enhancement field with getter and setter + */ + public static void addFieldWithGetterAndSetter(CtClass target, CtClass type, String field, String getter, String setter) { + addPrivateTransient( target, type, field ); + MethodWriter.addGetter( target, field, getter ); + MethodWriter.addSetter( target, field, setter ); + } + + /* --- */ + + private static void addPrivateTransient(CtClass target, CtClass type, String name) { + addWithModifiers( target, type, name, Modifier.PRIVATE | Modifier.TRANSIENT, Transient.class ); + log.debugf( "Wrote field into [%s]: @Transient private transient %s %s() %s;%n", target.getName(), type.getName(), name ); + } + + private static void addWithModifiers(CtClass target, CtClass type, String name, int modifiers, Class ... annotations ) { + try { + final CtField f = new CtField( type, name, target ); + f.setModifiers( f.getModifiers() | modifiers ); + addAnnotations( f.getFieldInfo(), annotations ); + target.addField( f ); + } + catch (CannotCompileException cce) { + final String msg = String.format( "Could not enhance class [%s] to add field [%s]", target.getName(), name ); + throw new EnhancementException( msg, cce ); + } + } + + /* --- */ + + private static void addAnnotations(FieldInfo fieldInfo, Class[] annotations) { + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag ); + if ( annotationsAttribute == null ) { + annotationsAttribute = new AnnotationsAttribute( fieldInfo.getConstPool(), AnnotationsAttribute.visibleTag ); + fieldInfo.addAttribute( annotationsAttribute ); + } + for (Class annotation : annotations) { + annotationsAttribute.addAnnotation( new Annotation( annotation.getName(), fieldInfo.getConstPool() ) ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/MethodWriter.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/MethodWriter.java new file mode 100644 index 0000000000..a690f09c03 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/MethodWriter.java @@ -0,0 +1,105 @@ +/* + * 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.internal; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.CtNewMethod; +import javassist.NotFoundException; +import javassist.bytecode.ConstPool; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; + +/** + * utility class to compile methods and add the to class files + * + * @author Luis Barreiro + */ +public class MethodWriter { + + private static final CoreMessageLogger log = CoreLogging.messageLogger( MethodWriter.class ); + + private MethodWriter() { } + + /* --- */ + + /** + * convenience method that builds a method from a format string. {@see String.format} for more details + * + * @throws CannotCompileException + */ + public static CtMethod write(CtClass target, String format, Object ... args) throws CannotCompileException { + final String body = String.format( format, args ); + // System.out.printf( "writing method into [%s]:%n%s%n", target.getName(), body ); + log.debugf( "writing method into [%s]:%n%s%n", target.getName(), body ); + final CtMethod method = CtNewMethod.make( body, target ); + target.addMethod( method ); + return method; + } + + /* --- */ + + public static CtMethod addGetter(CtClass target, String field, String name) { + try { + log.debugf( "Writing getter method [%s] into [%s] for field [%s]%n", name, target.getName(), field ); + final CtMethod method = CtNewMethod.getter( name, target.getField( field ) ); + target.addMethod( method ); + return method; + } + catch (CannotCompileException cce) { + final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field ); + throw new EnhancementException( msg, cce ); + } + catch (NotFoundException nfe) { + final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field ); + throw new EnhancementException( msg, nfe ); + } + } + + public static CtMethod addSetter(CtClass target, String field, String name) { + try { + log.debugf( "Writing setter method [%s] into [%s] for field [%s]%n", name, target.getName(), field ); + final CtMethod method = CtNewMethod.setter( name, target.getField( field ) ); + target.addMethod( method ); + return method; + } + catch (CannotCompileException cce) { + final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field ); + throw new EnhancementException( msg, cce ); + } + catch (NotFoundException nfe) { + final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field ); + throw new EnhancementException( msg, nfe ); + } + } + + /* --- */ + + public static int addMethod(ConstPool cPool, CtMethod method) { + return cPool.addMethodrefInfo( cPool.getThisClassInfo(), method.getName(), method.getSignature() ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java new file mode 100644 index 0000000000..372125f0f8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java @@ -0,0 +1,279 @@ +/* + * 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.internal; + +import javassist.CannotCompileException; +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.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +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; + +import javax.persistence.Embedded; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * enhancer for persistent attributes of any type of entity + * + * @author Luis Barreiro + */ +public class PersistentAttributesEnhancer extends Enhancer { + + private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesEnhancer.class ); + + public PersistentAttributesEnhancer(EnhancementContext 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 ) ); + } + + // lastly, find all references to the transformed fields and replace with calls to the added reader/writer methods + enhanceAttributesAccess( managedCtClass, attrDescriptorMap ); + } + + /* --- */ + + // TODO: drive this from the Hibernate metamodel instance... + private CtField[] collectPersistentFields(CtClass managedCtClass) { + final List persistentFieldList = new LinkedList(); + for ( CtField ctField : managedCtClass.getDeclaredFields() ) { + // skip static fields and skip fields added by enhancement + if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { + continue; + } + if ( enhancementContext.isPersistentField( ctField ) ) { + persistentFieldList.add( ctField ); + } + } + return enhancementContext.order( persistentFieldList.toArray( new CtField[ persistentFieldList.size() ] ) ); + } + + /* --- */ + + private PersistentAttributeAccessMethods enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) { + try { + final AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( 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 ); + } + } + + private CtMethod generateFieldReader(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { + final String fieldName = persistentField.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 ) ) { + return MethodWriter.addGetter( managedCtClass, fieldName, readerName ); + } + + // TODO: temporary solution... + try { + return MethodWriter.write( managedCtClass, "private %s %s() {%n %s%n return this.%s;%n}", + persistentField.getType().getName(), + readerName, + typeDescriptor.buildReadInterceptionBodyFragment( fieldName ), + fieldName); + } + 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 ); + } + } + + private CtMethod generateFieldWriter(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { + final String fieldName = persistentField.getName(); + final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName; + + try { + final CtMethod writer; + + if ( !enhancementContext.isLazyLoadable( persistentField ) ) { + writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName ); + } + else { + writer = MethodWriter.write( managedCtClass, "private void %s(%s %s) {%n %s%n}", + writerName, + persistentField.getType().getName(), + fieldName, + typeDescriptor.buildWriteInterceptionBodyFragment( fieldName ) ); + } + + if ( enhancementContext.isCompositeClass( managedCtClass ) ) { + writer.insertBefore( String.format( "if (%s != null) { % 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 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; + } + 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.INVOKESPECIAL, index ); + itr.write16bit( methodIndex, index + 1 ); + } + else { + final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getWriter() ); + itr.writeByte( Opcode.INVOKESPECIAL, 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; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/package-info.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/package-info.java new file mode 100644 index 0000000000..057506c6a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/package-info.java @@ -0,0 +1,4 @@ +/** + * package containing bytecode enhancement code (internals) + */ +package org.hibernate.bytecode.enhance.internal; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CollectionTracker.java similarity index 63% rename from hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java rename to hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CollectionTracker.java index 94399b3395..8f51e0c518 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CollectionTracker.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CollectionTracker.java @@ -21,32 +21,45 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.bytecode.enhance.spi; +package org.hibernate.bytecode.enhance.internal.tracker; -import java.util.HashMap; -import java.util.Map; +import java.util.Arrays; /** + * small low memory class to keep track of the number of elements in a collection + * * @author Ståle W. Pedersen */ -public class CollectionTracker { - private Map tracker; +public final class CollectionTracker { + + private String[] names; + private int[] sizes; public CollectionTracker() { - tracker = new HashMap(); + names = new String[0]; + sizes = new int[0]; } public void add(String name, int size) { - tracker.put( name, size ); + for ( int i = 0; i < names.length; i++ ) { + if ( names[i].equals( name ) ) { + sizes[i] = size; + return; + } + } + names = Arrays.copyOf( names, names.length + 1 ); + names[names.length - 1] = name; + sizes = Arrays.copyOf( sizes, sizes.length + 1 ); + sizes[sizes.length - 1] = size; } public int getSize(String name) { - Integer size = tracker.get( name ); - if ( size == null ) { - return -1; - } - else { - return size; + for ( int i = 0; i < names.length; i++ ) { + if ( name.equals( names[i] ) ) { + return sizes[i]; + } } + return -1; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CompositeOwnerTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CompositeOwnerTracker.java similarity index 61% rename from hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CompositeOwnerTracker.java rename to hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CompositeOwnerTracker.java index 7f55ee85a1..71673d69dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/CompositeOwnerTracker.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/CompositeOwnerTracker.java @@ -21,64 +21,63 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.bytecode.enhance.spi; +package org.hibernate.bytecode.enhance.internal.tracker; import org.hibernate.engine.spi.CompositeOwner; +import java.util.Arrays; + /** * small low memory class to keep references to composite owners * * @author Ståle W. Pedersen */ -public class CompositeOwnerTracker { +public final class CompositeOwnerTracker { + private String[] names; private CompositeOwner[] owners; - private int size; public CompositeOwnerTracker() { - names = new String[1]; - owners = new CompositeOwner[1]; + names = new String[0]; + owners = new CompositeOwner[0]; } public void add(String name, CompositeOwner owner) { - for ( int i = 0; i < size; i++ ) { + for ( int i = 0; i < names.length; i++ ) { if ( names[i].equals( name ) ) { owners[i] = owner; return; } } - if ( size >= names.length ) { - String[] tmpNames = new String[size + 1]; - System.arraycopy( names, 0, tmpNames, 0, size ); - names = tmpNames; - CompositeOwner[] tmpOwners = new CompositeOwner[size + 1]; - System.arraycopy( owners, 0, tmpOwners, 0, size ); - owners = tmpOwners; - } - names[size] = name; - owners[size] = owner; - size++; + names = Arrays.copyOf( names, names.length + 1 ); + names[names.length - 1] = name; + owners = Arrays.copyOf( owners, owners.length + 1 ); + owners[owners.length - 1] = owner; } public void callOwner(String fieldName) { - for ( int i = 0; i < size; i++ ) { - owners[i].$$_hibernate_trackChange( names[i] + fieldName ); + for ( int i = 0; i < owners.length ; i++ ) { + if ( owners[i] != null ) { + owners[i].$$_hibernate_trackChange( names[i] + fieldName ); + } } } public void removeOwner(String name) { - for ( int i = 0; i < size; i++ ) { - if ( names[i].equals( name ) ) { - if ( i < size ) { - for ( int j = i; j < size - 1; j++ ) { - names[j] = names[j + 1]; - owners[j] = owners[j + 1]; - } - names[size - 1] = null; - owners[size - 1] = null; - size--; - } + for ( int i = 0; i < names.length; i++ ) { + if ( name.equals( names[i] ) ) { + + final String[] newNames = Arrays.copyOf( names, names.length - 1 ); + System.arraycopy( names, i + 1, newNames, i, newNames.length - i); + names = newNames; + + final CompositeOwner[] newOwners = Arrays.copyOf( owners, owners.length - 1 ); + System.arraycopy( owners, i + 1, newOwners, i, newOwners.length - i); + owners = newOwners; + + return; } } } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SimpleDirtyTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SimpleDirtyTracker.java new file mode 100644 index 0000000000..f12616faf8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SimpleDirtyTracker.java @@ -0,0 +1,68 @@ +/* + * 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.internal.tracker; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * small low memory class to keep track of changed fields + * + * uses an array as a set (under the assumption that the number of elements will be low) to avoid having to instantiate an HashSet. + * if the assumption does not, hold the array can be kept ordered to reduce the cost of verifying duplicates + * + * @author Luis Barreiro + */ +public final class SimpleDirtyTracker { + + private String[] names; + + public SimpleDirtyTracker() { + names = new String[0]; + } + + public void add(String name) { + for (String existing : names) { + if ( existing.equals( name ) ) { + return; + } + } + names = Arrays.copyOf( names, names.length + 1 ); + names[names.length - 1] = name; + } + + public void clear() { + names = new String[0]; + } + + public boolean isEmpty() { + return names.length == 0; + } + + public Set asSet() { + return new CopyOnWriteArraySet( Arrays.asList( names ) ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedDirtyTracker.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedDirtyTracker.java new file mode 100644 index 0000000000..5819201a03 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/SortedDirtyTracker.java @@ -0,0 +1,82 @@ +/* + * 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.internal.tracker; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * small low memory class to keep track of changed fields + * + * similar to BasicTracker but where the array is kept ordered to reduce the cost of verifying duplicates + * + * @author Luis Barreiro + */ +public final class SortedDirtyTracker { + + private String[] names; + + public SortedDirtyTracker() { + names = new String[0]; + } + + public void add(String name) { + // we do a binary search: even if we don't find the name at least we get the position to insert into the array + int insert = 0; + for ( int low = 0, high = names.length - 1; low <= high; ) { + final int middle = low + ( ( high - low ) / 2 ); + if ( names[middle].compareTo( name ) > 0 ) { + // bottom half: higher bound in (middle - 1) and insert position in middle + high = middle - 1; + insert = middle; + } + else if( names[middle].compareTo( name ) < 0 ) { + // top half: lower bound in (middle + 1) and insert position after middle + insert = low = middle + 1; + } + else { + return; + } + } + final String[] newNames = new String[names.length + 1]; + System.arraycopy( names, 0, newNames, 0, insert); + System.arraycopy( names, insert, newNames, insert + 1, names.length - insert); + newNames[insert] = name; + names = newNames; + } + + public void clear() { + names = new String[0]; + } + + public boolean isEmpty() { + return names.length == 0; + } + + public Set asSet() { + return new CopyOnWriteArraySet( Arrays.asList( names ) ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/package-info.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/package-info.java new file mode 100644 index 0000000000..a765344f2e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/tracker/package-info.java @@ -0,0 +1,4 @@ +/** + * specialized classes to keep track of changes + */ +package org.hibernate.bytecode.enhance.internal.tracker; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/package-info.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/package-info.java deleted file mode 100644 index b8e84966d6..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Package defining bytecode code enhancement (instrumentation) support. - */ -package org.hibernate.bytecode.enhance; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/DefaultEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/DefaultEnhancementContext.java new file mode 100644 index 0000000000..d57a1acabc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/DefaultEnhancementContext.java @@ -0,0 +1,105 @@ +/* + * 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 javassist.CtClass; +import javassist.CtField; + +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Transient; + +/** + * default implementation of EnhancementContext. May be sub-classed as needed. + * + * @author Luis Barreiro + */ +public class DefaultEnhancementContext implements EnhancementContext { + + /** + * @return the classloader for this class + */ + public ClassLoader getLoadingClassLoader() { + return getClass().getClassLoader(); + } + + /** + * look for @Entity annotation + */ + public boolean isEntityClass(CtClass classDescriptor) { + return classDescriptor.hasAnnotation( Entity.class ); + } + + /** + * look for @Embeddable annotation + */ + public boolean isCompositeClass(CtClass classDescriptor) { + return classDescriptor.hasAnnotation( Embeddable.class ); + } + + /** + * @return true + */ + public boolean doDirtyCheckingInline(CtClass classDescriptor) { + return true; + } + + /** + * @return true + */ + public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { + return true; + } + + /** + * @return true + */ + public boolean isLazyLoadable(CtField field) { + return true; + } + + /** + * look for @Transient annotation + */ + public boolean isPersistentField(CtField ctField) { + return ! ctField.hasAnnotation( Transient.class ); + } + + /** + * look for @OneToMany, @ManyToMany and @ElementCollection annotations + */ + public boolean isMappedCollection(CtField field) { + return field.hasAnnotation( OneToMany.class ) || field.hasAnnotation( ManyToMany.class ) || field.hasAnnotation( ElementCollection.class ); + } + + /** + * keep the same order. + */ + public CtField[] order(CtField[] persistentFields) { + return persistentFields; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementException.java similarity index 97% rename from hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java rename to hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementException.java index 129f78cd9c..cea9d3968e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/EnhancementException.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementException.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.bytecode.enhance; +package org.hibernate.bytecode.enhance.spi; import org.hibernate.HibernateException; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java index 14ddec6d79..c11081cc1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java @@ -23,53 +23,28 @@ */ 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.bytecode.enhance.internal.CompositeEnhancer; +import org.hibernate.bytecode.enhance.internal.EntityEnhancer; +import org.hibernate.bytecode.enhance.internal.FieldWriter; 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; +import javax.tools.JavaFileObject; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; + /** * Class responsible for performing enhancement. * @@ -79,16 +54,13 @@ import org.hibernate.internal.CoreMessageLogger; public class Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); - private final EnhancementContext enhancementContext; + protected 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; + protected final ClassPool classPool; + protected final CtClass managedEntityCtClass; + protected final CtClass attributeInterceptorCtClass; + protected final CtClass attributeInterceptableCtClass; + protected final CtClass entityEntryCtClass; /** * Constructs the Enhancer, using the given context. @@ -97,53 +69,60 @@ public class Enhancer { * 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" - ) - ); + this.enhancementContext = enhancementContext; + this.classPool = buildClassPool( enhancementContext ); - // add ManagedComposite contract - this.managedCompositeCtClass = classPool.makeClass( - ManagedComposite.class.getClassLoader().getResourceAsStream( - ManagedComposite.class.getName().replace( '.', '/' ) + ".class" - ) - ); + // add ManagedEntity contract + this.managedEntityCtClass = loadCtClassFromClass( classPool, ManagedEntity.class ); // add PersistentAttributeInterceptable contract - this.attributeInterceptableCtClass = classPool.makeClass( - PersistentAttributeInterceptable.class.getClassLoader().getResourceAsStream( - PersistentAttributeInterceptable.class.getName().replace( '.', '/' ) + ".class" - ) - ); + this.attributeInterceptableCtClass = loadCtClassFromClass( classPool, PersistentAttributeInterceptable.class ); // add PersistentAttributeInterceptor contract - this.attributeInterceptorCtClass = classPool.makeClass( - PersistentAttributeInterceptor.class.getClassLoader().getResourceAsStream( - PersistentAttributeInterceptor.class.getName().replace( '.', '/' ) + ".class" - ) - ); + this.attributeInterceptorCtClass = loadCtClassFromClass( classPool, PersistentAttributeInterceptor.class ); - // "add" EntityEntry - this.entityEntryCtClass = classPool.makeClass( EntityEntry.class.getName() ); + // add PersistentAttributeInterceptor contract + this.entityEntryCtClass = loadCtClassFromClass( classPool, EntityEntry.class ); } catch (IOException e) { throw new EnhancementException( "Could not prepare Javassist ClassPool", e ); } + } + /** + * 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 { try { - this.objectCtClass = classPool.getCtClass( Object.class.getName() ); + final CtClass managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) ); + enhance( managedCtClass ); + return getByteCode( managedCtClass ); } - catch (NotFoundException e) { - throw new EnhancementException( "Could not prepare Javassist ClassPool", e ); + catch (IOException e) { + log.unableToBuildEnhancementMetamodel( className ); + return originalBytes; } } + /** + * @deprecated Should use enhance(String, byte[]) and a proper EnhancementContext + */ + @Deprecated( ) + public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException { + return enhance( className, originalBytes ); + } + + /* --- */ + private ClassPool buildClassPool(EnhancementContext enhancementContext) { final ClassPool classPool = new ClassPool( false ); final ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader(); @@ -153,1287 +132,80 @@ public class Enhancer { 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 ); + private CtClass loadCtClassFromClass(ClassPool cp, Class aClass) throws IOException { + return cp.makeClass( aClass.getClassLoader().getResourceAsStream( getFilenameForClass( aClass ) ) ); } - public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException { - final CtClass managedCtClass; - try { - managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) ); + private String getFilenameForClass(Class aClass) { + return aClass.getName().replace( '.', File.separatorChar ) + JavaFileObject.Kind.CLASS.extension; + } + + /* --- */ + + private void enhance(CtClass managedCtClass) { + // can't effectively enhance interfaces + if ( managedCtClass.isInterface() ) { + log.debugf( "Skipping enhancement of [%s]: it's an interface", managedCtClass ); + return; } - catch (IOException e) { - log.unableToBuildEnhancementMetamodel( className ); - return originalBytes; + // skip already enhanced classes + for ( String interfaceName : managedCtClass.getClassFile2().getInterfaces() ) { + if ( ManagedEntity.class.getName().equals( interfaceName ) || ManagedComposite.class.getName().equals( interfaceName ) ) { + log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() ); + return; + } } - enhance( managedCtClass, true ); - - return getByteCode( managedCtClass ); + if ( enhancementContext.isEntityClass( managedCtClass ) ) { + log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() ); + new EntityEnhancer( enhancementContext ).enhance( managedCtClass ); + } + else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { + log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() ); + new CompositeEnhancer( enhancementContext ).enhance( managedCtClass ); + } + else { + log.debug( "Skipping enhancement: not entity or composite" ); + } } private byte[] getByteCode(CtClass managedCtClass) { final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - final DataOutputStream out; + final DataOutputStream out = new DataOutputStream( byteStream ); try { - out = new DataOutputStream( byteStream ); - try { - managedCtClass.toBytecode( out ); - return byteStream.toByteArray(); - } - finally { - try { - out.close(); - } - catch (IOException e) { - //swallow - } - } + managedCtClass.toBytecode( out ); + return byteStream.toByteArray(); } 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; + finally { + try { + out.close(); + } + catch (IOException ignored) { } } - - 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) { + protected 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 ) ) { + if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) && !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { return; } + log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() ); - log.debug( "Weaving in PersistentAttributeInterceptable implementation" ); - - - // add in the PersistentAttributeInterceptable contract managedCtClass.addInterface( attributeInterceptableCtClass ); - addFieldWithGetterAndSetter( - managedCtClass, - attributeInterceptorCtClass, + FieldWriter.addFieldWithGetterAndSetter( managedCtClass, attributeInterceptorCtClass, EnhancerConstants.INTERCEPTOR_FIELD_NAME, EnhancerConstants.INTERCEPTOR_GETTER_NAME, - EnhancerConstants.INTERCEPTOR_SETTER_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 - ); - } - }; } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/package-info.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/package-info.java index ab08393eee..b5003a7482 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/package-info.java @@ -1,4 +1,4 @@ /** - * Package defining bytecode code enhancement (instrumentation) support. + * package defining bytecode code enhancement (instrumentation) support. */ package org.hibernate.bytecode.enhance.spi; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CustomerEnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CustomerEnhancerTest.java new file mode 100644 index 0000000000..753b109802 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CustomerEnhancerTest.java @@ -0,0 +1,123 @@ +/* + * 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.test.bytecode.enhancement; + +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.test.bytecode.enhancement.entity.customer.Address; +import org.hibernate.test.bytecode.enhancement.entity.customer.Customer; +import org.hibernate.test.bytecode.enhancement.entity.customer.CustomerInventory; +import org.hibernate.test.bytecode.enhancement.entity.customer.SupplierComponentPK; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import java.lang.reflect.Method; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * @author Steve Ebersole + */ +public class CustomerEnhancerTest extends BaseUnitTestCase { + + @Test + public void testEnhancement() throws Exception { + testFor(Customer.class); + } + + private void testFor(Class entityClassToEnhance) throws Exception { + ClassLoader cl = new ClassLoader() {}; + + // just for debugging + Class addressClass = EnhancerTestUtils.enhanceAndDecompile(Address.class, cl); + Class customerInventoryClass = EnhancerTestUtils.enhanceAndDecompile(CustomerInventory.class, cl); + Class supplierComponentPKCtClass = EnhancerTestUtils.enhanceAndDecompile(SupplierComponentPK.class, cl); + + Class entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl); + Object entityInstance = entityClass.newInstance(); + assertTyping(ManagedEntity.class, entityInstance); + + // call the new methods + Method setter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class); + Method getter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME); + assertNull(getter.invoke(entityInstance)); + setter.invoke(entityInstance, EnhancerTestUtils.makeEntityEntry()); + assertNotNull(getter.invoke(entityInstance)); + setter.invoke(entityInstance, new Object[] {null}); + assertNull(getter.invoke(entityInstance)); + + Method entityInstanceGetter = entityClass.getMethod(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME); + assertSame(entityInstance, entityInstanceGetter.invoke(entityInstance)); + + Method previousGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME); + Method previousSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class); + previousSetter.invoke(entityInstance, entityInstance); + assertSame(entityInstance, previousGetter.invoke(entityInstance)); + + Method nextGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME); + Method nextSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class); + nextSetter.invoke(entityInstance, entityInstance); + assertSame( entityInstance, nextGetter.invoke(entityInstance)); + + // add an attribute interceptor... + assertNull(entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME).invoke(entityInstance)); + entityClass.getMethod("getId").invoke(entityInstance); + + Method interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class); + interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor()); + assertNotNull(entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME).invoke(entityInstance)); + + // dirty checking is unfortunately just printlns for now... just verify the test output + entityClass.getMethod("getId").invoke(entityInstance); + entityClass.getMethod("setId", Integer.class).invoke(entityInstance, entityClass.getMethod("getId").invoke(entityInstance)); + entityClass.getMethod("setId", Integer.class).invoke(entityInstance, 1); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "id"); + + entityClass.getMethod("setFirstName", String.class).invoke(entityInstance, "Erik"); + entityClass.getMethod("setLastName", String.class).invoke(entityInstance, "Mykland"); + + EnhancerTestUtils.checkDirtyTracking(entityInstance, "id", "firstName", "lastName"); + EnhancerTestUtils.clearDirtyTracking(entityInstance); + + // testing composite object + Object address = addressClass.newInstance(); + + entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address); + addressClass.getMethod("setCity", String.class).invoke(address, "Arendal"); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.city"); + EnhancerTestUtils.clearDirtyTracking(entityInstance); + + //make sure that new composite instances are cleared + Object address2 = addressClass.newInstance(); + entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2); + addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien"); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "address"); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index bc68956627..cd9816f279 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -23,401 +23,148 @@ */ package org.hibernate.test.bytecode.enhancement; -import java.io.ByteArrayInputStream; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtField; -import javassist.LoaderClassPath; - -import org.hibernate.EntityMode; -import org.hibernate.LockMode; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.engine.spi.Status; - +import org.hibernate.test.bytecode.enhancement.entity.Address; +import org.hibernate.test.bytecode.enhancement.entity.Country; +import org.hibernate.test.bytecode.enhancement.entity.SimpleEntity; +import org.hibernate.test.bytecode.enhancement.entity.SubEntity; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; -import org.hibernate.testing.junit4.BaseUnitTestCase; - -import javax.persistence.ElementCollection; -import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; /** * @author Steve Ebersole */ public class EnhancerTest extends BaseUnitTestCase { - private static EnhancementContext enhancementContext = new EnhancementContext() { - @Override - public ClassLoader getLoadingClassLoader() { - return getClass().getClassLoader(); - } - @Override - public boolean isEntityClass(CtClass classDescriptor) { - return true; - } + @Test + public void testEnhancement() throws Exception { + testFor(SimpleEntity.class); + testFor(SubEntity.class); + } - @Override - public boolean isCompositeClass(CtClass classDescriptor) { - return false; - } + private void testFor(Class entityClassToEnhance) throws Exception { + ClassLoader cl = new ClassLoader() {}; + Class entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl); + Object entityInstance = entityClass.newInstance(); - @Override - public boolean doDirtyCheckingInline(CtClass classDescriptor) { - return true; - } + assertTyping(ManagedEntity.class, entityInstance); - @Override - public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { - return true; - } + // call the new methods + Method setter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class); + Method getter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME); - @Override - public boolean isLazyLoadable(CtField field) { - return true; - } + assertNull(getter.invoke(entityInstance)); + setter.invoke(entityInstance, EnhancerTestUtils.makeEntityEntry()); + assertNotNull(getter.invoke(entityInstance)); + setter.invoke(entityInstance, new Object[] { null } ); + assertNull(getter.invoke(entityInstance)); - @Override - public boolean isMappedCollection(CtField field) { - try { - return (field.getAnnotation(OneToMany.class) != null || - field.getAnnotation(ManyToMany.class) != null || - field.getAnnotation(ElementCollection.class) != null); - } - catch (ClassNotFoundException e) { - return false; - } - } + Method entityInstanceGetter = entityClass.getMethod(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME); + assertSame(entityInstance, entityInstanceGetter.invoke(entityInstance)); - @Override - public boolean isPersistentField(CtField ctField) { - return true; - } + Method previousGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME); + Method previousSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class); + previousSetter.invoke(entityInstance, entityInstance); + assertSame(entityInstance, previousGetter.invoke(entityInstance)); - @Override - public CtField[] order(CtField[] persistentFields) { - return persistentFields; - } - }; + Method nextGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME); + Method nextSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class); + nextSetter.invoke(entityInstance, entityInstance); + assertSame(entityInstance, nextGetter.invoke(entityInstance)); - @Test - public void testEnhancement() throws Exception { - testFor( SimpleEntity.class ); - testFor( SubEntity.class ); - } + // add an attribute interceptor... + Method interceptorGetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME); + assertNull(interceptorGetter.invoke(entityInstance)); + entityClass.getMethod("getId").invoke(entityInstance); - private void testFor(Class entityClassToEnhance) throws Exception { - Enhancer enhancer = new Enhancer( enhancementContext ); - CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance ); - byte[] original = entityCtClass.toBytecode(); - //byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); - byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); - assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) ); + Method interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class); + interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor()); + assertNotNull(interceptorGetter.invoke(entityInstance)); - ClassLoader cl = new ClassLoader() { }; - ClassPool cp = new ClassPool( false ); - cp.appendClassPath( new LoaderClassPath( cl ) ); - CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) ); - enhancedCtClass.debugWriteFile("/tmp"); - //just for debugging - Class addressClass = null; - Class countryClass = null; - if(entityClassToEnhance.getName().endsWith("SimpleEntity")) { - CtClass addressCtClass = generateCtClassForAnEntity( Address.class ); - byte[] enhancedAddress = enhancer.enhanceComposite(Address.class.getName(), addressCtClass.toBytecode()); - CtClass enhancedCtClassAddress = cp.makeClass( new ByteArrayInputStream( enhancedAddress ) ); - enhancedCtClassAddress.debugWriteFile("/tmp"); - addressClass = enhancedCtClassAddress.toClass( cl, this.getClass().getProtectionDomain() ); + // dirty checking is unfortunately just printlns for now... just verify the test output + entityClass.getMethod("getId").invoke(entityInstance); + entityClass.getMethod("setId", Long.class).invoke(entityInstance, entityClass.getMethod("getId").invoke(entityInstance)); + entityClass.getMethod("setId", Long.class).invoke(entityInstance, 1L); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "id"); - CtClass countryCtClass = generateCtClassForAnEntity( Country.class ); - byte[] enhancedCountry = enhancer.enhanceComposite(Country.class.getName(), countryCtClass.toBytecode()); - CtClass enhancedCtClassCountry = cp.makeClass( new ByteArrayInputStream( enhancedCountry ) ); - enhancedCtClassCountry.debugWriteFile("/tmp"); - countryClass = enhancedCtClassCountry.toClass( cl, this.getClass().getProtectionDomain() ); + entityClass.getMethod("isActive").invoke(entityInstance); + entityClass.getMethod("setActive", boolean.class).invoke(entityInstance, entityClass.getMethod("isActive").invoke(entityInstance)); + entityClass.getMethod("setActive", boolean.class).invoke(entityInstance, true); - } - Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() ); - Object entityInstance = entityClass.newInstance(); + entityClass.getMethod("getSomeNumber").invoke(entityInstance); + entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, entityClass.getMethod("getSomeNumber").invoke(entityInstance)); + entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, 1L); - assertTyping( ManagedEntity.class, entityInstance ); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "id", "active", "someNumber"); + EnhancerTestUtils.clearDirtyTracking(entityInstance); - // call the new methods - // - Method setter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class ); - Method getter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME ); - assertNull( getter.invoke( entityInstance ) ); - setter.invoke( entityInstance, makeEntityEntry() ); - assertNotNull( getter.invoke( entityInstance ) ); - setter.invoke( entityInstance, new Object[] {null} ); - assertNull( getter.invoke( entityInstance ) ); + // setting the same value should not make it dirty + entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, 1L); + EnhancerTestUtils.checkDirtyTracking(entityInstance); - Method entityInstanceGetter = entityClass.getMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME ); - assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) ); + if(entityClassToEnhance.getName().endsWith(SimpleEntity.class.getSimpleName())) { + cl = new ClassLoader() {}; - Method previousGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME ); - Method previousSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class ); - previousSetter.invoke( entityInstance, entityInstance ); - assertSame( entityInstance, previousGetter.invoke( entityInstance ) ); + Class addressClass = EnhancerTestUtils.enhanceAndDecompile(Address.class, cl); + Class countryClass = EnhancerTestUtils.enhanceAndDecompile(Country.class, cl); - Method nextGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME ); - Method nextSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class ); - nextSetter.invoke( entityInstance, entityInstance ); - assertSame( entityInstance, nextGetter.invoke( entityInstance ) ); + entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl); + entityInstance = entityClass.newInstance(); - // add an attribute interceptor... - Method interceptorGetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME ); - Method interceptorSetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class ); - - assertNull( interceptorGetter.invoke( entityInstance ) ); - entityClass.getMethod( "getId" ).invoke( entityInstance ); - - interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() ); - assertNotNull(interceptorGetter.invoke(entityInstance)); - - // dirty checking is unfortunately just printlns for now... just verify the test output - entityClass.getMethod( "getId" ).invoke( entityInstance ); - entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) ); - entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, 1L ); - assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance)); - - entityClass.getMethod( "isActive" ).invoke( entityInstance ); - entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, entityClass.getMethod( "isActive" ).invoke( entityInstance ) ); - entityClass.getMethod( "setActive", boolean.class ).invoke(entityInstance, true); - - entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ); - entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ) ); - entityClass.getMethod( "setSomeNumber", long.class ).invoke(entityInstance, 1L); - assertEquals(3, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size()); - - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - //setting the same value should not make it dirty - entityClass.getMethod( "setSomeNumber", long.class ).invoke(entityInstance, 1L); - //assertEquals(0, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size()); - //assertFalse((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance)); - - if(entityClass.getName().endsWith("SimpleEntity")) { + interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class); + interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor()); List strings = new ArrayList(); strings.add("FooBar"); + entityClass.getMethod("setSomeStrings", List.class).invoke(entityInstance, strings); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "someStrings"); + EnhancerTestUtils.clearDirtyTracking(entityInstance); - entityClass.getMethod( "setSomeStrings", java.util.List.class ).invoke(entityInstance, strings); + strings.add("JADA!"); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "someStrings"); + EnhancerTestUtils.clearDirtyTracking(entityInstance); - assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance)); + // this should not set the entity to dirty + Set intSet = new HashSet(); + intSet.add(42); + entityClass.getMethod("setSomeInts", Set.class).invoke(entityInstance, intSet); + EnhancerTestUtils.checkDirtyTracking(entityInstance); - Set tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(1, tracked.size()); - assertEquals("someStrings", tracked.iterator().next()); - - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - - ((List) entityClass.getMethod( "getSomeStrings").invoke(entityInstance)).add("JADA!"); - Boolean isDirty = (Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance); - assertTrue(isDirty); - assertEquals("someStrings", - ((Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance)).iterator().next()); - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - - //this should not set the entity to dirty - Set ints = new HashSet(); - ints.add(42); - entityClass.getMethod( "setSomeInts", java.util.Set.class ).invoke(entityInstance, ints); - isDirty = (Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance); - assertFalse(isDirty); - - //testing composite object - assert addressClass != null; - Object address = addressClass.newInstance(); - - assert countryClass != null; - Object country = countryClass.newInstance(); - - //Method adrInterceptorGetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME ); - //Method adrInterceptorSetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class ); - //adrInterceptorSetter.invoke( address, new LocalPersistentAttributeInterceptor() ); + // testing composite object + Object address = addressClass.newInstance(); + Object country = countryClass.newInstance(); entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address); - addressClass.getMethod("setCity", String.class).invoke(address, "Arendal"); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.city"); - tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(2, tracked.size()); - Iterator iter = tracked.iterator(); - assertEquals("address", iter.next()); - assertEquals("address.city", iter.next()); + entityClass.getMethod(EnhancerConstants.TRACKER_CLEAR_NAME).invoke(entityInstance); - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - - //make sure that new composite instances are cleared - Object address2 = addressClass.newInstance(); + // make sure that new composite instances are cleared + Object address2 = addressClass.newInstance(); entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2); addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien"); - - tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(1, tracked.size()); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "address"); addressClass.getMethod("setCountry", countryClass).invoke(address2, country); countryClass.getMethod("setName", String.class).invoke(country, "Norway"); - - tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(3, tracked.size()); + EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.country", "address.country.name"); } - } + } - private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { - ClassPool cp = new ClassPool( false ); - return cp.makeClass( - getClass().getClassLoader().getResourceAsStream( - entityClassToEnhance.getName().replace( '.', '/' ) + ".class" - ) - ); - } - - private EntityEntry makeEntityEntry() { - return new EntityEntry( - Status.MANAGED, - null, - null, - new Long(1), - null, - LockMode.NONE, - false, - null, - false, - false, - null - ); - } - - - private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - System.out.println( "Reading boolean [" + name + "]" ); - return oldValue; - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - System.out.println( "Writing boolean [" + name + "]" ); - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - System.out.println( "Reading byte [" + name + "]" ); - return oldValue; - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - System.out.println( "Writing byte [" + name + "]" ); - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - System.out.println( "Reading char [" + name + "]" ); - return oldValue; - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - System.out.println( "Writing char [" + name + "]" ); - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - System.out.println( "Reading short [" + name + "]" ); - return oldValue; - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - System.out.println( "Writing short [" + name + "]" ); - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - System.out.println( "Reading int [" + name + "]" ); - return oldValue; - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - System.out.println( "Writing int [" + name + "]" ); - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - System.out.println( "Reading float [" + name + "]" ); - return oldValue; - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - System.out.println( "Writing float [" + name + "]" ); - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - System.out.println( "Reading double [" + name + "]" ); - return oldValue; - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - System.out.println( "Writing double [" + name + "]" ); - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - System.out.println( "Reading long [" + name + "]" ); - return oldValue; - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - System.out.println( "Writing long [" + name + "]" ); - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - System.out.println( "Reading Object [" + name + "]" ); - return oldValue; - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - System.out.println( "Writing Object [" + name + "]" ); - return newValue; - } - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java new file mode 100644 index 0000000000..67b04f9d52 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java @@ -0,0 +1,324 @@ +/* + * 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.test.bytecode.enhancement; + +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.javap.JavapTask; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.LoaderClassPath; +import org.hibernate.LockMode; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; +import org.hibernate.engine.spi.CompositeOwner; +import org.hibernate.engine.spi.CompositeTracker; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.engine.spi.Status; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * utility class to use in bytecode enhancement tests + * + * @author Steve Ebersole + */ +public abstract class EnhancerTestUtils extends BaseUnitTestCase { + + private static EnhancementContext enhancementContext = new DefaultEnhancementContext(); + + private static String workingDir = System.getProperty("java.io.tmpdir"); + + private static final CoreMessageLogger log = CoreLogging.messageLogger(EnhancerTestUtils.class); + + /** + * method that performs the enhancement of a class + * also checks the signature of enhanced entities methods using 'javap' decompiler + */ + static Class enhanceAndDecompile(Class classToEnhance, ClassLoader cl) throws Exception { + CtClass entityCtClass = generateCtClassForAnEntity(classToEnhance); + + byte[] original = entityCtClass.toBytecode(); + byte[] enhanced = new Enhancer(enhancementContext).enhance(entityCtClass.getName(), original); + assertFalse("entity was not enhanced", Arrays.equals(original, enhanced)); + log.infof("enhanced entity [%s]", entityCtClass.getName()); + + ClassPool cp = new ClassPool(false); + cp.appendClassPath(new LoaderClassPath(cl)); + CtClass enhancedCtClass = cp.makeClass(new ByteArrayInputStream(enhanced)); + + enhancedCtClass.debugWriteFile(workingDir); + decompileDumpedClass(classToEnhance.getName()); + + Class enhancedClass = enhancedCtClass.toClass(cl, EnhancerTestUtils.class.getProtectionDomain()); + assertNotNull(enhancedClass); + return enhancedClass; + } + + private static void decompileDumpedClass(String className) { + try { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(new File(workingDir))); + + JavapTask javapTask = new JavapTask(); + for (JavaFileObject jfo : fileManager.getJavaFileObjects(workingDir + File.separator + getFilenameForClassName(className))) { + try { + Set interfaceNames = new HashSet(); + Set fieldNames = new HashSet(); + Set methodNames = new HashSet(); + + JavapTask.ClassFileInfo info = javapTask.read(jfo); + + log.infof("decompiled class [%s]", info.cf.getName()); + + for (int i : info.cf.interfaces) { + interfaceNames.add(info.cf.constant_pool.getClassInfo(i).getName()); + log.debugf("declared iFace = ", info.cf.constant_pool.getClassInfo(i).getName()); + } + for (com.sun.tools.classfile.Field f : info.cf.fields) { + fieldNames.add(f.getName(info.cf.constant_pool)); + log.debugf("declared field = ", f.getName(info.cf.constant_pool)); + } + for (com.sun.tools.classfile.Method m : info.cf.methods) { + methodNames.add(m.getName(info.cf.constant_pool)); + log.debugf("declared method = ", m.getName(info.cf.constant_pool)); + } + + // checks signature against known interfaces + if (interfaceNames.contains(PersistentAttributeInterceptor.class.getName())) { + assertTrue(fieldNames.contains(EnhancerConstants.INTERCEPTOR_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_GETTER_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_SETTER_NAME)); + } + if (interfaceNames.contains(ManagedEntity.class.getName())) { + assertTrue(methodNames.contains(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME)); + + assertTrue(fieldNames.contains(EnhancerConstants.ENTITY_ENTRY_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME)); + + assertTrue(fieldNames.contains(EnhancerConstants.PREVIOUS_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_GETTER_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_SETTER_NAME)); + + assertTrue(fieldNames.contains(EnhancerConstants.NEXT_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.NEXT_GETTER_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.NEXT_SETTER_NAME)); + } + if (interfaceNames.contains(SelfDirtinessTracker.class.getName())) { + assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_GET_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CLEAR_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_HAS_CHANGED_NAME)); + } + if (interfaceNames.contains(CompositeTracker.class.getName())) { + assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER)); + } + if (interfaceNames.contains(CompositeOwner.class.getName())) { + assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME)); + assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME)); + } + } catch (ConstantPoolException e) { + e.printStackTrace(); + } + } + } catch (IOException ioe) { + assertNull("Failed to open class file", ioe); + } catch (RuntimeException re) { + log.warnf(re, "WARNING: UNABLE DECOMPILE DUE TO %s", re.getMessage()); + } + } + + private static CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { + ClassPool cp = new ClassPool(false); + return cp.makeClass(EnhancerTestUtils.class.getClassLoader().getResourceAsStream(getFilenameForClassName(entityClassToEnhance.getName()))); + } + + private static String getFilenameForClassName(String className) { + return className.replace('.', File.separatorChar) + JavaFileObject.Kind.CLASS.extension; + } + + /** + * clears the dirty set for an entity + */ + public static void clearDirtyTracking (Object entityInstance) { + try { + entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_CLEAR_NAME).invoke(entityInstance); + checkDirtyTracking(entityInstance); + } catch (InvocationTargetException e) { + assertNull("Exception in clear dirty tracking", e); + } catch (NoSuchMethodException e) { + assertNull("Exception in clear dirty tracking", e); + } catch (IllegalAccessException e) { + assertNull("Exception in clear dirty tracking", e); + } + } + + /** + * compares the dirty fields of an entity with a set of expected values + */ + public static void checkDirtyTracking (Object entityInstance, String ... dirtyFields) { + try { + assertTrue((dirtyFields.length == 0) != (Boolean) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_HAS_CHANGED_NAME).invoke(entityInstance)); + Set tracked = (Set) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); + assertEquals(dirtyFields.length, tracked.size()); + assertTrue(tracked.containsAll(Arrays.asList(dirtyFields))); + } catch (InvocationTargetException e) { + assertNull("Exception while checking dirty tracking", e); + } catch (NoSuchMethodException e) { + assertNull("Exception while checking dirty tracking", e); + } catch (IllegalAccessException e) { + assertNull("Exception while checking dirty tracking", e); + } + } + + static EntityEntry makeEntityEntry() { + return new EntityEntry( + Status.MANAGED, + null, + null, + 1, + null, + LockMode.NONE, + false, + null, + false, + false, + null + ); + } + + public static class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { + + @Override public boolean readBoolean(Object obj, String name, boolean oldValue) { + log.infof( "Reading boolean [%s]" , name ); + return oldValue; + } + @Override public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + log.infof( "Writing boolean []", name ); + return newValue; + } + + @Override public byte readByte(Object obj, String name, byte oldValue) { + log.infof( "Reading byte [%s]", name ); + return oldValue; + } + @Override public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + log.infof( "Writing byte [%s]", name ); + return newValue; + } + + @Override public char readChar(Object obj, String name, char oldValue) { + log.infof( "Reading char [%s]", name ); + return oldValue; + } + @Override public char writeChar(Object obj, String name, char oldValue, char newValue) { + log.infof( "Writing char [%s]", name ); + return newValue; + } + + @Override public short readShort(Object obj, String name, short oldValue) { + log.infof( "Reading short [%s]", name ); + return oldValue; + } + @Override public short writeShort(Object obj, String name, short oldValue, short newValue) { + log.infof( "Writing short [%s]", name ); + return newValue; + } + + @Override public int readInt(Object obj, String name, int oldValue) { + log.infof( "Reading int [%s]", name ); + return oldValue; + } + @Override public int writeInt(Object obj, String name, int oldValue, int newValue) { + log.infof( "Writing int [%s]", name ); + return newValue; + } + + @Override public float readFloat(Object obj, String name, float oldValue) { + log.infof( "Reading float [%s]", name ); + return oldValue; + } + @Override public float writeFloat(Object obj, String name, float oldValue, float newValue) { + log.infof( "Writing float [%s]", name ); + return newValue; + } + + @Override public double readDouble(Object obj, String name, double oldValue) { + log.infof( "Reading double [%s]", name ); + return oldValue; + } + @Override public double writeDouble(Object obj, String name, double oldValue, double newValue) { + log.infof( "Writing double [%s]", name ); + return newValue; + } + + @Override public long readLong(Object obj, String name, long oldValue) { + log.infof( "Reading long [%s]", name ); + return oldValue; + } + @Override public long writeLong(Object obj, String name, long oldValue, long newValue) { + log.infof( "Writing long [%s]", name ); + return newValue; + } + + @Override public Object readObject(Object obj, String name, Object oldValue) { + log.infof( "Reading Object [%s]", name ); + return oldValue; + } + @Override public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + log.infof( "Writing Object [%s]", name ); + return newValue; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java index 8579c413b2..8c23804b89 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java @@ -25,6 +25,7 @@ package org.hibernate.test.bytecode.enhancement; import org.hibernate.Session; +import org.hibernate.test.bytecode.enhancement.entity.MyEntity; import org.junit.Test; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -53,14 +54,14 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase { s = openSession(); s.beginTransaction(); - MyEntity myEntity = (MyEntity) s.get( MyEntity.class, 1L ); + MyEntity myEntity1 = (MyEntity) s.get( MyEntity.class, 1L ); MyEntity myEntity2 = (MyEntity) s.get( MyEntity.class, 2L ); - assertNotNull( myEntity.$$_hibernate_getEntityInstance() ); - assertSame( myEntity, myEntity.$$_hibernate_getEntityInstance() ); - assertNotNull( myEntity.$$_hibernate_getEntityEntry() ); - assertNull( myEntity.$$_hibernate_getPreviousManagedEntity() ); - assertNotNull( myEntity.$$_hibernate_getNextManagedEntity() ); + assertNotNull( myEntity1.$$_hibernate_getEntityInstance() ); + assertSame( myEntity1, myEntity1.$$_hibernate_getEntityInstance() ); + assertNotNull( myEntity1.$$_hibernate_getEntityEntry() ); + assertNull( myEntity1.$$_hibernate_getPreviousManagedEntity() ); + assertNotNull( myEntity1.$$_hibernate_getNextManagedEntity() ); assertNotNull( myEntity2.$$_hibernate_getEntityInstance() ); assertSame( myEntity2, myEntity2.$$_hibernate_getEntityInstance() ); @@ -72,7 +73,7 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase { s.getTransaction().commit(); s.close(); - assertNull( myEntity.$$_hibernate_getEntityEntry() ); + assertNull( myEntity1.$$_hibernate_getEntityEntry() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerEnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerEnhancerTest.java deleted file mode 100644 index f2dd994132..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerEnhancerTest.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * 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.test.bytecode.enhancement.customer; - -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtField; -import javassist.LoaderClassPath; -import org.hibernate.EntityMode; -import org.hibernate.LockMode; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.Enhancer; -import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.engine.spi.Status; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.junit.Test; - -import javax.persistence.ElementCollection; -import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; -import java.io.ByteArrayInputStream; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -/** - * @author Steve Ebersole - */ -public class CustomerEnhancerTest extends BaseUnitTestCase { - private static EnhancementContext enhancementContext = new EnhancementContext() { - @Override - public ClassLoader getLoadingClassLoader() { - return getClass().getClassLoader(); - } - - @Override - public boolean isEntityClass(CtClass classDescriptor) { - return true; - } - - @Override - public boolean isCompositeClass(CtClass classDescriptor) { - return false; - } - - @Override - public boolean doDirtyCheckingInline(CtClass classDescriptor) { - return true; - } - - @Override - public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { - return true; - } - - @Override - public boolean isLazyLoadable(CtField field) { - return true; - } - - @Override - public boolean isMappedCollection(CtField field) { - try { - return (field.getAnnotation(OneToMany.class) != null || - field.getAnnotation(ManyToMany.class) != null || - field.getAnnotation(ElementCollection.class) != null); - } - catch (ClassNotFoundException e) { - return false; - } - } - - @Override - public boolean isPersistentField(CtField ctField) { - return true; - } - - @Override - public CtField[] order(CtField[] persistentFields) { - return persistentFields; - } - }; - - @Test - public void testEnhancement() throws Exception { - testFor( Customer.class ); - } - - private void testFor(Class entityClassToEnhance) throws Exception { - Enhancer enhancer = new Enhancer( enhancementContext ); - CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance ); - byte[] original = entityCtClass.toBytecode(); - //byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); - byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); - assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) ); - - ClassLoader cl = new ClassLoader() { }; - ClassPool cp = new ClassPool( false ); - cp.appendClassPath( new LoaderClassPath( cl ) ); - CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) ); - enhancedCtClass.debugWriteFile("/tmp"); - //just for debugging - Class addressClass = null; - Class customerInventoryClass = null; - Class supplierComponentPKClass = null; - - CtClass addressCtClass = generateCtClassForAnEntity( org.hibernate.test.bytecode.enhancement.customer.Address.class ); - byte[] enhancedAddress = enhancer.enhanceComposite(Address.class.getName(), addressCtClass.toBytecode()); - CtClass enhancedCtClassAddress = cp.makeClass( new ByteArrayInputStream( enhancedAddress ) ); - enhancedCtClassAddress.debugWriteFile("/tmp"); - addressClass = enhancedCtClassAddress.toClass( cl, this.getClass().getProtectionDomain() ); - - CtClass customerInventoryCtClass = generateCtClassForAnEntity( CustomerInventory.class ); - byte[] enhancedCustomerInventory = enhancer.enhance(CustomerInventory.class.getName(), customerInventoryCtClass.toBytecode()); - CtClass enhancedCtClassCustomerInventory = cp.makeClass( new ByteArrayInputStream( enhancedCustomerInventory ) ); - enhancedCtClassCustomerInventory.debugWriteFile("/tmp"); - customerInventoryClass = enhancedCtClassCustomerInventory.toClass( cl, this.getClass().getProtectionDomain() ); - - - CtClass supplierComponentPKCtClass = generateCtClassForAnEntity( SupplierComponentPK.class ); - byte[] enhancedSupplierComponentPK = enhancer.enhanceComposite(SupplierComponentPK.class.getName(), supplierComponentPKCtClass.toBytecode()); - CtClass enhancedCtClassSupplierComponentPK = cp.makeClass( new ByteArrayInputStream( enhancedSupplierComponentPK ) ); - enhancedCtClassSupplierComponentPK.debugWriteFile("/tmp"); - supplierComponentPKClass = enhancedCtClassSupplierComponentPK.toClass( cl, this.getClass().getProtectionDomain() ); - - Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() ); - Object entityInstance = entityClass.newInstance(); - - assertTyping( ManagedEntity.class, entityInstance ); - - // call the new methods - // - Method setter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class ); - Method getter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME ); - assertNull( getter.invoke( entityInstance ) ); - setter.invoke( entityInstance, makeEntityEntry() ); - assertNotNull( getter.invoke( entityInstance ) ); - setter.invoke( entityInstance, new Object[] {null} ); - assertNull( getter.invoke( entityInstance ) ); - - Method entityInstanceGetter = entityClass.getMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME ); - assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) ); - - Method previousGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME ); - Method previousSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class ); - previousSetter.invoke( entityInstance, entityInstance ); - assertSame( entityInstance, previousGetter.invoke( entityInstance ) ); - - Method nextGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME ); - Method nextSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class ); - nextSetter.invoke( entityInstance, entityInstance ); - assertSame( entityInstance, nextGetter.invoke( entityInstance ) ); - - // add an attribute interceptor... - Method interceptorGetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME ); - Method interceptorSetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class ); - - assertNull( interceptorGetter.invoke( entityInstance ) ); - entityClass.getMethod( "getId" ).invoke( entityInstance ); - - interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() ); - assertNotNull(interceptorGetter.invoke(entityInstance)); - - // dirty checking is unfortunately just printlns for now... just verify the test output - entityClass.getMethod( "getId" ).invoke( entityInstance ); - entityClass.getMethod( "setId", Integer.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) ); - entityClass.getMethod( "setId", Integer.class ).invoke( entityInstance, 1 ); - assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance)); - - entityClass.getMethod( "setFirstName", String.class ).invoke(entityInstance, "Erik"); - - entityClass.getMethod( "setLastName", String.class ).invoke(entityInstance, "Mykland"); - assertEquals(3, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size()); - - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - - //testing composite object - assert addressClass != null; - Object address = addressClass.newInstance(); - - assert customerInventoryClass != null; - Object customerInventory = customerInventoryClass.newInstance(); - - //Method adrInterceptorGetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME ); - //Method adrInterceptorSetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class ); - //adrInterceptorSetter.invoke( address, new LocalPersistentAttributeInterceptor() ); - - entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address); - - addressClass.getMethod("setCity", String.class).invoke(address, "Arendal"); - - Set tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(2, tracked.size()); - Iterator iter = tracked.iterator(); - assertEquals("address", iter.next()); - assertEquals("address.city", iter.next()); - - entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance); - - //make sure that new composite instances are cleared - Object address2 = addressClass.newInstance(); - - entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2); - addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien"); - - tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(1, tracked.size()); - - } - - private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { - ClassPool cp = new ClassPool( false ); - return cp.makeClass( - getClass().getClassLoader().getResourceAsStream( - entityClassToEnhance.getName().replace( '.', '/' ) + ".class" - ) - ); - } - - private EntityEntry makeEntityEntry() { - return new EntityEntry( - Status.MANAGED, - null, - null, - new Long(1), - null, - LockMode.NONE, - false, - null, - false, - false, - null - ); - } - - - private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { - @Override - public boolean readBoolean(Object obj, String name, boolean oldValue) { - System.out.println( "Reading boolean [" + name + "]" ); - return oldValue; - } - - @Override - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - System.out.println( "Writing boolean [" + name + "]" ); - return newValue; - } - - @Override - public byte readByte(Object obj, String name, byte oldValue) { - System.out.println( "Reading byte [" + name + "]" ); - return oldValue; - } - - @Override - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - System.out.println( "Writing byte [" + name + "]" ); - return newValue; - } - - @Override - public char readChar(Object obj, String name, char oldValue) { - System.out.println( "Reading char [" + name + "]" ); - return oldValue; - } - - @Override - public char writeChar(Object obj, String name, char oldValue, char newValue) { - System.out.println( "Writing char [" + name + "]" ); - return newValue; - } - - @Override - public short readShort(Object obj, String name, short oldValue) { - System.out.println( "Reading short [" + name + "]" ); - return oldValue; - } - - @Override - public short writeShort(Object obj, String name, short oldValue, short newValue) { - System.out.println( "Writing short [" + name + "]" ); - return newValue; - } - - @Override - public int readInt(Object obj, String name, int oldValue) { - System.out.println( "Reading int [" + name + "]" ); - return oldValue; - } - - @Override - public int writeInt(Object obj, String name, int oldValue, int newValue) { - System.out.println( "Writing int [" + name + "]" ); - return newValue; - } - - @Override - public float readFloat(Object obj, String name, float oldValue) { - System.out.println( "Reading float [" + name + "]" ); - return oldValue; - } - - @Override - public float writeFloat(Object obj, String name, float oldValue, float newValue) { - System.out.println( "Writing float [" + name + "]" ); - return newValue; - } - - @Override - public double readDouble(Object obj, String name, double oldValue) { - System.out.println( "Reading double [" + name + "]" ); - return oldValue; - } - - @Override - public double writeDouble(Object obj, String name, double oldValue, double newValue) { - System.out.println( "Writing double [" + name + "]" ); - return newValue; - } - - @Override - public long readLong(Object obj, String name, long oldValue) { - System.out.println( "Reading long [" + name + "]" ); - return oldValue; - } - - @Override - public long writeLong(Object obj, String name, long oldValue, long newValue) { - System.out.println( "Writing long [" + name + "]" ); - return newValue; - } - - @Override - public Object readObject(Object obj, String name, Object oldValue) { - System.out.println( "Reading Object [" + name + "]" ); - return oldValue; - } - - @Override - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - System.out.println( "Writing Object [" + name + "]" ); - return newValue; - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Address.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Address.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Address.java index fd3b425581..97229694b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Address.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Address.java @@ -4,7 +4,7 @@ * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; import javax.persistence.Embeddable; import javax.persistence.Embedded; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Country.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Country.java similarity index 89% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Country.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Country.java index d1de3a6c77..7dc1998215 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/Country.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/Country.java @@ -4,7 +4,7 @@ * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; import javax.persistence.Embeddable; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/MyEntity.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/MyEntity.java index a784044d89..c817117b67 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/MyEntity.java @@ -21,15 +21,15 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; + +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Transient; -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.EntityEntry; - /** * @author Steve Ebersole */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SampleEntity.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SampleEntity.java index 7681e53288..8927bc4ae5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SampleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SampleEntity.java @@ -21,16 +21,16 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; - -import javax.persistence.Id; -import javax.persistence.Transient; +package org.hibernate.test.bytecode.enhancement.entity; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import javax.persistence.Id; +import javax.persistence.Transient; + /** * @author Steve Ebersole */ diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SimpleEntity.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SimpleEntity.java index f9115be5f8..c9d2028dfe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SimpleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SimpleEntity.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; import javax.persistence.Embedded; import javax.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SubEntity.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SubEntity.java index 342eddabec..8124327cbd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SubEntity.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SuperEntity.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SuperEntity.java index 8306fa97ce..6d0518c3e4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/SuperEntity.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.entity; import javax.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Address.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Address.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Address.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Address.java index 2605958ff4..0c2a1f621c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Address.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Address.java @@ -20,13 +20,15 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - */package org.hibernate.test.bytecode.enhancement.customer; + */package org.hibernate.test.bytecode.enhancement.entity.customer; +import javax.persistence.Embeddable; import java.io.Serializable; /** * @author Ståle W. Pedersen */ +@Embeddable public class Address implements Serializable { private String street1; private String street2; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Customer.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Customer.java similarity index 99% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Customer.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Customer.java index b5fe1c557d..70dc6e032d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/Customer.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/Customer.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement.customer; +package org.hibernate.test.bytecode.enhancement.entity.customer; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventory.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventory.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventory.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventory.java index 8bad23e747..9490da8a87 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventory.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventory.java @@ -20,7 +20,8 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - */package org.hibernate.test.bytecode.enhancement.customer; + */ +package org.hibernate.test.bytecode.enhancement.entity.customer; /** * @author Ståle W. Pedersen diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventoryPK.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventoryPK.java similarity index 94% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventoryPK.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventoryPK.java index 773547c645..a44b78a21f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/CustomerInventoryPK.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/CustomerInventoryPK.java @@ -4,7 +4,7 @@ * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.hibernate.test.bytecode.enhancement.customer; +package org.hibernate.test.bytecode.enhancement.entity.customer; import java.io.Serializable; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/SupplierComponentPK.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/SupplierComponentPK.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/SupplierComponentPK.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/SupplierComponentPK.java index 45467d7bc9..bb8caaed85 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/customer/SupplierComponentPK.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/entity/customer/SupplierComponentPK.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement.customer; +package org.hibernate.test.bytecode.enhancement.entity.customer; import javax.persistence.Embeddable; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CompositeOwnerTrackerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/CompositeOwnerTrackerTest.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CompositeOwnerTrackerTest.java rename to hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/CompositeOwnerTrackerTest.java index cca6bea0f4..4ed54581f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/CompositeOwnerTrackerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/CompositeOwnerTrackerTest.java @@ -21,9 +21,9 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.test.bytecode.enhancement; +package org.hibernate.test.bytecode.enhancement.tracker; -import org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker; +import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; import org.hibernate.engine.spi.CompositeOwner; import org.junit.Test; @@ -32,7 +32,6 @@ import static org.junit.Assert.assertEquals; /** * @author Ståle W. Pedersen */ - public class CompositeOwnerTrackerTest { private int counter = 0; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/DirtyTrackerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/DirtyTrackerTest.java new file mode 100644 index 0000000000..f7ec6871d2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/tracker/DirtyTrackerTest.java @@ -0,0 +1,104 @@ +/* + * 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.test.bytecode.enhancement.tracker; + +import org.hibernate.bytecode.enhance.internal.tracker.SortedDirtyTracker; +import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Ståle W. Pedersen + */ +public class DirtyTrackerTest { + + @Test + public void testSimpleTracker() { + SimpleDirtyTracker tracker = new SimpleDirtyTracker(); + assertTrue(tracker.isEmpty()); + assertTrue(tracker.asSet().isEmpty()); + + tracker.add("foo"); + assertFalse(tracker.isEmpty()); + assertArrayEquals(tracker.asSet().toArray(), new String[]{"foo"}); + + tracker.clear(); + assertTrue(tracker.isEmpty()); + assertTrue(tracker.asSet().isEmpty()); + + tracker.add("foo"); + tracker.add("bar"); + tracker.add("another.bar"); + tracker.add("foo"); + tracker.add("another.foo"); + tracker.add("another.bar"); + assertTrue(tracker.asSet().size() == 4); + + } + + @Test + public void testSortedTracker() { + SortedDirtyTracker tracker = new SortedDirtyTracker(); + assertTrue(tracker.isEmpty()); + assertTrue(tracker.asSet().isEmpty()); + + tracker.add("foo"); + assertFalse(tracker.isEmpty()); + assertArrayEquals(tracker.asSet().toArray(), new String[]{"foo"}); + + tracker.clear(); + assertTrue(tracker.isEmpty()); + assertTrue(tracker.asSet().isEmpty()); + + tracker.add("foo"); + tracker.add("bar"); + tracker.add("another.bar"); + tracker.add("foo"); + tracker.add("another.foo"); + tracker.add("another.bar"); + assertTrue(tracker.asSet().size() == 4); + + // we the algorithm for this implementation relies on the fact that the array is kept sorted, so let's check it really is + assertTrue(isSorted(tracker.asSet())); + } + + private boolean isSorted(Set set) { + String[] arr = new String[set.size()]; + arr = set.toArray(arr); + for (int i = 1; i < arr.length; i++) { + if (arr[i - 1].compareTo(arr[i]) > 0) { + return false; + } + } + return true; + } + +} + +