diff --git a/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/extractor/impl/ContainerExtractorBinderDefaultExtractorTest.java b/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/extractor/impl/ContainerExtractorBinderDefaultExtractorTest.java new file mode 100644 index 00000000000..196ceabf1ef --- /dev/null +++ b/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/extractor/impl/ContainerExtractorBinderDefaultExtractorTest.java @@ -0,0 +1,167 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.mapper.pojo.extractor.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.extractor.spi.ContainerExtractorRegistry; +import org.hibernate.search.mapper.pojo.model.typepattern.impl.TypePatternMatcherFactory; +import org.hibernate.search.mapper.pojo.testsupport.TestBeanResolver; +import org.hibernate.search.mapper.pojo.testsupport.TestIntrospector; +import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; +import org.hibernate.search.util.impl.test.reflect.TypeCapture; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import org.assertj.core.api.InstanceOfAssertFactories; + +class ContainerExtractorBinderDefaultExtractorTest { + + static List defaultExtractorMatches() { + return List.of( + Arguments.of( new TypeCapture() {}, Collections.emptyList() ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_OBJECT ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_CHAR ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_BOOLEAN ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_BYTE ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_SHORT ) ), + Arguments.of( new TypeCapture() {}, Collections.singletonList( BuiltinContainerExtractors.ARRAY_INT ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_LONG ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_FLOAT ) ), + Arguments.of( new TypeCapture() {}, + Collections.singletonList( BuiltinContainerExtractors.ARRAY_DOUBLE ) ), + Arguments.of( new TypeCapture>() {}, + Collections.singletonList( BuiltinContainerExtractors.ITERABLE ) ), + Arguments.of( new TypeCapture>() {}, + Collections.singletonList( BuiltinContainerExtractors.COLLECTION ) ), + Arguments.of( + new TypeCapture>() {}, + Collections.singletonList( BuiltinContainerExtractors.MAP_VALUE ) ), + Arguments.of( + new TypeCapture>>() {}, + Arrays.asList( BuiltinContainerExtractors.COLLECTION, BuiltinContainerExtractors.MAP_VALUE ) ), + Arguments.of( + new TypeCapture>() {}, + Arrays.asList( BuiltinContainerExtractors.COLLECTION, BuiltinContainerExtractors.ARRAY_OBJECT ) ), + Arguments.of( + new TypeCapture>() {}, + Arrays.asList( BuiltinContainerExtractors.COLLECTION, BuiltinContainerExtractors.ARRAY_INT ) ), + Arguments.of( + new TypeCapture[]>() {}, + Arrays.asList( BuiltinContainerExtractors.ARRAY_OBJECT, BuiltinContainerExtractors.COLLECTION ) ) + ); + } + + @SuppressWarnings("unchecked") + static List defaultExtractorMismatches() { + List result = new ArrayList<>(); + for ( Arguments match : defaultExtractorMatches() ) { + for ( List mismatchingExtractors : matchingToMismatching( (List) match.get()[1] ) ) { + result.add( Arguments.of( match.get()[0], mismatchingExtractors ) ); + } + } + return result; + } + + private static List> matchingToMismatching(List matchingExtractorNames) { + List> nonMatching = new ArrayList<>(); + if ( !matchingExtractorNames.isEmpty() ) { + for ( int i = 0; i < ( matchingExtractorNames.size() - 1 ); i++ ) { + List tooShort = matchingExtractorNames.subList( 0, i ); + nonMatching.add( tooShort ); + } + List firstWrong = new ArrayList<>( matchingExtractorNames ); + firstWrong.set( 0, + BuiltinContainerExtractors.COLLECTION.equals( matchingExtractorNames.get( 0 ) ) + ? BuiltinContainerExtractors.ITERABLE + : BuiltinContainerExtractors.COLLECTION ); + nonMatching.add( firstWrong ); + } + for ( String extraName : new String[] { + BuiltinContainerExtractors.OPTIONAL, + BuiltinContainerExtractors.COLLECTION, + BuiltinContainerExtractors.ARRAY_INT + } ) { + List tooLong = new ArrayList<>( matchingExtractorNames ); + tooLong.add( extraName ); + nonMatching.add( tooLong ); + } + return nonMatching; + } + + private final TestIntrospector introspector = + new TestIntrospector( ValueHandleFactory.usingMethodHandle( MethodHandles.lookup() ) ); + private ContainerExtractorBinder binder; + + @BeforeEach + void init() { + binder = new ContainerExtractorBinder( new TestBeanResolver(), + ContainerExtractorRegistry.builder().build(), + new TypePatternMatcherFactory( introspector ) ); + } + + @ParameterizedTest + @MethodSource("defaultExtractorMatches") + void bindPath(TypeCapture typeCapture, List expectedExtractorNames) { + assertThat( binder.bindPath( + introspector.typeModel( typeCapture ), + ContainerExtractorPath.defaultExtractors() + ) ) + .extracting( BoundContainerExtractorPath::getExtractorPath ) + .extracting( + ContainerExtractorPath::explicitExtractorNames, InstanceOfAssertFactories.list( String.class ) ) + .containsExactlyElementsOf( expectedExtractorNames ); + } + + @ParameterizedTest + @MethodSource("defaultExtractorMatches") + void tryBindPath(TypeCapture typeCapture, List expectedExtractorNames) { + assertThat( binder.tryBindPath( introspector.typeModel( typeCapture ), + ContainerExtractorPath.defaultExtractors() ) ) + .hasValueSatisfying( v -> assertThat( v ) + .extracting( BoundContainerExtractorPath::getExtractorPath ) + .extracting( ContainerExtractorPath::explicitExtractorNames, + InstanceOfAssertFactories.list( String.class ) ) + .containsExactlyElementsOf( expectedExtractorNames ) ); + } + + @ParameterizedTest + @MethodSource("defaultExtractorMatches") + void isDefaultExtractor_match(TypeCapture typeCapture, List matchingExtractorNames) { + assertThat( binder.isDefaultExtractorPath( introspector.typeModel( typeCapture ), + ContainerExtractorPath.explicitExtractors( matchingExtractorNames ) + ) ).isTrue(); + } + + @ParameterizedTest + @MethodSource("defaultExtractorMismatches") + void isDefaultExtractor_mismatch(TypeCapture typeCapture, List mismatchingExtractorNames) { + assertThat( binder.isDefaultExtractorPath( introspector.typeModel( typeCapture ), + ContainerExtractorPath.explicitExtractors( mismatchingExtractorNames ) ) ) + .isFalse(); + } + +} diff --git a/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/testsupport/TestBeanResolver.java b/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/testsupport/TestBeanResolver.java new file mode 100644 index 00000000000..7a9b8aa484f --- /dev/null +++ b/mapper/pojo-base/src/test/java/org/hibernate/search/mapper/pojo/testsupport/TestBeanResolver.java @@ -0,0 +1,54 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.mapper.pojo.testsupport; + +import java.util.List; +import java.util.Map; + +import org.hibernate.search.engine.environment.bean.BeanHolder; +import org.hibernate.search.engine.environment.bean.BeanReference; +import org.hibernate.search.engine.environment.bean.BeanResolver; +import org.hibernate.search.engine.environment.bean.BeanRetrieval; +import org.hibernate.search.util.common.AssertionFailure; + +public class TestBeanResolver implements BeanResolver { + @Override + public BeanHolder resolve(Class typeReference, BeanRetrieval retrieval) { + switch ( retrieval ) { + case ANY: + case CONSTRUCTOR: + try { + return BeanHolder.of( typeReference.getConstructor().newInstance() ); + } + catch (Exception e) { + throw new AssertionFailure( "Exception instantiating " + typeReference, e ); + } + default: + throw new AssertionFailure( + "Retrieval " + retrieval + " is not supported in this test implementation" ); + } + } + + @Override + public BeanHolder resolve(Class typeReference, String nameReference, BeanRetrieval retrieval) { + throw notSupported(); + } + + @Override + public List> allConfiguredForRole(Class role) { + throw notSupported(); + } + + @Override + public Map> namedConfiguredForRole(Class role) { + throw notSupported(); + } + + private AssertionFailure notSupported() { + return new AssertionFailure( "This method is not supported in this test implementation" ); + } +}