Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
*/
package org.hibernate.property.access.internal;

import java.beans.Introspector;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;

import org.hibernate.MappingException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.engine.spi.CompositeOwner;
Expand All @@ -31,6 +28,7 @@
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType;
import static org.hibernate.internal.util.ReflectHelper.NO_PARAM_SIGNATURE;
import static org.hibernate.internal.util.ReflectHelper.findField;
import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull;
import static org.hibernate.internal.util.ReflectHelper.isRecord;

/**
Expand Down Expand Up @@ -85,89 +83,9 @@ public static AccessType getAccessType(Class<?> containerJavaType, String proper
return AccessType.FIELD;
}

for ( Method method : containerClass.getDeclaredMethods() ) {
// if the method has parameters, skip it
if ( method.getParameterCount() != 0 ) {
continue;
}

// if the method is a "bridge", skip it
if ( method.isBridge() ) {
continue;
}

if ( method.isAnnotationPresent( Transient.class ) ) {
continue;
}

if ( Modifier.isStatic( method.getModifiers() ) ) {
continue;
}

final String methodName = method.getName();

// try "get"
if ( methodName.startsWith( "get" ) ) {
final String stemName = methodName.substring( 3 );
final String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
if ( method.isAnnotationPresent( Access.class ) ) {
return AccessType.PROPERTY;
}
else {
checkIsMethodVariant( containerClass, propertyName, method, stemName );
}
}
}

// if not "get", then try "is"
if ( methodName.startsWith( "is" ) ) {
final String stemName = methodName.substring( 2 );
String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
if ( method.isAnnotationPresent( Access.class ) ) {
return AccessType.PROPERTY;
}
}
}
}

return null;
}

private static void checkIsMethodVariant(
Class<?> containerClass,
String propertyName,
Method method,
String stemName) {
final Method isMethodVariant = findIsMethodVariant( containerClass, stemName );
if ( isMethodVariant == null ) {
return;
}

if ( !isMethodVariant.isAnnotationPresent( Access.class ) ) {
throw new MappingException(
String.format(
Locale.ROOT,
"Class '%s' declares both 'get' [%s] and 'is' [%s] variants of getter for property '%s'",
containerClass.getName(),
method.toString(),
isMethodVariant,
propertyName
)
);
}
}

public static @Nullable Method findIsMethodVariant(Class<?> containerClass, String stemName) {
// verify that the Class does not also define a method with the same stem name with 'is'
try {
final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName );
if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) {
return isMethod;
}
}
catch (NoSuchMethodException ignore) {
final Method getter = getterMethodOrNull( containerClass, propertyName );
if ( getter != null && getter.isAnnotationPresent( Access.class ) ) {
return AccessType.PROPERTY;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.bytecode.enhancement.access;

import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Basic;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

import static org.assertj.core.api.Assertions.assertThat;

@DomainModel(
annotatedClasses = {
HierarchyPropertyAccessTest.ChildEntity.class,
}
)
@SessionFactory
@JiraKey("HHH-19140")
@BytecodeEnhanced
public class HierarchyPropertyAccessTest {


@Test
public void testParent(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.persist( new ParentEntity( 1L, "field", "transient: property" ) );
} );

scope.inTransaction( session -> {
ParentEntity entity = session.get( ParentEntity.class, 1L );
assertThat( entity.persistProperty ).isEqualTo( "property" );
assertThat( entity.property ).isEqualTo( "transient: property" );

entity.setProperty( "transient: updated" );
} );

scope.inTransaction( session -> {
ParentEntity entity = session.get( ParentEntity.class, 1L );
assertThat( entity.persistProperty ).isEqualTo( "updated" );
assertThat( entity.property ).isEqualTo( "transient: updated" );
} );
}

@Test
public void testChild(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.persist( new ChildEntity( 2L, "field", "transient: property" ) );
} );

scope.inTransaction( session -> {
ChildEntity entity = session.get( ChildEntity.class, 2L );
assertThat( entity.persistProperty ).isEqualTo( "property" );
assertThat( entity.property ).isEqualTo( "transient: property" );

entity.setProperty( "transient: updated" );
} );

scope.inTransaction( session -> {
ChildEntity entity = session.get( ChildEntity.class, 2L );
assertThat( entity.persistProperty ).isEqualTo( "updated" );
assertThat( entity.property ).isEqualTo( "transient: updated" );
} );
}

@AfterEach
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( session -> {
ParentEntity parentEntity = session.get( ParentEntity.class, 1L );
if (parentEntity != null) {
session.remove( parentEntity );
}
ChildEntity childEntity = session.get( ChildEntity.class, 2L );
if (childEntity != null) {
session.remove( childEntity );
}
} );
}

@Entity
@Table(name = "PARENT_ENTITY")
@Inheritance
@DiscriminatorColumn(name = "type")
@DiscriminatorValue("Parent")
static class ParentEntity {
@Id
Long id;

@Basic
String field;

String persistProperty;

@Transient
String property;

public ParentEntity() {
}

public ParentEntity(Long id, String field, String property) {
this.id = id;
this.field = field;
this.property = property;
}

@Basic
@Access(AccessType.PROPERTY)
public String getPersistProperty() {
this.persistProperty = this.property.substring( 11 );
return this.persistProperty;
}

public void setPersistProperty(String persistProperty) {
this.property = "transient: " + persistProperty;
this.persistProperty = persistProperty;
}

public String getProperty() {
return this.property;
}

public void setProperty(String property) {
this.property = property;
}
}

@Entity
@DiscriminatorValue("Child")
static class ChildEntity extends ParentEntity {

public ChildEntity() {
}

public ChildEntity(Long id, String field, String property) {
super(id, field, property);
}
}
}
Loading