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;
+ }
+
+}
+
+