Skip to content

Commit 417baff

Browse files
barreirosebersole
authored andcommitted
HHH-9938 - bytecode enhancer - field access enhancement feature
1 parent 6d77ac3 commit 417baff

13 files changed

+402
-66
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java

+15-12
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
*/
77
package org.hibernate.bytecode.enhance.internal;
88

9+
import java.util.Collection;
10+
import java.util.Locale;
11+
import javax.persistence.Id;
12+
913
import javassist.CtClass;
1014
import javassist.CtField;
1115
import javassist.NotFoundException;
16+
1217
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
1318
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
14-
15-
import javax.persistence.Id;
16-
import java.util.Collection;
17-
import java.util.Locale;
19+
import org.hibernate.internal.util.compare.EqualsHelper;
1820

1921
/**
2022
* utility class to generate interceptor methods
@@ -40,7 +42,7 @@ public String buildInLineDirtyCheckingBodyFragment(EnhancementContext context, C
4042

4143
// primitives || enums
4244
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
43-
builder.append( String.format( "if (%s != $1)", currentValue.getName()) );
45+
builder.append( String.format( "if (%s != $1)", currentValue.getName() ) );
4446
}
4547
// simple data types
4648
else if ( currentValue.getType().getName().startsWith( "java.lang" )
@@ -62,10 +64,11 @@ else if ( currentValue.getType().getName().startsWith( "java.lang" )
6264
}
6365
}
6466
}
65-
// TODO: for now just call equals, should probably do something else here
66-
builder.append( String.format( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
67+
builder.append( String.format( "if (%1$s == null || !%2$s.equals(%1$s, $1))",
68+
currentValue.getName(),
69+
EqualsHelper.class.getName() ) );
6770
}
68-
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
71+
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
6972
}
7073
catch (ClassNotFoundException e) {
7174
e.printStackTrace();
@@ -131,7 +134,7 @@ public String buildReadInterceptionBodyFragment(String fieldName) {
131134
"}",
132135
fieldName,
133136
type,
134-
EnhancerConstants.INTERCEPTOR_GETTER_NAME);
137+
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
135138
}
136139

137140
public String buildWriteInterceptionBodyFragment(String fieldName) {
@@ -143,7 +146,7 @@ public String buildWriteInterceptionBodyFragment(String fieldName) {
143146
"this.%1$s = localVar;",
144147
fieldName,
145148
type,
146-
EnhancerConstants.INTERCEPTOR_GETTER_NAME);
149+
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
147150
}
148151
}
149152

@@ -159,7 +162,7 @@ private PrimitiveAttributeTypeDescriptor(Class<?> primitiveType) {
159162
throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" );
160163
}
161164
// capitalize first letter
162-
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase(Locale.ROOT) + primitiveType.getSimpleName().substring( 1 );
165+
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase( Locale.ROOT ) + primitiveType.getSimpleName().substring( 1 );
163166
}
164167

165168
public String buildReadInterceptionBodyFragment(String fieldName) {
@@ -180,7 +183,7 @@ public String buildWriteInterceptionBodyFragment(String fieldName) {
180183
"}%n" +
181184
"this.%1$s = localVar;",
182185
fieldName,
183-
type.toLowerCase(Locale.ROOT ),
186+
type.toLowerCase( Locale.ROOT ),
184187
type,
185188
EnhancerConstants.INTERCEPTOR_GETTER_NAME
186189
);

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java

+90-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,13 @@ public void enhance(CtClass managedCtClass) {
6363
);
6464
}
6565

66-
// lastly, find all references to the transformed fields and replace with calls to the added reader/writer methods
66+
// find all references to the transformed fields and replace with calls to the added reader/writer methods
6767
enhanceAttributesAccess( managedCtClass, attrDescriptorMap );
68+
69+
// same thing for direct access to fields of other entities
70+
if ( this.enhancementContext.doFieldAccessEnhancement( managedCtClass ) ) {
71+
enhanceFieldAccess( managedCtClass );
72+
}
6873
}
6974

7075
private CtField[] collectPersistentFields(CtClass managedCtClass) {
@@ -74,6 +79,10 @@ private CtField[] collectPersistentFields(CtClass managedCtClass) {
7479
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
7580
continue;
7681
}
82+
// skip outer reference in inner classes
83+
if ( "this$0".equals( ctField.getName() ) ) {
84+
continue;
85+
}
7786
if ( enhancementContext.isPersistentField( ctField ) ) {
7887
persistentFieldList.add( ctField );
7988
}
@@ -114,7 +123,6 @@ private CtMethod generateFieldReader(
114123
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
115124
}
116125

117-
// TODO: temporary solution...
118126
try {
119127
return MethodWriter.write(
120128
managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}",
@@ -520,4 +528,84 @@ private CtMethod getWriter() {
520528
}
521529
}
522530

531+
/**
532+
* Replace access to fields of entities (for example, entity.field) with a call to the enhanced getter / setter
533+
* (in this example, entity.$$_hibernate_read_field()). It's assumed that the target entity is enhanced as well.
534+
*
535+
* @param managedCtClass Class to enhance
536+
*/
537+
public void enhanceFieldAccess(CtClass managedCtClass) {
538+
final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
539+
540+
for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
541+
final MethodInfo methodInfo = (MethodInfo) oMethod;
542+
final String methodName = methodInfo.getName();
543+
544+
// skip methods added by enhancement and abstract methods (methods without any code)
545+
if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
546+
continue;
547+
}
548+
549+
try {
550+
final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
551+
while ( itr.hasNext() ) {
552+
int index = itr.next();
553+
int op = itr.byteAt( index );
554+
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
555+
continue;
556+
}
557+
String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
558+
String fieldClassName = constPool.getClassInfo( constPool.getFieldrefClass( itr.u16bitAt( index + 1 ) ) );
559+
CtClass targetCtClass = this.classPool.getCtClass( fieldClassName );
560+
561+
if ( !enhancementContext.isEntityClass( targetCtClass ) && !enhancementContext.isCompositeClass( targetCtClass ) ) {
562+
continue;
563+
}
564+
if ( targetCtClass == managedCtClass
565+
|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
566+
|| "this$0".equals( fieldName ) ) {
567+
continue;
568+
}
569+
570+
log.debugf( "Transforming access to field [%s] from method [%s]", fieldName, methodName );
571+
572+
if ( op == Opcode.GETFIELD ) {
573+
int fieldReaderMethodIndex = constPool.addMethodrefInfo(
574+
constPool.addClassInfo( fieldClassName ),
575+
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName,
576+
"()" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) )
577+
);
578+
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
579+
itr.write16bit( fieldReaderMethodIndex, index + 1 );
580+
}
581+
else {
582+
int fieldWriterMethodIndex = constPool.addMethodrefInfo(
583+
constPool.addClassInfo( fieldClassName ),
584+
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName,
585+
"(" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) ) + ")V"
586+
);
587+
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
588+
itr.write16bit( fieldWriterMethodIndex, index + 1 );
589+
}
590+
591+
}
592+
methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
593+
}
594+
catch (BadBytecode bb) {
595+
final String msg = String.format(
596+
"Unable to perform field access transformation in method [%s]",
597+
methodName
598+
);
599+
throw new EnhancementException( msg, bb );
600+
}
601+
catch (NotFoundException nfe) {
602+
final String msg = String.format(
603+
"Unable to perform field access transformation in method [%s]",
604+
methodName
605+
);
606+
throw new EnhancementException( msg, nfe );
607+
}
608+
}
609+
}
610+
523611
}

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/DefaultEnhancementContext.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
*/
77
package org.hibernate.bytecode.enhance.spi;
88

9-
import javassist.CtClass;
10-
import javassist.CtField;
11-
129
import javax.persistence.ElementCollection;
1310
import javax.persistence.Embeddable;
1411
import javax.persistence.Entity;
1512
import javax.persistence.ManyToMany;
1613
import javax.persistence.OneToMany;
1714
import javax.persistence.Transient;
1815

16+
import javassist.CtClass;
17+
import javassist.CtField;
18+
1919
/**
2020
* default implementation of EnhancementContext. May be sub-classed as needed.
2121
*
@@ -58,6 +58,13 @@ public boolean doDirtyCheckingInline(CtClass classDescriptor) {
5858
return true;
5959
}
6060

61+
/**
62+
* @return false
63+
*/
64+
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
65+
return false;
66+
}
67+
6168
/**
6269
* @return true
6370
*/

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ public interface EnhancementContext {
7373
*/
7474
public boolean doDirtyCheckingInline(CtClass classDescriptor);
7575

76+
/**
77+
* Should we enhance field access to entities from this class?
78+
*
79+
* @param classDescriptor The descriptor of the class to check.
80+
*
81+
* @return {@code true} indicates that any direct access to fields of entities should be routed to the enhanced
82+
* getter / setter method.
83+
*/
84+
public boolean doFieldAccessEnhancement(CtClass classDescriptor);
85+
7686
/**
7787
* Does the given class define any lazy loadable attributes?
7888
*

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.hibernate.bytecode.enhance.internal.CompositeEnhancer;
2121
import org.hibernate.bytecode.enhance.internal.EntityEnhancer;
2222
import org.hibernate.bytecode.enhance.internal.FieldWriter;
23+
import org.hibernate.bytecode.enhance.internal.PersistentAttributesEnhancer;
2324
import org.hibernate.engine.spi.EntityEntry;
2425
import org.hibernate.engine.spi.ManagedComposite;
2526
import org.hibernate.engine.spi.ManagedEntity;
@@ -143,6 +144,10 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
143144
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );
144145
new CompositeEnhancer( enhancementContext ).enhance( managedCtClass );
145146
}
147+
else if ( enhancementContext.doFieldAccessEnhancement( managedCtClass ) ) {
148+
log.debugf( "Enhancing field access in [%s]", managedCtClass.getName() );
149+
new PersistentAttributesEnhancer( enhancementContext ).enhanceFieldAccess( managedCtClass );
150+
}
146151
else {
147152
log.debug( "Skipping enhancement: not entity or composite" );
148153
}

hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java

+22-17
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,33 @@
66
*/
77
package org.hibernate.tool.enhance;
88

9+
import java.io.File;
10+
import java.io.FileInputStream;
11+
import java.io.FileNotFoundException;
12+
import java.io.FileOutputStream;
13+
import java.io.IOException;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import javax.persistence.ElementCollection;
17+
import javax.persistence.Embeddable;
18+
import javax.persistence.Entity;
19+
import javax.persistence.ManyToMany;
20+
import javax.persistence.OneToMany;
21+
import javax.persistence.Transient;
22+
923
import javassist.ClassPool;
1024
import javassist.CtClass;
1125
import javassist.CtField;
1226

27+
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
28+
import org.hibernate.bytecode.enhance.spi.Enhancer;
29+
1330
import org.apache.tools.ant.BuildException;
1431
import org.apache.tools.ant.DirectoryScanner;
1532
import org.apache.tools.ant.Project;
1633
import org.apache.tools.ant.Task;
1734
import org.apache.tools.ant.types.FileSet;
1835

19-
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
20-
import org.hibernate.bytecode.enhance.spi.Enhancer;
21-
22-
import javax.persistence.ElementCollection;
23-
import javax.persistence.Embeddable;
24-
import javax.persistence.Entity;
25-
import javax.persistence.ManyToMany;
26-
import javax.persistence.OneToMany;
27-
import javax.persistence.Transient;
28-
import java.io.File;
29-
import java.io.FileInputStream;
30-
import java.io.FileNotFoundException;
31-
import java.io.FileOutputStream;
32-
import java.io.IOException;
33-
import java.util.ArrayList;
34-
import java.util.List;
35-
3636
/**
3737
* Ant task for performing build-time enhancement of entities and component/embeddable classes.
3838
* <p/>
@@ -184,6 +184,11 @@ public boolean doDirtyCheckingInline(CtClass classDescriptor) {
184184
return true;
185185
}
186186

187+
@Override
188+
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
189+
return false;
190+
}
191+
187192
@Override
188193
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
189194
return true;

hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.hibernate.test.bytecode.enhancement.association.OneToOneAssociationTestTask;
1515
import org.hibernate.test.bytecode.enhancement.basic.BasicEnhancementTestTask;
1616
import org.hibernate.test.bytecode.enhancement.dirty.DirtyTrackingTestTask;
17+
import org.hibernate.test.bytecode.enhancement.field.FieldAccessBidirectionalTestTasK;
18+
import org.hibernate.test.bytecode.enhancement.field.FieldAccessEnhancementTestTask;
1719
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask1;
1820
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask2;
1921
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask3;
@@ -51,6 +53,12 @@ public void testLazy() {
5153
EnhancerTestUtils.runEnhancerTestTask( LazyLoadingIntegrationTestTask.class );
5254
}
5355

56+
@Test
57+
public void testFieldAccess() {
58+
EnhancerTestUtils.runEnhancerTestTask( FieldAccessEnhancementTestTask.class );
59+
EnhancerTestUtils.runEnhancerTestTask( FieldAccessBidirectionalTestTasK.class );
60+
}
61+
5462
@Test
5563
@TestForIssue( jiraKey = "HHH-3949" )
5664
@FailureExpected( jiraKey = "HHH-3949" )
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.bytecode.enhancement;
8+
9+
import javassist.CtClass;
10+
11+
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
12+
13+
/**
14+
* Enhancement context used in tests
15+
*
16+
* @author Luis Barreiro
17+
*/
18+
public class EnhancerTestContext extends DefaultEnhancementContext {
19+
20+
@Override
21+
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
22+
return true;
23+
}
24+
}

0 commit comments

Comments
 (0)