diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/Book.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/Book.java new file mode 100644 index 00000000000..9360481007e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/Book.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.documentation.mapper.orm.binding.document.model.dsl.injection; + +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.search.documentation.testsupport.data.ISBN; +import org.hibernate.search.documentation.testsupport.data.ISBNAttributeConverter; +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding; + +@Entity +@Indexed +public class Book { + + @Id + @GeneratedValue + @GenericField + private Integer id; + + @Convert(converter = ISBNAttributeConverter.class) + @PropertyBinding(binder = @PropertyBinderRef(type = ISBNBinder.class)) + private ISBN isbn; + + public Integer getId() { + return id; + } + + public ISBN getIsbn() { + return isbn; + } + + public void setIsbn(ISBN isbn) { + this.isbn = isbn; + } +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/DocumentModelDslInjectIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/DocumentModelDslInjectIT.java new file mode 100644 index 00000000000..ce11fcb176c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/DocumentModelDslInjectIT.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.documentation.mapper.orm.binding.document.model.dsl.injection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils.with; + +import java.util.Arrays; +import java.util.List; + +import jakarta.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper; +import org.hibernate.search.documentation.testsupport.data.ISBN; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.session.SearchSession; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class DocumentModelDslInjectIT { + @RegisterExtension + public DocumentationSetupHelper setupHelper = DocumentationSetupHelper.withSingleBackend( BackendConfigurations.simple() ); + + private EntityManagerFactory entityManagerFactory; + + @BeforeEach + void setup() { + entityManagerFactory = setupHelper.start().setup( Book.class ); + } + + @Test + void smoke() { + with( entityManagerFactory ).runInTransaction( entityManager -> { + Book book = new Book(); + book.setIsbn( ISBN.parse( "978-0-58-600835-5" ) ); + entityManager.persist( book ); + } ); + + with( entityManagerFactory ).runInTransaction( entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + List result = searchSession.search( Arrays.asList( Book.class ) ) + .where( f -> f.match().field( "isbn" ).matching( "978-0-58-600835-5" ) ) + .fetchHits( 20 ); + + assertThat( result ).hasSize( 1 ); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/ISBNBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/ISBNBinder.java new file mode 100644 index 00000000000..4f81bb77e48 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/document/model/dsl/injection/ISBNBinder.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.documentation.mapper.orm.binding.document.model.dsl.injection; + +import org.hibernate.search.documentation.testsupport.data.ISBN; +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.pojo.bridge.PropertyBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; + +//tag::bind[] +public class ISBNBinder implements PropertyBinder { + + @KeywordField(normalizer = "isbn") + IndexFieldReference isbn; + + IndexFieldReference isbnNoAnnotation; + + @Override + public void bind(PropertyBindingContext context) { + context.dependencies() + .useRootOnly(); + + context.bridge( // <6> + ISBN.class, // <7> + new ISBNBridge( isbn ) // <8> + ); + } + //end::bind[] + + //tag::write[] + private static class ISBNBridge implements PropertyBridge { + + private final IndexFieldReference fieldReference; + + private ISBNBridge(IndexFieldReference fieldReference) { + this.fieldReference = fieldReference; + } + + @Override + public void write(DocumentElement target, ISBN bridgedElement, PropertyBridgeWriteContext context) { + String indexedValue = /* ... (extraction of data, not relevant) ... */ + //end::write[] + bridgedElement.getStringValue(); + //tag::write[] + target.addValue( this.fieldReference, indexedValue ); // <1> + } + } + //end::write[] + //tag::bind[] +} +//end::bind[] diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java index c298911d907..42babe68ed0 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java @@ -21,7 +21,6 @@ import org.hibernate.models.spi.ClassDetailsRegistry; import org.hibernate.search.mapper.orm.logging.impl.MappingLog; import org.hibernate.search.mapper.pojo.model.models.spi.AbstractPojoModelsBootstrapIntrospector; -import org.hibernate.search.mapper.pojo.model.models.spi.PojoModelsGenericContextHelper; import org.hibernate.search.mapper.pojo.model.spi.AbstractPojoRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext; import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; @@ -30,6 +29,7 @@ import org.hibernate.search.util.common.reflect.spi.ValueCreateHandle; import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; public class HibernateOrmBootstrapIntrospector extends AbstractPojoModelsBootstrapIntrospector implements PojoBootstrapIntrospector { @@ -44,7 +44,6 @@ public static HibernateOrmBootstrapIntrospector create( } private final HibernateOrmBasicTypeMetadataProvider basicTypeMetadataProvider; - private final PojoModelsGenericContextHelper genericContextHelper; /* * Note: the main purpose of these caches is not to improve performance, @@ -64,11 +63,10 @@ private HibernateOrmBootstrapIntrospector( ValueHandleFactory valueHandleFactory) { super( classDetailsRegistry, valueHandleFactory ); this.basicTypeMetadataProvider = basicTypeMetadataProvider; - this.genericContextHelper = new PojoModelsGenericContextHelper( this ); } @Override - public AbstractPojoRawTypeModel typeModel(String name) { + public AbstractPojoRawTypeModel typeModel(String name) { HibernateOrmBasicDynamicMapTypeMetadata dynamicMapTypeOrmMetadata = basicTypeMetadataProvider.getBasicDynamicMapTypeMetadata( name ); if ( dynamicMapTypeOrmMetadata != null ) { @@ -112,6 +110,12 @@ protected ValueReadHandle createValueReadHandle(Member member) throws Illegal return super.createValueReadHandle( member ); } + @Override + protected ValueReadWriteHandle createValueReadWriteHandle(Member member) throws IllegalAccessException { + setAccessible( member ); + return super.createValueReadWriteHandle( member ); + } + ValueReadHandle createValueReadHandle(Class holderClass, Member member, HibernateOrmBasicClassPropertyMetadata ormPropertyMetadata) throws IllegalAccessException { diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassPropertyModel.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassPropertyModel.java index 4c4976bec8a..7a36214b87c 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassPropertyModel.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassPropertyModel.java @@ -12,7 +12,7 @@ import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; class HibernateOrmClassPropertyModel - extends AbstractPojoModelsPropertyModel { + extends AbstractPojoModelsPropertyModel> { private final HibernateOrmBasicClassPropertyMetadata ormPropertyMetadata; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassRawTypeModel.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassRawTypeModel.java index 0cc787aeb79..04fef3ef6d2 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassRawTypeModel.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmClassRawTypeModel.java @@ -18,10 +18,11 @@ import org.hibernate.models.spi.MemberDetails; import org.hibernate.search.mapper.pojo.model.models.spi.AbstractPojoModelsRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext; +import org.hibernate.search.mapper.pojo.model.spi.PojoPropertyModel; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; public class HibernateOrmClassRawTypeModel - extends AbstractPojoModelsRawTypeModel { + extends AbstractPojoModelsRawTypeModel> { private final HibernateOrmBasicClassTypeMetadata ormTypeMetadata; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmDynamicMapRawTypeModel.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmDynamicMapRawTypeModel.java index b09f0f90bc0..a3be487629f 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmDynamicMapRawTypeModel.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmDynamicMapRawTypeModel.java @@ -14,13 +14,14 @@ import org.hibernate.search.engine.mapper.model.spi.MappableTypeModel; import org.hibernate.search.mapper.pojo.model.spi.AbstractPojoRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.PojoConstructorModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoPropertyModel; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.PojoTypeModel; @SuppressWarnings("rawtypes") public class HibernateOrmDynamicMapRawTypeModel - extends AbstractPojoRawTypeModel { + extends AbstractPojoRawTypeModel> { private final HibernateOrmBasicDynamicMapTypeMetadata ormTypeMetadata; diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/binding/impl/PropertyBindingContextImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/binding/impl/PropertyBindingContextImpl.java index 0ac331ec990..d712c7b43a4 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/binding/impl/PropertyBindingContextImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/binding/impl/PropertyBindingContextImpl.java @@ -14,8 +14,10 @@ import org.hibernate.search.engine.mapper.mapping.building.spi.IndexBindingContext; import org.hibernate.search.mapper.pojo.bridge.PropertyBridge; import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.impl.BeanDelegatingBinder; import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; import org.hibernate.search.mapper.pojo.logging.impl.MappingLog; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.PojoInjectableBinderInjector; import org.hibernate.search.mapper.pojo.model.PojoModelProperty; import org.hibernate.search.mapper.pojo.model.dependency.PojoPropertyIndexingDependencyConfigurationContext; import org.hibernate.search.mapper.pojo.model.dependency.impl.PojoPropertyIndexingDependencyConfigurationContextImpl; @@ -31,6 +33,7 @@ public class PropertyBindingContextImpl

extends AbstractCompositeBindingConte implements PropertyBindingContext { private final PojoBootstrapIntrospector introspector; + private final PojoInjectableBinderInjector binderInjector; private final PojoTypeModel propertyTypeModel; private final PojoModelPropertyRootElement

bridgedElement; private final PojoPropertyIndexingDependencyConfigurationContextImpl

dependencyContext; @@ -42,6 +45,7 @@ public class PropertyBindingContextImpl

extends AbstractCompositeBindingConte public PropertyBindingContextImpl(BeanResolver beanResolver, PojoBootstrapIntrospector introspector, + PojoInjectableBinderInjector binderInjector, PojoTypeModel

propertyTypeModel, IndexBindingContext indexBindingContext, PojoModelPropertyRootElement

bridgedElement, @@ -49,6 +53,7 @@ public PropertyBindingContextImpl(BeanResolver beanResolver, Map params) { super( beanResolver, params ); this.introspector = introspector; + this.binderInjector = binderInjector; this.propertyTypeModel = propertyTypeModel; this.bridgedElement = bridgedElement; this.dependencyContext = dependencyContext; @@ -90,6 +95,11 @@ public IndexSchemaElement indexSchemaElement() { public Optional> applyBinder(PropertyBinder binder) { try { // This call should set the partial binding + // TODO: we probably should delay the injection of the binder properties till this point + // here we have access to the index schema element and can create the sub-elements we need. + if ( !BeanDelegatingBinder.class.equals( binder.getClass() ) ) { + injectBinderFields( binder ); + } binder.bind( this ); if ( partialBinding == null ) { throw MappingLog.INSTANCE.missingBridgeForBinder( binder ); @@ -135,6 +145,13 @@ private void checkAndBind(BeanHolder> bridgeHo this.partialBinding = new PartialBinding<>( castedBridgeHolder ); } + // TODO: if users have their delegating binders, those won't get injected with fields ... + // we probably do not care as much about it as in that case we won't get anything out for the static model ... + // but just in case we think of some valid use case then maybe expose this method (or rather whatever it will grow into) through the context to the user. + public void injectBinderFields(PropertyBinder binder) { + binderInjector.injectFields( binder, indexSchemaElement, params() ); + } + private static class PartialBinding

{ private final BeanHolder> bridgeHolder; diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/mapping/impl/BeanDelegatingBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/mapping/impl/BeanDelegatingBinder.java index cc1ac5a3af2..88627c8b82e 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/mapping/impl/BeanDelegatingBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/bridge/mapping/impl/BeanDelegatingBinder.java @@ -13,6 +13,7 @@ import org.hibernate.search.mapper.pojo.bridge.binding.RoutingBindingContext; import org.hibernate.search.mapper.pojo.bridge.binding.TypeBindingContext; import org.hibernate.search.mapper.pojo.bridge.binding.ValueBindingContext; +import org.hibernate.search.mapper.pojo.bridge.binding.impl.PropertyBindingContextImpl; import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.IdentifierBinder; import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.MarkerBinder; import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; @@ -29,9 +30,11 @@ public final class BeanDelegatingBinder MarkerBinder, IdentifierBinder, ValueBinder { private final BeanReference delegateReference; + private final Class type; - public BeanDelegatingBinder(BeanReference delegateReference) { + public BeanDelegatingBinder(BeanReference delegateReference, Class type) { this.delegateReference = delegateReference; + this.type = type; } @Override @@ -51,7 +54,11 @@ public void bind(TypeBindingContext context) { public void bind(PropertyBindingContext context) { try ( BeanHolder delegateHolder = createDelegate( context.beanResolver(), PropertyBinder.class ) ) { - delegateHolder.get().bind( context ); + PropertyBinder binder = delegateHolder.get(); + // TODO: injecting into this delegating binder itself will make no sense, + // and we need to inject into the delegate itself ... + ( (PropertyBindingContextImpl) context ).injectBinderFields( binder ); + binder.bind( context ); } } @@ -91,4 +98,7 @@ private BeanHolder createDelegate(BeanResolver beanResolver, Cl return delegateReference.asSubTypeOf( expectedType ).resolve( beanResolver ); } + public Class getDelegateType() { + return type; + } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/MappingLog.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/MappingLog.java index 5f489ab9d03..8399fa95c0e 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/MappingLog.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/MappingLog.java @@ -713,4 +713,12 @@ SearchException usingNonDefaultValueConvertAndValueModelNotAllowed(String model, @Message(id = ID_OFFSET + 170, value = "Property name '%1$s' cannot contain dots.") IllegalArgumentException propertyNameCannotContainDots(String propertyName); + + @Message(id = ID_OFFSET + 179, value = "Unable to retrieve injectable binder model for class '%1$s'.") + SearchException errorRetrievingInjectableBinderModel(@FormatWith(ClassFormatter.class) Class clazz, + @Cause Exception cause); + + @LogMessage(level = Logger.Level.DEBUG) + @Message(id = ID_OFFSET + 180, value = "Unable to retrieve injectable binder model for class '%1$s': %2$s") + void cannotLoadClassDetailsButIgnore(@FormatWith(ClassFormatter.class) Class binderType, String message); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/PojoMapperLog.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/PojoMapperLog.java index 5879de6bad6..6c2b87b87c8 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/PojoMapperLog.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/PojoMapperLog.java @@ -55,7 +55,7 @@ public interface PojoMapperLog * here to the next value. */ @LogMessage(level = TRACE) - @Message(id = ID_OFFSET + 179, value = "") + @Message(id = ID_OFFSET + 181, value = "") void nextLoggerIdForConvenience(); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoIndexModelBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoIndexModelBinder.java index b8b4f1177dc..3d3fc330e47 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoIndexModelBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoIndexModelBinder.java @@ -34,6 +34,7 @@ import org.hibernate.search.mapper.pojo.extractor.impl.ContainerExtractorBinder; import org.hibernate.search.mapper.pojo.extractor.impl.ContainerExtractorHolder; import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.PojoInjectableBinderInjector; import org.hibernate.search.mapper.pojo.model.additionalmetadata.building.impl.PojoTypeAdditionalMetadataProvider; import org.hibernate.search.mapper.pojo.model.additionalmetadata.impl.PojoEntityTypeAdditionalMetadata; import org.hibernate.search.mapper.pojo.model.dependency.impl.PojoPropertyIndexingDependencyConfigurationContextImpl; @@ -64,16 +65,19 @@ public final class PojoIndexModelBinder { private final BeanResolver beanResolver; private final PojoBootstrapIntrospector introspector; + private final PojoInjectableBinderInjector binderInjector; private final ContainerExtractorBinder extractorBinder; private final BridgeResolver bridgeResolver; private final PojoTypeAdditionalMetadataProvider typeAdditionalMetadataProvider; PojoIndexModelBinder(MappingBuildContext buildContext, PojoBootstrapIntrospector introspector, + PojoInjectableBinderInjector binderInjector, ContainerExtractorBinder extractorBinder, BridgeResolver bridgeResolver, PojoTypeAdditionalMetadataProvider typeAdditionalMetadataProvider) { this.beanResolver = buildContext.beanResolver(); this.introspector = introspector; + this.binderInjector = binderInjector; this.extractorBinder = extractorBinder; this.bridgeResolver = bridgeResolver; this.typeAdditionalMetadataProvider = typeAdditionalMetadataProvider; @@ -171,7 +175,7 @@ public

Optional> bindProperty(IndexBindingContext ind PojoTypeModel

propertyTypeModel = modelPath.getPropertyModel().typeModel(); PropertyBindingContextImpl

bindingContext = new PropertyBindingContextImpl<>( - beanResolver, introspector, + beanResolver, introspector, binderInjector, propertyTypeModel, indexBindingContext, pojoModelRootElement, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoMapper.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoMapper.java index 3ef89662dc0..72385e8dd46 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoMapper.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/building/impl/PojoMapper.java @@ -41,6 +41,7 @@ import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoIndexMappingCollectorTypeNode; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoMapperDelegate; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.PojoInjectableBinderInjector; import org.hibernate.search.mapper.pojo.mapping.impl.AbstractPojoTypeManager; import org.hibernate.search.mapper.pojo.mapping.impl.PojoIndexedTypeManager; import org.hibernate.search.mapper.pojo.mapping.impl.PojoMappingDelegateImpl; @@ -92,6 +93,7 @@ public class PojoMapper implements Mapper public PojoMapper(MappingBuildContext buildContext, TypeMetadataContributorProvider contributorProvider, PojoBootstrapIntrospector introspector, + PojoInjectableBinderInjector binderInjector, ContainerExtractorBinder extractorBinder, BridgeResolver bridgeResolver, BeanReference> providedIdentifierBridge, @@ -118,7 +120,7 @@ public PojoMapper(MappingBuildContext buildContext, this.extractorBinder = extractorBinder; PojoIndexModelBinder indexModelBinder = new PojoIndexModelBinder( - buildContext, introspector, extractorBinder, bridgeResolver, typeAdditionalMetadataProvider + buildContext, introspector, binderInjector, extractorBinder, bridgeResolver, typeAdditionalMetadataProvider ); mappingHelper = new PojoMappingHelper( buildContext.beanResolver(), failureCollector, contributorProvider, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationMappingConfigurationContextImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationMappingConfigurationContextImpl.java index 29aefe2ce24..1d4ab4b65bb 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationMappingConfigurationContextImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationMappingConfigurationContextImpl.java @@ -45,6 +45,7 @@ public class AnnotationMappingConfigurationContextImpl private final PojoBootstrapIntrospector introspector; private final MapperHints mapperHints; + private final AnnotationPojoInjectableBinderCollector injectableBinderCollector; private boolean discoverAnnotatedTypesFromRootMappingAnnotations = false; private boolean discoverJandexIndexesFromAddedTypes = false; @@ -56,9 +57,11 @@ public class AnnotationMappingConfigurationContextImpl private final List explicitJandexIndexes = new ArrayList<>(); public AnnotationMappingConfigurationContextImpl(PojoBootstrapIntrospector introspector, - MapperHints mapperHints) { + MapperHints mapperHints, + AnnotationPojoInjectableBinderCollector injectableBinderCollector) { this.introspector = introspector; this.mapperHints = mapperHints; + this.injectableBinderCollector = injectableBinderCollector; } @Override @@ -111,7 +114,7 @@ public void configure(MappingBuildContext buildContext, PojoMappingConfiguration AnnotationHelper annotationHelper = new AnnotationHelper( introspector.annotationValueHandleFactory() ); AnnotationPojoTypeMetadataContributorFactory contributorFactory = new AnnotationPojoTypeMetadataContributorFactory( beanResolver, failureCollector, configurationContext, - annotationHelper ); + annotationHelper, injectableBinderCollector ); Set> typesToProcess = new LinkedHashSet<>(); @@ -154,6 +157,8 @@ public void configure(MappingBuildContext buildContext, PojoMappingConfiguration new PojoAnnotationTypeMetadataDiscoverer( contributorFactory, alreadyContributedTypes ); collector.collectDiscoverer( discoverer ); } + + injectableBinderCollector.processDiscoveredBinders(); } private void discoverAnnotatedTypesFromRootMappingAnnotation(Set> annotatedTypes, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoInjectableBinderCollector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoInjectableBinderCollector.java new file mode 100644 index 00000000000..3cddca0a8af --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoInjectableBinderCollector.java @@ -0,0 +1,251 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.hibernate.models.UnknownClassException; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.backend.types.Highlightable; +import org.hibernate.search.engine.backend.types.Norms; +import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.backend.types.Searchable; +import org.hibernate.search.engine.backend.types.TermVector; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFactory; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.engine.backend.types.dsl.StringIndexFieldTypeOptionsStep; +import org.hibernate.search.engine.search.common.NamedValues; +import org.hibernate.search.mapper.pojo.bridge.mapping.impl.BeanDelegatingBinder; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.logging.impl.MappingLog; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectableBinderModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectablePropertyModel; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; + +public final class AnnotationPojoInjectableBinderCollector { + + private final PojoBootstrapIntrospector introspector; + private List> injectableBinderModels; + private Set> alreadyAddedBinders = new HashSet<>(); + private Map, List> injectors = new HashMap<>(); + + public AnnotationPojoInjectableBinderCollector(PojoBootstrapIntrospector introspector) { + this.introspector = introspector; + } + + void processDiscoveredBinders() { + if ( hasContent() ) { + for ( PojoInjectableBinderModel model : injectableBinderModels ) { + List injectorsPerModel = new ArrayList<>(); + for ( PojoInjectablePropertyModel property : model.declaredProperties() ) { + InjectableBinderFieldInjector injector = null; + for ( Annotation annotation : property.annotations().toList() ) { + Optional> processor = + getAnnotationProcessor( annotation ); + if ( processor.isPresent() ) { + if ( injector != null ) { + throw new IllegalStateException( + "Found more than one Search annotations on a single injectable field" ); + } + injector = processor.get().process( property, annotation ); + injectorsPerModel.add( injector ); + } + } + } + injectors.put( model.rawType().typeIdentifier().javaClass(), + Collections.unmodifiableList( injectorsPerModel ) ); + } + injectableBinderModels = null; + } + } + + @SuppressWarnings("unchecked") + private Optional> getAnnotationProcessor(A annotation) { + if ( annotation instanceof FullTextField ) { + return Optional + .of( (InjectableBinderAnnotationProcessor) FullTextFieldInjectableBinderAnnotationProcessor.INSTANCE ); + } + else if ( annotation instanceof KeywordField ) { + return Optional + .of( (InjectableBinderAnnotationProcessor) KeywordFieldInjectableBinderAnnotationProcessor.INSTANCE ); + } + else { + return Optional.empty(); + } + } + + public void add(PropertyBinder binder) { + Class binderType = null; + if ( binder instanceof BeanDelegatingBinder delegatingBinder ) { + binderType = delegatingBinder.getDelegateType(); + } + else { + binderType = binder.getClass(); + } + if ( alreadyAddedBinders.add( binderType ) ) { + try { + add( introspector.injectableBinderModel( binderType ) ); + } + catch (UnknownClassException e) { + // assume it's a lambda and we don't have what to collect from it anyway + MappingLog.INSTANCE.cannotLoadClassDetailsButIgnore( binderType, e.getMessage() ); + } + } + } + + public AnnotationPojoInjectableBinderCollector add(PojoInjectableBinderModel model) { + initChildren(); + this.injectableBinderModels.add( model ); + return this; + } + + public boolean hasContent() { + return injectableBinderModels != null && !injectableBinderModels.isEmpty(); + } + + private void initChildren() { + if ( this.injectableBinderModels == null ) { + this.injectableBinderModels = new ArrayList<>(); + } + } + + List injector(Class classType) { + return injectors.getOrDefault( classType, List.of() ); + } + + + interface InjectableBinderAnnotationProcessor { + InjectableBinderFieldInjector process(PojoInjectablePropertyModel property, A annotation); + } + + + interface InjectableBinderFieldInjector { + // todo: create some context class instead of this list of params... + void inject(Object binder, IndexSchemaElement indexSchemaElement, NamedValues params); + } + + private static class FullTextFieldInjectableBinderAnnotationProcessor + implements InjectableBinderAnnotationProcessor { + + static final FullTextFieldInjectableBinderAnnotationProcessor INSTANCE = + new FullTextFieldInjectableBinderAnnotationProcessor(); + + @Override + public InjectableBinderFieldInjector process(PojoInjectablePropertyModel property, FullTextField annotation) { + String name = annotation.name(); + if ( name == null || name.isBlank() ) { + name = property.name(); + } + return new InjectableBinderFieldInjectorImpl<>( property, name, f -> { + StringIndexFieldTypeOptionsStep step = f.asString(); + + Projectable projectable = annotation.projectable(); + if ( !Projectable.DEFAULT.equals( projectable ) ) { + step.projectable( projectable ); + } + + Searchable searchable = annotation.searchable(); + if ( !Searchable.DEFAULT.equals( searchable ) ) { + step.searchable( searchable ); + } + + if ( !annotation.searchAnalyzer().isEmpty() ) { + step.searchAnalyzer( annotation.searchAnalyzer() ); + } + + Norms norms = annotation.norms(); + if ( !Norms.DEFAULT.equals( norms ) ) { + step.norms( norms ); + } + + TermVector termVector = annotation.termVector(); + if ( !TermVector.DEFAULT.equals( termVector ) ) { + step.termVector( termVector ); + } + Highlightable[] highlightable = annotation.highlightable(); + if ( !( highlightable.length == 1 && Highlightable.DEFAULT.equals( highlightable[0] ) ) ) { + step.highlightable( + highlightable.length == 0 ? Collections.emptyList() : Arrays.asList( highlightable ) + ); + } + + return step; + } ); + } + } + + private static class KeywordFieldInjectableBinderAnnotationProcessor + implements InjectableBinderAnnotationProcessor { + + static final KeywordFieldInjectableBinderAnnotationProcessor INSTANCE = + new KeywordFieldInjectableBinderAnnotationProcessor(); + + @Override + public InjectableBinderFieldInjector process(PojoInjectablePropertyModel property, KeywordField annotation) { + String name = annotation.name(); + if ( name == null || name.isBlank() ) { + name = property.name(); + } + return new InjectableBinderFieldInjectorImpl<>( property, name, f -> { + StringIndexFieldTypeOptionsStep step = f.asString(); + + Projectable projectable = annotation.projectable(); + if ( !Projectable.DEFAULT.equals( projectable ) ) { + step.projectable( projectable ); + } + + Searchable searchable = annotation.searchable(); + if ( !Searchable.DEFAULT.equals( searchable ) ) { + step.searchable( searchable ); + } + + String normalizer = annotation.normalizer(); + if ( !normalizer.isEmpty() ) { + step.normalizer( annotation.normalizer() ); + } + + Norms norms = annotation.norms(); + if ( !Norms.DEFAULT.equals( norms ) ) { + step.norms( norms ); + } + + return step; + } ); + } + } + + private static class InjectableBinderFieldInjectorImpl implements InjectableBinderFieldInjector { + private final ValueReadWriteHandle writeHandle; + private final String relativeFieldName; + private final Function> typeContributor; + + private InjectableBinderFieldInjectorImpl(PojoInjectablePropertyModel property, String relativeFieldName, + Function> typeContributor) { + this.writeHandle = property.handle(); + this.relativeFieldName = relativeFieldName; + this.typeContributor = typeContributor; + } + + @Override + public void inject(Object binder, IndexSchemaElement indexSchemaElement, NamedValues params) { + IndexFieldReference reference = indexSchemaElement.field( relativeFieldName, typeContributor ).toReference(); + writeHandle.set( binder, reference ); + } + } +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoTypeMetadataContributorFactory.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoTypeMetadataContributorFactory.java index 026bac47b27..8c792d5221e 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoTypeMetadataContributorFactory.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/AnnotationPojoTypeMetadataContributorFactory.java @@ -43,19 +43,22 @@ class AnnotationPojoTypeMetadataContributorFactory { private final FailureCollector rootFailureCollector; private final PojoMappingConfigurationContext configurationContext; private final AnnotationHelper annotationHelper; + private final AnnotationPojoInjectableBinderCollector injectableBinderCollector; private final AnnotationProcessorProvider annotationProcessorProvider; AnnotationPojoTypeMetadataContributorFactory(BeanResolver beanResolver, FailureCollector rootFailureCollector, - PojoMappingConfigurationContext configurationContext, AnnotationHelper annotationHelper) { + PojoMappingConfigurationContext configurationContext, AnnotationHelper annotationHelper, + AnnotationPojoInjectableBinderCollector injectableBinderCollector) { this.rootFailureCollector = rootFailureCollector; this.configurationContext = configurationContext; this.annotationHelper = annotationHelper; + this.injectableBinderCollector = injectableBinderCollector; this.annotationProcessorProvider = new AnnotationProcessorProvider( beanResolver, rootFailureCollector ); } public Optional createIfAnnotated(PojoRawTypeModel typeModel) { // Create a programmatic type mapping object - TypeMappingStepImpl typeMappingStep = new TypeMappingStepImpl( typeModel ); + TypeMappingStepImpl typeMappingStep = new TypeMappingStepImpl( typeModel, injectableBinderCollector ); // Process annotations and add metadata to the type mapping boolean processedAtLeastOneAnnotation = processAnnotations( typeMappingStep, typeModel ); diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/PojoInjectableBinderInjector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/PojoInjectableBinderInjector.java new file mode 100644 index 00000000000..080a6e364b0 --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/impl/PojoInjectableBinderInjector.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl; + +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.search.common.NamedValues; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; + +public final class PojoInjectableBinderInjector { + + private final AnnotationPojoInjectableBinderCollector binderCollector; + + public PojoInjectableBinderInjector(PojoBootstrapIntrospector introspector) { + this.binderCollector = new AnnotationPojoInjectableBinderCollector( introspector ); + } + + public AnnotationPojoInjectableBinderCollector binderCollector() { + return binderCollector; + } + + public void injectFields(PropertyBinder binder, IndexSchemaElement indexSchemaElement, NamedValues params) { + var injectors = binderCollector.injector( binder.getClass() ); + + for ( var injector : injectors ) { + injector.inject( binder, indexSchemaElement, params ); + } + } +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/AbstractFieldAnnotationProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/AbstractFieldAnnotationProcessor.java index c1ebddff6ff..95333dc189d 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/AbstractFieldAnnotationProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/AbstractFieldAnnotationProcessor.java @@ -84,7 +84,7 @@ else if ( bridgeReference.isPresent() ) { return new BeanBinder( bridgeReference.get() ); } else if ( binderReference.isPresent() ) { - return new BeanDelegatingBinder( binderReference.get() ); + return new BeanDelegatingBinder( binderReference.get(), binderReferenceAnnotation.type() ); } else { // The bridge will be auto-detected from the property type diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/DocumentIdProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/DocumentIdProcessor.java index 58a80ff6e65..e2a08c35f26 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/DocumentIdProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/DocumentIdProcessor.java @@ -57,7 +57,7 @@ else if ( bridgeReference.isPresent() ) { return new BeanBinder( bridgeReference.get() ); } else if ( binderReference.isPresent() ) { - return new BeanDelegatingBinder( binderReference.get() ); + return new BeanDelegatingBinder( binderReference.get(), binderReferenceAnnotation.type() ); } else { // The bridge will be auto-detected from the property type diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/IndexedProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/IndexedProcessor.java index 4d8b7c38f45..05929edaa68 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/IndexedProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/IndexedProcessor.java @@ -40,6 +40,7 @@ public void process(TypeMappingStep mappingContext, Indexed annotation, } Map params = context.toMap( routingBinderReferenceAnnotation.params() ); - indexedStep.routingBinder( new BeanDelegatingBinder( routingBinderReference.get() ), params ); + indexedStep.routingBinder( + new BeanDelegatingBinder( routingBinderReference.get(), routingBinderReferenceAnnotation.type() ), params ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/MarkerBindingProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/MarkerBindingProcessor.java index 356057b660c..191e9a271d7 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/MarkerBindingProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/MarkerBindingProcessor.java @@ -42,6 +42,6 @@ private MarkerBinder createBinder(MarkerBinderRef binderReferenceAnnotation, Map throw MappingLog.INSTANCE.missingBinderReferenceInBinding(); } - return new BeanDelegatingBinder( binderReference.get() ); + return new BeanDelegatingBinder( binderReference.get(), binderReferenceAnnotation.type() ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/PropertyBindingProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/PropertyBindingProcessor.java index a683377d4de..47a6c2c9a8c 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/PropertyBindingProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/PropertyBindingProcessor.java @@ -43,6 +43,6 @@ private PropertyBinder createBinder(PropertyBinderRef binderReferenceAnnotation, throw MappingLog.INSTANCE.missingBinderReferenceInBinding(); } - return new BeanDelegatingBinder( binderReference.get() ); + return new BeanDelegatingBinder( binderReference.get(), binderReferenceAnnotation.type() ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/TypeBindingProcessor.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/TypeBindingProcessor.java index 9eb265633b3..c777eeb1840 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/TypeBindingProcessor.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/annotation/processing/impl/TypeBindingProcessor.java @@ -42,6 +42,6 @@ private TypeBinder createBinder(TypeBinderRef binderReferenceAnnotation, Mapping throw MappingLog.INSTANCE.missingBinderReferenceInBinding(); } - return new BeanDelegatingBinder( binderReference.get() ); + return new BeanDelegatingBinder( binderReference.get(), binderReferenceAnnotation.type() ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/InitialPropertyMappingStep.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/InitialPropertyMappingStep.java index c6cbcc0cc1f..ad668f9afd5 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/InitialPropertyMappingStep.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/InitialPropertyMappingStep.java @@ -12,6 +12,7 @@ import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoIndexMappingCollectorPropertyNode; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoIndexMappingCollectorTypeNode; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.AnnotationPojoInjectableBinderCollector; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.AssociationInverseSideOptionsStep; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.IndexingDependencyOptionsStep; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingDocumentIdOptionsStep; @@ -33,13 +34,16 @@ class InitialPropertyMappingStep private final TypeMappingStepImpl parent; private final PojoPropertyModel propertyModel; + private final AnnotationPojoInjectableBinderCollector injectableBinderCollector; private final ErrorCollectingPojoPropertyMetadataContributor children = new ErrorCollectingPojoPropertyMetadataContributor(); - InitialPropertyMappingStep(TypeMappingStepImpl parent, PojoPropertyModel propertyModel) { + InitialPropertyMappingStep(TypeMappingStepImpl parent, PojoPropertyModel propertyModel, + AnnotationPojoInjectableBinderCollector injectableBinderCollector) { this.parent = parent; this.propertyModel = propertyModel; + this.injectableBinderCollector = injectableBinderCollector; } @Override @@ -72,6 +76,7 @@ public PropertyMappingDocumentIdOptionsStep documentId() { @Override public PropertyMappingStep binder(PropertyBinder binder, Map params) { children.add( new PropertyBridgeMappingContributor( binder, params ) ); + injectableBinderCollector.add( binder ); return this; } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/ProgrammaticMappingConfigurationContextImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/ProgrammaticMappingConfigurationContextImpl.java index 0358e94f615..b68c4df55e2 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/ProgrammaticMappingConfigurationContextImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/ProgrammaticMappingConfigurationContextImpl.java @@ -10,6 +10,7 @@ import org.hibernate.search.engine.mapper.mapping.building.spi.MappingBuildContext; import org.hibernate.search.engine.mapper.mapping.building.spi.MappingConfigurationCollector; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.AnnotationPojoInjectableBinderCollector; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.ProgrammaticMappingConfigurationContext; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep; import org.hibernate.search.mapper.pojo.mapping.spi.PojoMappingConfigurationContext; @@ -24,9 +25,12 @@ public class ProgrammaticMappingConfigurationContextImpl // Use a LinkedHashMap for deterministic iteration private final Map, TypeMappingStepImpl> typeMappingContributors = new LinkedHashMap<>(); + private final AnnotationPojoInjectableBinderCollector binderCollector; - public ProgrammaticMappingConfigurationContextImpl(PojoBootstrapIntrospector introspector) { + public ProgrammaticMappingConfigurationContextImpl(PojoBootstrapIntrospector introspector, + AnnotationPojoInjectableBinderCollector annotationPojoInjectableBinderCollector) { this.introspector = introspector; + this.binderCollector = annotationPojoInjectableBinderCollector; } @Override @@ -48,7 +52,11 @@ public TypeMappingStep type(String typeName) { } private TypeMappingStep type(PojoRawTypeModel typeModel) { - return typeMappingContributors.computeIfAbsent( typeModel, TypeMappingStepImpl::new ); + return typeMappingContributors.computeIfAbsent( typeModel, this::typeMappingStep ); + } + + private TypeMappingStepImpl typeMappingStep(PojoRawTypeModel typeModel) { + return new TypeMappingStepImpl( typeModel, binderCollector ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/TypeMappingStepImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/TypeMappingStepImpl.java index f8875ae3d08..a79232b9110 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/TypeMappingStepImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/definition/programmatic/impl/TypeMappingStepImpl.java @@ -17,6 +17,7 @@ import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoIndexMappingCollectorTypeNode; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoSearchMappingConstructorNode; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.AnnotationPojoInjectableBinderCollector; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.ConstructorMappingStep; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingIndexedStep; @@ -32,13 +33,16 @@ public class TypeMappingStepImpl implements TypeMappingStep, PojoMappingConfigurationContributor, PojoTypeMetadataContributor { private final PojoRawTypeModel typeModel; + private final AnnotationPojoInjectableBinderCollector injectableBinderCollector; private final ErrorCollectingPojoTypeMetadataContributor children = new ErrorCollectingPojoTypeMetadataContributor(); private Map>, InitialConstructorMappingStep> constructors; - public TypeMappingStepImpl(PojoRawTypeModel typeModel) { + public TypeMappingStepImpl(PojoRawTypeModel typeModel, + AnnotationPojoInjectableBinderCollector injectableBinderCollector) { this.typeModel = typeModel; + this.injectableBinderCollector = injectableBinderCollector; } @Override @@ -104,7 +108,7 @@ public ConstructorMappingStep constructor(Class... parameterTypes) { @Override public PropertyMappingStep property(String propertyName) { PojoPropertyModel propertyModel = typeModel.property( propertyName ); - InitialPropertyMappingStep child = new InitialPropertyMappingStep( this, propertyModel ); + InitialPropertyMappingStep child = new InitialPropertyMappingStep( this, propertyModel, injectableBinderCollector ); children.add( child ); return child; } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/spi/AbstractPojoMappingInitiator.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/spi/AbstractPojoMappingInitiator.java index d75edeec38a..6005609f1d9 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/spi/AbstractPojoMappingInitiator.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/mapping/spi/AbstractPojoMappingInitiator.java @@ -29,6 +29,7 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotationMappingConfigurationContext; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.AnnotationMappingConfigurationContextImpl; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.impl.PojoInjectableBinderInjector; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.ProgrammaticMappingConfigurationContext; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.impl.ProgrammaticMappingConfigurationContextImpl; import org.hibernate.search.mapper.pojo.mapping.impl.PojoMappingConfigurationContextImpl; @@ -40,6 +41,7 @@ public abstract class AbstractPojoMappingInitiator { private final PojoBootstrapIntrospector introspector; + private final PojoInjectableBinderInjector binderInjector; private BeanReference> providedIdentifierBridge; private IdentityMappingMode containedEntityIdentityMappingMode = IdentityMappingMode.OPTIONAL; @@ -59,13 +61,15 @@ public abstract class AbstractPojoMappingInitiator createMapper(MappingBuildContext buildContext, TypeMetadataContributorProvider contributorProvider) { return new PojoMapper<>( buildContext, contributorProvider, - introspector, + introspector, binderInjector, extractorBinder, bridgeResolver, providedIdentifierBridge, containedEntityIdentityMappingMode, tenancyMode, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java index 517a0e516ed..99643195f9e 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java @@ -9,6 +9,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -18,6 +19,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.hibernate.models.UnknownClassException; import org.hibernate.models.internal.BasicModelBuildingContextImpl; import org.hibernate.models.internal.SimpleClassLoading; import org.hibernate.models.jandex.internal.JandexModelBuildingContextImpl; @@ -26,20 +28,27 @@ import org.hibernate.models.spi.ClassDetailsRegistry; import org.hibernate.models.spi.MemberDetails; import org.hibernate.models.spi.MethodDetails; +import org.hibernate.search.mapper.pojo.logging.impl.MappingLog; +import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel; import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectableBinderModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; import org.hibernate.search.util.common.AssertionFailure; import org.hibernate.search.util.common.impl.StreamHelper; import org.hibernate.search.util.common.reflect.spi.ValueCreateHandle; import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; import org.jboss.jandex.IndexView; public abstract class AbstractPojoModelsBootstrapIntrospector implements PojoBootstrapIntrospector { private final PojoModelsClassOrdering typeOrdering; - protected final ValueHandleFactory valueHandleFactory; private final ClassDetailsRegistry classDetailsRegistry; + private final Map, PojoInjectableBinderModel> injectableBinderModelCache = new HashMap<>(); + protected final PojoModelsGenericContextHelper genericContextHelper; + protected final ValueHandleFactory valueHandleFactory; public AbstractPojoModelsBootstrapIntrospector(ValueHandleFactory valueHandleFactory) { this( simpleClassDetailsRegistry( null ), valueHandleFactory ); @@ -50,6 +59,7 @@ public AbstractPojoModelsBootstrapIntrospector(ClassDetailsRegistry classDetails this.classDetailsRegistry = classDetailsRegistry; this.typeOrdering = new PojoModelsClassOrdering( classDetailsRegistry ); this.valueHandleFactory = valueHandleFactory; + this.genericContextHelper = new PojoModelsGenericContextHelper( this ); } protected static ClassDetailsRegistry simpleClassDetailsRegistry(IndexView indexView) { @@ -72,6 +82,29 @@ public ValueHandleFactory annotationValueHandleFactory() { return valueHandleFactory; } + @SuppressWarnings("unchecked") + @Override + public PojoInjectableBinderModel injectableBinderModel(Class clazz) { + return (PojoInjectableBinderModel) injectableBinderModelCache.computeIfAbsent( + clazz, this::createInjectableBinderModel ); + } + + private PojoInjectableBinderModel createInjectableBinderModel(Class clazz) { + PojoRawTypeIdentifier typeIdentifier = PojoRawTypeIdentifier.of( clazz ); + try { + return new PojoSimpleModelsRawInjectableBinderModel<>( + this, typeIdentifier, + new GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext<>( genericContextHelper, clazz ) + ); + } + catch (UnknownClassException e) { + throw e; + } + catch (RuntimeException e) { + throw MappingLog.INSTANCE.errorRetrievingInjectableBinderModel( clazz, e ); + } + } + public Stream annotations(AnnotationTarget annotationTarget) { return annotationTarget.getDirectAnnotationUsages().stream(); } @@ -121,6 +154,15 @@ else if ( member instanceof Field ) { } } + protected ValueReadWriteHandle createValueReadWriteHandle(Member member) throws IllegalAccessException { + if ( member instanceof Field field ) { + return valueHandleFactory.createForFieldWrite( field ); + } + else { + throw new AssertionFailure( "Unexpected type for a " + Member.class.getName() + ": " + member ); + } + } + public Class toClass(ClassDetails xClass) { return xClass.toJavaClass(); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsPropertyModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsPropertyModel.java index 9a534ec6bac..7de3b3bf648 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsPropertyModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsPropertyModel.java @@ -20,11 +20,14 @@ import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; -public abstract class AbstractPojoModelsPropertyModel +public abstract class AbstractPojoModelsPropertyModel< + T, + I extends AbstractPojoModelsBootstrapIntrospector, + H extends ValueReadHandle> implements PojoPropertyModel { protected final I introspector; - protected final AbstractPojoModelsRawTypeModel holderTypeModel; + protected final AbstractPojoModelsRawTypeModel holderTypeModel; protected final String name; /** @@ -35,11 +38,11 @@ public abstract class AbstractPojoModelsPropertyModel declaredProperties; private final List members; - private ValueReadHandle handleCache; + private H handleCache; private PojoTypeModel typeModelCache; private Member memberCache; - public AbstractPojoModelsPropertyModel(I introspector, AbstractPojoModelsRawTypeModel holderTypeModel, + public AbstractPojoModelsPropertyModel(I introspector, AbstractPojoModelsRawTypeModel holderTypeModel, String name, List declaredProperties, List members) { Contracts.assertNotNullNorEmpty( members, "members" ); this.introspector = introspector; @@ -79,7 +82,7 @@ public final PojoTypeModel typeModel() { } @Override - public final ValueReadHandle handle() { + public final H handle() { if ( handleCache == null ) { try { handleCache = createHandle( member() ); @@ -102,7 +105,7 @@ protected final Member member() { return memberCache; } - protected abstract ValueReadHandle createHandle(Member member) throws ReflectiveOperationException; + protected abstract H createHandle(Member member) throws ReflectiveOperationException; final Type getterGenericReturnType() { Member member = member(); diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsRawTypeModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsRawTypeModel.java index 832e40b6494..a322feb66c7 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsRawTypeModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsRawTypeModel.java @@ -20,11 +20,15 @@ import org.hibernate.search.mapper.pojo.model.spi.AbstractPojoRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext; import org.hibernate.search.mapper.pojo.model.spi.PojoConstructorModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoPropertyModel; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; import org.hibernate.search.mapper.pojo.model.spi.PojoTypeModel; -public abstract class AbstractPojoModelsRawTypeModel - extends AbstractPojoRawTypeModel { +public abstract class AbstractPojoModelsRawTypeModel< + T, + I extends AbstractPojoModelsBootstrapIntrospector, + P extends PojoPropertyModel> + extends AbstractPojoRawTypeModel { protected final ClassDetails classDetails; final RawTypeDeclaringContext rawTypeDeclaringContext; @@ -47,7 +51,7 @@ public boolean isAbstract() { @Override public final boolean isSubTypeOf(MappableTypeModel other) { return other instanceof AbstractPojoModelsRawTypeModel - && ( (AbstractPojoModelsRawTypeModel) other ).classDetails.toJavaClass() + && ( (AbstractPojoModelsRawTypeModel) other ).classDetails.toJavaClass() .isAssignableFrom( classDetails.toJavaClass() ); } @@ -80,7 +84,7 @@ Class javaClass() { } @Override - protected final Stream declaredPropertyNames() { + protected Stream declaredPropertyNames() { return Stream.concat( declaredFieldAccessPropertiesByName().keySet().stream(), declaredMethodAccessPropertiesByName().keySet().stream() diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsConstructorModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsConstructorModel.java index f512b56d0cb..b7dd3b8fba3 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsConstructorModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsConstructorModel.java @@ -23,14 +23,14 @@ public class PojoModelsConstructorModel implements PojoConstructorModel { private final AbstractPojoModelsBootstrapIntrospector introspector; - final AbstractPojoModelsRawTypeModel declaringTypeModel; + final AbstractPojoModelsRawTypeModel declaringTypeModel; private final Constructor constructor; private List> declaredParameters; private ValueCreateHandle handleCache; public PojoModelsConstructorModel(AbstractPojoModelsBootstrapIntrospector introspector, - AbstractPojoModelsRawTypeModel declaringTypeModel, Constructor constructor) { + AbstractPojoModelsRawTypeModel declaringTypeModel, Constructor constructor) { this.introspector = introspector; this.declaringTypeModel = declaringTypeModel; this.constructor = constructor; diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsGenericContextHelper.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsGenericContextHelper.java index 5adf09f8c7d..69cf076ce67 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsGenericContextHelper.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoModelsGenericContextHelper.java @@ -29,7 +29,7 @@ public Object propertyCacheKey(PojoPropertyModel rawPropertyModel) { @Override public Type propertyGenericType(PojoPropertyModel rawPropertyModel) { - AbstractPojoModelsPropertyModel propertyModel = (AbstractPojoModelsPropertyModel) rawPropertyModel; + AbstractPojoModelsPropertyModel propertyModel = (AbstractPojoModelsPropertyModel) rawPropertyModel; return propertyModel.getterGenericReturnType(); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsInjectablePropertyModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsInjectablePropertyModel.java new file mode 100644 index 00000000000..746192a49a7 --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsInjectablePropertyModel.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.model.models.spi; + +import java.lang.reflect.Member; +import java.util.List; + +import org.hibernate.models.spi.MemberDetails; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectablePropertyModel; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; + +final class PojoSimpleModelsInjectablePropertyModel + extends + AbstractPojoModelsPropertyModel> + implements PojoInjectablePropertyModel { + + PojoSimpleModelsInjectablePropertyModel(AbstractPojoModelsBootstrapIntrospector introspector, + PojoSimpleModelsRawInjectableBinderModel holderTypeModel, + String name, List declaredProperties, + List members) { + super( introspector, holderTypeModel, name, declaredProperties, members ); + } + + @Override + @SuppressWarnings("unchecked") // By construction, we know the member returns values of type T + protected ValueReadWriteHandle createHandle(Member member) throws IllegalAccessException { + return (ValueReadWriteHandle) introspector.createValueReadWriteHandle( member ); + } +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsPropertyModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsPropertyModel.java index 67993595fec..51b4c75b00c 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsPropertyModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsPropertyModel.java @@ -12,7 +12,7 @@ final class PojoSimpleModelsPropertyModel extends - AbstractPojoModelsPropertyModel { + AbstractPojoModelsPropertyModel> { PojoSimpleModelsPropertyModel(AbstractPojoModelsBootstrapIntrospector introspector, PojoSimpleModelsRawTypeModel holderTypeModel, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawInjectableBinderModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawInjectableBinderModel.java new file mode 100644 index 00000000000..6d8995f4b37 --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawInjectableBinderModel.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.model.models.spi; + +import java.lang.reflect.Member; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.hibernate.models.spi.MemberDetails; +import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectableBinderModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoInjectablePropertyModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; + +public final class PojoSimpleModelsRawInjectableBinderModel + extends AbstractPojoModelsRawTypeModel> + implements PojoInjectableBinderModel { + + public PojoSimpleModelsRawInjectableBinderModel(AbstractPojoModelsBootstrapIntrospector introspector, + PojoRawTypeIdentifier typeIdentifier, + RawTypeDeclaringContext rawTypeDeclaringContext) { + super( introspector, typeIdentifier, rawTypeDeclaringContext ); + } + + @Override + @SuppressWarnings("unchecked") // xClass represents T, so its supertypes represent ? super T + public Stream> ascendingSuperTypes() { + return introspector.ascendingSuperClasses( classDetails ) + .map( xc -> (PojoSimpleModelsRawInjectableBinderModel) introspector.injectableBinderModel( xc ) ); + } + + @Override + @SuppressWarnings("unchecked") // xClass represents T, so its supertypes represent ? super T + public Stream> descendingSuperTypes() { + return introspector.descendingSuperClasses( classDetails ) + .map( xc -> (PojoSimpleModelsRawInjectableBinderModel) introspector.injectableBinderModel( xc ) ); + } + + @Override + protected Stream declaredPropertyNames() { + return declaredFieldAccessPropertiesByName().keySet().stream(); + } + + @Override + protected PojoInjectablePropertyModel createPropertyModel(String propertyName) { + List declaredProperties = new ArrayList<>( 1 ); + MemberDetails fieldAccessProperty = declaredFieldAccessPropertiesByName().get( propertyName ); + if ( fieldAccessProperty != null ) { + declaredProperties.add( fieldAccessProperty ); + } + + List members = findPropertyMember( propertyName ); + if ( members == null ) { + return null; + } + + return new PojoSimpleModelsInjectablePropertyModel<>( introspector, this, propertyName, + declaredProperties, members ); + } + + private List findPropertyMember(String propertyName) { + // Try using the getter first (if declared)... + List getters = findInSelfOrParents( t -> t.declaredPropertyGetters( propertyName ) ); + if ( getters != null ) { + return getters; + } + // ... and fall back to the field (or null if not found) + Member field = findInSelfOrParents( t -> t.declaredPropertyField( propertyName ) ); + return field == null ? null : Collections.singletonList( field ); + } + + private T2 findInSelfOrParents(Function, T2> getter) { + return ascendingSuperTypes() + .map( getter ) + .filter( Objects::nonNull ) + .findFirst() + .orElse( null ); + } + +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawTypeModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawTypeModel.java index 2520594c28b..66311700326 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawTypeModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/PojoSimpleModelsRawTypeModel.java @@ -18,7 +18,7 @@ import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; public final class PojoSimpleModelsRawTypeModel - extends AbstractPojoModelsRawTypeModel { + extends AbstractPojoModelsRawTypeModel> { public PojoSimpleModelsRawTypeModel(AbstractPojoModelsBootstrapIntrospector introspector, PojoRawTypeIdentifier typeIdentifier, diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/AbstractPojoRawTypeModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/AbstractPojoRawTypeModel.java index 3cb5a5a1fac..ce1ffe69d0f 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/AbstractPojoRawTypeModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/AbstractPojoRawTypeModel.java @@ -17,15 +17,16 @@ import org.hibernate.search.mapper.pojo.logging.impl.MappingLog; -public abstract class AbstractPojoRawTypeModel implements PojoRawTypeModel { +public abstract class AbstractPojoRawTypeModel> + implements PojoRawTypeModel { protected final I introspector; protected final PojoRawTypeIdentifier typeIdentifier; private final PojoCaster caster; - private final Map> propertyModelCache = new HashMap<>(); + private final Map propertyModelCache = new HashMap<>(); - private List> declaredProperties; + private List

declaredProperties; private List> declaredConstructors; public AbstractPojoRawTypeModel(I introspector, PojoRawTypeIdentifier typeIdentifier) { @@ -42,7 +43,7 @@ public final boolean equals(Object o) { if ( o == null || getClass() != o.getClass() ) { return false; } - AbstractPojoRawTypeModel that = (AbstractPojoRawTypeModel) o; + AbstractPojoRawTypeModel that = (AbstractPojoRawTypeModel) o; /* * We need to take the introspector into account, so that the engine does not confuse * type models from different mappers during bootstrap. @@ -101,8 +102,8 @@ public Collection> declaredConstructors() { protected abstract List> createDeclaredConstructors(); @Override - public final PojoPropertyModel property(String propertyName) { - PojoPropertyModel propertyModel = propertyOrNull( propertyName ); + public final P property(String propertyName) { + P propertyModel = propertyOrNull( propertyName ); if ( propertyModel == null ) { throw MappingLog.INSTANCE.cannotFindReadableProperty( this, propertyName ); } @@ -110,7 +111,7 @@ public final PojoPropertyModel property(String propertyName) { } @Override - public final Collection> declaredProperties() { + public Collection

declaredProperties() { if ( declaredProperties == null ) { declaredProperties = Collections.unmodifiableList( declaredPropertyNames() .map( this::propertyOrNull ) @@ -150,9 +151,9 @@ public Optional> castTo(Class target) { protected abstract Stream declaredPropertyNames(); - protected abstract PojoPropertyModel createPropertyModel(String propertyName); + protected abstract P createPropertyModel(String propertyName); - private PojoPropertyModel propertyOrNull(String propertyName) { + private P propertyOrNull(String propertyName) { return propertyModelCache.computeIfAbsent( propertyName, this::createPropertyModel ); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java index 5c0bae6a14c..b50adb52042 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java @@ -4,6 +4,7 @@ */ package org.hibernate.search.mapper.pojo.model.spi; +import org.hibernate.search.util.common.annotation.Incubating; import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; /** @@ -38,4 +39,7 @@ default org.hibernate.search.util.common.reflect.spi.ValueReadHandleFactory anno return (org.hibernate.search.util.common.reflect.spi.ValueReadHandleFactory) annotationValueHandleFactory(); } + @Incubating + PojoInjectableBinderModel injectableBinderModel(Class clazz); + } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectableBinderModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectableBinderModel.java new file mode 100644 index 00000000000..f95654973cc --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectableBinderModel.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.model.spi; + +import java.util.Collection; + +import org.hibernate.search.util.common.annotation.Incubating; + +@Incubating +public interface PojoInjectableBinderModel extends PojoRawTypeModel { + + @Override + PojoInjectablePropertyModel property(String propertyName); + + @Override + Collection> declaredProperties(); + +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectablePropertyModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectablePropertyModel.java new file mode 100644 index 00000000000..22af32464fd --- /dev/null +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoInjectablePropertyModel.java @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.mapper.pojo.model.spi; + +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; + +public interface PojoInjectablePropertyModel extends PojoPropertyModel { + + /** + * @return A handle to read the value of this property on a instance of its hosting type. + */ + ValueReadWriteHandle handle(); + +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoRawTypeModel.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoRawTypeModel.java index 608af1af1f7..36bc6635b3d 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoRawTypeModel.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoRawTypeModel.java @@ -77,7 +77,7 @@ default PojoRawTypeModel rawType() { /** * @return All declared properties of this type. */ - Collection> declaredProperties(); + Collection> declaredProperties(); /** * @param other The type to cast to this type. diff --git a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/model/impl/StandalonePojoBootstrapIntrospector.java b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/model/impl/StandalonePojoBootstrapIntrospector.java index be87a104d63..ae849739a5e 100644 --- a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/model/impl/StandalonePojoBootstrapIntrospector.java +++ b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/model/impl/StandalonePojoBootstrapIntrospector.java @@ -11,7 +11,6 @@ import java.util.Map; import org.hibernate.search.mapper.pojo.model.models.spi.AbstractPojoModelsBootstrapIntrospector; -import org.hibernate.search.mapper.pojo.model.models.spi.PojoModelsGenericContextHelper; import org.hibernate.search.mapper.pojo.model.models.spi.PojoSimpleModelsRawTypeModel; import org.hibernate.search.mapper.pojo.model.spi.GenericContextAwarePojoGenericTypeModel.RawTypeDeclaringContext; import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; @@ -22,6 +21,7 @@ import org.hibernate.search.util.common.reflect.spi.ValueCreateHandle; import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; import org.jboss.jandex.IndexView; @@ -35,13 +35,11 @@ public static StandalonePojoBootstrapIntrospector create(IndexView indexView, Va return new StandalonePojoBootstrapIntrospector( indexView, valueHandleFactory ); } - private final PojoModelsGenericContextHelper genericContextHelper; private final Map, PojoRawTypeModel> typeModelCache = new HashMap<>(); private StandalonePojoBootstrapIntrospector(IndexView indexView, ValueHandleFactory valueHandleFactory) { super( simpleClassDetailsRegistry( indexView ), valueHandleFactory ); - this.genericContextHelper = new PojoModelsGenericContextHelper( this ); } @Override @@ -68,6 +66,12 @@ protected ValueReadHandle createValueReadHandle(Member member) throws Illegal return super.createValueReadHandle( member ); } + @Override + protected ValueReadWriteHandle createValueReadWriteHandle(Member member) throws IllegalAccessException { + setAccessible( member ); + return super.createValueReadWriteHandle( member ); + } + @Override protected ValueCreateHandle createValueCreateHandle(Constructor constructor) throws IllegalAccessException { setAccessible( constructor ); diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/FieldValueReadWriteHandle.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/FieldValueReadWriteHandle.java new file mode 100644 index 00000000000..5ca0656e2eb --- /dev/null +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/FieldValueReadWriteHandle.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.util.common.reflect.impl; + +import java.lang.reflect.Field; + +import org.hibernate.search.util.common.impl.Throwables; +import org.hibernate.search.util.common.logging.impl.CommonMiscLog; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; + +public final class FieldValueReadWriteHandle implements ValueReadWriteHandle { + + private final Field field; + + public FieldValueReadWriteHandle(Field field) { + this.field = field; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + field + "]"; + } + + @SuppressWarnings("unchecked") + @Override + public T get(Object thiz) { + try { + return (T) field.get( thiz ); + } + catch (RuntimeException | IllegalAccessException e) { + throw CommonMiscLog.INSTANCE.errorInvokingMember( field, Throwables.safeToString( e, thiz ), e, + e.getMessage() ); + } + } + + @Override + public void set(Object thiz, Object value) { + try { + field.set( thiz, value ); + } + catch (RuntimeException | IllegalAccessException e) { + throw CommonMiscLog.INSTANCE.errorInvokingMember( field, Throwables.safeToString( e, thiz ), e, + e.getMessage() ); + } + } + + @Override + public int hashCode() { + return field.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if ( obj == null || !obj.getClass().equals( getClass() ) ) { + return false; + } + FieldValueReadWriteHandle other = (FieldValueReadWriteHandle) obj; + return field.equals( other.field ); + } + +} diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/MethodHandleValueReadWriteHandle.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/MethodHandleValueReadWriteHandle.java new file mode 100644 index 00000000000..b4e43243db2 --- /dev/null +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/impl/MethodHandleValueReadWriteHandle.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.util.common.reflect.impl; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Member; + +import org.hibernate.search.util.common.impl.Throwables; +import org.hibernate.search.util.common.logging.impl.CommonMiscLog; +import org.hibernate.search.util.common.reflect.spi.ValueReadWriteHandle; + +public final class MethodHandleValueReadWriteHandle implements ValueReadWriteHandle { + + private final Member member; + private final MethodHandle setter; + private final MethodHandle getter; + + public MethodHandleValueReadWriteHandle(Member member, MethodHandle setter, MethodHandle getter) { + this.member = member; + this.setter = setter; + this.getter = getter; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + member + "]"; + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object thiz) { + try { + return (T) getter.invoke( thiz ); + } + catch (Error e) { + throw e; + } + catch (Throwable e) { + if ( e instanceof InterruptedException ) { + Thread.currentThread().interrupt(); + } + throw CommonMiscLog.INSTANCE.errorInvokingMember( member, Throwables.safeToString( e, thiz ), e, + e.getMessage() ); + } + } + + @Override + public void set(Object thiz, Object value) { + try { + setter.invoke( thiz, value ); + } + catch (Error e) { + throw e; + } + catch (Throwable e) { + if ( e instanceof InterruptedException ) { + Thread.currentThread().interrupt(); + } + throw CommonMiscLog.INSTANCE.errorInvokingMember( member, Throwables.safeToString( e, thiz ), e, + e.getMessage() ); + } + } + + @Override + public int hashCode() { + return member.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if ( obj == null || !obj.getClass().equals( getClass() ) ) { + return false; + } + MethodHandleValueReadWriteHandle other = (MethodHandleValueReadWriteHandle) obj; + return member.equals( other.member ); + } + +} diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MemberValueHandleFactory.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MemberValueHandleFactory.java index 5da793d48a5..8e9bec41c86 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MemberValueHandleFactory.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MemberValueHandleFactory.java @@ -10,6 +10,7 @@ import org.hibernate.search.util.common.reflect.impl.ConstructorValueCreateHandle; import org.hibernate.search.util.common.reflect.impl.FieldValueReadHandle; +import org.hibernate.search.util.common.reflect.impl.FieldValueReadWriteHandle; import org.hibernate.search.util.common.reflect.impl.MethodValueReadHandle; @SuppressWarnings("deprecation") @@ -28,4 +29,9 @@ public ValueReadHandle createForField(Field field) { public ValueReadHandle createForMethod(Method method) { return new MethodValueReadHandle<>( method ); } + + @Override + public ValueReadWriteHandle createForFieldWrite(Field field) { + return new FieldValueReadWriteHandle<>( field ); + } } diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MethodHandleValueHandleFactory.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MethodHandleValueHandleFactory.java index c83c12de43e..b7a069b7fec 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MethodHandleValueHandleFactory.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/MethodHandleValueHandleFactory.java @@ -12,6 +12,7 @@ import org.hibernate.search.util.common.annotation.impl.SuppressForbiddenApis; import org.hibernate.search.util.common.reflect.impl.MethodHandleValueCreateHandle; import org.hibernate.search.util.common.reflect.impl.MethodHandleValueReadHandle; +import org.hibernate.search.util.common.reflect.impl.MethodHandleValueReadWriteHandle; @SuppressWarnings("deprecation") @SuppressForbiddenApis(reason = "MethodHandles don't always work, but usage of this class is configurable," @@ -38,4 +39,10 @@ public ValueReadHandle createForField(Field field) throws IllegalAccessExcept public ValueReadHandle createForMethod(Method method) throws IllegalAccessException { return new MethodHandleValueReadHandle<>( method, lookup.unreflect( method ) ); } + + @Override + public ValueReadWriteHandle createForFieldWrite(Field field) throws IllegalAccessException { + return new MethodHandleValueReadWriteHandle<>( field, lookup.unreflectSetter( field ), + lookup.unreflectGetter( field ) ); + } } diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueHandleFactory.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueHandleFactory.java index 3e89291220a..0eed99e6888 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueHandleFactory.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueHandleFactory.java @@ -9,6 +9,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import org.hibernate.search.util.common.annotation.Incubating; + public interface ValueHandleFactory { ValueCreateHandle createForConstructor(Constructor constructor) throws IllegalAccessException; @@ -17,6 +19,9 @@ public interface ValueHandleFactory { ValueReadHandle createForMethod(Method method) throws IllegalAccessException; + @Incubating + ValueReadWriteHandle createForFieldWrite(Field field) throws IllegalAccessException; + /** * @return A factory producing value handles that rely on {@code java.lang.reflect} * to get the value of a field/method, diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueReadWriteHandle.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueReadWriteHandle.java new file mode 100644 index 00000000000..89188085056 --- /dev/null +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueReadWriteHandle.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.util.common.reflect.spi; + +/** + * A handle giving write access to a property within an object instance: + * field, setter method, ... + * + * @param The value type. + */ +public interface ValueReadWriteHandle extends ValueReadHandle, ValueWriteHandle { + + /** + * @return {@code true} if {@code obj} is a {@link ValueReadWriteHandle} referencing the exact same + * value accessor: same API (java.lang.invoke or java.lang.reflect), + * same element (same field or method), ... + */ + @Override + boolean equals(Object obj); + + /* + * Note to implementors: you must override hashCode to be consistent with equals(). + */ + @Override + int hashCode(); + +} diff --git a/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueWriteHandle.java b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueWriteHandle.java new file mode 100644 index 00000000000..2ffbb436e7c --- /dev/null +++ b/util/common/src/main/java/org/hibernate/search/util/common/reflect/spi/ValueWriteHandle.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.util.common.reflect.spi; + +/** + * A handle giving write access to a property within an object instance: + * field, setter method, ... + */ +public interface ValueWriteHandle { + + void set(Object thiz, Object value); + + /** + * @return {@code true} if {@code obj} is a {@link ValueWriteHandle} referencing the exact same + * value accessor: same API (java.lang.invoke or java.lang.reflect), + * same element (same field or method), ... + */ + @Override + boolean equals(Object obj); + + /* + * Note to implementors: you must override hashCode to be consistent with equals(). + */ + @Override + int hashCode(); + +}