diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/logging/impl/Log.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/logging/impl/Log.java index 2e06706fd10..a8bd51d8861 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/logging/impl/Log.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/logging/impl/Log.java @@ -602,7 +602,7 @@ SearchException invalidFieldValueType(@FormatWith(ClassFormatter.class) Class @Message(id = ID_OFFSET + 113, value = "Invalid cardinality for projection on field '%1$s': the projection is single-valued," + " but this field is multi-valued." - + " Make sure to call '.multi()' when you create the projection.") + + " Make sure to call '.collector(...)' when you create the projection.") SearchException invalidSingleValuedProjectionOnMultiValuedField(String absolutePath, @Param EventContext context); @Message(id = ID_OFFSET + 117, @@ -732,9 +732,9 @@ SearchException invalidContextForProjectionOnField(String absolutePath, value = "Invalid cardinality for projection on field '%1$s': the projection is single-valued," + " but this field is effectively multi-valued in this context," + " because parent object field '%2$s' is multi-valued." - + " Either call '.multi()' when you create the projection on field '%1$s'," + + " Either call '.collector(...)' when you create the projection on field '%1$s'," + " or wrap that projection in an object projection like this:" - + " 'f.object(\"%2$s\").from().as(...).multi()'.") + + " 'f.object(\"%2$s\").from().as(...).collector(...)'.") SearchException invalidSingleValuedProjectionOnValueFieldInMultiValuedObjectField(String absolutePath, String objectFieldAbsolutePath); diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighter.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighter.java index 63fdd009066..76b46c8ce91 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighter.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighter.java @@ -14,7 +14,7 @@ import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; import org.hibernate.search.engine.search.highlighter.SearchHighlighter; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import com.google.gson.JsonObject; @@ -31,7 +31,7 @@ public interface ElasticsearchSearchHighlighter extends SearchHighlighter { SearchHighlighterType type(); - boolean isCompatible(ProjectionAccumulator.Provider accumulatorProvider); + boolean isCompatible(ProjectionCollector.Provider collectorProvider); static ElasticsearchSearchHighlighter from(ElasticsearchSearchIndexScope scope, SearchHighlighter highlighter) { if ( !( highlighter instanceof ElasticsearchSearchHighlighter ) ) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighterImpl.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighterImpl.java index 131b205ec63..edf56f0a129 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighterImpl.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/highlighter/impl/ElasticsearchSearchHighlighterImpl.java @@ -21,7 +21,7 @@ import org.hibernate.search.engine.search.highlighter.spi.BoundaryScannerType; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterBuilder; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -143,9 +143,9 @@ public SearchHighlighterType type() { } @Override - public boolean isCompatible(ProjectionAccumulator.Provider provider) { - return !provider.isSingleValued() - || ( provider.isSingleValued() && ( numberOfFragments != null && numberOfFragments.equals( 1 ) ) ); + public boolean isCompatible(ProjectionCollector.Provider collectorProvider) { + return !collectorProvider.isSingleValued() + || ( collectorProvider.isSingleValued() && ( numberOfFragments != null && numberOfFragments.equals( 1 ) ) ); } private JsonObject toJson(JsonObject result) { diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/AccumulatingSourceExtractor.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/AccumulatingSourceExtractor.java index d081666a5d3..471d2432dea 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/AccumulatingSourceExtractor.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/AccumulatingSourceExtractor.java @@ -12,7 +12,7 @@ import org.hibernate.search.backend.elasticsearch.gson.impl.JsonElementTypes; import org.hibernate.search.backend.elasticsearch.gson.impl.UnexpectedJsonElementTypeException; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -24,18 +24,18 @@ abstract class AccumulatingSourceExtractor JsonAccessor.root().property( "_source" ).asArray(); private final String[] fieldPathComponents; - final ProjectionAccumulator accumulator; + final ProjectionCollector collector; public AccumulatingSourceExtractor(String[] fieldPathComponents, - ProjectionAccumulator accumulator) { + ProjectionCollector collector) { this.fieldPathComponents = fieldPathComponents; - this.accumulator = accumulator; + this.collector = collector; } @Override public final A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, JsonObject source, ProjectionExtractContext context) { - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); accumulated = collect( projectionHitMapper, hit, source, context, accumulated, 0 ); return accumulated; } @@ -83,17 +83,17 @@ private A collectTargetField(ProjectionHitMapper projectionHitMapper, JsonObj } else if ( fieldValue.isJsonNull() ) { // Present, but null - return accumulator.accumulate( accumulated, extract( projectionHitMapper, hit, fieldValue, context ) ); + return collector.accumulate( accumulated, extract( projectionHitMapper, hit, fieldValue, context ) ); } else if ( !canDecodeArrays() && fieldValue.isJsonArray() ) { for ( JsonElement childElement : fieldValue.getAsJsonArray() ) { - accumulated = accumulator.accumulate( accumulated, + accumulated = collector.accumulate( accumulated, extract( projectionHitMapper, hit, childElement, context ) ); } return accumulated; } else { - return accumulator.accumulate( accumulated, extract( projectionHitMapper, hit, fieldValue, context ) ); + return collector.accumulate( accumulated, extract( projectionHitMapper, hit, fieldValue, context ) ); } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchCompositeProjection.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchCompositeProjection.java index 1cada46bdd6..065c138a5d2 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchCompositeProjection.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchCompositeProjection.java @@ -9,9 +9,9 @@ import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import com.google.gson.JsonObject; @@ -31,14 +31,14 @@ class ElasticsearchCompositeProjection private final ElasticsearchSearchProjection[] inners; private final ProjectionCompositor compositor; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; public ElasticsearchCompositeProjection(Builder builder, ElasticsearchSearchProjection[] inners, - ProjectionCompositor compositor, ProjectionAccumulator accumulator) { + ProjectionCompositor compositor, ProjectionCollector collector) { super( builder.scope ); this.inners = inners; this.compositor = compositor; - this.accumulator = accumulator; + this.collector = collector; } @Override @@ -46,7 +46,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -71,21 +71,21 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @Override public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, JsonObject source, ProjectionExtractContext context) { - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); E components = compositor.createInitial(); for ( int i = 0; i < inners.length; i++ ) { Object extractedDataForInner = inners[i].extract( projectionHitMapper, hit, source, context ); components = compositor.set( components, i, extractedDataForInner ); } - accumulated = accumulator.accumulate( accumulated, components ); + accumulated = collector.accumulate( accumulated, components ); return accumulated; } @@ -93,8 +93,8 @@ public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, @Override public final P transform(LoadingResult loadingResult, A accumulated, ProjectionTransformContext context) { - for ( int i = 0; i < accumulator.size( accumulated ); i++ ) { - E transformedData = accumulator.get( accumulated, i ); + for ( int i = 0; i < collector.size( accumulated ); i++ ) { + E transformedData = collector.get( accumulated, i ); // Transform in-place for ( int j = 0; j < inners.length; j++ ) { Object extractedDataForInner = compositor.get( transformedData, j ); @@ -103,9 +103,9 @@ public final P transform(LoadingResult loadingResult, A accumulated, transformedData = compositor.set( transformedData, j, transformedDataForInner ); } - accumulated = accumulator.transform( accumulated, i, compositor.finish( transformedData ) ); + accumulated = collector.transform( accumulated, i, compositor.finish( transformedData ) ); } - return accumulator.finish( accumulated ); + return collector.finish( accumulated ); } } @@ -119,14 +119,14 @@ static class Builder implements CompositeProjectionBuilder { @Override public SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { ElasticsearchSearchProjection[] typedInners = new ElasticsearchSearchProjection[inners.length]; for ( int i = 0; i < inners.length; i++ ) { typedInners[i] = ElasticsearchSearchProjection.from( scope, inners[i] ); } return new ElasticsearchCompositeProjection<>( this, typedInners, - compositor, accumulatorProvider.get() ); + compositor, collectorProvider.get() ); } } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchDistanceToFieldProjection.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchDistanceToFieldProjection.java index 6df42ab7423..79a9920256e 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchDistanceToFieldProjection.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchDistanceToFieldProjection.java @@ -18,9 +18,9 @@ import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.DistanceToFieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; @@ -62,14 +62,14 @@ public class ElasticsearchDistanceToFieldProjection extends AbstractElasti private final GeoPoint center; private final DistanceUnit unit; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private final String scriptFieldName; private final ElasticsearchFieldProjection sourceProjection; private ElasticsearchDistanceToFieldProjection(Builder builder, - ProjectionAccumulator.Provider accumulatorProvider, - ProjectionAccumulator accumulator) { + ProjectionCollector.Provider collectorProvider, + ProjectionCollector collector) { super( builder ); this.codec = builder.field.type().codec(); @@ -77,7 +77,7 @@ private ElasticsearchDistanceToFieldProjection(Builder builder, this.singleValuedInRoot = !builder.field.multiValuedInRoot(); this.center = builder.center; this.unit = builder.unit; - this.accumulator = accumulator; + this.collector = collector; if ( singleValuedInRoot && builder.field.nestedPathHierarchy().isEmpty() ) { // Rely on docValues when there is no sort to extract the distance from. scriptFieldName = createScriptFieldName( absoluteFieldPath, center ); @@ -88,7 +88,7 @@ private ElasticsearchDistanceToFieldProjection(Builder builder, scriptFieldName = null; this.sourceProjection = new ElasticsearchFieldProjection<>( builder.scope, builder.field, - this::computeDistanceWithUnit, false, NO_OP_DOUBLE_CONVERTER, accumulatorProvider + this::computeDistanceWithUnit, false, NO_OP_DOUBLE_CONVERTER, collectorProvider ); } } @@ -99,7 +99,7 @@ public String toString() { + "absoluteFieldPath=" + absoluteFieldPath + ", center=" + center + ", unit=" + unit - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -130,13 +130,13 @@ public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, Integer distanceSortIndex = singleValuedInRoot ? context.getDistanceSortIndex( absoluteFieldPath, center ) : null; if ( distanceSortIndex != null ) { - A accumulated = accumulator.createInitial(); - accumulated = accumulator.accumulate( accumulated, extractDistanceFromSortKey( hit, distanceSortIndex ) ); + A accumulated = collector.createInitial(); + accumulated = collector.accumulate( accumulated, extractDistanceFromSortKey( hit, distanceSortIndex ) ); return accumulated; } else { - A accumulated = accumulator.createInitial(); - accumulated = accumulator.accumulate( accumulated, extractDistanceFromScriptField( hit ) ); + A accumulated = collector.createInitial(); + accumulated = collector.accumulate( accumulated, extractDistanceFromScriptField( hit ) ); return accumulated; } } @@ -145,7 +145,7 @@ public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, public P transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { // Nothing to transform: we take the values as they are. - return accumulator.finish( extractedData ); + return collector.finish( extractedData ); } private Double extractDistanceFromScriptField(JsonObject hit) { @@ -255,9 +255,9 @@ public void unit(DistanceUnit unit) { } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { - return new ElasticsearchDistanceToFieldProjection<>( this, accumulatorProvider, - accumulatorProvider.get() ); + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { + return new ElasticsearchDistanceToFieldProjection<>( this, collectorProvider, + collectorProvider.get() ); } } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldHighlightProjection.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldHighlightProjection.java index 99002c18c63..b748dee288b 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldHighlightProjection.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldHighlightProjection.java @@ -19,9 +19,9 @@ import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.spi.HighlightProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -38,23 +38,23 @@ public class ElasticsearchFieldHighlightProjection implements ElasticsearchSe private final String highlighterName; private final ElasticsearchSearchIndexValueFieldTypeContext typeContext; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; private ElasticsearchFieldHighlightProjection(Builder builder, - ProjectionAccumulator.Provider accumulatorProvider) { - this( builder.scope, builder.field, builder.highlighterName(), accumulatorProvider ); + ProjectionCollector.Provider collectorProvider) { + this( builder.scope, builder.field, builder.highlighterName(), collectorProvider ); } private ElasticsearchFieldHighlightProjection(ElasticsearchSearchIndexScope scope, ElasticsearchSearchIndexValueFieldContext field, String highlighterName, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { this.indexNames = scope.hibernateSearchIndexNames(); this.absoluteFieldPath = field.absolutePath(); this.absoluteFieldPathComponents = field.absolutePathComponents(); this.highlighterName = highlighterName; this.typeContext = field.type(); - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override @@ -93,7 +93,7 @@ public Extractor request(JsonObject requestBody, ProjectionRequestContext if ( !typeContext.highlighterTypeSupported( highlighterType ) ) { throw log.highlighterTypeNotSupported( highlighterType, absoluteFieldPath ); } - if ( !context.root().isCompatibleHighlighter( highlighterName, accumulatorProvider ) ) { + if ( !context.root().isCompatibleHighlighter( highlighterName, collectorProvider ) ) { throw log.highlighterIncompatibleCardinality(); } @@ -102,26 +102,26 @@ public Extractor request(JsonObject requestBody, ProjectionRequestContext REQUEST_HIGHLIGHT_FIELDS_ACCESSOR.getOrCreate( requestBody, JsonObject::new ) ); - return new FieldHighlightExtractor<>( innerContext.absoluteCurrentFieldPath(), accumulatorProvider.get() ); + return new FieldHighlightExtractor<>( innerContext.absoluteCurrentFieldPath(), collectorProvider.get() ); } private class FieldHighlightExtractor implements Extractor { private final JsonArrayAccessor highlightAccessor; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; - private FieldHighlightExtractor(String fieldPath, ProjectionAccumulator accumulator) { + private FieldHighlightExtractor(String fieldPath, ProjectionCollector collector) { this.highlightAccessor = JsonAccessor.root().property( "highlight" ).property( fieldPath ).asArray(); - this.accumulator = accumulator; + this.collector = collector; } @Override public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, JsonObject source, ProjectionExtractContext context) { - A initial = accumulator.createInitial(); + A initial = collector.createInitial(); Optional highlights = highlightAccessor.get( hit ); if ( highlights.isPresent() ) { for ( JsonElement element : highlights.get() ) { - initial = accumulator.accumulate( initial, element.getAsString() ); + initial = collector.accumulate( initial, element.getAsString() ); } } return initial; @@ -129,7 +129,7 @@ public A extract(ProjectionHitMapper projectionHitMapper, JsonObject hit, Jso @Override public T transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { - return accumulator.finish( extractedData ); + return collector.finish( extractedData ); } } @@ -164,8 +164,8 @@ protected String highlighterName() { } @Override - public SearchProjection build(ProjectionAccumulator.Provider accumulatorProvider) { - return new ElasticsearchFieldHighlightProjection<>( this, accumulatorProvider ); + public SearchProjection build(ProjectionCollector.Provider collectorProvider) { + return new ElasticsearchFieldHighlightProjection<>( this, collectorProvider ); } } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldProjection.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldProjection.java index 7893586ddf2..891d8aec867 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldProjection.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchFieldProjection.java @@ -17,9 +17,9 @@ import org.hibernate.search.engine.search.common.ValueModel; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.FieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import com.google.gson.JsonElement; @@ -44,36 +44,37 @@ public class ElasticsearchFieldProjection extends AbstractElasticsea private final Function decodeFunction; private final boolean canDecodeArrays; private final ProjectionConverter converter; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; private ElasticsearchFieldProjection(Builder builder, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { this( builder.scope, builder.field, builder.decodeFunction, builder.canDecodeArrays, builder.converter, - accumulatorProvider ); + collectorProvider + ); } ElasticsearchFieldProjection(ElasticsearchSearchIndexScope scope, ElasticsearchSearchIndexValueFieldContext field, Function decodeFunction, boolean canDecodeArrays, ProjectionConverter converter, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { super( scope ); this.absoluteFieldPath = field.absolutePath(); this.absoluteFieldPathComponents = field.absolutePathComponents(); - this.requiredContextAbsoluteFieldPath = accumulatorProvider.isSingleValued() + this.requiredContextAbsoluteFieldPath = collectorProvider.isSingleValued() ? field.closestMultiValuedParentAbsolutePath() : null; this.decodeFunction = decodeFunction; this.canDecodeArrays = canDecodeArrays; this.converter = converter; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath - + ", accumulatorProvider=" + accumulatorProvider + + ", collectorProvider=" + collectorProvider + "]"; } @@ -87,22 +88,22 @@ public ValueFieldExtractor request(JsonObject requestBody, ProjectionRequestC } JsonPrimitive fieldPathJson = new JsonPrimitive( absoluteFieldPath ); AccumulatingSourceExtractor.REQUEST_SOURCE_ACCESSOR.addElementIfAbsent( requestBody, fieldPathJson ); - return new ValueFieldExtractor<>( innerContext.relativeCurrentFieldPathComponents(), accumulatorProvider.get() ); + return new ValueFieldExtractor<>( innerContext.relativeCurrentFieldPathComponents(), collectorProvider.get() ); } /** * @param The type of the temporary storage for accumulated values, before and after being transformed. */ private class ValueFieldExtractor extends AccumulatingSourceExtractor { - public ValueFieldExtractor(String[] fieldPathComponents, ProjectionAccumulator accumulator) { - super( fieldPathComponents, accumulator ); + public ValueFieldExtractor(String[] fieldPathComponents, ProjectionCollector collector) { + super( fieldPathComponents, collector ); } @Override public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -121,8 +122,8 @@ protected boolean canDecodeArrays() { public P transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { FromDocumentValueConvertContext convertContext = context.fromDocumentValueConvertContext(); - A transformedData = accumulator.transformAll( extractedData, converter, convertContext ); - return accumulator.finish( transformedData ); + A transformedData = collector.transformAll( extractedData, converter.delegate(), convertContext ); + return collector.finish( transformedData ); } } @@ -193,11 +194,11 @@ private Builder(Function decodeFunction, boolean canDecodeArrays } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { - if ( accumulatorProvider.isSingleValued() && field.multiValued() ) { + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { + if ( collectorProvider.isSingleValued() && field.multiValued() ) { throw log.invalidSingleValuedProjectionOnMultiValuedField( field.absolutePath(), field.eventContext() ); } - return new ElasticsearchFieldProjection<>( this, accumulatorProvider ); + return new ElasticsearchFieldProjection<>( this, collectorProvider ); } } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchObjectProjection.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchObjectProjection.java index 46f0826a4f7..50f4e99642a 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchObjectProjection.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ElasticsearchObjectProjection.java @@ -11,9 +11,9 @@ import org.hibernate.search.backend.elasticsearch.search.common.impl.ElasticsearchSearchIndexScope; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import com.google.gson.JsonElement; @@ -37,19 +37,19 @@ public class ElasticsearchObjectProjection private final String requiredContextAbsoluteFieldPath; private final ElasticsearchSearchProjection[] inners; private final ProjectionCompositor compositor; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; public ElasticsearchObjectProjection(Builder builder, ElasticsearchSearchProjection[] inners, - ProjectionCompositor compositor, ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCompositor compositor, ProjectionCollector.Provider collectorProvider) { super( builder.scope ); this.absoluteFieldPath = builder.objectField.absolutePath(); this.absoluteFieldPathComponents = builder.objectField.absolutePathComponents(); - this.requiredContextAbsoluteFieldPath = accumulatorProvider.isSingleValued() + this.requiredContextAbsoluteFieldPath = collectorProvider.isSingleValued() ? builder.objectField.closestMultiValuedParentAbsolutePath() : null; this.inners = inners; this.compositor = compositor; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override @@ -57,7 +57,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulatorProvider=" + accumulatorProvider + + ", collectorProvider=" + collectorProvider + "]"; } @@ -76,7 +76,7 @@ public Extractor request(JsonObject requestBody, ProjectionRequestContext for ( int i = 0; i < inners.length; i++ ) { innerExtractors[i] = inners[i].request( requestBody, innerContext ); } - return new ObjectFieldExtractor<>( extractorFieldPathComponents, accumulatorProvider.get(), innerExtractors ); + return new ObjectFieldExtractor<>( extractorFieldPathComponents, collectorProvider.get(), innerExtractors ); } /** @@ -85,9 +85,9 @@ public Extractor request(JsonObject requestBody, ProjectionRequestContext private class ObjectFieldExtractor extends AccumulatingSourceExtractor { private final Extractor[] inners; - private ObjectFieldExtractor(String[] fieldPathComponents, ProjectionAccumulator accumulator, + private ObjectFieldExtractor(String[] fieldPathComponents, ProjectionCollector collector, Extractor[] inners) { - super( fieldPathComponents, accumulator ); + super( fieldPathComponents, collector ); this.inners = inners; } @@ -96,7 +96,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -123,8 +123,8 @@ protected boolean canDecodeArrays() { @Override public final P transform(LoadingResult loadingResult, A accumulated, ProjectionTransformContext context) { - for ( int i = 0; i < accumulator.size( accumulated ); i++ ) { - E transformedData = accumulator.get( accumulated, i ); + for ( int i = 0; i < collector.size( accumulated ); i++ ) { + E transformedData = collector.get( accumulated, i ); if ( transformedData == null ) { continue; } @@ -136,9 +136,9 @@ public final P transform(LoadingResult loadingResult, A accumulated, transformedData = compositor.set( transformedData, j, transformedDataForInner ); } - accumulated = accumulator.transform( accumulated, i, compositor.finish( transformedData ) ); + accumulated = collector.transform( accumulated, i, compositor.finish( transformedData ) ); } - return accumulator.finish( accumulated ); + return collector.finish( accumulated ); } } @@ -163,8 +163,8 @@ static class Builder implements CompositeProjectionBuilder { @Override public SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { - if ( accumulatorProvider.isSingleValued() && objectField.multiValued() ) { + ProjectionCollector.Provider collectorProvider) { + if ( collectorProvider.isSingleValued() && objectField.multiValued() ) { throw log.invalidSingleValuedProjectionOnMultiValuedField( objectField.absolutePath(), objectField.eventContext() ); } @@ -174,7 +174,7 @@ public SearchProjection

build(SearchProjection[] inners, Project typedInners[i] = ElasticsearchSearchProjection.from( scope, inners[i] ); } return new ElasticsearchObjectProjection<>( this, typedInners, - compositor, accumulatorProvider ); + compositor, collectorProvider ); } } } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ProjectionRequestRootContext.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ProjectionRequestRootContext.java index 01db0cb817a..e0b82dc52bb 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ProjectionRequestRootContext.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/projection/impl/ProjectionRequestRootContext.java @@ -6,7 +6,7 @@ import org.hibernate.search.backend.elasticsearch.lowlevel.syntax.search.impl.ElasticsearchSearchSyntax; import org.hibernate.search.backend.elasticsearch.search.highlighter.impl.ElasticsearchSearchHighlighter; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.spatial.GeoPoint; public interface ProjectionRequestRootContext extends ProjectionRequestContext { @@ -19,5 +19,5 @@ public interface ProjectionRequestRootContext extends ProjectionRequestContext { ElasticsearchSearchHighlighter queryHighlighter(); - boolean isCompatibleHighlighter(String highlighterName, ProjectionAccumulator.Provider accumulatorProvider); + boolean isCompatibleHighlighter(String highlighterName, ProjectionCollector.Provider collectorProvider); } diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/dsl/impl/ElasticsearchSearchQuerySelectStepImpl.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/dsl/impl/ElasticsearchSearchQuerySelectStepImpl.java index ae875675f1a..af452ffb0d2 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/dsl/impl/ElasticsearchSearchQuerySelectStepImpl.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/dsl/impl/ElasticsearchSearchQuerySelectStepImpl.java @@ -20,9 +20,9 @@ import org.hibernate.search.engine.search.predicate.SearchPredicate; import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; import org.hibernate.search.engine.search.predicate.dsl.SimpleBooleanPredicateClausesCollector; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.hibernate.search.engine.search.query.dsl.spi.AbstractSearchQuerySelectStep; import org.hibernate.search.engine.search.query.spi.SearchQueryIndexScope; @@ -83,7 +83,7 @@ public

ElasticsearchSearchQueryWhereStep select(SearchProjection

public ElasticsearchSearchQueryWhereStep, LOS> select(SearchProjection... projections) { return select( scope.projectionBuilders().composite() .build( projections, ProjectionCompositor.fromList( projections.length ), - ProjectionAccumulator.single() ) ); + ProjectionCollector.nullable() ) ); } @Override diff --git a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/impl/ElasticsearchSearchQueryRequestContext.java b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/impl/ElasticsearchSearchQueryRequestContext.java index 9da2e5fa0c9..80a2efb6e90 100644 --- a/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/impl/ElasticsearchSearchQueryRequestContext.java +++ b/backend/elasticsearch/src/main/java/org/hibernate/search/backend/elasticsearch/search/query/impl/ElasticsearchSearchQueryRequestContext.java @@ -23,7 +23,7 @@ import org.hibernate.search.engine.search.common.NamedValues; import org.hibernate.search.engine.search.common.spi.SearchQueryElementTypeKey; import org.hibernate.search.engine.search.loading.spi.SearchLoadingContext; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.query.spi.QueryParameters; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -144,17 +144,17 @@ public ElasticsearchSearchHighlighter queryHighlighter() { } @Override - public boolean isCompatibleHighlighter(String highlighterName, ProjectionAccumulator.Provider accumulatorProvider) { + public boolean isCompatibleHighlighter(String highlighterName, ProjectionCollector.Provider collectorProvider) { ElasticsearchSearchHighlighter highlighter = highlighter( highlighterName ); if ( ElasticsearchSearchHighlighterImpl.NO_OPTIONS_CONFIGURATION == highlighter ) { // if there was no highlighter configured at all it means that the settings are default, - // and we assume that they are incompatible with the single-valued accumulator: + // and we assume that they are incompatible with the single-valued collector: return queryHighlighter != null - ? queryHighlighter.isCompatible( accumulatorProvider ) - : !accumulatorProvider.isSingleValued(); + ? queryHighlighter.isCompatible( collectorProvider ) + : !collectorProvider.isSingleValued(); } else { - return highlighter.isCompatible( accumulatorProvider ); + return highlighter.isCompatible( collectorProvider ); } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java index f4d827d44c1..7473ee29b58 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java @@ -537,7 +537,7 @@ SearchException invalidFieldValueType(@FormatWith(ClassFormatter.class) Class @Message(id = ID_OFFSET + 131, value = "Invalid cardinality for projection on field '%1$s': the projection is single-valued," + " but this field is multi-valued." - + " Make sure to call '.multi()' when you create the projection.") + + " Make sure to call '.collector(...)' when you create the projection.") SearchException invalidSingleValuedProjectionOnMultiValuedField(String absolutePath, @Param EventContext context); @Message(id = ID_OFFSET + 135, @@ -600,9 +600,9 @@ SearchException invalidContextForProjectionOnField(String absolutePath, value = "Invalid cardinality for projection on field '%1$s': the projection is single-valued," + " but this field is effectively multi-valued in this context," + " because parent object field '%2$s' is multi-valued." - + " Either call '.multi()' when you create the projection on field '%1$s'," + + " Either call '.collector(...)' when you create the projection on field '%1$s'," + " or wrap that projection in an object projection like this:" - + " 'f.object(\"%2$s\").from().as(...).multi()'.") + + " 'f.object(\"%2$s\").from().as(...).collector(...)'.") SearchException invalidSingleValuedProjectionOnValueFieldInMultiValuedObjectField(String absolutePath, String objectFieldAbsolutePath); diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneAbstractSearchHighlighter.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneAbstractSearchHighlighter.java index 956a225ac83..a9bbaa0332c 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneAbstractSearchHighlighter.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneAbstractSearchHighlighter.java @@ -26,7 +26,7 @@ import org.hibernate.search.engine.search.highlighter.spi.BoundaryScannerType; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterBuilder; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -193,9 +193,9 @@ public Set indexNames() { public abstract Values createValues(String parentDocumentPath, String nestedDocumentPath, String absoluteFieldPath, Analyzer analyzer, ProjectionExtractContext context, - ProjectionAccumulator accumulator); + ProjectionCollector collector); - public boolean isCompatible(ProjectionAccumulator.Provider provider) { + public boolean isCompatible(ProjectionCollector.Provider provider) { return !provider.isSingleValued() || ( provider.isSingleValued() && ( numberOfFragments != null && numberOfFragments.equals( 1 ) ) ); } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneFastVectorSearchHighlighter.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneFastVectorSearchHighlighter.java index a2669d61aab..d425c33fc87 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneFastVectorSearchHighlighter.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneFastVectorSearchHighlighter.java @@ -20,7 +20,7 @@ import org.hibernate.search.engine.search.highlighter.dsl.HighlighterFragmenter; import org.hibernate.search.engine.search.highlighter.spi.BoundaryScannerType; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Field; @@ -86,9 +86,10 @@ public LuceneAbstractSearchHighlighter withFallbackDefaults() { @Override public Values createValues(String parentDocumentPath, String nestedDocumentPath, String absoluteFieldPath, Analyzer analyzer, ProjectionExtractContext context, - ProjectionAccumulator accumulator) { + ProjectionCollector collector) { return new FastVectorHighlighterValues<>( parentDocumentPath, nestedDocumentPath, absoluteFieldPath, context, - accumulator ); + collector + ); } @Override @@ -108,8 +109,8 @@ private final class FastVectorHighlighterValues extends HighlighterValues< private final Integer maxNumFragments; FastVectorHighlighterValues(String parentDocumentPath, String nestedDocumentPath, String field, - ProjectionExtractContext context, ProjectionAccumulator accumulator) { - super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), accumulator ); + ProjectionExtractContext context, ProjectionCollector collector) { + super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), collector ); this.field = field; this.highlighter = new FastVectorHighlighter(); diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LucenePlainSearchHighlighter.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LucenePlainSearchHighlighter.java index 6ab573d2898..6596291e568 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LucenePlainSearchHighlighter.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LucenePlainSearchHighlighter.java @@ -21,7 +21,7 @@ import org.hibernate.search.engine.search.highlighter.dsl.HighlighterFragmenter; import org.hibernate.search.engine.search.highlighter.spi.BoundaryScannerType; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.IndexableField; @@ -87,9 +87,9 @@ public LuceneAbstractSearchHighlighter withFallbackDefaults() { @Override public Values createValues(String parentDocumentPath, String nestedDocumentPath, String absoluteFieldPath, Analyzer analyzer, ProjectionExtractContext context, - ProjectionAccumulator accumulator) { + ProjectionCollector collector) { return new PlainHighlighterValues<>( - parentDocumentPath, nestedDocumentPath, absoluteFieldPath, analyzer, context, accumulator ); + parentDocumentPath, nestedDocumentPath, absoluteFieldPath, analyzer, context, collector ); } @Override @@ -109,8 +109,8 @@ private final class PlainHighlighterValues extends HighlighterValues private final String field; PlainHighlighterValues(String parentDocumentPath, String nestedDocumentPath, String field, Analyzer analyzer, - ProjectionExtractContext context, ProjectionAccumulator accumulator) { - super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), accumulator ); + ProjectionExtractContext context, ProjectionCollector collector) { + super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), collector ); this.storedFieldsValuesDelegate = context.collectorExecutionContext().storedFieldsValuesDelegate(); this.field = field; this.analyzer = analyzer; diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneUnifiedSearchHighlighter.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneUnifiedSearchHighlighter.java index 969145187ad..385f2d563a4 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneUnifiedSearchHighlighter.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/highlighter/impl/LuceneUnifiedSearchHighlighter.java @@ -21,7 +21,7 @@ import org.hibernate.search.engine.search.highlighter.dsl.HighlighterFragmenter; import org.hibernate.search.engine.search.highlighter.spi.BoundaryScannerType; import org.hibernate.search.engine.search.highlighter.spi.SearchHighlighterType; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.search.IndexSearcher; @@ -90,9 +90,9 @@ public LuceneAbstractSearchHighlighter withFallbackDefaults() { @Override public Values createValues(String parentDocumentPath, String nestedDocumentPath, String absoluteFieldPath, Analyzer analyzer, ProjectionExtractContext context, - ProjectionAccumulator accumulator) { + ProjectionCollector collector) { return new UnifiedHighlighterValues<>( - parentDocumentPath, nestedDocumentPath, absoluteFieldPath, analyzer, context, accumulator ); + parentDocumentPath, nestedDocumentPath, absoluteFieldPath, analyzer, context, collector ); } @Override @@ -109,8 +109,8 @@ private final class UnifiedHighlighterValues extends HighlighterValues accumulator) { - super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), accumulator ); + ProjectionExtractContext context, ProjectionCollector collector) { + super( parentDocumentPath, nestedDocumentPath, context.collectorExecutionContext(), collector ); this.fieldsIn = new String[] { field }; this.maxPassagesIn = new int[] { LuceneUnifiedSearchHighlighter.this.numberOfFragments }; this.query = context.collectorExecutionContext().originalQuery(); diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/AbstractNestingAwareAccumulatingValues.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/AbstractNestingAwareAccumulatingValues.java index cc564d49720..c5cb96b82dc 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/AbstractNestingAwareAccumulatingValues.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/AbstractNestingAwareAccumulatingValues.java @@ -10,23 +10,23 @@ import org.hibernate.search.backend.lucene.lowlevel.collector.impl.Values; import org.hibernate.search.backend.lucene.lowlevel.join.impl.ChildDocIds; import org.hibernate.search.backend.lucene.lowlevel.join.impl.NestedDocsProvider; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.DocIdSetIterator; abstract class AbstractNestingAwareAccumulatingValues implements Values { private final NestedDocsProvider nestedDocsProvider; - protected final ProjectionAccumulator accumulator; + protected final ProjectionCollector collector; protected ChildDocIds currentLeafChildDocIds; AbstractNestingAwareAccumulatingValues(String parentDocumentPath, String nestedDocumentPath, - ProjectionAccumulator accumulator, TopDocsDataCollectorExecutionContext context) { + ProjectionCollector collector, TopDocsDataCollectorExecutionContext context) { this.nestedDocsProvider = nestedDocumentPath == null || nestedDocumentPath.equals( parentDocumentPath ) ? null : context.createNestedDocsProvider( parentDocumentPath, nestedDocumentPath ); - this.accumulator = accumulator; + this.collector = collector; } @Override @@ -44,7 +44,7 @@ protected DocIdSetIterator doContext(LeafReaderContext context) throws IOExcepti @Override public final A get(int parentDocId) throws IOException { - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); if ( nestedDocsProvider == null ) { // No nesting: we work directly on the parent doc. diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneCompositeProjection.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneCompositeProjection.java index 36d41b6c76f..c61fd1c8803 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneCompositeProjection.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneCompositeProjection.java @@ -10,9 +10,9 @@ import org.hibernate.search.backend.lucene.lowlevel.collector.impl.Values; import org.hibernate.search.backend.lucene.search.common.impl.LuceneSearchIndexScope; import org.hibernate.search.engine.search.loading.spi.LoadingResult; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.apache.lucene.index.LeafReaderContext; @@ -32,14 +32,14 @@ class LuceneCompositeProjection private final LuceneSearchProjection[] inners; private final ProjectionCompositor compositor; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; public LuceneCompositeProjection(Builder builder, LuceneSearchProjection[] inners, - ProjectionCompositor compositor, ProjectionAccumulator accumulator) { + ProjectionCompositor compositor, ProjectionCollector collector) { super( builder.scope ); this.inners = inners; this.compositor = compositor; - this.accumulator = accumulator; + this.collector = collector; } @Override @@ -47,7 +47,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -72,7 +72,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -101,14 +101,14 @@ public void context(LeafReaderContext context) throws IOException { @Override public A get(int doc) throws IOException { - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); E components = compositor.createInitial(); for ( int i = 0; i < inners.length; i++ ) { Object extractedDataForInner = inners[i].get( doc ); components = compositor.set( components, i, extractedDataForInner ); } - accumulated = accumulator.accumulate( accumulated, components ); + accumulated = collector.accumulate( accumulated, components ); return accumulated; } @@ -117,8 +117,8 @@ public A get(int doc) throws IOException { @Override public final P transform(LoadingResult loadingResult, A accumulated, ProjectionTransformContext context) { - for ( int i = 0; i < accumulator.size( accumulated ); i++ ) { - E transformedData = accumulator.get( accumulated, i ); + for ( int i = 0; i < collector.size( accumulated ); i++ ) { + E transformedData = collector.get( accumulated, i ); // Transform in-place for ( int j = 0; j < inners.length; j++ ) { Object extractedDataForInner = compositor.get( transformedData, j ); @@ -126,9 +126,9 @@ public final P transform(LoadingResult loadingResult, A accumulated, extractedDataForInner, context ); transformedData = compositor.set( transformedData, j, transformedDataForInner ); } - accumulated = accumulator.transform( accumulated, i, compositor.finish( transformedData ) ); + accumulated = collector.transform( accumulated, i, compositor.finish( transformedData ) ); } - return accumulator.finish( accumulated ); + return collector.finish( accumulated ); } } @@ -142,14 +142,14 @@ static class Builder implements CompositeProjectionBuilder { @Override public SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { LuceneSearchProjection[] typedInners = new LuceneSearchProjection[inners.length]; for ( int i = 0; i < inners.length; i++ ) { typedInners[i] = LuceneSearchProjection.from( scope, inners[i] ); } return new LuceneCompositeProjection<>( this, typedInners, - compositor, accumulatorProvider.get() ); + compositor, collectorProvider.get() ); } } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneDistanceToFieldProjection.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneDistanceToFieldProjection.java index 7a4f2bfbebe..5ffad286d11 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneDistanceToFieldProjection.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneDistanceToFieldProjection.java @@ -17,9 +17,9 @@ import org.hibernate.search.backend.lucene.types.codec.impl.LuceneFieldCodec; import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; import org.hibernate.search.engine.search.loading.spi.LoadingResult; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.DistanceToFieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -49,27 +49,27 @@ public class LuceneDistanceToFieldProjection

extends AbstractLuceneProjection private final GeoPoint center; private final DistanceUnit unit; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; private final LuceneFieldProjection fieldProjection; private LuceneDistanceToFieldProjection(Builder builder, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { super( builder ); this.absoluteFieldPath = builder.field.absolutePath(); this.nestedDocumentPath = builder.field.nestedDocumentPath(); - this.requiredContextAbsoluteFieldPath = accumulatorProvider.isSingleValued() + this.requiredContextAbsoluteFieldPath = collectorProvider.isSingleValued() ? builder.field.closestMultiValuedParentAbsolutePath() : null; this.codec = builder.codec; this.center = builder.center; this.unit = builder.unit; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; if ( builder.field.multiValued() ) { // For multi-valued fields, use a field projection, because we need order to be preserved. this.fieldProjection = new LuceneFieldProjection<>( builder.scope, builder.field, - this::computeDistanceWithUnit, NO_OP_DOUBLE_CONVERTER, accumulatorProvider + this::computeDistanceWithUnit, NO_OP_DOUBLE_CONVERTER, collectorProvider ); } else { @@ -83,7 +83,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath + ", center=" + center - + ", accumulatorProvider=" + accumulatorProvider + + ", collectorProvider=" + collectorProvider + "]"; } @@ -99,7 +99,7 @@ public Extractor request(ProjectionRequestContext context) { throw log.invalidSingleValuedProjectionOnValueFieldInMultiValuedObjectField( absoluteFieldPath, requiredContextAbsoluteFieldPath ); } - return new DocValuesBasedDistanceExtractor<>( accumulatorProvider.get(), + return new DocValuesBasedDistanceExtractor<>( collectorProvider.get(), context.absoluteCurrentNestedFieldPath() ); } } @@ -108,12 +108,12 @@ public Extractor request(ProjectionRequestContext context) { * @param The type of the temporary storage for accumulated values, before and after being transformed. */ private class DocValuesBasedDistanceExtractor implements Extractor { - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private final String contextAbsoluteFieldPath; - private DocValuesBasedDistanceExtractor(ProjectionAccumulator accumulator, + private DocValuesBasedDistanceExtractor(ProjectionCollector collector, String contextAbsoluteFieldPath) { - this.accumulator = accumulator; + this.collector = collector; this.contextAbsoluteFieldPath = contextAbsoluteFieldPath; } @@ -122,7 +122,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath + ", center=" + center - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -140,7 +140,7 @@ private class DocValuesBasedDistanceValues public DocValuesBasedDistanceValues(TopDocsDataCollectorExecutionContext context) { super( contextAbsoluteFieldPath, nestedDocumentPath, - DocValuesBasedDistanceExtractor.this.accumulator, context ); + DocValuesBasedDistanceExtractor.this.collector, context ); } @Override @@ -155,7 +155,7 @@ protected A accumulate(A accumulated, int docId) throws IOException { if ( currentLeafValues.advanceExact( docId ) ) { for ( int i = 0; i < currentLeafValues.docValueCount(); i++ ) { Double distanceOrNull = currentLeafValues.nextValue(); - accumulated = accumulator.accumulate( accumulated, unit.fromMeters( distanceOrNull ) ); + accumulated = collector.accumulate( accumulated, unit.fromMeters( distanceOrNull ) ); } } return accumulated; @@ -166,7 +166,7 @@ protected A accumulate(A accumulated, int docId) throws IOException { public P transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { // Nothing to transform: we take the values as they are. - return accumulator.finish( extractedData ); + return collector.finish( extractedData ); } } @@ -229,11 +229,11 @@ public void unit(DistanceUnit unit) { } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { - if ( accumulatorProvider.isSingleValued() && field.multiValued() ) { + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { + if ( collectorProvider.isSingleValued() && field.multiValued() ) { throw log.invalidSingleValuedProjectionOnMultiValuedField( field.absolutePath(), field.eventContext() ); } - return new LuceneDistanceToFieldProjection<>( this, accumulatorProvider ); + return new LuceneDistanceToFieldProjection<>( this, collectorProvider ); } } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldHighlightProjection.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldHighlightProjection.java index 8c0cd0ebc2b..cf753c3e500 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldHighlightProjection.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldHighlightProjection.java @@ -17,9 +17,9 @@ import org.hibernate.search.backend.lucene.search.highlighter.impl.LuceneAbstractSearchHighlighter; import org.hibernate.search.engine.reporting.spi.EventContexts; import org.hibernate.search.engine.search.loading.spi.LoadingResult; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.spi.HighlightProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.LeafReaderContext; @@ -32,22 +32,22 @@ public class LuceneFieldHighlightProjection implements LuceneSearchProjection private final String highlighterName; private final String nestedDocumentPath; private final LuceneSearchIndexValueFieldTypeContext typeContext; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; - private LuceneFieldHighlightProjection(Builder builder, ProjectionAccumulator.Provider accumulatorProvider) { - this( builder.scope, builder.field, builder.highlighterName(), accumulatorProvider ); + private LuceneFieldHighlightProjection(Builder builder, ProjectionCollector.Provider collectorProvider) { + this( builder.scope, builder.field, builder.highlighterName(), collectorProvider ); } LuceneFieldHighlightProjection(LuceneSearchIndexScope scope, LuceneSearchIndexValueFieldContext field, - String highlighterName, ProjectionAccumulator.Provider accumulatorProvider) { + String highlighterName, ProjectionCollector.Provider collectorProvider) { this.indexNames = scope.hibernateSearchIndexNames(); this.analyzer = field.type().searchAnalyzerOrNormalizer(); this.absoluteFieldPath = field.absolutePath(); this.highlighterName = highlighterName; this.nestedDocumentPath = field.nestedDocumentPath(); this.typeContext = field.type(); - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override @@ -77,12 +77,12 @@ public Extractor request(ProjectionRequestContext context) { throw log.highlighterTypeNotSupported( highlighter.type(), absoluteFieldPath ); } highlighter.request( context, absoluteFieldPath ); - if ( !highlighter.isCompatible( accumulatorProvider ) ) { + if ( !highlighter.isCompatible( collectorProvider ) ) { throw log.highlighterIncompatibleCardinality(); } return new FieldHighlightExtractor<>( context.absoluteCurrentNestedFieldPath(), highlighter, - accumulatorProvider.get() + collectorProvider.get() ); } @@ -90,13 +90,13 @@ public Extractor request(ProjectionRequestContext context) { private class FieldHighlightExtractor implements Extractor { private final String parentDocumentPath; private final LuceneAbstractSearchHighlighter highlighter; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private FieldHighlightExtractor(String parentDocumentPath, LuceneAbstractSearchHighlighter highlighter, - ProjectionAccumulator accumulator) { + ProjectionCollector collector) { this.parentDocumentPath = parentDocumentPath; this.highlighter = highlighter; - this.accumulator = accumulator; + this.collector = collector; } @Override @@ -107,14 +107,14 @@ public Values values(ProjectionExtractContext context) { absoluteFieldPath, analyzer, context, - accumulator + collector ); } @Override public T transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { - return accumulator.finish( extractedData ); + return collector.finish( extractedData ); } } @@ -124,8 +124,8 @@ public abstract static class HighlighterValues extends AbstractNestingAwar protected HighlighterValues(String parentDocumentPath, String nestedDocumentPath, TopDocsDataCollectorExecutionContext context, - ProjectionAccumulator accumulator) { - super( parentDocumentPath, nestedDocumentPath, accumulator, context ); + ProjectionCollector collector) { + super( parentDocumentPath, nestedDocumentPath, collector, context ); } @Override @@ -139,7 +139,7 @@ public void context(LeafReaderContext context) throws IOException { @Override protected A accumulate(A accumulated, int docId) throws IOException { - return accumulator.accumulateAll( accumulated, highlight( docId ) ); + return collector.accumulateAll( accumulated, highlight( docId ) ); } protected abstract List highlight(int doc) throws IOException; @@ -175,8 +175,8 @@ protected String highlighterName() { } @Override - public SearchProjection build(ProjectionAccumulator.Provider accumulatorProvider) { - return new LuceneFieldHighlightProjection<>( this, accumulatorProvider ); + public SearchProjection build(ProjectionCollector.Provider collectorProvider) { + return new LuceneFieldHighlightProjection<>( this, collectorProvider ); } } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldProjection.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldProjection.java index 582cbc3c2f2..541a5522055 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldProjection.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneFieldProjection.java @@ -19,9 +19,9 @@ import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; import org.hibernate.search.engine.search.common.ValueModel; import org.hibernate.search.engine.search.loading.spi.LoadingResult; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.FieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import org.apache.lucene.document.Document; @@ -44,33 +44,33 @@ public class LuceneFieldProjection extends AbstractLuceneProjection< private final Function decodeFunction; private final ProjectionConverter converter; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; - private LuceneFieldProjection(Builder builder, ProjectionAccumulator.Provider accumulatorProvider) { - this( builder.scope, builder.field, builder.decodeFunction, builder.converter, accumulatorProvider ); + private LuceneFieldProjection(Builder builder, ProjectionCollector.Provider collectorProvider) { + this( builder.scope, builder.field, builder.decodeFunction, builder.converter, collectorProvider ); } LuceneFieldProjection(LuceneSearchIndexScope scope, LuceneSearchIndexValueFieldContext field, Function decodeFunction, ProjectionConverter converter, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { super( scope ); this.absoluteFieldPath = field.absolutePath(); this.nestedDocumentPath = field.nestedDocumentPath(); - this.requiredContextAbsoluteFieldPath = accumulatorProvider.isSingleValued() + this.requiredContextAbsoluteFieldPath = collectorProvider.isSingleValued() ? field.closestMultiValuedParentAbsolutePath() : null; this.decodeFunction = decodeFunction; this.converter = converter; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath - + ", accumulatorProvider=" + accumulatorProvider + + ", collectorProvider=" + collectorProvider + "]"; } @@ -83,7 +83,7 @@ public ValueFieldExtractor request(ProjectionRequestContext context) { absoluteFieldPath, requiredContextAbsoluteFieldPath ); } context.requireStoredField( absoluteFieldPath, nestedDocumentPath ); - return new ValueFieldExtractor<>( context.absoluteCurrentNestedFieldPath(), accumulatorProvider.get() ); + return new ValueFieldExtractor<>( context.absoluteCurrentNestedFieldPath(), collectorProvider.get() ); } /** @@ -92,10 +92,10 @@ public ValueFieldExtractor request(ProjectionRequestContext context) { private class ValueFieldExtractor implements LuceneSearchProjection.Extractor { private final String contextAbsoluteFieldPath; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; - public ValueFieldExtractor(String contextAbsoluteFieldPath, ProjectionAccumulator accumulator) { - this.accumulator = accumulator; + public ValueFieldExtractor(String contextAbsoluteFieldPath, ProjectionCollector collector) { + this.collector = collector; this.contextAbsoluteFieldPath = contextAbsoluteFieldPath; } @@ -103,21 +103,21 @@ public ValueFieldExtractor(String contextAbsoluteFieldPath, ProjectionAccumulato public String toString() { return getClass().getSimpleName() + "[" + "absoluteFieldPath=" + absoluteFieldPath - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @Override public Values values(ProjectionExtractContext context) { - return new StoredFieldValues( accumulator, context.collectorExecutionContext() ); + return new StoredFieldValues( collector, context.collectorExecutionContext() ); } private class StoredFieldValues extends AbstractNestingAwareAccumulatingValues { private final StoredFieldsValuesDelegate delegate; - public StoredFieldValues(ProjectionAccumulator accumulator, + public StoredFieldValues(ProjectionCollector collector, TopDocsDataCollectorExecutionContext context) { - super( contextAbsoluteFieldPath, nestedDocumentPath, accumulator, context ); + super( contextAbsoluteFieldPath, nestedDocumentPath, collector, context ); this.delegate = context.storedFieldsValuesDelegate(); } @@ -133,7 +133,7 @@ protected A accumulate(A accumulated, int docId) { for ( IndexableField field : document.getFields() ) { if ( field.name().equals( absoluteFieldPath ) ) { T decoded = decodeFunction.apply( field ); - accumulated = accumulator.accumulate( accumulated, decoded ); + accumulated = collector.accumulate( accumulated, decoded ); } } return accumulated; @@ -143,8 +143,8 @@ protected A accumulate(A accumulated, int docId) { @Override public P transform(LoadingResult loadingResult, A extractedData, ProjectionTransformContext context) { FromDocumentValueConvertContext convertContext = context.fromDocumentValueConvertContext(); - A transformedData = accumulator.transformAll( extractedData, converter, convertContext ); - return accumulator.finish( transformedData ); + A transformedData = collector.transformAll( extractedData, converter.delegate(), convertContext ); + return collector.finish( transformedData ); } } @@ -216,11 +216,11 @@ private Builder(LuceneSearchIndexScope scope, LuceneSearchIndexValueFieldCont } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { - if ( accumulatorProvider.isSingleValued() && field.multiValued() ) { + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { + if ( collectorProvider.isSingleValued() && field.multiValued() ) { throw log.invalidSingleValuedProjectionOnMultiValuedField( field.absolutePath(), field.eventContext() ); } - return new LuceneFieldProjection<>( this, accumulatorProvider ); + return new LuceneFieldProjection<>( this, collectorProvider ); } } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneObjectProjection.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneObjectProjection.java index 62c748546a9..de1c02b82c2 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneObjectProjection.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/projection/impl/LuceneObjectProjection.java @@ -16,9 +16,9 @@ import org.hibernate.search.backend.lucene.search.predicate.impl.PredicateRequestContext; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.predicate.spi.PredicateTypeKeys; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.hibernate.search.engine.search.projection.spi.ProjectionTypeKeys; import org.hibernate.search.util.common.SearchException; @@ -47,21 +47,21 @@ public class LuceneObjectProjection private final String requiredContextAbsoluteFieldPath; private final LuceneSearchProjection[] inners; private final ProjectionCompositor compositor; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; public LuceneObjectProjection(Builder builder, LuceneSearchProjection[] inners, - ProjectionCompositor compositor, ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCompositor compositor, ProjectionCollector.Provider collectorProvider) { super( builder.scope ); this.absoluteFieldPath = builder.objectField.absolutePath(); this.nested = builder.objectField.type().nested(); this.filter = builder.filter; this.nestedDocumentPath = builder.objectField.nestedDocumentPath(); - this.requiredContextAbsoluteFieldPath = accumulatorProvider.isSingleValued() + this.requiredContextAbsoluteFieldPath = collectorProvider.isSingleValued() ? builder.objectField.closestMultiValuedParentAbsolutePath() : null; this.inners = inners; this.compositor = compositor; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override @@ -69,7 +69,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulatorProvider=" + accumulatorProvider + + ", collectorProvider=" + collectorProvider + "]"; } @@ -86,7 +86,7 @@ public Extractor request(ProjectionRequestContext context) { innerExtractors[i] = inners[i].request( innerContext ); } return new ObjectFieldExtractor<>( context.absoluteCurrentNestedFieldPath(), innerExtractors, - accumulatorProvider.get() ); + collectorProvider.get() ); } /** @@ -95,13 +95,13 @@ public Extractor request(ProjectionRequestContext context) { private class ObjectFieldExtractor implements Extractor { private final String contextAbsoluteFieldPath; private final Extractor[] inners; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private ObjectFieldExtractor(String contextAbsoluteFieldPath, - Extractor[] inners, ProjectionAccumulator accumulator) { + Extractor[] inners, ProjectionCollector collector) { this.contextAbsoluteFieldPath = contextAbsoluteFieldPath; this.inners = inners; - this.accumulator = accumulator; + this.collector = collector; } @Override @@ -109,7 +109,7 @@ public String toString() { return getClass().getSimpleName() + "[" + "inners=" + Arrays.toString( inners ) + ", compositor=" + compositor - + ", accumulator=" + accumulator + + ", collector=" + collector + "]"; } @@ -129,7 +129,7 @@ private class ObjectFieldValues extends AbstractNestingAwareAccumulatingValues[] inners) { - super( contextAbsoluteFieldPath, nestedDocumentPath, ObjectFieldExtractor.this.accumulator, context ); + super( contextAbsoluteFieldPath, nestedDocumentPath, ObjectFieldExtractor.this.collector, context ); this.inners = inners; this.filterBitSetProducer = filter == null ? null : new QueryBitSetProducer( filter ); @@ -158,15 +158,15 @@ protected A accumulate(A accumulated, int docId) throws IOException { Object extractedDataForInner = inners[i].get( docId ); components = compositor.set( components, i, extractedDataForInner ); } - return accumulator.accumulate( accumulated, components ); + return collector.accumulate( accumulated, components ); } } @Override public final P transform(LoadingResult loadingResult, A accumulated, ProjectionTransformContext context) { - for ( int i = 0; i < accumulator.size( accumulated ); i++ ) { - E transformedData = accumulator.get( accumulated, i ); + for ( int i = 0; i < collector.size( accumulated ); i++ ) { + E transformedData = collector.get( accumulated, i ); // Transform in-place for ( int j = 0; j < inners.length; j++ ) { Object extractedDataForInner = compositor.get( transformedData, j ); @@ -174,9 +174,9 @@ public final P transform(LoadingResult loadingResult, A accumulated, extractedDataForInner, context ); transformedData = compositor.set( transformedData, j, transformedDataForInner ); } - accumulated = accumulator.transform( accumulated, i, compositor.finish( transformedData ) ); + accumulated = collector.transform( accumulated, i, compositor.finish( transformedData ) ); } - return accumulator.finish( accumulated ); + return collector.finish( accumulated ); } } @@ -222,8 +222,8 @@ static class Builder implements CompositeProjectionBuilder { @Override public SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { - if ( accumulatorProvider.isSingleValued() && objectField.multiValued() ) { + ProjectionCollector.Provider collectorProvider) { + if ( collectorProvider.isSingleValued() && objectField.multiValued() ) { throw log.invalidSingleValuedProjectionOnMultiValuedField( objectField.absolutePath(), objectField.eventContext() ); } @@ -233,7 +233,7 @@ public SearchProjection

build(SearchProjection[] inners, Project typedInners[i] = LuceneSearchProjection.from( scope, inners[i] ); } return new LuceneObjectProjection<>( this, typedInners, - compositor, accumulatorProvider ); + compositor, collectorProvider ); } } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/query/dsl/impl/LuceneSearchQuerySelectStepImpl.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/query/dsl/impl/LuceneSearchQuerySelectStepImpl.java index ec231ab5ee9..4ed9a537a84 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/query/dsl/impl/LuceneSearchQuerySelectStepImpl.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/search/query/dsl/impl/LuceneSearchQuerySelectStepImpl.java @@ -20,9 +20,9 @@ import org.hibernate.search.engine.search.predicate.SearchPredicate; import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; import org.hibernate.search.engine.search.predicate.dsl.SimpleBooleanPredicateClausesCollector; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.hibernate.search.engine.search.query.dsl.spi.AbstractSearchQuerySelectStep; @@ -81,7 +81,7 @@ public

LuceneSearchQueryWhereStep select(SearchProjection

project public LuceneSearchQueryWhereStep, LOS> select(SearchProjection... projections) { return select( scope.projectionBuilders().composite() .build( projections, ProjectionCompositor.fromList( projections.length ), - ProjectionAccumulator.single() ) ); + ProjectionCollector.nullable() ) ); } @Override diff --git a/documentation/src/main/asciidoc/migration/index.adoc b/documentation/src/main/asciidoc/migration/index.adoc index 3b0d2205cb9..a577f290d5c 100644 --- a/documentation/src/main/asciidoc/migration/index.adoc +++ b/documentation/src/main/asciidoc/migration/index.adoc @@ -103,6 +103,10 @@ interfaces are removed in this version. They have their alternatives in a `org.h Instead, we are introducing the `org.hibernate.search.mapper.pojo.massindexing.MassIndexingTypeGroupMonitor` that can be obtained through `org.hibernate.search.mapper.pojo.massindexing.MassIndexingMonitor#typeGroupMonitor(..)`. This new type group monitor has more flexibility and also allows implementors to skip total count computations if needed. +- `multi()` methods exposed in various projection DSL steps are deprecated in favour of a `collector(ProjectionCollector.Provider)`, +or one of the "shortcut-methods": `.list()`/`.set()`/`.sortedSet()`... +Check the `ProjectionCollector` factory methods to see the list of built-in collectors that provide support for nullable/optional single-valued projections +and for multivalued ones such as lists, sets arrays and more. [[spi]] == SPI diff --git a/documentation/src/main/asciidoc/public/reference/_binding-projection.adoc b/documentation/src/main/asciidoc/public/reference/_binding-projection.adoc index e22843a0f61..3d0b956163c 100644 --- a/documentation/src/main/asciidoc/public/reference/_binding-projection.adoc +++ b/documentation/src/main/asciidoc/public/reference/_binding-projection.adoc @@ -87,10 +87,13 @@ include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/binding/proje include::../components/_incubating-warning.adoc[] -You can call `.multi()` on the context passed to the projection binder -in order to discover whether the constructor parameter being bound is multi-valued -(according to the same rules as <>), -and to bind a multi-valued projection. +You can call `.containerElement()` on the context passed to the projection binder +in order to discover whether the constructor parameter being bound is some sort of container wrapping the value/values +(according to the same rules as <>). +If the returned value is a non-empty optional, then the `.constructorParameter()` will provide access to the container type in use. +Additionally, the same context has access to a factory (`.projectionCollectorProviderFactory()`) +from which a projection collector can be obtained based on a container and element types +(if the container type is `null` then the factory will return the `nullable()` single-valued collector). .Implementing and using a `ProjectionBinder` supporting multi-valued projections ==== @@ -98,15 +101,18 @@ and to bind a multi-valued projection. ---- include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java[tags=include] ---- -<1> `multi()` returns an optional that contains a context -if and only if the constructor parameter is considered multi-valued. -<2> Call `multi.definition(...)` to define the projection to use. -<3> Here we're failing for single-valued constructor parameters, -but we could theoreticall fall back to a single-valued projection. -<4> The projection definition, being multi-valued, -must implement `ProjectionDefinition>`, -where `T` is the exepected type of projected values, +<1> `containerElement()` returns an optional that contains a type of the container elements +if and only if the constructor parameter is considered as such that is wrapped in a container, e.g. a multivalued collection. +<2> Call `context.definition(...)` to define the projection to use. +<3> Here we're failing for single-valued (nullable) constructor parameters, +but we could theoretically fall back to a single-valued projection using the `ProjectionCollector.nullable()`. +<4> The projection definition, being multivalued, +must implement `ProjectionDefinition>`, +where `T` is the expected type of projected values, +`SomeCollection` is one of the supported collection types available in `ProjectionCollector`, and must configure returned projections accordingly. +If the required collection type is not present in the `ProjectionCollector`, +then a custom projection collector provider can be supplied. [source, JAVA, indent=0, subs="+callouts"] ---- @@ -138,7 +144,8 @@ or by delegating to it in a custom projection definition. Other methods exposed on the binding context work similarly: -* `.createObjectDefinitionMulti(...)` returns a <> object projection definition. +* `.createObjectDefinition(..., ProjectionCollector.Provider)` returns an object projection definition with the provided collector applied +and can be used for <> object projections or object projections wrapped in some other containers. * `.createCompositeDefinition(...)` returns a (single-valued) <> definition (which, on contrary to an <>, is not bound to an object field in the index). diff --git a/documentation/src/main/asciidoc/public/reference/_mapping-projection.adoc b/documentation/src/main/asciidoc/public/reference/_mapping-projection.adoc index c246f389711..9eb26dfd6a1 100644 --- a/documentation/src/main/asciidoc/public/reference/_mapping-projection.adoc +++ b/documentation/src/main/asciidoc/public/reference/_mapping-projection.adoc @@ -39,7 +39,7 @@ See <> for more information on how construct + Alternatively, the field projection can be configured explicitly with <>. <4> To project on an object field, add a constructor parameter named after that field and with its own custom projection type. -Multivalued projections <`>> or supertype. +Multivalued projections <> or their supertype. + Alternatively, the object projection can be configured explicitly with <>. <5> Annotate any custom projection type used for object fields with `@ProjectionConstructor` as well. @@ -106,16 +106,23 @@ for the target field, which in general is the type of the property annotated wit (generally mapped using <>), set the parameter type to another custom type annotated with `@ProjectionConstructor`, whose constructor will define which fields to extract from that object field. -* For a multivalued projection, follow the rules above then wrap the type with `Iterable`, `Collection` or `List`, -e.g. `Iterable`, `Collection` or `List`. +* For projections where values are wrapped in a container, be it a multivalued projection represented by some collection or array, +or a single-valued projection wrapped in an optional, +follow the rules above for the elements inside the container and then wrap the type with one of the containers +available in `ProjectionCollector` (`Iterable`, `Collection`, `List`, etc.), +e.g. `Iterable`, `Collection`, `List`, etc. [IMPORTANT] ==== Constructor parameters meant to represent a multivalued projection -can **only** have the type `Iterable<...>`, `Collection<...>` or `List<...>`. +**must** have the type of one of the supported multivalued containers. +==== -Other container types such as `Map` or `Optional` are not supported -https://hibernate.atlassian.net/browse/HSEARCH-4577[at the moment]. +[NOTE] +==== +In case the `ProjectionCollector` does not provide a suitable collector for a container/collection +needed for a constructor parameter mapping, <> can be implemented +and a user-implemented projection collector applied. ==== [[mapping-projection-inner-inference-fieldpath]] diff --git a/documentation/src/main/asciidoc/public/reference/_search-dsl-projection.adoc b/documentation/src/main/asciidoc/public/reference/_search-dsl-projection.adoc index a0a0c2a808e..af45e8c5870 100644 --- a/documentation/src/main/asciidoc/public/reference/_search-dsl-projection.adoc +++ b/documentation/src/main/asciidoc/public/reference/_search-dsl-projection.adoc @@ -72,7 +72,7 @@ See <> for more information on how construct + Alternatively, the field projection can be configured explicitly with <>. <4> To project on an object field, add a constructor parameter named after that field and with its own custom projection type. -Multivalued projections <`>> or supertype. +Multivalued projections <> or their supertype. + Alternatively, the object projection can be configured explicitly with <>. <5> Annotate any custom projection type used for object fields with `@ProjectionConstructor` as well. @@ -403,8 +403,8 @@ include::{sourcedir}/org/hibernate/search/documentation/search/projection/Projec [[search-dsl-projection-field-multivalued]] === Multivalued fields -To return multiple values, and thus allow projection on multivalued fields, use `.multi()`. -This will change the return type of the projection to `List` where `T` is what the single-valued projection +To return multiple values, and thus allow projection on multivalued fields, use `.collector(..)`. +This will change the return type of the projection to `SomeContainer` where `T` is what the single-valued projection would have returned. .Returning field values from matched documents, for multivalued fields @@ -610,8 +610,8 @@ include::{sourcedir}/org/hibernate/search/documentation/search/projection/Projec [[search-dsl-projection-distance-multivalued]] === Multivalued fields -To return multiple values, and thus allow projection on multivalued fields, use `.multi()`. -This will change the return type of the projection to `List`. +To return multiple values, and thus allow projection on multivalued fields, use `.collector(..)`. +This will change the return type of the projection to `SomeContainer`. .Returning the distance to a point, for multivalued fields ==== @@ -834,7 +834,7 @@ See <> for more information on how construct + Alternatively, the field projection can be configured explicitly with <>. <4> To project on an object field, add a constructor parameter named after that field and with its own custom projection type. -Multivalued projections <`>> or supertype. +Multivalued projections <> or their supertype. + Alternatively, the object projection can be configured explicitly with <>. <5> Annotate any custom projection type used for object fields with `@ProjectionConstructor` as well. diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java index be6328e06e1..c11f9ebfe24 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java @@ -7,24 +7,25 @@ import java.util.List; import java.util.Optional; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; -import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; //tag::include[] public class MyFieldProjectionBinder implements ProjectionBinder { @Override public void bind(ProjectionBindingContext context) { - Optional multi = context.multi(); // <1> - if ( multi.isPresent() ) { - multi.get().definition( String.class, new MyProjectionDefinition() ); // <2> + Optional> containerElement = context.containerElement(); // <1> + if ( containerElement.isPresent() ) { + context.definition( String.class, new MyProjectionDefinition() ); // <2> } else { - throw new RuntimeException( "This binder only supports multi-valued constructor parameters" ); // <3> + throw new RuntimeException( "This binder only supports container-wrapped constructor parameters" ); // <3> } } @@ -34,7 +35,7 @@ private static class MyProjectionDefinition public SearchProjection> create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { return factory.field( "tags", String.class ) - .multi() // <4> + .collector( ProjectionCollector.list() ) // <4> .toProjection(); } } diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/projection/ProjectionConstructorMappingJava17IT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/projection/ProjectionConstructorMappingJava17IT.java index 0bbc40364c3..5488dc5a9cd 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/projection/ProjectionConstructorMappingJava17IT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/projection/ProjectionConstructorMappingJava17IT.java @@ -16,6 +16,7 @@ import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper; import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.orm.Search; import org.hibernate.search.mapper.orm.session.SearchSession; import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep; @@ -144,7 +145,7 @@ void simple(DocumentationSetupHelper.SetupVariant variant) { f.field( "authors.lastName", String.class ) ) .as( MyBookProjection.Author::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( MyBookProjection::new ) ) .where( f -> f.matchAll() ) diff --git a/documentation/src/test/java/org/hibernate/search/documentation/search/projection/MyBookTitleAndAuthorNamesInSetProjection.java b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/MyBookTitleAndAuthorNamesInSetProjection.java new file mode 100644 index 00000000000..9b5d802d820 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/MyBookTitleAndAuthorNamesInSetProjection.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.documentation.search.projection; + +import java.util.Set; + +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FieldProjection; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor; + +// @formatter:off +//tag::include[] +@ProjectionConstructor // <1> +public record MyBookTitleAndAuthorNamesInSetProjection( + @FieldProjection // <2> + String title, // <3> + @FieldProjection(path = "authors.lastName") // <4> + Set authorLastNames // <5> +) { +} +//end::include[] +// @formatter:on diff --git a/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslIT.java b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslIT.java index 51f29d3c9df..2801f65fc0f 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslIT.java @@ -27,6 +27,7 @@ import org.hibernate.search.engine.backend.common.DocumentReference; import org.hibernate.search.engine.common.EntityReference; import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.query.SearchResult; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; @@ -240,7 +241,7 @@ void field() { withinSearchSession( searchSession -> { // tag::field-multiValued[] List> hits = searchSession.search( Book.class ) - .select( f -> f.field( "authors.lastName", String.class ).multi() ) + .select( f -> f.field( "authors.lastName", String.class ).collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .fetchHits( 20 ); // end::field-multiValued[] @@ -333,7 +334,7 @@ void distance() { // tag::distance-multiValued[] GeoPoint center = GeoPoint.of( 47.506060, 2.473916 ); SearchResult> result = searchSession.search( Book.class ) - .select( f -> f.distance( "authors.placeOfBirth", center ).multi() ) + .select( f -> f.distance( "authors.placeOfBirth", center ).collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .fetch( 20 ); // end::distance-multiValued[] @@ -580,7 +581,7 @@ void object() { .from( f.field( "authors.firstName", String.class ), // <2> f.field( "authors.lastName", String.class ) ) // <3> .as( MyAuthorName::new ) // <4> - .multi() ) // <5> + .collector( ProjectionCollector.list() ) ) // <5> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <6> // end::object-customObject[] @@ -630,7 +631,7 @@ void object() { new MyAuthorNameAndBirthDateAndPlaceOfBirthDistance( (String) list.get( 0 ), (String) list.get( 1 ), (LocalDate) list.get( 2 ), (Double) list.get( 3 ) ) ) - .multi() ) // <7> + .collector( ProjectionCollector.list() ) ) // <7> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <8> // end::object-customObject-asList[] @@ -692,7 +693,7 @@ void object() { new MyAuthorNameAndBirthDateAndPlaceOfBirthDistance( (String) array[0], (String) array[1], (LocalDate) array[2], (Double) array[3] ) ) - .multi() ) // <7> + .collector( ProjectionCollector.list() ) ) // <7> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <8> // end::object-customObject-asArray[] @@ -745,7 +746,7 @@ void object() { .from( f.field( "authors.firstName", String.class ), // <2> f.field( "authors.lastName", String.class ) ) // <3> .asList() // <4> - .multi() ) // <5> + .collector( ProjectionCollector.list() ) ) // <5> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <6> // end::object-list[] @@ -785,7 +786,7 @@ void object() { .from( f.field( "authors.firstName", String.class ), // <2> f.field( "authors.lastName", String.class ) ) // <3> .asArray() // <4> - .multi() ) // <5> + .collector( ProjectionCollector.list() ) ) // <5> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <6> // end::object-array[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslJava17IT.java b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslJava17IT.java index 150e41ff981..634b18115b5 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslJava17IT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslJava17IT.java @@ -20,6 +20,7 @@ import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper; import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.mapper.orm.Search; import org.hibernate.search.mapper.orm.session.SearchSession; @@ -62,8 +63,7 @@ public static List params() { // This wouldn't be needed in a typical application. CollectionHelper.asSet( MyBookProjection.class, MyBookProjection.Author.class, MyAuthorProjection.class, MyBookIdAndTitleProjection.class, MyBookTitleAndAuthorNamesProjection.class, - MyBookTitleAndAuthorsProjection.class, - MyBookIdAndTitleProjection.class, MyBookTitleAndAuthorNamesProjection.class, + MyBookTitleAndAuthorsProjection.class, MyBookTitleAndAuthorNamesInSetProjection.class, MyBookScoreAndTitleProjection.class, MyBookDocRefAndTitleProjection.class, MyBookEntityAndTitleProjection.class, @@ -136,6 +136,15 @@ public static List params() { .projection( FieldProjectionBinder.create( "authors.lastName" ) ); //end::programmatic-field-projection[] + TypeMappingStep myBookTitleAndAuthorNamesInSetProjectionMapping = + mapping.type( MyBookTitleAndAuthorNamesInSetProjection.class ); + myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor() + .projectionConstructor(); + myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor().parameter( 0 ) + .projection( FieldProjectionBinder.create() ); + myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor().parameter( 1 ) + .projection( FieldProjectionBinder.create( "authors.lastName" ) ); + //tag::programmatic-score-projection[] TypeMappingStep myBookScoreAndTitleProjection = mapping.type( MyBookScoreAndTitleProjection.class ); @@ -299,7 +308,7 @@ void object_mapped_record(DocumentationSetupHelper.SetupVariant variant) { List> hits = searchSession.search( Book.class ) .select( f -> f.object( "authors" ) // <1> .as( MyAuthorProjection.class ) // <2> - .multi() ) // <3> + .collector( ProjectionCollector.list() ) ) // <3> .where( f -> f.matchAll() ) .fetchHits( 20 ); // <4> // end::object-mapped-record[] @@ -364,6 +373,32 @@ void projectionConstructor_field(DocumentationSetupHelper.SetupVariant variant) } ); } + @ParameterizedTest(name = "{0}") + @MethodSource("params") + void projectionConstructor_field_set(DocumentationSetupHelper.SetupVariant variant) { + init( variant ); + with( entityManagerFactory ).runInTransaction( entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + // tag::projection-constructor-field-set[] + List hits = searchSession.search( Book.class ) + .select( MyBookTitleAndAuthorNamesInSetProjection.class )// <1> + .where( f -> f.matchAll() ) + .fetchHits( 20 ); // <2> + // end::projection-constructor-field-set[] + assertThat( hits ).containsExactlyInAnyOrderElementsOf( + entityManager.createQuery( "select b from Book b", Book.class ).getResultList().stream() + .map( book -> new MyBookTitleAndAuthorNamesInSetProjection( + book.getTitle(), + book.getAuthors().stream() + .map( Author::getLastName ) + .collect( Collectors.toSet() ) + ) ) + .collect( Collectors.toList() ) + ); + } ); + } + @ParameterizedTest(name = "{0}") @MethodSource("params") void projectionConstructor_score(DocumentationSetupHelper.SetupVariant variant) { diff --git a/engine/src/main/java/org/hibernate/search/engine/backend/types/converter/spi/ProjectionConverter.java b/engine/src/main/java/org/hibernate/search/engine/backend/types/converter/spi/ProjectionConverter.java index 6952c3ecec2..6d251f84014 100644 --- a/engine/src/main/java/org/hibernate/search/engine/backend/types/converter/spi/ProjectionConverter.java +++ b/engine/src/main/java/org/hibernate/search/engine/backend/types/converter/spi/ProjectionConverter.java @@ -88,4 +88,11 @@ public boolean isCompatibleWith(ProjectionConverter other) { return delegate.isCompatibleWith( other.delegate ); } + + /** + * @return The document value converter that is backing up this converter. + */ + public FromDocumentValueConverter delegate() { + return delegate; + } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollector.java new file mode 100644 index 00000000000..60b091cf715 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollector.java @@ -0,0 +1,253 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.function.Function; + +import org.hibernate.search.engine.backend.types.converter.FromDocumentValueConverter; +import org.hibernate.search.engine.backend.types.converter.runtime.FromDocumentValueConvertContext; +import org.hibernate.search.engine.search.projection.spi.BuiltInProjectionCollectors; +import org.hibernate.search.util.common.annotation.Incubating; + +/** + * A variation on {@link java.util.stream.Collector} suitable for projections on field values. + *

+ * Compared to {@link java.util.stream.Collector}: + *

+ * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + * @param The type of the temporary storage for accumulated values, + * before and after being transformed. + * @param The type of the final result containing values of type {@code V}. + */ +@Incubating +public interface ProjectionCollector { + + /** + * @return The projection collector capable of accumulating single-valued projections in an as-is form, + * i.e. the value is returned without any extra transformations. + * @param The type of values to accumulate. + */ + static ProjectionCollector.Provider nullable() { + return BuiltInProjectionCollectors.nullable(); + } + + /** + * @return The projection collector capable of accumulating single-valued projections and wrapping the values in an {@link Optional}. + * @param The type of values to accumulate. + */ + static Provider> optional() { + return BuiltInProjectionCollectors.optional(); + } + + /** + * @param converter The function that defines how to convert a list of collected values to the final collection. + * @return An collector based on a list as a temporary storage. + * @param The type of values to accumulate. + * @param The type of the resulting collection. + */ + static Provider simple(Function, C> converter) { + return BuiltInProjectionCollectors.simple( converter ); + } + + /** + * @return The projection collector capable of accumulating multivalued projections as a {@link List}. + * @param The type of values to accumulate. + */ + static Provider> list() { + return BuiltInProjectionCollectors.list(); + } + + /** + * @return The projection collector capable of accumulating multivalued projections as a {@link Set}. + * @param The type of values to accumulate. + */ + static Provider> set() { + return BuiltInProjectionCollectors.set(); + } + + /** + * @return The projection collector capable of accumulating multivalued projections as a {@link SortedSet}. + * @param The type of values to accumulate. + */ + static Provider> sortedSet() { + return BuiltInProjectionCollectors.sortedSet(); + } + + /** + * @return The projection collector capable of accumulating multivalued projections as a {@link SortedSet} + * using a custom comparator. + * @param comparator The comparator which should be used by the sorted set. + * @param The type of values to accumulate. + */ + static Provider> sortedSet(Comparator comparator) { + return BuiltInProjectionCollectors.sortedSet( comparator ); + } + + /** + * @return The projection collector capable of accumulating multivalued projections as an array. + * @param componentType The type of the array elements. + * @param The type of values to accumulate. + */ + static Provider array(Class componentType) { + return BuiltInProjectionCollectors.array( componentType ); + } + + /** + * Creates the initial accumulated container. + *

+ * This operation should be non-blocking. + * + * @return The initial accumulated container, + * to pass to the first call to {@link #accumulate(Object, Object)}. + */ + A createInitial(); + + /** + * Folds a new value in the given accumulated container. + *

+ * This operation should be non-blocking. + * + * @param accumulated The accumulated value so far. + * For the first call, this is a value returned by {@link #createInitial()}. + * For the next calls, this is the value returned by the previous call to {@link #accumulate(Object, Object)}. + * @param value The value to accumulate. + * @return The new accumulated value. + */ + A accumulate(A accumulated, E value); + + /** + * Folds a collection of new values in the given accumulated container. + *

+ * This operation should be non-blocking. + * + * @param accumulated The accumulated value so far. + * For the first call, this is a value returned by {@link #createInitial()}. + * For the next calls, this is the value returned by the previous call to {@link #accumulate(Object, Object)}. + * @param values The values to accumulate. + * @return The new accumulated value. + */ + default A accumulateAll(A accumulated, Collection values) { + for ( E value : values ) { + accumulated = accumulate( accumulated, value ); + } + return accumulated; + } + + /** + * @param accumulated The accumulated value so far, + * returned by the last call to {@link #accumulate(Object, Object)}. + * @return The number of elements in the accumulated value. + */ + int size(A accumulated); + + /** + * Retrieves the value at the given index. + *

+ * This operation should be non-blocking. + * + * @param accumulated The accumulated value so far, + * returned by the last call to {@link #accumulate(Object, Object)}. + * @param index The index of the value to retrieve. + * @return The value at the given index. + */ + E get(A accumulated, int index); + + /** + * Transforms the value at the given index, + * replacing it with the given transformed value. + *

+ * This operation should be non-blocking. + * + * @param accumulated The accumulated value so far, + * returned by the last call to {@link #accumulate(Object, Object)}. + * @param index The index of the value being transformed. + * @param transformed The transformed value. + * @return The new accumulated value. + */ + A transform(A accumulated, int index, V transformed); + + /** + * Transforms all values with the given converter and the given context. + *

+ * This operation may be blocking. + * + * @param accumulated The accumulated value so far, + * returned by the last call to {@link #accumulate(Object, Object)}. + * @param converter The projection converter (from {@code F} to {@code V}). + * @param context The context to be passed to the projection converter. + * @return The new accumulated value. + */ + default A transformAll(A accumulated, FromDocumentValueConverter converter, + FromDocumentValueConvertContext context) { + for ( int i = 0; i < size( accumulated ); i++ ) { + E initial = get( accumulated, i ); + V transformed = converter.fromDocumentValue( initial, context ); + accumulated = transform( accumulated, i, transformed ); + } + return accumulated; + } + + /** + * Finishes the accumulation, converting the accumulated container into the final result. + *

+ * This operation may be blocking. + * + * @param accumulated The temporary storage created by {@link #createInitial()}, + * then populated by successive calls to {@link #accumulate(Object, Object)}, + * then transformed by a single call to {@link #transformAll(Object, FromDocumentValueConverter, FromDocumentValueConvertContext)} + * or by successive calls to {@link #transform(Object, int, Object)}. + * @return The final result of the accumulation. + */ + R finish(A accumulated); + + /** + * @return An "empty" final value, i.e. when a {@link #finish(Object) final transformation} + * is applied to {@link #createInitial() the initial value}. + */ + default R empty() { + return finish( createInitial() ); + } + + /** + * Provides a collector for a given type of values to accumulate ({@code T}). + *

+ * The provider may always return the same collector, + * if generics are irrelevant and it's safe to do so. + * + * @param The type of values to accumulate after being transformed. + * @param The type of the final result containing values of type {@code V}. + */ + interface Provider { + /** + * @param The type of values to accumulate before being transformed. + * @return A collector for the given type. + */ + ProjectionCollector get(); + + /** + * @return {@code true} if collectors returned by {@link #get()} can only accept a single value, + * and will fail beyond that. + */ + boolean isSingleValued(); + } + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollectorProviderFactory.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollectorProviderFactory.java new file mode 100644 index 00000000000..821d8021342 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/ProjectionCollectorProviderFactory.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection; + +import org.hibernate.search.util.common.annotation.Incubating; + +/** + * Defines the factory that can create {@link ProjectionCollector.Provider projection collector providers} based + * on a container type {@code R} and container element type {@code U}. + */ +@Incubating +public interface ProjectionCollectorProviderFactory { + /** + * + * @param containerType The type of the expected container. + * Passing a {@code null} value as a container type will result in {@link ProjectionCollector#nullable() a nullable collector} + * being returned, i.e. a collector that does not wrap the value in any sort of container. + * @param containerElementType The type of the container elements + * @return The projection collector provider for a requested container/element types. + * @param The type of values to collector after being transformed. + * @param The type of the final result containing values of type {@code V}. + */ + ProjectionCollector.Provider projectionCollectorProvider(Class containerType, + Class containerElementType); +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java index f03fc888486..a931329c1a4 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java @@ -6,8 +6,12 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; import org.hibernate.search.engine.environment.bean.BeanHolder; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; @@ -22,17 +26,53 @@ public final class ConstantProjectionDefinition extends AbstractProjectionDef @SuppressWarnings("rawtypes") private static final BeanHolder EMPTY_LIST_INSTANCE = BeanHolder.of( new ConstantProjectionDefinition( Collections.emptyList() ) ); + @SuppressWarnings("rawtypes") + private static final BeanHolder EMPTY_SET_INSTANCE = + BeanHolder.of( new ConstantProjectionDefinition( Collections.emptySet() ) ); + @SuppressWarnings("rawtypes") + private static final BeanHolder EMPTY_SORTED_SET_INSTANCE = + BeanHolder.of( new ConstantProjectionDefinition( Collections.emptySortedSet() ) ); + @SuppressWarnings("rawtypes") + private static final BeanHolder OPTIONAL_EMPTY_INSTANCE = + BeanHolder.of( new ConstantProjectionDefinition( Optional.empty() ) ); @SuppressWarnings("unchecked") // NULL_VALUE_INSTANCE works for any T public static BeanHolder> nullValue() { return (BeanHolder>) NULL_VALUE_INSTANCE; } + /** + * @deprecated Use {@link #empty(ProjectionCollector.Provider)} instead. + */ + @Deprecated(since = "8.0") @SuppressWarnings("unchecked") // EMPTY_LIST_INSTANCE works for any T public static BeanHolder>> emptyList() { return (BeanHolder>>) EMPTY_LIST_INSTANCE; } + @SuppressWarnings("unchecked") // empty collections works for any T + public static BeanHolder> empty(ProjectionCollector.Provider collector) { + T empty = collector.get().empty(); + + if ( ProjectionCollector.nullable().equals( collector ) ) { + return nullValue(); + } + if ( ProjectionCollector.optional().equals( collector ) ) { + return (BeanHolder>) OPTIONAL_EMPTY_INSTANCE; + } + if ( ProjectionCollector.list().equals( collector ) ) { + return (BeanHolder>) EMPTY_LIST_INSTANCE; + } + if ( ProjectionCollector.set().equals( collector ) ) { + return (BeanHolder>) EMPTY_SET_INSTANCE; + } + if ( empty instanceof SortedSet ) { + return (BeanHolder>) EMPTY_SORTED_SET_INSTANCE; + } + + return BeanHolder.of( new ConstantProjectionDefinition<>( empty ) ); + } + private final T value; private ConstantProjectionDefinition(T value) { diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java index 10438863b3e..e1d2b39442b 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java @@ -6,6 +6,7 @@ import java.util.List; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; @@ -42,6 +43,7 @@ public void appendTo(ToStringTreeAppender appender) { protected abstract boolean multi(); + @Deprecated(since = "8.0") @Incubating public static final class SingleValued extends DistanceProjectionDefinition { public SingleValued(String fieldPath, String parameterName, DistanceUnit unit) { @@ -62,8 +64,10 @@ public SearchProjection create(SearchProjectionFactory factory, Pr } } + @Deprecated(since = "8.0") @Incubating public static final class MultiValued extends DistanceProjectionDefinition> { + public MultiValued(String fieldPath, String parameterName, DistanceUnit unit) { super( fieldPath, parameterName, unit ); } @@ -78,7 +82,33 @@ public SearchProjection> create(SearchProjectionFactory facto ProjectionDefinitionContext context) { return factory.withParameters( params -> factory .distance( fieldPath, params.get( parameterName, GeoPoint.class ) ) - .multi() + .collector( ProjectionCollector.list() ) + .unit( unit ) + ).toProjection(); + } + } + + @Incubating + public static final class WrappedValued extends DistanceProjectionDefinition { + private final ProjectionCollector.Provider collector; + + public WrappedValued(String fieldPath, String parameterName, DistanceUnit unit, + ProjectionCollector.Provider collector) { + super( fieldPath, parameterName, unit ); + this.collector = collector; + } + + @Override + protected boolean multi() { + return !collector.isSingleValued(); + } + + @Override + public SearchProjection create(SearchProjectionFactory factory, + ProjectionDefinitionContext context) { + return factory.withParameters( params -> factory + .distance( fieldPath, params.get( parameterName, GeoPoint.class ) ) + .collector( collector ) .unit( unit ) ).toProjection(); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/FieldProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/FieldProjectionDefinition.java index 1f704bbac5c..b19befe9fc6 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/FieldProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/FieldProjectionDefinition.java @@ -7,6 +7,7 @@ import java.util.List; import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; @@ -42,6 +43,7 @@ public void appendTo(ToStringTreeAppender appender) { protected abstract boolean multi(); + @Deprecated(since = "8.0") @Incubating public static final class SingleValued extends FieldProjectionDefinition { public SingleValued(String fieldPath, Class fieldType, ValueModel valueModel) { @@ -60,8 +62,10 @@ public SearchProjection create(SearchProjectionFactory factory, } } + @Deprecated(since = "8.0") @Incubating public static final class MultiValued extends FieldProjectionDefinition, F> { + public MultiValued(String fieldPath, Class fieldType, ValueModel valueModel) { super( fieldPath, fieldType, valueModel ); } @@ -72,9 +76,32 @@ protected boolean multi() { } @Override - public SearchProjection> create(SearchProjectionFactory factory, + public SearchProjection> create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { + return factory.field( fieldPath, fieldType, valueModel ) + .collector( ProjectionCollector.list() ).toProjection(); + } + } + + @Incubating + public static final class AccumulatedValued extends FieldProjectionDefinition { + private final ProjectionCollector.Provider collector; + + public AccumulatedValued(String fieldPath, Class fieldType, ProjectionCollector.Provider collector, + ValueModel valueModel) { + super( fieldPath, fieldType, valueModel ); + this.collector = collector; + } + + @Override + protected boolean multi() { + return !collector.isSingleValued(); + } + + @Override + public SearchProjection create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { - return factory.field( fieldPath, fieldType, valueModel ).multi().toProjection(); + return factory.field( fieldPath, fieldType, valueModel ) + .collector( collector ).toProjection(); } } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ObjectProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ObjectProjectionDefinition.java index c98e858a37f..05c57bf3de4 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ObjectProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ObjectProjectionDefinition.java @@ -6,6 +6,7 @@ import java.util.List; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; @@ -45,6 +46,7 @@ public void close() throws Exception { delegate.close(); } + @Deprecated(since = "8.0") @Incubating public static final class SingleValued extends ObjectProjectionDefinition { public SingleValued(String fieldPath, CompositeProjectionDefinition delegate) { @@ -64,8 +66,10 @@ public SearchProjection create(SearchProjectionFactory factory, } } + @Deprecated(since = "8.0") @Incubating public static final class MultiValued extends ObjectProjectionDefinition, T> { + public MultiValued(String fieldPath, CompositeProjectionDefinition delegate) { super( fieldPath, delegate ); } @@ -79,7 +83,30 @@ protected boolean multi() { public SearchProjection> create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { return delegate.apply( factory.withRoot( fieldPath ), factory.object( fieldPath ), context ) - .multi().toProjection(); + .collector( ProjectionCollector.list() ).toProjection(); + } + } + + @Incubating + public static final class WrappedValued extends ObjectProjectionDefinition { + private final ProjectionCollector.Provider collector; + + public WrappedValued(String fieldPath, CompositeProjectionDefinition delegate, + ProjectionCollector.Provider collector) { + super( fieldPath, delegate ); + this.collector = collector; + } + + @Override + protected boolean multi() { + return !collector.isSingleValued(); + } + + @Override + public SearchProjection create(SearchProjectionFactory factory, + ProjectionDefinitionContext context) { + return delegate.apply( factory.withRoot( fieldPath ), factory.object( fieldPath ), context ) + .collector( collector ).toProjection(); } } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/CompositeProjectionValueStep.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/CompositeProjectionValueStep.java index c1f36407d21..8f5898246e6 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/CompositeProjectionValueStep.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/CompositeProjectionValueStep.java @@ -4,16 +4,24 @@ */ package org.hibernate.search.engine.search.projection.dsl; +import java.util.Comparator; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.util.common.annotation.Incubating; /** * The step in a composite projection definition - * where the projection (optionally) can be marked as multi-valued (returning Lists), + * where (optionally) the projection collector can be provided, e.g. to mark a projection as multi-valued (returning {@code List}/{@code Set} etc.) + * or wrapped in some other container (e.g. {@code Optional<..>}), * and where optional parameters can be set. *

- * By default (if {@link #multi()} is not called), the projection is single-valued. + * By default (if {@link #collector(ProjectionCollector.Provider)} is not called), the projection is single-valued. * - * @param The next step if a method other than {@link #multi()} is called, + * @param The next step if a method other than {@link #collector(ProjectionCollector.Provider)} is called, * i.e. the return type of methods defined in {@link CompositeProjectionOptionsStep} * when called directly on this object. * @param The type of composed projections. @@ -32,7 +40,82 @@ public interface CompositeProjectionValueStep> multi() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines how to accumulate composite projection values. + *

+ * Calling {@code .collector(someMultiValuedCollectorProvider) } is mandatory for multi-valued fields, + * e.g. {@code .collector(ProjectionCollector.list())}, + * otherwise the projection will throw an exception upon creating the query. + * + * @param collector The collector provider to apply to this projection. + * @return A new step to define optional parameters for the accumulated projections. + * @param The type of the final result. + */ + @Incubating + CompositeProjectionOptionsStep collector(ProjectionCollector.Provider collector); + + /** + * Defines the projection as single-valued wrapped in an {@link Optional}, i.e. returning {@code Optional} instead of {@code T}. + * + * @return A new step to define optional parameters. + */ + @Incubating + default CompositeProjectionOptionsStep> optional() { + return collector( ProjectionCollector.optional() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code List} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default CompositeProjectionOptionsStep> list() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code Set} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default CompositeProjectionOptionsStep> set() { + return collector( ProjectionCollector.set() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default CompositeProjectionOptionsStep> sortedSet() { + return collector( ProjectionCollector.sortedSet() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @param comparator The comparator to use for sorting elements within the set. + * @return A new step to define optional parameters. + */ + @Incubating + default CompositeProjectionOptionsStep> sortedSet(Comparator comparator) { + return collector( ProjectionCollector.sortedSet( comparator ) ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code T[]} instead of {@code T}. + * @param type The type of array elements. + * @return A new step to define optional parameters. */ - CompositeProjectionOptionsStep> multi(); + @Incubating + default CompositeProjectionOptionsStep array(Class type) { + return collector( ProjectionCollector.array( type ) ); + } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/DistanceToFieldProjectionValueStep.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/DistanceToFieldProjectionValueStep.java index 2b889fa130d..34ba0f5adae 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/DistanceToFieldProjectionValueStep.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/DistanceToFieldProjectionValueStep.java @@ -4,17 +4,25 @@ */ package org.hibernate.search.engine.search.projection.dsl; +import java.util.Comparator; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.util.common.annotation.Incubating; /** * The initial step in a "distance to field" projection definition, - * where the projection (optionally) can be marked as multi-valued (returning Lists), + * where (optionally) the projection collector can be provided, e.g. to mark a projection as multi-valued (returning {@code List}/{@code Set} etc.) + * or wrapped in some other container (e.g. {@code Optional<..>}), * and where optional parameters can be set. *

- * By default (if {@link #multi()} is not called), the projection is considered single-valued, + * By default (if {@link #collector(ProjectionCollector.Provider)} is not called), the projection is considered single-valued, * and its creation will fail if the field is multi-valued. * - * @param The next step if a method other than {@link #multi()} is called, + * @param The next step if a method other than {@link #collector(ProjectionCollector.Provider)} is called, * i.e. the return type of methods defined in {@link FieldProjectionOptionsStep} * when called directly on this object. * @param The type of projected distances. @@ -29,7 +37,81 @@ public interface DistanceToFieldProjectionValueStep> multi() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines how to accumulate distance projection values. + *

+ * Calling {@code .collector(someMultiValuedCollectorProvider) } is mandatory for multi-valued fields, + * e.g. {@code .collector(ProjectionCollector.list())}, + * otherwise the projection will throw an exception upon creating the query. + * + * @param collector The collector provider to apply to this projection. + * @return A new step to define optional parameters for the accumulated projections. + * @param The type of the final result. */ - DistanceToFieldProjectionOptionsStep> multi(); + @Incubating + DistanceToFieldProjectionOptionsStep collector(ProjectionCollector.Provider collector); + /** + * Defines the projection as single-valued wrapped in an {@link Optional}, i.e. returning {@code Optional} instead of {@code T}. + * + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep> optional() { + return collector( ProjectionCollector.optional() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code List} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep> list() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code Set} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep> set() { + return collector( ProjectionCollector.set() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep> sortedSet() { + return collector( ProjectionCollector.sortedSet() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @param comparator The comparator to use for sorting elements within the set. + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep> sortedSet(Comparator comparator) { + return collector( ProjectionCollector.sortedSet( comparator ) ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code T[]} instead of {@code T}. + * @param type The type of array elements. + * @return A new step to define optional parameters. + */ + @Incubating + default DistanceToFieldProjectionOptionsStep array(Class type) { + return collector( ProjectionCollector.array( type ) ); + } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/FieldProjectionValueStep.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/FieldProjectionValueStep.java index d4251c695e7..3b84e67c205 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/FieldProjectionValueStep.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/FieldProjectionValueStep.java @@ -4,17 +4,25 @@ */ package org.hibernate.search.engine.search.projection.dsl; +import java.util.Comparator; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.util.common.annotation.Incubating; /** * The initial step in a "field" projection definition, - * where the projection (optionally) can be marked as multi-valued (returning Lists), + * where (optionally) the projection collector can be provided, e.g. to mark a projection as multi-valued (returning {@code List}/{@code Set} etc.) + * or wrapped in some other container (e.g. {@code Optional<..>}), * and where optional parameters can be set. *

- * By default (if {@link #multi()} is not called), the projection is considered single-valued, + * By default (if {@link #collector(ProjectionCollector.Provider)} is not called), the projection is considered single-valued, * and its creation will fail if the field is multi-valued. * - * @param The next step if a method other than {@link #multi()} is called, + * @param The next step if a method other than {@link #collector(ProjectionCollector.Provider)} is called, * i.e. the return type of methods defined in {@link FieldProjectionOptionsStep} * when called directly on this object. * @param The type of projected field values. @@ -29,7 +37,81 @@ public interface FieldProjectionValueStep> multi() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines how to accumulate field projection values. + *

+ * Calling {@code .collector(someMultiValuedCollectorProvider) } is mandatory for multivalued fields, + * e.g. {@code .collector(ProjectionCollector.list())}, + * otherwise the projection will throw an exception upon creating the query. + * + * @param collector The collector provider to apply to this projection. + * @return A new step to define optional parameters for the accumulated projections. + * @param The type of the final result. */ - FieldProjectionOptionsStep> multi(); + @Incubating + FieldProjectionOptionsStep collector(ProjectionCollector.Provider collector); + /** + * Defines the projection as single-valued wrapped in an {@link Optional}, i.e. returning {@code Optional} instead of {@code T}. + * + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep> optional() { + return collector( ProjectionCollector.optional() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code List} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep> list() { + return collector( ProjectionCollector.list() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code Set} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep> set() { + return collector( ProjectionCollector.set() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep> sortedSet() { + return collector( ProjectionCollector.sortedSet() ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code SortedSet} instead of {@code T}. + * @param comparator The comparator to use for sorting elements within the set. + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep> sortedSet(Comparator comparator) { + return collector( ProjectionCollector.sortedSet( comparator ) ); + } + + /** + * Defines the projection as multivalued, i.e. returning {@code T[]} instead of {@code T}. + * @param type The type of array elements. + * @return A new step to define optional parameters. + */ + @Incubating + default FieldProjectionOptionsStep array(Class type) { + return collector( ProjectionCollector.array( type ) ); + } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/HighlightProjectionOptionsStep.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/HighlightProjectionOptionsStep.java index d3f3fd97d2d..8e0a25f22a5 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/HighlightProjectionOptionsStep.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/HighlightProjectionOptionsStep.java @@ -4,9 +4,15 @@ */ package org.hibernate.search.engine.search.projection.dsl; +import java.util.Comparator; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; import java.util.function.Function; import org.hibernate.search.engine.search.highlighter.dsl.HighlighterOptionsStep; +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.util.common.annotation.Incubating; /** * The initial and final step in a highlight definition, where optional parameters can be set. @@ -35,7 +41,97 @@ public interface HighlightProjectionOptionsStep extends HighlightProjectionFinal * * @return A final step in the highlight projection definition. * @see HighlighterOptionsStep#numberOfFragments(int) + * @deprecated Use the {@link #collector(ProjectionCollector.Provider)} instead, e.g. {@code .collector(ProjectionCollector.single())} */ + @Deprecated(since = "8.0") SingleHighlightProjectionFinalStep single(); + /** + * Defines the collector to apply to the highlighted strings. + *

+ * By default, highlighting results in a list {@code List} of highlighted strings. + * This method allows changing the returned type to a different collection of strings, e.g. {@code Set}/{@code String[]} + * or obtaining a single-valued projection (i.e. {@code projectionCollectorProvider.isSingleValued () == true}). + *

+ * Note: single-valued projections can only be used when the highlighter + * that creates highlighted fragments for this projection is configured + * to return a single fragment at most, i.e. when {@link HighlighterOptionsStep#numberOfFragments(int) .numberOfFragments(1)} + * is applied to the highlighter. + * Otherwise, it will lead to an exception being thrown when the query is created. + * + * @param collector The collector provider to apply to this projection. + * @return A final step in the highlight projection definition. + * @param The type of the final result. + */ + @Incubating + ProjectionFinalStep collector(ProjectionCollector.Provider collector); + + /** + * Defines the projection as single-valued, i.e. returning {@code String} instead of {@code List}. + *

+ * Can only be used when the highlighter that creates highlighted fragments for this projection is configured + * to return a single fragment at most, i.e. when {@link HighlighterOptionsStep#numberOfFragments(int) .numberOfFragments(1)} + * is applied to the highlighter. + * Otherwise, it will lead to an exception being thrown when the query is created. + * + * @return A final step in the highlight projection definition. + * @see HighlighterOptionsStep#numberOfFragments(int) + */ + @Incubating + default ProjectionFinalStep nullable() { + return collector( ProjectionCollector.nullable() ); + } + + /** + * Defines the projection as single-valued wrapped in an {@link Optional}, i.e. returning {@code Optional} instead of {@code List}. + *

+ * Can only be used when the highlighter that creates highlighted fragments for this projection is configured + * to return a single fragment at most, i.e. when {@link HighlighterOptionsStep#numberOfFragments(int) .numberOfFragments(1)} + * is applied to the highlighter. + * Otherwise, it will lead to an exception being thrown when the query is created. + * + * @return A final step in the highlight projection definition. + * @see HighlighterOptionsStep#numberOfFragments(int) + */ + @Incubating + default ProjectionFinalStep> optional() { + return collector( ProjectionCollector.optional() ); + } + + /** + * Changes the collection accumulating the values to {@link Set} instead of {@link java.util.List}. + * @return A final step in the highlight projection definition. + */ + @Incubating + default ProjectionFinalStep> set() { + return collector( ProjectionCollector.set() ); + } + + /** + * Changes the collection accumulating the values to {@link SortedSet} instead of {@link java.util.List}. + * @return A final step in the highlight projection definition. + */ + @Incubating + default ProjectionFinalStep> sortedSet() { + return collector( ProjectionCollector.sortedSet() ); + } + + /** + * Changes the collection accumulating the values to {@link SortedSet} instead of {@link java.util.List}. + * @param comparator The comparator to use for sorting strings within the set. + * @return A final step in the highlight projection definition. + */ + @Incubating + default ProjectionFinalStep> sortedSet(Comparator comparator) { + return collector( ProjectionCollector.sortedSet( comparator ) ); + } + + /** + * Changes the collection accumulating the values to {@code String[]} instead of {@link java.util.List}. + * @return A final step in the highlight projection definition. + */ + @Incubating + default ProjectionFinalStep array() { + return collector( ProjectionCollector.array( String.class ) ); + } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/SearchProjectionFactory.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/SearchProjectionFactory.java index 91b6752c7f9..1fef0644846 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/SearchProjectionFactory.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/SearchProjectionFactory.java @@ -12,6 +12,7 @@ import org.hibernate.search.engine.common.EntityReference; import org.hibernate.search.engine.search.common.NamedValues; import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.util.common.SearchException; @@ -217,7 +218,7 @@ default FieldProjectionValueStep field(String fieldPath, * Compared to the basic {@link #composite() composite projection}, * an object projection is bound to a specific object field, * and thus it yields zero, one or many values, as many as there are objects in the targeted object field. - * Therefore, you must take care of calling {@link CompositeProjectionValueStep#multi()} + * Therefore, you must take care of calling {@link CompositeProjectionValueStep#collector(ProjectionCollector.Provider)} * if the object field is multi-valued. * * @param objectFieldPath The path to the object field whose object(s) will be extracted. @@ -232,7 +233,7 @@ default FieldProjectionValueStep field(String fieldPath, * On contrary to the {@link #object(String) object projection}, * a composite projection is not bound to a specific object field, * and thus it will always yield one and only one value, - * regardless of whether {@link CompositeProjectionValueStep#multi()} is called. + * regardless of whether {@link CompositeProjectionValueStep#collector(ProjectionCollector.Provider)} is called. * * @return A DSL step where the "composite" projection can be defined in more details. */ diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionOptionsStepImpl.java index 2694ffb7bc2..764bba1dcee 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionOptionsStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionOptionsStepImpl.java @@ -4,10 +4,10 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionOptionsStep; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; public class CompositeProjectionOptionsStepImpl @@ -16,19 +16,19 @@ public class CompositeProjectionOptionsStepImpl final CompositeProjectionBuilder builder; final SearchProjection[] inners; final ProjectionCompositor compositor; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; public CompositeProjectionOptionsStepImpl(CompositeProjectionBuilder builder, SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { this.builder = builder; this.inners = inners; this.compositor = compositor; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override public SearchProjection

toProjection() { - return builder.build( inners, compositor, accumulatorProvider ); + return builder.build( inners, compositor, collectorProvider ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionValueStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionValueStepImpl.java index 3cd4f9741a7..3be534ad2fb 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionValueStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/CompositeProjectionValueStepImpl.java @@ -4,13 +4,11 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; -import java.util.List; - +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionOptionsStep; import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionValueStep; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; public class CompositeProjectionValueStepImpl @@ -19,12 +17,11 @@ public class CompositeProjectionValueStepImpl public CompositeProjectionValueStepImpl(CompositeProjectionBuilder builder, SearchProjection[] inners, ProjectionCompositor compositor) { - super( builder, inners, compositor, ProjectionAccumulator.single() ); + super( builder, inners, compositor, ProjectionCollector.nullable() ); } @Override - public CompositeProjectionOptionsStep> multi() { - return new CompositeProjectionOptionsStepImpl<>( builder, inners, compositor, - ProjectionAccumulator.list() ); + public CompositeProjectionOptionsStep collector(ProjectionCollector.Provider collector) { + return new CompositeProjectionOptionsStepImpl<>( builder, inners, compositor, collector ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionOptionsStepImpl.java index e5a94c343c4..3cea9dd1e75 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionOptionsStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionOptionsStepImpl.java @@ -4,10 +4,10 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionOptionsStep; import org.hibernate.search.engine.search.projection.spi.DistanceToFieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.util.common.impl.Contracts; @@ -15,12 +15,12 @@ public class DistanceToFieldProjectionOptionsStepImpl

implements DistanceToFieldProjectionOptionsStep, P> { protected final DistanceToFieldProjectionBuilder distanceFieldProjectionBuilder; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; DistanceToFieldProjectionOptionsStepImpl(DistanceToFieldProjectionBuilder distanceFieldProjectionBuilder, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { this.distanceFieldProjectionBuilder = distanceFieldProjectionBuilder; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override @@ -33,7 +33,7 @@ public DistanceToFieldProjectionOptionsStepImpl

unit(DistanceUnit unit) { @Override public SearchProjection

toProjection() { - return distanceFieldProjectionBuilder.build( accumulatorProvider ); + return distanceFieldProjectionBuilder.build( collectorProvider ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionValueStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionValueStepImpl.java index 9c739097e24..26e1a5e4d11 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionValueStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/DistanceToFieldProjectionValueStepImpl.java @@ -4,12 +4,10 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; -import java.util.List; - +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionOptionsStep; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionValueStep; import org.hibernate.search.engine.search.projection.dsl.spi.SearchProjectionDslContext; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionTypeKeys; import org.hibernate.search.engine.spatial.GeoPoint; @@ -20,14 +18,15 @@ public final class DistanceToFieldProjectionValueStepImpl public DistanceToFieldProjectionValueStepImpl(SearchProjectionDslContext dslContext, String fieldPath, GeoPoint center) { super( dslContext.scope().fieldQueryElement( fieldPath, ProjectionTypeKeys.DISTANCE ), - ProjectionAccumulator.single() ); + ProjectionCollector.nullable() ); distanceFieldProjectionBuilder.center( center ); } @Override - public DistanceToFieldProjectionOptionsStep> multi() { + public DistanceToFieldProjectionOptionsStep collector(ProjectionCollector.Provider collector) { return new DistanceToFieldProjectionOptionsStepImpl<>( distanceFieldProjectionBuilder, - ProjectionAccumulator.list() ); + collector + ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionOptionsStepImpl.java index 0c56aeb6483..95b020b8dd3 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionOptionsStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionOptionsStepImpl.java @@ -4,26 +4,26 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.FieldProjectionOptionsStep; import org.hibernate.search.engine.search.projection.spi.FieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; public class FieldProjectionOptionsStepImpl implements FieldProjectionOptionsStep, P> { protected final FieldProjectionBuilder fieldProjectionBuilder; - private final ProjectionAccumulator.Provider accumulatorProvider; + private final ProjectionCollector.Provider collectorProvider; FieldProjectionOptionsStepImpl(FieldProjectionBuilder fieldProjectionBuilder, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { this.fieldProjectionBuilder = fieldProjectionBuilder; - this.accumulatorProvider = accumulatorProvider; + this.collectorProvider = collectorProvider; } @Override public SearchProjection

toProjection() { - return fieldProjectionBuilder.build( accumulatorProvider ); + return fieldProjectionBuilder.build( collectorProvider ); } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionValueStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionValueStepImpl.java index 0732dc56bda..24605d0d583 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionValueStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/FieldProjectionValueStepImpl.java @@ -4,14 +4,12 @@ */ package org.hibernate.search.engine.search.projection.dsl.impl; -import java.util.List; - import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.FieldProjectionOptionsStep; import org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep; import org.hibernate.search.engine.search.projection.dsl.spi.SearchProjectionDslContext; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionTypeKeys; public final class FieldProjectionValueStepImpl @@ -22,12 +20,12 @@ public FieldProjectionValueStepImpl(SearchProjectionDslContext dslContext, St Class clazz, ValueModel valueModel) { super( dslContext.scope().fieldQueryElement( fieldPath, ProjectionTypeKeys.FIELD ) .type( clazz, valueModel ), - ProjectionAccumulator.single() ); + ProjectionCollector.nullable() ); } @Override - public FieldProjectionOptionsStep> multi() { - return new FieldProjectionOptionsStepImpl<>( fieldProjectionBuilder, ProjectionAccumulator.list() ); + public FieldProjectionOptionsStep collector(ProjectionCollector.Provider collector) { + return new FieldProjectionOptionsStepImpl<>( fieldProjectionBuilder, collector ); } @Override diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/HighlightProjectionOptionsStepImpl.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/HighlightProjectionOptionsStepImpl.java index 6e01893b1f8..0122d728a1e 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/HighlightProjectionOptionsStepImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/HighlightProjectionOptionsStepImpl.java @@ -6,13 +6,14 @@ import java.util.List; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.HighlightProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.HighlightProjectionOptionsStep; +import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SingleHighlightProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.spi.HighlightProjectionBuilder; import org.hibernate.search.engine.search.projection.dsl.spi.SearchProjectionDslContext; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionTypeKeys; public class HighlightProjectionOptionsStepImpl @@ -31,21 +32,40 @@ public HighlightProjectionOptionsStep highlighter(String highlighterName) { return this; } + @SuppressWarnings("deprecation") @Override public SingleHighlightProjectionFinalStep single() { return new SingleHighlightProjectionFinalStepImpl(); } + @Override + public ProjectionFinalStep collector( + ProjectionCollector.Provider collector) { + return new CollectorHighlightProjectionFinalStepImpl<>( collector ); + } + @Override public SearchProjection> toProjection() { - return highlight.build( ProjectionAccumulator.list() ); + return highlight.build( ProjectionCollector.list() ); } - private class SingleHighlightProjectionFinalStepImpl implements SingleHighlightProjectionFinalStep { - @Override - public SearchProjection toProjection() { - return highlight.build( ProjectionAccumulator.single() ); + private class SingleHighlightProjectionFinalStepImpl extends CollectorHighlightProjectionFinalStepImpl + implements SingleHighlightProjectionFinalStep { + public SingleHighlightProjectionFinalStepImpl() { + super( ProjectionCollector.nullable() ); + } + } + + private class CollectorHighlightProjectionFinalStepImpl implements ProjectionFinalStep { + private final ProjectionCollector.Provider collectorProvider; + + private CollectorHighlightProjectionFinalStepImpl(ProjectionCollector.Provider collectorProvider) { + this.collectorProvider = collectorProvider; } + @Override + public SearchProjection toProjection() { + return highlight.build( collectorProvider ); + } } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/spi/HighlightProjectionBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/spi/HighlightProjectionBuilder.java index 1fc0dd1995e..2d0c9b0d905 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/spi/HighlightProjectionBuilder.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/spi/HighlightProjectionBuilder.java @@ -4,8 +4,8 @@ */ package org.hibernate.search.engine.search.projection.dsl.spi; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; public abstract class HighlightProjectionBuilder { protected final String path; @@ -20,5 +20,5 @@ public HighlightProjectionBuilder highlighter(String highlighterName) { return this; } - public abstract SearchProjection build(ProjectionAccumulator.Provider accumulatorProvider); + public abstract SearchProjection build(ProjectionCollector.Provider collectorProvider); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ArrayProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ArrayProjectionCollector.java new file mode 100644 index 00000000000..17de6a669c3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ArrayProjectionCollector.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.lang.reflect.Array; +import java.util.List; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@code V[]}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +final class ArrayProjectionCollector extends ListBasedProjectionCollector { + + static Provider provider(Class elementType) { + return new ArrayProvider<>( elementType ); + } + + private ArrayProjectionCollector(Class elementType) { + this.elementType = elementType; + } + + private final Class elementType; + + @SuppressWarnings("unchecked") + @Override + public V[] doFinish(List accumulated) { + V[] array = (V[]) Array.newInstance( elementType, accumulated.size() ); + int i = 0; + for ( V v : accumulated ) { + array[i++] = v; + } + return array; + } + + @SuppressWarnings("unchecked") + private static class ArrayProvider implements Provider { + private final ArrayProjectionCollector instance; + + private ArrayProvider(Class elementType) { + instance = new ArrayProjectionCollector<>( elementType ); + } + + @Override + public ProjectionCollector get() { + return (ProjectionCollector) instance; + } + + @Override + public boolean isSingleValued() { + return false; + } + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BaseSingleValuedProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BaseSingleValuedProjectionCollector.java new file mode 100644 index 00000000000..684e8c272f3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BaseSingleValuedProjectionCollector.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.lang.invoke.MethodHandles; + +import org.hibernate.search.engine.backend.types.converter.FromDocumentValueConverter; +import org.hibernate.search.engine.backend.types.converter.runtime.FromDocumentValueConvertContext; +import org.hibernate.search.engine.logging.impl.Log; +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.util.common.logging.impl.LoggerFactory; + +/** + * A {@link ProjectionCollector} that can accumulate up to one value, and will throw an exception beyond that. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +abstract class BaseSingleValuedProjectionCollector + implements ProjectionCollector { + + private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + protected BaseSingleValuedProjectionCollector() { + } + + @Override + public final String toString() { + return getClass().getSimpleName(); + } + + @Override + public final E createInitial() { + return null; + } + + @Override + public final E accumulate(Object accumulated, E value) { + if ( accumulated != null ) { + throw log.unexpectedMultiValuedField( accumulated, value ); + } + return value; + } + + @Override + public final int size(Object accumulated) { + return accumulated == null ? 0 : 1; + } + + @Override + @SuppressWarnings("unchecked") + public final E get(Object accumulated, int index) { + return (E) accumulated; + } + + @Override + public final Object transform(Object accumulated, int index, V transformed) { + if ( index != 0 ) { + throw new IndexOutOfBoundsException( "Invalid index passed to " + this + ": " + index ); + } + return transformed; + } + + @Override + @SuppressWarnings("unchecked") + public final Object transformAll(Object accumulated, FromDocumentValueConverter converter, + FromDocumentValueConvertContext context) { + return converter.fromDocumentValue( (E) accumulated, context ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BuiltInProjectionCollectors.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BuiltInProjectionCollectors.java new file mode 100644 index 00000000000..24af64dbdb3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/BuiltInProjectionCollectors.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.function.Function; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * Provides access to built-in projection collectors. + */ +public interface BuiltInProjectionCollectors { + + @SuppressWarnings("unchecked") // PROVIDER works for any V. + static ProjectionCollector.Provider nullable() { + return SingleValuedProjectionAccumulator.PROVIDER; + } + + @SuppressWarnings("unchecked") // PROVIDER works for any V. + static ProjectionCollector.Provider> list() { + return ListProjectionAccumulator.PROVIDER; + } + + static ProjectionCollector.Provider simple( + Function, C> converter) { + return new SimpleProjectionCollector.Provider<>( converter ); + } + + static ProjectionCollector.Provider array( + Class componentType) { + return ArrayProjectionCollector.provider( componentType ); + } + + @SuppressWarnings("unchecked") // PROVIDER works for any V. + static ProjectionCollector.Provider> set() { + return SetProjectionCollector.PROVIDER; + } + + @SuppressWarnings("unchecked") // PROVIDER works for any V. + static ProjectionCollector.Provider> sortedSet() { + return SortedSetProjectionCollector.PROVIDER; + } + + static ProjectionCollector.Provider> sortedSet( + Comparator comparator) { + return SortedSetComparatorProjectionCollector.provider( comparator ); + } + + @SuppressWarnings("unchecked") // PROVIDER works for any V. + static ProjectionCollector.Provider> optional() { + return OptionalProjectionCollector.PROVIDER; + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/CompositeProjectionBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/CompositeProjectionBuilder.java index 5365ca5d765..d3d54041ca0 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/CompositeProjectionBuilder.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/CompositeProjectionBuilder.java @@ -4,11 +4,12 @@ */ package org.hibernate.search.engine.search.projection.spi; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; public interface CompositeProjectionBuilder { SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider); + ProjectionCollector.Provider collectorProvider); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/DistanceToFieldProjectionBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/DistanceToFieldProjectionBuilder.java index 3df387d47ae..ed0d44009e9 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/DistanceToFieldProjectionBuilder.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/DistanceToFieldProjectionBuilder.java @@ -4,6 +4,7 @@ */ package org.hibernate.search.engine.search.projection.spi; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; @@ -16,9 +17,9 @@ public interface DistanceToFieldProjectionBuilder extends SearchProjectionBuilde @Override default SearchProjection build() { - return build( ProjectionAccumulator.single() ); + return build( ProjectionCollector.nullable() ); } -

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider); +

SearchProjection

build(ProjectionCollector.Provider collectorProvider); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/FieldProjectionBuilder.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/FieldProjectionBuilder.java index 3ffcd725fb6..0d18fbc1abe 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/FieldProjectionBuilder.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/FieldProjectionBuilder.java @@ -5,6 +5,7 @@ package org.hibernate.search.engine.search.projection.spi; import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; public interface FieldProjectionBuilder extends SearchProjectionBuilder { @@ -15,9 +16,9 @@ interface TypeSelector { @Override default SearchProjection build() { - return build( ProjectionAccumulator.single() ); + return build( ProjectionCollector.nullable() ); } -

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider); +

SearchProjection

build(ProjectionCollector.Provider collectorProvider); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListBasedProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListBasedProjectionCollector.java new file mode 100644 index 00000000000..2e9c14a1950 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListBasedProjectionCollector.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link List}, + * and transforms that list into an arbitrary container on {@link #finish(List)}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + * @param The type of the final result containing values of type {@code V}. + */ +@SuppressWarnings("deprecation") +abstract class ListBasedProjectionCollector + implements ProjectionCollector, R> { + + ListBasedProjectionCollector() { + } + + @Override + public final String toString() { + return getClass().getSimpleName(); + } + + @Override + public final List createInitial() { + return new ArrayList<>(); + } + + @Override + public final List accumulate(List accumulated, E value) { + accumulated.add( value ); + return accumulated; + } + + @Override + public final List accumulateAll(List accumulated, Collection values) { + accumulated.addAll( values ); + return accumulated; + } + + @Override + public final int size(List accumulated) { + return accumulated.size(); + } + + @Override + @SuppressWarnings("unchecked") + public final E get(List accumulated, int index) { + return (E) accumulated.get( index ); + } + + @Override + public final List transform(List accumulated, int index, V transformed) { + accumulated.set( index, transformed ); + return accumulated; + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final R finish(List accumulated) { + // Hack to avoid instantiating another list: we convert a List into a List just by replacing its elements. + // It works *only* because we know the actual underlying type of the list, + // and we know it can work just as well with U as with Object. + return doFinish( (List) (List) accumulated ); + } + + protected abstract R doFinish(List accumulated); +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListProjectionAccumulator.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListProjectionAccumulator.java index cd21f4ef49a..3cfc0bf6f8b 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListProjectionAccumulator.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ListProjectionAccumulator.java @@ -4,20 +4,22 @@ */ package org.hibernate.search.engine.search.projection.spi; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import org.hibernate.search.engine.search.projection.ProjectionCollector; + /** - * A {@link ProjectionAccumulator} that can accumulate any number of values into a {@link java.util.List}. + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link java.util.List}. * * @param The type of extracted values to accumulate before being transformed. * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). */ -final class ListProjectionAccumulator implements ProjectionAccumulator, List> { +@SuppressWarnings("deprecation") +final class ListProjectionAccumulator extends ListBasedProjectionCollector> + implements ProjectionAccumulator, List> { @SuppressWarnings("rawtypes") - static final Provider PROVIDER = new Provider() { + static final ProjectionAccumulator.Provider PROVIDER = new ProjectionAccumulator.Provider() { private final ListProjectionAccumulator instance = new ListProjectionAccumulator(); @Override @@ -35,50 +37,7 @@ private ListProjectionAccumulator() { } @Override - public String toString() { - return getClass().getSimpleName(); - } - - @Override - public List createInitial() { - return new ArrayList<>(); - } - - @Override - public List accumulate(List accumulated, E value) { - accumulated.add( value ); - return accumulated; - } - - @Override - public List accumulateAll(List accumulated, Collection values) { - accumulated.addAll( values ); - return accumulated; - } - - @Override - public int size(List accumulated) { - return accumulated.size(); - } - - @Override - @SuppressWarnings("unchecked") - public E get(List accumulated, int index) { - return (E) accumulated.get( index ); - } - - @Override - public List transform(List accumulated, int index, V transformed) { - accumulated.set( index, transformed ); + public List doFinish(List accumulated) { return accumulated; } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public List finish(List accumulated) { - // Hack to avoid instantiating another list: we convert a List into a List just by replacing its elements. - // It works *only* because we know the actual underlying type of the list, - // and we know it can work just as well with U as with Object. - return (List) (List) accumulated; - } } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/OptionalProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/OptionalProjectionCollector.java new file mode 100644 index 00000000000..8db24265fe8 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/OptionalProjectionCollector.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.Optional; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate up to one value, and will throw an exception beyond that. + * The value is wrapped in an {@link Optional}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +final class OptionalProjectionCollector extends BaseSingleValuedProjectionCollector> { + + @SuppressWarnings("rawtypes") + static final Provider PROVIDER = new Provider() { + private final OptionalProjectionCollector instance = new OptionalProjectionCollector(); + + @Override + public ProjectionCollector get() { + return instance; + } + + @Override + public boolean isSingleValued() { + return true; + } + }; + + private OptionalProjectionCollector() { + } + + @Override + @SuppressWarnings("unchecked") + public Optional finish(Object accumulated) { + return Optional.ofNullable( (V) accumulated ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ProjectionAccumulator.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ProjectionAccumulator.java index d90e5a44632..8a380a9367a 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ProjectionAccumulator.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/ProjectionAccumulator.java @@ -9,6 +9,7 @@ import org.hibernate.search.engine.backend.types.converter.runtime.FromDocumentValueConvertContext; import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; +import org.hibernate.search.engine.search.projection.ProjectionCollector; /** * A variation on {@link java.util.stream.Collector} suitable for projections on field values. @@ -30,11 +31,15 @@ * @param The type of the temporary storage for accumulated values, * before and after being transformed. * @param The type of the final result containing values of type {@code V}. + * + * @deprecated Use {@link ProjectionCollector} instead. */ -public interface ProjectionAccumulator { +@Deprecated(since = "8.0") +public interface ProjectionAccumulator + extends ProjectionCollector { @SuppressWarnings("unchecked") // PROVIDER works for any V. - static ProjectionAccumulator.Provider single() { + static Provider single() { return SingleValuedProjectionAccumulator.PROVIDER; } @@ -51,6 +56,7 @@ static Provider> list() { * @return The initial accumulated container, * to pass to the first call to {@link #accumulate(Object, Object)}. */ + @Override A createInitial(); /** @@ -64,6 +70,7 @@ static Provider> list() { * @param value The value to accumulate. * @return The new accumulated value. */ + @Override A accumulate(A accumulated, E value); /** @@ -77,6 +84,7 @@ static Provider> list() { * @param values The values to accumulate. * @return The new accumulated value. */ + @Override default A accumulateAll(A accumulated, Collection values) { for ( E value : values ) { accumulated = accumulate( accumulated, value ); @@ -89,6 +97,7 @@ default A accumulateAll(A accumulated, Collection values) { * returned by the last call to {@link #accumulate(Object, Object)}. * @return The number of elements in the accumulated value. */ + @Override int size(A accumulated); /** @@ -101,6 +110,7 @@ default A accumulateAll(A accumulated, Collection values) { * @param index The index of the value to retrieve. * @return The value at the given index. */ + @Override E get(A accumulated, int index); /** @@ -115,6 +125,7 @@ default A accumulateAll(A accumulated, Collection values) { * @param transformed The transformed value. * @return The new accumulated value. */ + @Override A transform(A accumulated, int index, V transformed); /** @@ -130,12 +141,7 @@ default A accumulateAll(A accumulated, Collection values) { */ default A transformAll(A accumulated, ProjectionConverter converter, FromDocumentValueConvertContext context) { - for ( int i = 0; i < size( accumulated ); i++ ) { - E initial = get( accumulated, i ); - V transformed = converter.fromDocumentValue( initial, context ); - accumulated = transform( accumulated, i, transformed ); - } - return accumulated; + return transformAll( accumulated, converter.delegate(), context ); } /** @@ -149,6 +155,7 @@ default A transformAll(A accumulated, ProjectionConverter The type of values to accumulate after being transformed. * @param The type of the final result containing values of type {@code V}. */ - interface Provider { + interface Provider extends ProjectionCollector.Provider { /** * @param The type of values to accumulate before being transformed. * @return An accumulator for the given type. */ + @Override ProjectionAccumulator get(); /** * @return {@code true} if accumulators returned by {@link #get()} can only accept a single value, * and will fail beyond that. */ + @Override boolean isSingleValued(); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SetProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SetProjectionCollector.java new file mode 100644 index 00000000000..9ddf96ff002 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SetProjectionCollector.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link Set}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +final class SetProjectionCollector extends ListBasedProjectionCollector> { + + @SuppressWarnings("rawtypes") + static final Provider PROVIDER = new Provider() { + private final SetProjectionCollector instance = new SetProjectionCollector(); + + @Override + public ProjectionCollector get() { + return instance; + } + + @Override + public boolean isSingleValued() { + return false; + } + }; + + private SetProjectionCollector() { + } + + @Override + public Set doFinish(List accumulated) { + return new HashSet<>( accumulated ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SimpleProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SimpleProjectionCollector.java new file mode 100644 index 00000000000..20a6a59ab2d --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SimpleProjectionCollector.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.List; +import java.util.function.Function; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link List}, + * and transforms that list into an arbitrary container using a given {@link Function}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + * @param The type of the final result containing values of type {@code V}. + */ +final class SimpleProjectionCollector extends ListBasedProjectionCollector { + + static final class Provider implements ProjectionCollector.Provider { + private final Function, R> finisher; + + Provider(Function, R> finisher) { + this.finisher = finisher; + } + + @Override + public ProjectionCollector get() { + return new SimpleProjectionCollector<>( finisher ); + } + + @Override + public boolean isSingleValued() { + return false; + } + } + + private final Function, R> finisher; + + private SimpleProjectionCollector(Function, R> finisher) { + this.finisher = finisher; + } + + @Override + public R doFinish(List accumulated) { + return finisher.apply( accumulated ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SingleValuedProjectionAccumulator.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SingleValuedProjectionAccumulator.java index 4fc546e425e..ed0aad0f39c 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SingleValuedProjectionAccumulator.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SingleValuedProjectionAccumulator.java @@ -4,22 +4,19 @@ */ package org.hibernate.search.engine.search.projection.spi; -import java.lang.invoke.MethodHandles; - import org.hibernate.search.engine.backend.types.converter.runtime.FromDocumentValueConvertContext; import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; -import org.hibernate.search.engine.logging.impl.Log; -import org.hibernate.search.util.common.logging.impl.LoggerFactory; +import org.hibernate.search.engine.search.projection.ProjectionCollector; /** - * A {@link ProjectionAccumulator} that can accumulate up to one value, and will throw an exception beyond that. + * A {@link ProjectionCollector} that can accumulate up to one value, and will throw an exception beyond that. * * @param The type of extracted values to accumulate before being transformed. * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). */ -final class SingleValuedProjectionAccumulator implements ProjectionAccumulator { - - private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); +@SuppressWarnings("deprecation") +final class SingleValuedProjectionAccumulator extends BaseSingleValuedProjectionCollector + implements ProjectionAccumulator { @SuppressWarnings("rawtypes") static final ProjectionAccumulator.Provider PROVIDER = new ProjectionAccumulator.Provider() { @@ -39,43 +36,6 @@ public boolean isSingleValued() { private SingleValuedProjectionAccumulator() { } - @Override - public String toString() { - return getClass().getSimpleName(); - } - - @Override - public E createInitial() { - return null; - } - - @Override - public E accumulate(Object accumulated, E value) { - if ( accumulated != null ) { - throw log.unexpectedMultiValuedField( accumulated, value ); - } - return value; - } - - @Override - public int size(Object accumulated) { - return accumulated == null ? 0 : 1; - } - - @Override - @SuppressWarnings("unchecked") - public E get(Object accumulated, int index) { - return (E) accumulated; - } - - @Override - public Object transform(Object accumulated, int index, V transformed) { - if ( index != 0 ) { - throw new IndexOutOfBoundsException( "Invalid index passed to " + this + ": " + index ); - } - return transformed; - } - @Override @SuppressWarnings("unchecked") public Object transformAll(Object accumulated, ProjectionConverter converter, diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetComparatorProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetComparatorProjectionCollector.java new file mode 100644 index 00000000000..af8e1b2f151 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetComparatorProjectionCollector.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link SortedSet}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +final class SortedSetComparatorProjectionCollector extends ListBasedProjectionCollector> { + + static Provider provider(Comparator comparator) { + return new ComparatorBasedSortedSetProvider<>( comparator ); + } + + private SortedSetComparatorProjectionCollector(Comparator comparator) { + this.comparator = comparator; + } + + private final Comparator comparator; + + @Override + public SortedSet doFinish(List accumulated) { + TreeSet set = new TreeSet<>( comparator ); + set.addAll( accumulated ); + return set; + } + + @SuppressWarnings("unchecked") + private static class ComparatorBasedSortedSetProvider implements Provider { + private final SortedSetComparatorProjectionCollector instance; + + private ComparatorBasedSortedSetProvider(Comparator comparator) { + instance = new SortedSetComparatorProjectionCollector<>( comparator ); + } + + @Override + public ProjectionCollector get() { + return (ProjectionCollector) instance; + } + + @Override + public boolean isSingleValued() { + return false; + } + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetProjectionCollector.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetProjectionCollector.java new file mode 100644 index 00000000000..418152c2e1e --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/spi/SortedSetProjectionCollector.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.spi; + +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.hibernate.search.engine.search.projection.ProjectionCollector; + +/** + * A {@link ProjectionCollector} that can accumulate any number of values into a {@link SortedSet}. + * + * @param The type of extracted values to accumulate before being transformed. + * @param The type of values to accumulate obtained by transforming extracted values ({@code E}). + */ +final class SortedSetProjectionCollector extends ListBasedProjectionCollector> { + + @SuppressWarnings("rawtypes") + static final ProjectionCollector.Provider PROVIDER = + new ProjectionCollector.Provider() { + private final SortedSetProjectionCollector instance = new SortedSetProjectionCollector(); + + @Override + public ProjectionCollector get() { + return instance; + } + + @Override + public boolean isSingleValued() { + return false; + } + }; + + private SortedSetProjectionCollector() { + } + + @Override + public SortedSet doFinish(List accumulated) { + return new TreeSet<>( accumulated ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/query/dsl/impl/DefaultSearchQuerySelectStep.java b/engine/src/main/java/org/hibernate/search/engine/search/query/dsl/impl/DefaultSearchQuerySelectStep.java index 71d5846d564..37ec11663df 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/query/dsl/impl/DefaultSearchQuerySelectStep.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/query/dsl/impl/DefaultSearchQuerySelectStep.java @@ -14,10 +14,10 @@ import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; import org.hibernate.search.engine.search.predicate.dsl.SimpleBooleanPredicateClausesCollector; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; import org.hibernate.search.engine.search.query.dsl.SearchQueryWhereStep; @@ -77,7 +77,7 @@ public

DefaultSearchQueryOptionsStep select(SearchProjection

proj public DefaultSearchQueryOptionsStep, LOS> select(SearchProjection... projections) { return select( scope.projectionBuilders().composite() .build( projections, ProjectionCompositor.fromList( projections.length ), - ProjectionAccumulator.single() ) ); + ProjectionCollector.nullable() ) ); } @Override diff --git a/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/ElasticsearchExtensionIT.java b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/ElasticsearchExtensionIT.java index 026f6319ab1..14e879b645a 100644 --- a/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/ElasticsearchExtensionIT.java +++ b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/ElasticsearchExtensionIT.java @@ -47,6 +47,7 @@ import org.hibernate.search.engine.search.common.ValueModel; import org.hibernate.search.engine.search.loading.spi.SearchLoadingContext; import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.query.SearchQuery; import org.hibernate.search.engine.search.sort.SearchSort; @@ -1107,7 +1108,7 @@ void jsonHitProjectionInsideNested() { assertThatThrownBy( () -> mainIndex.createScope().query() .select( f -> f.object( "nestedObject" ).from( f.extension( ElasticsearchExtension.get() ).jsonHit() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -1123,7 +1124,7 @@ void sourceProjectionInsideNested() { assertThatThrownBy( () -> mainIndex.createScope().query() .select( f -> f.object( "nestedObject" ).from( f.extension( ElasticsearchExtension.get() ).source() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -1139,7 +1140,7 @@ void explanationProjectionInsideNested() { assertThatThrownBy( () -> mainIndex.createScope().query() .select( f -> f.object( "nestedObject" ).from( f.extension( ElasticsearchExtension.get() ).explanation() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() diff --git a/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/testsupport/util/ElasticsearchTckBackendFeatures.java b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/testsupport/util/ElasticsearchTckBackendFeatures.java index bf92608302b..70aaa2fb4e3 100644 --- a/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/testsupport/util/ElasticsearchTckBackendFeatures.java +++ b/integrationtest/backend/elasticsearch/src/test/java/org/hibernate/search/integrationtest/backend/elasticsearch/testsupport/util/ElasticsearchTckBackendFeatures.java @@ -22,6 +22,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Locale; @@ -31,6 +32,7 @@ import org.hibernate.search.backend.elasticsearch.types.format.impl.ElasticsearchDefaultFieldFormatProvider; import org.hibernate.search.engine.backend.types.VectorSimilarity; import org.hibernate.search.engine.cfg.spi.FormatUtils; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.BigDecimalFieldTypeDescriptor; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.FieldTypeDescriptor; @@ -514,4 +516,15 @@ public boolean rawAggregationProduceSensibleDoubleValue(FieldTypeDescriptor< } return super.rawAggregationProduceSensibleDoubleValue( fFieldTypeDescriptor ); } + + @Override + public R accumulatedNullValue(ProjectionCollector.Provider collector) { + return accumulatedNullValue( collector.get() ); + } + + private R accumulatedNullValue(ProjectionCollector collector) { + ArrayList values = new ArrayList<>(); + values.add( null ); + return collector.finish( collector.accumulateAll( collector.createInitial(), values ) ); + } } diff --git a/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/LuceneExtensionIT.java b/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/LuceneExtensionIT.java index c0c5d764b18..006f1b671bc 100644 --- a/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/LuceneExtensionIT.java +++ b/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/LuceneExtensionIT.java @@ -54,6 +54,7 @@ import org.hibernate.search.engine.search.common.ValueModel; import org.hibernate.search.engine.search.loading.spi.SearchLoadingContext; import org.hibernate.search.engine.search.predicate.SearchPredicate; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.query.SearchQuery; import org.hibernate.search.engine.search.sort.SearchSort; @@ -887,7 +888,7 @@ void documentProjectionInsideNested() { assertThatThrownBy( () -> mainIndex.createScope().query() .select( f -> f.object( "nestedObject" ).from( f.extension( LuceneExtension.get() ).document() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -903,7 +904,7 @@ void explanationProjectionInsideNested() { assertThatThrownBy( () -> mainIndex.createScope().query() .select( f -> f.object( "nestedObject" ).from( f.extension( LuceneExtension.get() ).explanation() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -933,8 +934,9 @@ void documentTreeMultipleIndexes() { someOtherIndex.query().select( f -> f.object( "someOtherNestedObject" ).from( f .object( "someOtherNestedObject.someOtherNestedNestedObject" ) .from( f.field( "someOtherNestedObject.someOtherNestedNestedObject.nestedNestedSomeOtherInteger" ) - .multi() ) - .asList().multi() ).asList().multi() ) + .collector( ProjectionCollector.list() ) ) + .asList().collector( ProjectionCollector.list() ) ).asList() + .collector( ProjectionCollector.list() ) ) .where( f -> f.match().field( "someOtherString" ).matching( "text 2" ) ).fetchAllHits() ).containsExactly( List.of( diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/highlight/AbstractHighlighterIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/highlight/AbstractHighlighterIT.java index 6cbd5f4aea4..34e1afbb67a 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/highlight/AbstractHighlighterIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/highlight/AbstractHighlighterIT.java @@ -14,7 +14,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; import org.hibernate.search.engine.backend.document.IndexFieldReference; import org.hibernate.search.engine.backend.document.IndexObjectFieldReference; @@ -137,6 +141,58 @@ void highlighterNoConfigurationAtAll() { ); } + @Test + void collectAsSet() { + StubMappingScope scope = index.createScope(); + + SearchQuery> highlights = scope.query().select( + f -> f.highlight( "string" ).set() + ) + .where( f -> f.match().field( "string" ).matching( "another" ) ) + .toQuery(); + + assertThatHits( highlights.fetchAllHits() ) + .hasHitsAnyOrder( + Set.of( "some another value" ), + Set.of( "some yet another value" ) + ); + } + + @Test + void collectAsSortedSet() { + StubMappingScope scope = index.createScope(); + + SearchQuery> highlights = scope.query().select( + f -> f.highlight( "string" ).sortedSet() + ) + .where( f -> f.match().field( "string" ).matching( "another" ) ) + .toQuery(); + + assertThatHits( highlights.fetchAllHits() ) + .hasHitsAnyOrder( + new TreeSet<>( Set.of( "some another value" ) ), + new TreeSet<>( Set.of( "some yet another value" ) + ) + ); + } + + @Test + void collectAsArray() { + StubMappingScope scope = index.createScope(); + + SearchQuery highlights = scope.query().select( + f -> f.highlight( "string" ).array() + ) + .where( f -> f.match().field( "string" ).matching( "another" ) ) + .toQuery(); + + assertThatHits( highlights.fetchAllHits() ) + .hasHitsAnyOrder( + new String[] { "some another value" }, + new String[] { "some yet another value" } + ); + } + @Test void highlighterNoSettings() { StubMappingScope scope = index.createScope(); @@ -443,7 +499,25 @@ void numberOfFragmentsSingle() { StubMappingScope scope = index.createScope(); SearchQuery highlights = scope.query().select( - f -> f.highlight( "anotherString" ).single() + f -> f.highlight( "anotherString" ).nullable() + ) + .where( f -> f.match().field( "anotherString" ).matching( "ipsum" ) ) + .highlighter( h -> highlighter( h ).numberOfFragments( 1 ) ) + .toQuery(); + + assertThatHits( highlights.fetchAllHits() ) + .hasHitsAnyOrder( + numberOfFragmentsResult() + ); + } + + + @Test + void numberOfFragmentsSingleOptional() { + StubMappingScope scope = index.createScope(); + + SearchQuery> highlights = scope.query().select( + f -> f.highlight( "anotherString" ).optional() ) .where( f -> f.match().field( "anotherString" ).matching( "ipsum" ) ) .highlighter( h -> highlighter( h ).numberOfFragments( 1 ) ) @@ -452,10 +526,13 @@ void numberOfFragmentsSingle() { assertThatHits( highlights.fetchAllHits() ) .hasHitsAnyOrder( numberOfFragmentsResult() + .stream().map( Optional::of ) + .collect( Collectors.toList() ) ); } @Test + @Deprecated(since = "8.0") void numberOfFragmentsSingleNamedHighlighter() { StubMappingScope scope = index.createScope(); @@ -472,8 +549,9 @@ void numberOfFragmentsSingleNamedHighlighter() { ); } + @Deprecated @Test - void numberOfFragmentsSingleError() { + void numberOfFragmentsSingleErrorSingle() { StubMappingScope scope = index.createScope(); // numberOfFragments > 1 @@ -499,8 +577,9 @@ void numberOfFragmentsSingleError() { "A single-valued highlight projection requested, but the corresponding highlighter does not set number of fragments to 1" ); } + @Deprecated @Test - void numberOfFragmentsSingleButNoExpectedValuesReturned() { + void numberOfFragmentsSingleButNoExpectedValuesReturnedSingle() { StubMappingScope scope = index.createScope(); SearchQuery highlights = scope.query().select( @@ -514,6 +593,48 @@ void numberOfFragmentsSingleButNoExpectedValuesReturned() { .hasHitsAnyOrder( List.of() ); } + @Test + void numberOfFragmentsSingleError() { + StubMappingScope scope = index.createScope(); + + // numberOfFragments > 1 + assertThatThrownBy( () -> scope.query().select( + f -> f.highlight( "anotherString" ).nullable() + ) + .where( f -> f.match().field( "anotherString" ).matching( "ipsum" ) ) + .highlighter( h -> highlighter( h ).numberOfFragments( 2 ) ) + .toQuery() + ).isInstanceOf( SearchException.class ) + .hasMessageContainingAll( + "A single-valued highlight projection requested, but the corresponding highlighter does not set number of fragments to 1" ); + + // numberOfFragments not defined + assertThatThrownBy( () -> scope.query().select( + f -> f.highlight( "anotherString" ).nullable() + ) + .where( f -> f.match().field( "anotherString" ).matching( "ipsum" ) ) + .highlighter( h -> highlighter( h ) ) + .toQuery() + ).isInstanceOf( SearchException.class ) + .hasMessageContainingAll( + "A single-valued highlight projection requested, but the corresponding highlighter does not set number of fragments to 1" ); + } + + @Test + void numberOfFragmentsSingleButNoExpectedValuesReturned() { + StubMappingScope scope = index.createScope(); + + SearchQuery highlights = scope.query().select( + f -> f.highlight( "anotherString" ).nullable() + ) + .where( f -> f.match().field( "anotherString" ).matching( "thisCannotBeMatchedToAnythingInTheText" ) ) + .highlighter( h -> highlighter( h ).numberOfFragments( 1 ) ) + .toQuery(); + + assertThatHits( highlights.fetchAllHits() ) + .hasHitsAnyOrder( List.of() ); + } + protected List numberOfFragmentsResult() { return Arrays.asList( "Lorem ipsum dolor sit amet, consectetur adipiscing elit." diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/predicate/KnnPredicateSpecificsIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/predicate/KnnPredicateSpecificsIT.java index 2fb6ab03690..99dc34d185d 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/predicate/KnnPredicateSpecificsIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/predicate/KnnPredicateSpecificsIT.java @@ -36,6 +36,7 @@ import org.hibernate.search.engine.backend.types.dsl.SearchableProjectableIndexFieldTypeOptionsStep; import org.hibernate.search.engine.search.predicate.dsl.KnnPredicateOptionsStep; import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.engine.search.query.SearchQuery; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.FieldTypeDescriptor; @@ -775,7 +776,7 @@ void nestedVector() { f.field( "nested.floatVector" ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ).asList() ).where( f -> f.knn( 1 ).field( "nested.byteVector" ).matching( bytes( 2, (byte) -120 ) ) diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractCompositeProjectionFromAsIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractCompositeProjectionFromAsIT.java index ee8cad1044a..36938735b6b 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractCompositeProjectionFromAsIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractCompositeProjectionFromAsIT.java @@ -5,12 +5,17 @@ package org.hibernate.search.integrationtest.backend.tck.search.projection; import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.when; import java.time.LocalDate; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; @@ -20,6 +25,7 @@ import org.hibernate.search.engine.backend.document.DocumentElement; import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.definition.spi.ProjectionRegistry; import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionFrom1AsStep; import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionFrom2AsStep; @@ -109,7 +115,8 @@ public void asList_transformer_flexibleFunctionArgumentTypes() { @Test public void asList_multi() { assertThatQuery( index.query() - .select( f -> doFromForMulti( f, startProjectionForMulti( f ) ).asList().multi() ) + .select( f -> doFromForMulti( f, startProjectionForMulti( f ) ).asList() + .collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) ) .hasHitsAnyOrder( expectedListsForMulti() ); } @@ -151,12 +158,25 @@ public void asArray_transformer_flexibleFunctionArgumentTypes() { @Test public void asArray_multi() { assertThatQuery( index.query() - .select( f -> doFromForMulti( f, startProjectionForMulti( f ) ).asArray().multi() ) + .select( f -> doFromForMulti( f, startProjectionForMulti( f ) ).asArray() + .collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) ) .hits().asIs().usingRecursiveFieldByFieldElementComparator() .containsExactlyInAnyOrderElementsOf( expectedArraysForMulti() ); } + @Test + public void asArray_set() { + assertThatQuery( index.query() + .select( f -> doFromForMulti( f, startProjectionForMulti( f ) ).asArray() + .collector( ProjectionCollector.set() ) ) + .where( f -> f.matchAll() ) ) + .hits().asIs().usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrderElementsOf( expectedArraysForMulti() + .stream().map( HashSet::new ) + .toList() ); + } + @Test @TestForIssue(jiraKey = "HSEARCH-3927") public void as_class() { @@ -195,7 +215,8 @@ public void as_class_multi() { doFrom( f, index.binding().compositeForMulti(), CompositeBinding::relativePath, initialStep ) .asArray( ValueWrapper::new ) ); assertThatQuery( index.createScope().query() - .select( f -> startProjectionForMulti( f ).as( ValueWrapper.class ).multi() ) + .select( f -> startProjectionForMulti( f ).as( ValueWrapper.class ) + .collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) ) .hits().asIs().usingRecursiveFieldByFieldElementComparator() .containsExactlyInAnyOrderElementsOf( expectedArraysForMulti().stream() @@ -269,6 +290,15 @@ public void as_transformer() { .hasHitsAnyOrder( expectedTransformed() ); } + @Test + public void as_transformer_accumulator() { + assertThatQuery( index.query() + .select( f -> doAs( doFromForMulti( f, startProjectionForMulti( f ) ) ).list() ) + .where( f -> f.matchAll() ) ) + .hasHitsAnyOrder( expectedTransformedForMulti() ); + } + + @Deprecated @Test public void as_transformer_multi() { assertThatQuery( index.query() @@ -277,6 +307,35 @@ public void as_transformer_multi() { .hasHitsAnyOrder( expectedTransformedForMulti() ); } + @Test + public void as_transformer_set() { + assertThatQuery( index.query() + .select( f -> doAs( doFromForMulti( f, startProjectionForMulti( f ) ) ).set() ) + .where( f -> f.matchAll() ) ) + .hasHitsAnyOrder( expectedTransformedForMulti() + .stream() + .map( l -> (Set) new HashSet<>( l ) ) + .toList() + ); + } + + @Test + public void as_transformer_sortedSet() { + Collection> lists = expectedTransformedForMulti(); + assumeTrue( + lists.iterator().next().get( 0 ) instanceof Comparable, + "This test only makes sense for comparable types" + ); + + assertThatQuery( index.query() + .select( f -> doAs( doFromForMulti( f, startProjectionForMulti( f ) ) ).sortedSet() ) + .where( f -> f.matchAll() ) ) + .hasHitsAnyOrder( lists.stream() + .map( l -> (SortedSet) new TreeSet<>( l ) ) + .toList() + ); + } + @Override protected final S doFrom(SearchProjectionFactory f, CompositeProjectionInnerStep step) { return doFrom( f, index.binding().composite(), CompositeBinding::absolutePath, step ); diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractDistanceProjectionSingleValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractDistanceProjectionSingleValuedBaseIT.java index fd08b53941c..0ae726e901e 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractDistanceProjectionSingleValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractDistanceProjectionSingleValuedBaseIT.java @@ -84,7 +84,7 @@ public static List params() { @RegisterExtension public static SearchSetupHelper setupHelper = SearchSetupHelper.create(); - private static final SimpleMappedIndex mainIndex = + protected static final SimpleMappedIndex mainIndex = SimpleMappedIndex.of( root -> SingleFieldIndexBinding.createWithSingleValuedNestedFields( root, @@ -435,7 +435,7 @@ void factoryWithRoot(TestedFieldStructure fieldStructure, DataSet dataSet) { ); } - private String getFieldPath(TestedFieldStructure fieldStructure) { + protected String getFieldPath(TestedFieldStructure fieldStructure) { return mainIndex.binding().getFieldPath( fieldStructure, fieldType ); } @@ -456,9 +456,9 @@ protected abstract ProjectionFinalStep distance( protected abstract SortFinalStep sort(SearchSortFactory sort, String path, GeoPoint center, String parameterName); - private static class DataSet { - private final TestedFieldStructure fieldStructure; - private final String routingKey; + protected static class DataSet { + protected final TestedFieldStructure fieldStructure; + protected final String routingKey; private DataSet(TestedFieldStructure fieldStructure) { this.fieldStructure = fieldStructure; @@ -506,7 +506,7 @@ private GeoPoint getSortableFieldValue(int documentNumber) { return AscendingUniqueDistanceFromCenterValues.INSTANCE.getSingle().get( documentNumber - 1 ); } - private double getFieldDistanceFromCenter1(int documentNumber) { + protected double getFieldDistanceFromCenter1(int documentNumber) { return IndexableGeoPointWithDistanceFromCenterValues.INSTANCE.getSingleDistancesFromCenterPoint1() .get( documentNumber - 1 ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractProjectionInObjectProjectionIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractProjectionInObjectProjectionIT.java index 985357c9a42..869554a75e3 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractProjectionInObjectProjectionIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/AbstractProjectionInObjectProjectionIT.java @@ -21,6 +21,7 @@ import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.Projectable; import org.hibernate.search.engine.backend.types.dsl.SearchableProjectableIndexFieldTypeOptionsStep; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.FieldTypeDescriptor; @@ -90,10 +91,10 @@ void objectOnLevel1AndObjectOnLevel2(SimpleMappedIndex mainIndex, mainIndex, dataSet ), dataSet ) ) .as( ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( Level1ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( IdAndObjectDto::new ) ) .where( f -> f.matchAll() ) @@ -256,7 +257,7 @@ void objectOnLevel1AndNoLevel2(SimpleMappedIndex mainIndex, ), dataSet ) ) .as( ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( IdAndObjectDto::new ) ) .where( f -> f.matchAll() ) @@ -362,7 +363,7 @@ void objectOnLevel1AndFlattenedLevel2(SimpleMappedIndex mainIndex, .as( FlattenedObjectDto::new ) ) .as( Level1ObjectWithFlattenedLevel2Dto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( IdAndObjectDto::new ) ) .where( f -> f.matchAll() ) @@ -562,7 +563,7 @@ void noLevel1AndObjectOnLevel2(SimpleMappedIndex mainIndex, ), dataSet ) ) .as( ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( IdAndObjectDto::new ) ) .where( f -> f.matchAll() ) @@ -663,7 +664,7 @@ void flattenedLevel1AndObjectOnLevel2(SimpleMappedIndex mainIndex, mainIndex, dataSet ), dataSet ) ) .as( ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( FlattenedLevel1ObjectDto::new ) ) @@ -789,7 +790,7 @@ void fieldOutsideObjectFieldTree(SimpleMappedIndex mainIndex, ) ) .asList() - .multi() ) + .collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .routing( dataSet.routingKey ) .toQuery() ) @@ -836,7 +837,7 @@ void singleValuedField_effectivelyMultiValuedInContext(SimpleMappedIndex f.matchAll() ) .routing( dataSet.routingKey ) .toQuery() ) @@ -847,13 +848,13 @@ void singleValuedField_effectivelyMultiValuedInContext(SimpleMappedIndex).as(...).multi()'." + + ">).as(...).collector(...)'." ); } @@ -891,10 +892,10 @@ void missingFields(SimpleMappedIndex mainIndex, mainIndex, dataSet ), dataSet ) ) .as( ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( Level1ObjectDto::new ) - .multi() + .collector( ProjectionCollector.list() ) ) .as( IdAndObjectDto::new ) ) .where( f -> f.matchAll() ) diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionBaseIT.java index 931693eee14..cddafd71461 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionBaseIT.java @@ -9,6 +9,7 @@ import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.dsl.SearchableProjectableIndexFieldTypeOptionsStep; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.engine.spatial.GeoPoint; @@ -153,7 +154,8 @@ protected ProjectionFinalStep singleValuedProjection(SearchProjectionFac @Override protected ProjectionFinalStep> multiValuedProjection(SearchProjectionFactory f, String absoluteFieldPath, DataSet dataSet) { - return f.distance( absoluteFieldPath, dataSet.values.projectionCenterPoint() ).multi(); + return f.distance( absoluteFieldPath, dataSet.values.projectionCenterPoint() ) + .collector( ProjectionCollector.list() ); } } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedAccumulatorBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedAccumulatorBaseIT.java new file mode 100644 index 00000000000..b1f05619d73 --- /dev/null +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedAccumulatorBaseIT.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.integrationtest.backend.tck.search.projection; + +import java.util.List; + +import org.hibernate.search.engine.backend.common.DocumentReference; +import org.hibernate.search.engine.common.EntityReference; +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; +import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; +import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; +import org.hibernate.search.engine.search.sort.dsl.SearchSortFactory; +import org.hibernate.search.engine.search.sort.dsl.SortFinalStep; +import org.hibernate.search.engine.spatial.DistanceUnit; +import org.hibernate.search.engine.spatial.GeoPoint; + +class DistanceProjectionMultiValuedAccumulatorBaseIT extends AbstractDistanceProjectionMultiValuedBaseIT { + + @Override + protected void addParameter(SearchQueryOptionsStep query, String parameterName, Object value) { + // do nothing + } + + @Override + protected ProjectionFinalStep> distance( + SearchProjectionFactory projection, String path, GeoPoint center, + String parameterName) { + return projection.distance( path, center ).collector( ProjectionCollector.list() ); + } + + @Override + protected ProjectionFinalStep> distance( + SearchProjectionFactory projection, String path, GeoPoint center, + DistanceUnit unit, String centerParam, String unitParam) { + return projection.distance( path, center ).collector( ProjectionCollector.list() ).unit( unit ); + } + + @Override + protected SortFinalStep sort(SearchSortFactory sort, String path, GeoPoint center, + String parameterName) { + return sort.distance( path, center ); + } +} diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedBaseIT.java index bf9e888dc4d..ccd492c66de 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionMultiValuedBaseIT.java @@ -16,6 +16,7 @@ import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; +@Deprecated(since = "8.0") class DistanceProjectionMultiValuedBaseIT extends AbstractDistanceProjectionMultiValuedBaseIT { @Override diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterMultiValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterMultiValuedBaseIT.java index 8a7fabc7371..a91578301d2 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterMultiValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterMultiValuedBaseIT.java @@ -8,6 +8,7 @@ import org.hibernate.search.engine.backend.common.DocumentReference; import org.hibernate.search.engine.common.EntityReference; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; @@ -30,7 +31,8 @@ protected ProjectionFinalStep> distance( SearchProjectionFactory projection, String path, GeoPoint center, String parameterName) { return projection.withParameters( - params -> projection.distance( path, params.get( parameterName, GeoPoint.class ) ).multi() ); + params -> projection.distance( path, params.get( parameterName, GeoPoint.class ) ) + .collector( ProjectionCollector.list() ) ); } @Override @@ -38,7 +40,8 @@ protected ProjectionFinalStep> distance( SearchProjectionFactory projection, String path, GeoPoint center, DistanceUnit unit, String centerParam, String unitParam) { return projection.withParameters( - params -> projection.distance( path, params.get( centerParam, GeoPoint.class ) ).multi() + params -> projection.distance( path, params.get( centerParam, GeoPoint.class ) ) + .collector( ProjectionCollector.list() ) .unit( params.get( unitParam, DistanceUnit.class ) ) ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterSingleValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterSingleValuedBaseIT.java index fe07f7b79e2..ed0be013f2d 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterSingleValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionParameterSingleValuedBaseIT.java @@ -8,6 +8,7 @@ import org.hibernate.search.engine.backend.common.DocumentReference; import org.hibernate.search.engine.common.EntityReference; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; @@ -37,7 +38,8 @@ protected ProjectionFinalStep> distanceMulti( SearchProjectionFactory projection, String path, GeoPoint center, String parameterName) { return projection.withParameters( - params -> projection.distance( path, params.get( parameterName, GeoPoint.class ) ).multi() ); + params -> projection.distance( path, params.get( parameterName, GeoPoint.class ) ) + .collector( ProjectionCollector.list() ) ); } @Override diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionSingleValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionSingleValuedBaseIT.java index 0c1d4dffed7..c879f65a3fd 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionSingleValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionSingleValuedBaseIT.java @@ -4,10 +4,19 @@ */ package org.hibernate.search.integrationtest.backend.tck.search.projection; +import static org.hibernate.search.integrationtest.backend.tck.testsupport.types.values.IndexableGeoPointWithDistanceFromCenterValues.CENTER_POINT_1; +import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery; +import static org.hibernate.search.util.impl.integrationtest.common.assertion.TestComparators.APPROX_M_COMPARATOR; + +import java.util.Comparator; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import org.hibernate.search.engine.backend.common.DocumentReference; import org.hibernate.search.engine.common.EntityReference; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionOptionsStep; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionValueStep; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; @@ -17,9 +26,123 @@ import org.hibernate.search.engine.search.sort.dsl.SortFinalStep; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; +import org.hibernate.search.integrationtest.backend.tck.testsupport.util.TckConfiguration; +import org.hibernate.search.integrationtest.backend.tck.testsupport.util.TestedFieldStructure; +import org.hibernate.search.util.impl.integrationtest.common.assertion.TestComparators; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingScope; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; class DistanceProjectionSingleValuedBaseIT extends AbstractDistanceProjectionSingleValuedBaseIT { + @ParameterizedTest(name = "{0}") + @MethodSource("params") + void optional(TestedFieldStructure fieldStructure, DataSet dataSet) { + StubMappingScope scope = mainIndex.createScope(); + + String fieldPath = getFieldPath( fieldStructure ); + + var projection = scope.projection().distance( fieldPath, CENTER_POINT_1 ).optional().toProjection(); + + var query = scope.query() + // Do NOT add any additional projection here: this serves as a non-regression test for HSEARCH-3618 + .select( projection ) + .where( f -> f.matchAll() ); + addParameter( query, "center-param", CENTER_POINT_1 ); + + assertThatQuery( query.routing( dataSet.routingKey ).toQuery() ) + .hits().asIs() + .usingElementComparator( TestComparators.optional( APPROX_M_COMPARATOR ) ) + .containsExactlyInAnyOrder( + Optional.of( dataSet.getFieldDistanceFromCenter1( 1 ) ), + Optional.of( dataSet.getFieldDistanceFromCenter1( 2 ) ), + Optional.of( dataSet.getFieldDistanceFromCenter1( 3 ) ), + Optional.empty() // Empty document + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("params") + void set(TestedFieldStructure fieldStructure, DataSet dataSet) { + StubMappingScope scope = mainIndex.createScope(); + + String fieldPath = getFieldPath( fieldStructure ); + + var projection = scope.projection().distance( fieldPath, CENTER_POINT_1 ).set().toProjection(); + + var query = scope.query() + // Do NOT add any additional projection here: this serves as a non-regression test for HSEARCH-3618 + .select( projection ) + .where( f -> f.matchAll() ); + addParameter( query, "center-param", CENTER_POINT_1 ); + + assertThatQuery( query.routing( dataSet.routingKey ).toQuery() ) + .hits().asIs() + .usingElementComparator( TestComparators.lexicographic( APPROX_M_COMPARATOR ) ) + .containsExactlyInAnyOrder( + Set.of( dataSet.getFieldDistanceFromCenter1( 1 ) ), + Set.of( dataSet.getFieldDistanceFromCenter1( 2 ) ), + Set.of( dataSet.getFieldDistanceFromCenter1( 3 ) ), + TckConfiguration.get().getBackendFeatures().accumulatedNullValue( ProjectionCollector.set() ) // Empty document + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("params") + void sortedSet(TestedFieldStructure fieldStructure, DataSet dataSet) { + StubMappingScope scope = mainIndex.createScope(); + + String fieldPath = getFieldPath( fieldStructure ); + + Comparator comparator = Comparator.nullsLast( Comparable::compareTo ); + var projection = scope.projection().distance( fieldPath, CENTER_POINT_1 ) + .sortedSet( comparator ).toProjection(); + + var query = scope.query() + // Do NOT add any additional projection here: this serves as a non-regression test for HSEARCH-3618 + .select( projection ) + .where( f -> f.matchAll() ); + addParameter( query, "center-param", CENTER_POINT_1 ); + + assertThatQuery( query.routing( dataSet.routingKey ).toQuery() ) + .hits().asIs() + .usingElementComparator( TestComparators.lexicographic( APPROX_M_COMPARATOR ) ) + .containsExactlyInAnyOrder( + new TreeSet<>( Set.of( dataSet.getFieldDistanceFromCenter1( 1 ) ) ), + new TreeSet<>( Set.of( dataSet.getFieldDistanceFromCenter1( 2 ) ) ), + new TreeSet<>( Set.of( dataSet.getFieldDistanceFromCenter1( 3 ) ) ), + TckConfiguration.get().getBackendFeatures() + .accumulatedNullValue( ProjectionCollector.sortedSet( comparator ) ) // Empty document + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("params") + void array(TestedFieldStructure fieldStructure, DataSet dataSet) { + StubMappingScope scope = mainIndex.createScope(); + + String fieldPath = getFieldPath( fieldStructure ); + + var projection = scope.projection().distance( fieldPath, CENTER_POINT_1 ).array( Double.class ).toProjection(); + + var query = scope.query() + // Do NOT add any additional projection here: this serves as a non-regression test for HSEARCH-3618 + .select( projection ) + .where( f -> f.matchAll() ); + addParameter( query, "center-param", CENTER_POINT_1 ); + + assertThatQuery( query.routing( dataSet.routingKey ).toQuery() ) + .hits().asIs() + .usingElementComparator( TestComparators.array( APPROX_M_COMPARATOR ) ) + .containsExactlyInAnyOrder( + new Double[] { dataSet.getFieldDistanceFromCenter1( 1 ) }, + new Double[] { dataSet.getFieldDistanceFromCenter1( 2 ) }, + new Double[] { dataSet.getFieldDistanceFromCenter1( 3 ) }, + TckConfiguration.get().getBackendFeatures() + .accumulatedNullValue( ProjectionCollector.array( Double.class ) ) // Empty document + ); + } @Override protected void addParameter(SearchQueryOptionsStep query, String parameterName, Object value) { @@ -37,7 +160,7 @@ protected DistanceToFieldProjectionValueStep distance( protected ProjectionFinalStep> distanceMulti( SearchProjectionFactory projection, String path, GeoPoint center, String parameterName) { - return projection.distance( path, center ).multi(); + return projection.distance( path, center ).list(); } @Override diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionTypeCheckingAndConversionIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionTypeCheckingAndConversionIT.java index c2fc1c30399..2837d2f0837 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionTypeCheckingAndConversionIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/DistanceProjectionTypeCheckingAndConversionIT.java @@ -92,7 +92,7 @@ void multiValuedField_singleValuedProjection() { .hasMessageContainingAll( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } @@ -110,7 +110,7 @@ void singleValuedFieldInMultiValuedObjectField_flattened_singleValuedProjection( .hasMessageContaining( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } @@ -128,7 +128,7 @@ void singleValuedFieldInMultiValuedObjectField_nested_singleValuedProjection() { .hasMessageContaining( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/EntityProjectionIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/EntityProjectionIT.java index ad8e0fb0b49..6d85f087109 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/EntityProjectionIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/EntityProjectionIT.java @@ -21,6 +21,7 @@ import org.hibernate.search.engine.common.EntityReference; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; import org.hibernate.search.engine.search.loading.spi.SearchLoadingContext; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.definition.spi.CompositeProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.ProjectionRegistry; import org.hibernate.search.engine.search.query.SearchQuery; @@ -122,7 +123,7 @@ void projectionRegistryFallback_noLoadingAvailable_withProjectionRegistryEntry_i .select( f -> f.composite().from( f.object( binding.nested.absolutePath ) .from( f.field( binding.nested.fieldPath(), String.class ) ) - .asList().multi(), + .asList().collector( ProjectionCollector.list() ), f.entity() ).asList() ) diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionBaseIT.java index 7eaeba53010..986ad72eba9 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionBaseIT.java @@ -11,6 +11,7 @@ import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.dsl.SearchableProjectableIndexFieldTypeOptionsStep; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.FieldTypeDescriptor; @@ -143,7 +144,7 @@ protected ProjectionFinalStep singleValuedProjection(SearchProjectionFactory< @Override protected ProjectionFinalStep> multiValuedProjection(SearchProjectionFactory f, String absoluteFieldPath, DataSet> dataSet) { - return f.field( absoluteFieldPath, dataSet.fieldType.getJavaType() ).multi(); + return f.field( absoluteFieldPath, dataSet.fieldType.getJavaType() ).collector( ProjectionCollector.list() ); } } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionMultiValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionMultiValuedBaseIT.java index 50bf4db0aac..9a71ae593cd 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionMultiValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionMultiValuedBaseIT.java @@ -6,11 +6,17 @@ import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery; import static org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMapperUtils.documentProvider; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.TreeSet; import java.util.function.Function; import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; @@ -88,6 +94,7 @@ static void setup() { indexer.join(); } + @Deprecated @ParameterizedTest(name = "{0} - {1}") @MethodSource("params") void simple(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { @@ -108,6 +115,69 @@ void simple(TestedFieldStructure fieldStructure, FieldTypeDescriptor field ); } + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void simpleList(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + assertThatQuery( scope.query() + .select( f -> f.field( fieldPath, fieldType.getJavaType() ).list() ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + dataSet.getFieldValues( 1 ), + dataSet.getFieldValues( 2 ), + dataSet.getFieldValues( 3 ), + Collections.emptyList() // Empty document + ); + } + + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void simpleSet(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + assertThatQuery( scope.query() + .select( f -> f.field( fieldPath, fieldType.getJavaType() ).set() ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + new HashSet<>( dataSet.getFieldValues( 1 ) ), + new HashSet<>( dataSet.getFieldValues( 2 ) ), + new HashSet<>( dataSet.getFieldValues( 3 ) ), + Collections.emptySet() // Empty document + ); + } + + @SuppressWarnings("unchecked") + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void simpleArray(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + assertThatQuery( scope.query() + .select( f -> f.field( fieldPath, fieldType.getJavaType() ) + .array( fieldType.getJavaType() ) ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + dataSet.getFieldValues( 1 ).toArray( n -> (F[]) Array.newInstance( fieldType.getJavaType(), n ) ), + dataSet.getFieldValues( 2 ).toArray( n -> (F[]) Array.newInstance( fieldType.getJavaType(), n ) ), + dataSet.getFieldValues( 3 ).toArray( n -> (F[]) Array.newInstance( fieldType.getJavaType(), n ) ), + (F[]) Array.newInstance( fieldType.getJavaType(), 0 ) // Empty document + ); + + } + @ParameterizedTest(name = "{0} - {1}") @MethodSource("params") void noClass(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { @@ -116,7 +186,7 @@ void noClass(TestedFieldStructure fieldStructure, FieldTypeDescriptor fiel String fieldPath = getFieldPath( fieldStructure, fieldType ); assertThatQuery( scope.query() - .select( f -> f.field( fieldPath ).multi() ) + .select( f -> f.field( fieldPath ).list() ) .where( f -> f.matchAll() ) .routing( dataSet.routingKey ) .toQuery() ) @@ -140,8 +210,8 @@ void duplicated(TestedFieldStructure fieldStructure, FieldTypeDescriptor f assertThatQuery( scope.query() .select( f -> f.composite( - f.field( fieldPath, fieldType.getJavaType() ).multi(), - f.field( fieldPath, fieldType.getJavaType() ).multi() + f.field( fieldPath, fieldType.getJavaType() ).list(), + f.field( fieldPath, fieldType.getJavaType() ).list() ) ) .where( f -> f.matchAll() ) @@ -155,6 +225,134 @@ void duplicated(TestedFieldStructure fieldStructure, FieldTypeDescriptor f ); } + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void set(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + var multi = scope.projection().field( fieldPath, fieldType.getJavaType() ) + .set() + .toProjection(); + + assertThatQuery( scope.query() + .select( multi ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + new HashSet<>( dataSet.getFieldValues( 1 ) ), + new HashSet<>( dataSet.getFieldValues( 2 ) ), + new HashSet<>( dataSet.getFieldValues( 3 ) ), + Collections.emptySet() // Empty document + ); + } + + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void sortedSet(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + assumeTrue( Comparable.class.isAssignableFrom( fieldType.getJavaType() ), + "Some types that cannot be compared will fail the test as they cannot be added to a set without a comparator defined" ); + + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + var multi = scope.projection().field( fieldPath, fieldType.getJavaType() ) + .sortedSet() + .toProjection(); + + assertThatQuery( scope.query() + .select( multi ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + new TreeSet<>( dataSet.getFieldValues( 1 ) ), + new TreeSet<>( dataSet.getFieldValues( 2 ) ), + new TreeSet<>( dataSet.getFieldValues( 3 ) ), + Collections.emptySortedSet() // Empty document + ); + } + + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void sortedSetComparator(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + Comparator comparator = Comparator.comparing( Objects::toString ); + var multi = scope.projection().field( fieldPath, fieldType.getJavaType() ) + .sortedSet( comparator ) + .toProjection(); + + assertThatQuery( scope.query() + .select( multi ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + treeSet( comparator, dataSet.getFieldValues( 1 ) ), + treeSet( comparator, dataSet.getFieldValues( 2 ) ), + treeSet( comparator, dataSet.getFieldValues( 3 ) ), + Collections.emptySortedSet() // Empty document + ); + } + + private static TreeSet treeSet(Comparator comparator, List values) { + TreeSet set = new TreeSet<>( comparator ); + set.addAll( values ); + return set; + } + + @SuppressWarnings("unchecked") + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void array(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + var multi = scope.projection().field( fieldPath, fieldType.getJavaType() ) + .array( fieldType.getJavaType() ) + .toProjection(); + + F[] arrayToConvert = (F[]) Array.newInstance( fieldType.getJavaType(), 0 ); + assertThatQuery( scope.query() + .select( multi ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + dataSet.getFieldValues( 1 ).toArray( arrayToConvert ), + dataSet.getFieldValues( 2 ).toArray( arrayToConvert ), + dataSet.getFieldValues( 3 ).toArray( arrayToConvert ), + arrayToConvert // Empty document + ); + } + + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void explicitList(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + assertThatQuery( scope.query() + .select( f -> f.field( fieldPath, fieldType.getJavaType() ).list() ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() ) + .hasHitsAnyOrder( + dataSet.getFieldValues( 1 ), + dataSet.getFieldValues( 2 ), + dataSet.getFieldValues( 3 ), + Collections.emptyList() // Empty document + ); + } + private String getFieldPath(TestedFieldStructure fieldStructure, FieldTypeDescriptor fieldType) { return index.binding().getFieldPath( fieldStructure, fieldType ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionSingleValuedBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionSingleValuedBaseIT.java index 7344e381631..412e42eb625 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionSingleValuedBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionSingleValuedBaseIT.java @@ -4,6 +4,7 @@ */ package org.hibernate.search.integrationtest.backend.tck.search.projection; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery; import static org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMapperUtils.documentProvider; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -12,6 +13,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.Function; import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; @@ -111,6 +113,29 @@ void simple(TestedFieldStructure fieldStructure, ); } + @ParameterizedTest(name = "{0} - {1}") + @MethodSource("params") + void optional(TestedFieldStructure fieldStructure, + FieldTypeDescriptor fieldType, DataSet dataSet) { + StubMappingScope scope = index.createScope(); + + String fieldPath = getFieldPath( fieldStructure, fieldType ); + + assertThat( scope.query() + .select( f -> f.field( fieldPath, fieldType.getJavaType() ).optional() ) + .where( f -> f.matchAll() ) + .routing( dataSet.routingKey ) + .toQuery() + .fetchAllHits() ) + .usingRecursiveFieldByFieldElementComparator() + .contains( + Optional.of( dataSet.getFieldValue( 1 ) ), + Optional.of( dataSet.getFieldValue( 2 ) ), + Optional.of( dataSet.getFieldValue( 3 ) ), + Optional.empty() // Empty document + ); + } + @ParameterizedTest(name = "{0} - {1}") @MethodSource("params") void noClass(TestedFieldStructure fieldStructure, @@ -145,7 +170,7 @@ void multi(TestedFieldStructure fieldStructure, String fieldPath = getFieldPath( fieldStructure, fieldType ); assertThatQuery( scope.query() - .select( f -> f.field( fieldPath, fieldType.getJavaType() ).multi() ) + .select( f -> f.field( fieldPath, fieldType.getJavaType() ).list() ) .where( f -> f.matchAll() ) .routing( dataSet.routingKey ) .toQuery() ) diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionTypeCheckingAndConversionIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionTypeCheckingAndConversionIT.java index 533f45eb512..dd9643d08f2 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionTypeCheckingAndConversionIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/FieldProjectionTypeCheckingAndConversionIT.java @@ -185,7 +185,7 @@ void multiValuedField_singleValuedProjection(FieldTypeDescriptor fieldType .hasMessageContainingAll( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } @@ -204,7 +204,7 @@ void singleValuedFieldInMultiValuedObjectField_flattened_singleValuedProjection( .hasMessageContaining( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } @@ -223,7 +223,7 @@ void singleValuedFieldInMultiValuedObjectField_nested_singleValuedProjection(Fie .hasMessageContaining( "Invalid cardinality for projection on field '" + fieldPath + "'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/ObjectProjectionSpecificsIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/ObjectProjectionSpecificsIT.java index fc6cacc3104..ac2651743f3 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/ObjectProjectionSpecificsIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/ObjectProjectionSpecificsIT.java @@ -16,6 +16,7 @@ import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField; import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.integrationtest.backend.tck.testsupport.util.TckConfiguration; import org.hibernate.search.integrationtest.backend.tck.testsupport.util.extension.SearchSetupHelper; @@ -88,10 +89,10 @@ void innerObjectProjectionOnFieldOutsideOuterObjectProjectionFieldTree() { f.object( "level1" ) .from( f.field( "level1.field1" ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList() - .multi() ) + .collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() ) .isInstanceOf( SearchException.class ) @@ -132,7 +133,7 @@ void multiValuedObjectField_singleValuedObjectProjection() { .hasMessageContainingAll( "Invalid cardinality for projection on field 'level1'", "the projection is single-valued, but this field is multi-valued", - "Make sure to call '.multi()' when you create the projection" + "Make sure to call '.collector(...)' when you create the projection" ); } @@ -151,9 +152,9 @@ void singleValuedObjectField_effectivelyMultiValuedInContext() { "Invalid cardinality for projection on field 'level1WithSingleValuedLevel2.level2'", "the projection is single-valued, but this field is effectively multi-valued in this context", "because parent object field 'level1WithSingleValuedLevel2' is multi-valued", - "call '.multi()' when you create the projection on field 'level1WithSingleValuedLevel2.level2'", + "call '.collector(...)' when you create the projection on field 'level1WithSingleValuedLevel2.level2'", "or wrap that projection in an object projection like this:" - + " 'f.object(\"level1WithSingleValuedLevel2\").from().as(...).multi()'." + + " 'f.object(\"level1WithSingleValuedLevel2\").from().as(...).collector(...)'." ); } diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/UnsupportedNestingProjectionBaseIT.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/UnsupportedNestingProjectionBaseIT.java index a27647ad8c2..9ee02c51925 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/UnsupportedNestingProjectionBaseIT.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/search/projection/UnsupportedNestingProjectionBaseIT.java @@ -12,6 +12,7 @@ import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField; import org.hibernate.search.engine.backend.types.ObjectStructure; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.integrationtest.backend.tck.testsupport.util.extension.SearchSetupHelper; import org.hibernate.search.util.common.SearchException; import org.hibernate.search.util.impl.integrationtest.mapper.stub.SimpleMappedIndex; @@ -39,7 +40,7 @@ void id() { assertThatThrownBy( () -> index.createScope().query() .select( f -> f.object( "nested" ).from( f.id() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -56,7 +57,7 @@ void entity() { assertThatThrownBy( () -> index.createScope().query() .select( f -> f.object( "nested" ).from( f.entity() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -73,7 +74,7 @@ void entityReference() { assertThatThrownBy( () -> index.createScope().query() .select( f -> f.object( "nested" ).from( f.entityReference() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -90,7 +91,7 @@ void documentReference() { assertThatThrownBy( () -> index.createScope().query() .select( f -> f.object( "nested" ).from( f.documentReference() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() @@ -107,7 +108,7 @@ void score() { assertThatThrownBy( () -> index.createScope().query() .select( f -> f.object( "nested" ).from( f.score() - ).asList().multi() + ).asList().collector( ProjectionCollector.list() ) ) .where( f -> f.matchAll() ) .toQuery() diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/TckBackendFeatures.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/TckBackendFeatures.java index 05bbeb8537e..df62b670195 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/TckBackendFeatures.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/TckBackendFeatures.java @@ -10,6 +10,7 @@ import org.hibernate.search.engine.backend.types.ObjectStructure; import org.hibernate.search.engine.backend.types.VectorSimilarity; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.integrationtest.backend.tck.testsupport.types.FieldTypeDescriptor; import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingBackendFeatures; @@ -221,4 +222,8 @@ public Double toDoubleValue(FieldTypeDescriptor descriptor, F fieldVal public boolean rawAggregationProduceSensibleDoubleValue(FieldTypeDescriptor fFieldTypeDescriptor) { return true; } + + public R accumulatedNullValue(ProjectionCollector.Provider collector) { + return collector.get().empty(); + } } diff --git a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorFieldProjectionIT.java b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorFieldProjectionIT.java index 938153ae9f9..d78f57ba1b5 100644 --- a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorFieldProjectionIT.java +++ b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorFieldProjectionIT.java @@ -14,6 +14,7 @@ import java.util.Set; import org.hibernate.search.engine.search.common.ValueModel; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FieldProjection; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; @@ -24,7 +25,6 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor; import org.hibernate.search.mapper.pojo.standalone.mapping.SearchMapping; import org.hibernate.search.util.common.SearchException; -import org.hibernate.search.util.impl.integrationtest.common.reporting.FailureReportUtils; import org.hibernate.search.util.impl.integrationtest.mapper.pojo.standalone.StandalonePojoMappingSetupHelper; import org.hibernate.search.util.impl.test.annotation.TestForIssue; @@ -580,8 +580,8 @@ public MyProjection(@FieldProjection List text, @FieldProjection List f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -631,8 +631,8 @@ public MyProjection(@FieldProjection Collection text, @FieldProjection C f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -682,8 +682,8 @@ public MyProjection(@FieldProjection Iterable text, @FieldProjection Ite f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -717,19 +717,33 @@ public MyProjection(@FieldProjection Set text, @FieldProjection List setupHelper.start() + backendMock.expectAnySchema( INDEX_NAME ); + SearchMapping mapping = setupHelper.start() .withAnnotatedTypes( MyProjection.class ) - .setup( IndexedEntity.class ) ) - .isInstanceOf( SearchException.class ) - .satisfies( FailureReportUtils.hasFailureReport() - .typeContext( MyProjection.class.getName() ) - .constructorContext( ProjectionConstructorFieldProjectionIT.class, Set.class, List.class ) - .methodParameterContext( 1, "text" ) - .failure( "Invalid parameter type for projection constructor", - "java.util.Set", - "When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" ) ); + .setup( IndexedEntity.class ); + + testSuccessfulRootProjection( + mapping, IndexedEntity.class, MyProjection.class, + Arrays.asList( + Arrays.asList( Set.of( "result1_1", "result1_2" ), Arrays.asList( 11, 12 ) ), + Arrays.asList( Set.of( "result2_1" ), Arrays.asList( 21 ) ), + Arrays.asList( Set.of(), Collections.emptyList() ), + Arrays.asList( Set.of( "result4_1" ), Arrays.asList( 41 ) ) + ), + f -> f.composite() + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "text", String.class ).collector( ProjectionCollector.set() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) + ) + .asList(), + Arrays.asList( + new MyProjection( Set.of( "result1_1", "result1_2" ), Arrays.asList( 11, 12 ) ), + new MyProjection( Set.of( "result2_1" ), Arrays.asList( 21 ) ), + new MyProjection( Set.of(), Collections.emptyList() ), + new MyProjection( Set.of( "result4_1" ), Arrays.asList( 41 ) ) + ) + ); } } diff --git a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorHighlightProjectionIT.java b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorHighlightProjectionIT.java index 364689312ef..20587c4a440 100644 --- a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorHighlightProjectionIT.java +++ b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorHighlightProjectionIT.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Set; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; @@ -236,7 +237,7 @@ public MyProjection(@HighlightProjection String text) { f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.highlight( "text" ).single() + f.highlight( "text" ).collector( ProjectionCollector.nullable() ) ) .asList(), Arrays.asList( @@ -422,16 +423,28 @@ public MyProjection(@HighlightProjection Set text) { } } - assertThatThrownBy( () -> setupHelper.start() + backendMock.expectAnySchema( INDEX_NAME ); + SearchMapping mapping = setupHelper.start() .withAnnotatedTypes( MyProjection.class ) - .setup( IndexedEntity.class ) ) - .isInstanceOf( SearchException.class ) - .satisfies( FailureReportUtils.hasFailureReport() - .typeContext( MyProjection.class.getName() ) - .constructorContext( ProjectionConstructorHighlightProjectionIT.class, Set.class ) - .methodParameterContext( 1, "text" ) - .failure( "Invalid parameter type for projection constructor: java.util.Set", - "When inferring the cardinality of inner projections from constructor parameters, multi-valued constructor parameters must be lists (java.util.List<...>) or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" ) ); + .setup( IndexedEntity.class ); + + testSuccessfulRootProjection( + mapping, IndexedEntity.class, MyProjection.class, + Arrays.asList( + Arrays.asList( Arrays.asList( "result1_term1", "result1_term2" ) ), + Arrays.asList( Arrays.asList( "result1_term1" ) ) + ), + f -> f.composite() + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.highlight( "text" ) + ) + .asList(), + Arrays.asList( + new MyProjection( Set.of( "result1_term1", "result1_term2" ) ), + new MyProjection( Set.of( "result1_term1" ) ) + ) + ); } @Test diff --git a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorInnerInferredIT.java b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorInnerInferredIT.java index 1e06cffee73..626af40cd0b 100644 --- a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorInnerInferredIT.java +++ b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorInnerInferredIT.java @@ -4,8 +4,6 @@ */ package org.hibernate.search.integrationtest.mapper.pojo.mapping.definition; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.Collection; @@ -13,6 +11,7 @@ import java.util.List; import java.util.Set; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; @@ -20,8 +19,6 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor; import org.hibernate.search.mapper.pojo.standalone.mapping.SearchMapping; -import org.hibernate.search.util.common.SearchException; -import org.hibernate.search.util.impl.integrationtest.common.reporting.FailureReportUtils; import org.hibernate.search.util.impl.integrationtest.mapper.pojo.standalone.StandalonePojoMappingSetupHelper; import org.hibernate.search.util.impl.test.annotation.TestForIssue; @@ -120,8 +117,8 @@ public MyProjection(List text, List integer) { f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -171,8 +168,8 @@ public MyProjection(Collection text, Collection integer) { f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -222,8 +219,8 @@ public MyProjection(Iterable text, Iterable integer) { f -> f.composite() .from( dummyProjectionForEnclosingClassInstance( f ), - f.field( "text", String.class ).multi(), - f.field( "integer", Integer.class ).multi() + f.field( "text", String.class ).collector( ProjectionCollector.list() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -257,19 +254,33 @@ public MyProjection(Set text, List integer) { } } - assertThatThrownBy( () -> setupHelper.start() + backendMock.expectAnySchema( INDEX_NAME ); + SearchMapping mapping = setupHelper.start() .withAnnotatedTypes( MyProjection.class ) - .setup( IndexedEntity.class ) ) - .isInstanceOf( SearchException.class ) - .satisfies( FailureReportUtils.hasFailureReport() - .typeContext( MyProjection.class.getName() ) - .constructorContext( ProjectionConstructorInnerInferredIT.class, Set.class, List.class ) - .methodParameterContext( 1, "text" ) - .failure( "Invalid parameter type for projection constructor", - "java.util.Set", - "When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" ) ); + .setup( IndexedEntity.class ); + + testSuccessfulRootProjection( + mapping, IndexedEntity.class, MyProjection.class, + Arrays.asList( + Arrays.asList( Set.of( "result1_1", "result1_2" ), Arrays.asList( 11, 12 ) ), + Arrays.asList( Set.of( "result2_1" ), Arrays.asList( 21 ) ), + Arrays.asList( Set.of(), Collections.emptyList() ), + Arrays.asList( Set.of( "result4_1" ), Arrays.asList( 41 ) ) + ), + f -> f.composite() + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "text", String.class ).collector( ProjectionCollector.set() ), + f.field( "integer", Integer.class ).collector( ProjectionCollector.list() ) + ) + .asList(), + Arrays.asList( + new MyProjection( Set.of( "result1_1", "result1_2" ), Arrays.asList( 11, 12 ) ), + new MyProjection( Set.of( "result2_1" ), Arrays.asList( 21 ) ), + new MyProjection( Set.of(), Collections.emptyList() ), + new MyProjection( Set.of( "result4_1" ), Arrays.asList( 41 ) ) + ) + ); } @Test @@ -493,7 +504,7 @@ public MyProjection(String text, List contained) { f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -583,7 +594,7 @@ public MyProjection(String text, Collection contained) { f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -673,7 +684,7 @@ public MyProjection(String text, Iterable contained) { f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -732,19 +743,54 @@ public MyProjection(String text, Set contained) { } } - assertThatThrownBy( () -> setupHelper.start() - .withAnnotatedTypes( MyProjection.class ) - .setup( IndexedEntity.class ) ) - .isInstanceOf( SearchException.class ) - .satisfies( FailureReportUtils.hasFailureReport() - .typeContext( MyProjection.class.getName() ) - .constructorContext( ProjectionConstructorInnerInferredIT.class, String.class, Set.class ) - .methodParameterContext( 2, "contained" ) - .failure( "Invalid parameter type for projection constructor", - "java.util.Set<" + MyInnerProjection.class.getName() + ">", - "When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" ) ); + backendMock.expectAnySchema( INDEX_NAME ); + SearchMapping mapping = setupHelper.start() + .withAnnotatedTypes( MyProjection.class, MyInnerProjection.class ) + .setup( IndexedEntity.class ); + + testSuccessfulRootProjection( + mapping, IndexedEntity.class, MyProjection.class, + Arrays.asList( + Arrays.asList( "result1", Set.of( + Arrays.asList( "result1_1", 11 ), + Arrays.asList( "result1_2", 12 ) + ) ), + Arrays.asList( "result2", Set.of( + Arrays.asList( "result2_1", 21 ) + ) ), + Arrays.asList( "result3", Set.of() ), + Arrays.asList( "result4", Set.of( + Arrays.asList( "result4_1", 41 ) + ) ) + ), + f -> f.composite() + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "text", String.class ), + f.object( "contained" ) + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "contained.text", String.class ), + f.field( "contained.integer", Integer.class ) + ) + .asList() + .collector( ProjectionCollector.set() ) + ) + .asList(), + Arrays.asList( + new MyProjection( "result1", Set.of( + new MyInnerProjection( "result1_1", 11 ), + new MyInnerProjection( "result1_2", 12 ) + ) ), + new MyProjection( "result2", Set.of( + new MyInnerProjection( "result2_1", 21 ) + ) ), + new MyProjection( "result3", Set.of() ), + new MyProjection( "result4", Set.of( + new MyInnerProjection( "result4_1", 41 ) + ) ) + ) + ); } } diff --git a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorObjectProjectionIT.java b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorObjectProjectionIT.java index 07d5a2669a0..43d7f2da1c8 100644 --- a/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorObjectProjectionIT.java +++ b/integrationtest/mapper/pojo-base/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/mapping/definition/ProjectionConstructorObjectProjectionIT.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Set; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; @@ -705,7 +706,7 @@ public MyProjection(String text, @ObjectProjection List conta f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -795,7 +796,7 @@ public MyProjection(String text, @ObjectProjection Collection f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -885,7 +886,7 @@ public MyProjection(String text, @ObjectProjection Iterable c f.field( "contained.integer", Integer.class ) ) .asList() - .multi() + .collector( ProjectionCollector.list() ) ) .asList(), Arrays.asList( @@ -944,18 +945,53 @@ public MyProjection(String text, @ObjectProjection Set contai } } - assertThatThrownBy( () -> setupHelper.start() - .withAnnotatedTypes( MyProjection.class ) - .setup( IndexedEntity.class ) ) - .isInstanceOf( SearchException.class ) - .satisfies( FailureReportUtils.hasFailureReport() - .typeContext( MyProjection.class.getName() ) - .constructorContext( ProjectionConstructorObjectProjectionIT.class, String.class, Set.class ) - .methodParameterContext( 2, "contained" ) - .failure( "Invalid parameter type for projection constructor", - "java.util.Set<" + MyInnerProjection.class.getName() + ">", - "When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" ) ); + backendMock.expectAnySchema( INDEX_NAME ); + SearchMapping mapping = setupHelper.start() + .withAnnotatedTypes( MyProjection.class, MyInnerProjection.class ) + .setup( IndexedEntity.class ); + + testSuccessfulRootProjection( + mapping, IndexedEntity.class, MyProjection.class, + Arrays.asList( + Arrays.asList( "result1", Arrays.asList( + Arrays.asList( "result1_1", 11 ), + Arrays.asList( "result1_2", 12 ) + ) ), + Arrays.asList( "result2", Arrays.asList( + Arrays.asList( "result2_1", 21 ) + ) ), + Arrays.asList( "result3", Collections.emptyList() ), + Arrays.asList( "result4", Arrays.asList( + Arrays.asList( "result4_1", 41 ) + ) ) + ), + f -> f.composite() + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "text", String.class ), + f.object( "contained" ) + .from( + dummyProjectionForEnclosingClassInstance( f ), + f.field( "contained.text", String.class ), + f.field( "contained.integer", Integer.class ) + ) + .asList() + .collector( ProjectionCollector.set() ) + ) + .asList(), + Arrays.asList( + new MyProjection( "result1", Set.of( + new MyInnerProjection( "result1_1", 11 ), + new MyInnerProjection( "result1_2", 12 ) + ) ), + new MyProjection( "result2", Set.of( + new MyInnerProjection( "result2_1", 21 ) + ) ), + new MyProjection( "result3", Set.of() ), + new MyProjection( "result4", Set.of( + new MyInnerProjection( "result4_1", 41 ) + ) ) + ) + ); } } diff --git a/integrationtest/mapper/pojo-standalone-realbackend/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/standalone/realbackend/mapping/ProjectionConstructorDistanceProjectionIT.java b/integrationtest/mapper/pojo-standalone-realbackend/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/standalone/realbackend/mapping/ProjectionConstructorDistanceProjectionIT.java index 99bffd5d9a2..828f71ea77d 100644 --- a/integrationtest/mapper/pojo-standalone-realbackend/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/standalone/realbackend/mapping/ProjectionConstructorDistanceProjectionIT.java +++ b/integrationtest/mapper/pojo-standalone-realbackend/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/standalone/realbackend/mapping/ProjectionConstructorDistanceProjectionIT.java @@ -337,6 +337,6 @@ public MyProjection(@DistanceProjection(fromParam = "param", path = "points") Li .setup( IndexedEntity.class ) ).isInstanceOf( SearchException.class ) .hasMessageContainingAll( - "Invalid constructor parameter type: 'java.lang.String'. The distance projection results in values of type 'List'" ); + "Invalid constructor parameter type: 'java.lang.String'. The distance projection results in values of type 'SomeContainer'" ); } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java index 52bca587bd9..239b1915eb2 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java @@ -705,8 +705,9 @@ SearchException invalidAbstractTypeForProjectionConstructor( @Message(id = ID_OFFSET + 115, value = "Invalid parameter type for projection constructor: %1$s." + " When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)") + + " multi-valued constructor parameters must be lists/sets (java.util.List<...>/java.util.Set<...>/java.util.SortedSet<...>)" + + ", their supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" + + " or arrays") SearchException invalidMultiValuedParameterTypeForProjectionConstructor( @FormatWith(PojoTypeModelFormatter.class) PojoTypeModel parentTypeModel); @@ -1046,4 +1047,10 @@ void indexingProgressWithRemainingTime(float estimatePercentileComplete, long do @Message(id = ID_OFFSET + 169, value = "Mass indexing is going to index approx. %1$d entities (%2$s). Actual number may change once the indexing starts.") void indexingEntitiesApprox(long count, String types); + + @Message(id = ID_OFFSET + 170, + value = "Implicit binding of a java.util.SortedSet<%1$s> constructor parameter is not possible since %1$s is not implementing java.lang.Comparable." + + " Either make %1$s implement java.lang.Comparable or create a custom @ProjectionBinding and use the ProjectionCollector.sortedSet(comparator) collector provider in it.") + SearchException cannotBindSortedSetWithNonComparableElements(@FormatWith(ClassFormatter.class) Class elementType, + @Param EventContext eventContext); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingContext.java index 593da93a6bf..18748bca436 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingContext.java @@ -12,11 +12,14 @@ 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.search.projection.ProjectionCollector; +import org.hibernate.search.engine.search.projection.ProjectionCollectorProviderFactory; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectProjection; import org.hibernate.search.mapper.pojo.model.PojoModelConstructorParameter; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.util.common.SearchException; import org.hibernate.search.util.common.annotation.Incubating; @@ -35,9 +38,12 @@ public interface ProjectionBindingContext { * Hibernate Search will check that these expectations are met, and throw an exception if they are not. * @param definition A definition of the projection * to bind to the {@link #constructorParameter()}. - * @param

The type of values returned by the projection. + * @param

The type of single projected value. + * @param The type of values returned by the projection. + * It may be the same as {@code P}, if it is a simple single-valued projection, + * or a type of the container if the {@code P} values are wrapped in some type of container (e.g. {@code Optional<..>}, {@code Collection<..>}..). */ -

void definition(Class

expectedValueType, ProjectionDefinition definition); + void definition(Class

expectedValueType, ProjectionDefinition definition); /** * Binds the {@link #constructorParameter()} to the given projection definition. @@ -47,9 +53,13 @@ public interface ProjectionBindingContext { * Hibernate Search will check that these expectations are met, and throw an exception if they are not. * @param definitionHolder A {@link BeanHolder} containing the definition of the projection * to bind to the {@link #constructorParameter()}. - * @param

The type of values returned by the projection. + * @param

The type of single projected value. + * @param The type of values returned by the projection. + * It may be the same as {@code P}, if it is a simple single-valued projection, + * or a type of the container if the {@code P} values are wrapped in some type of container (e.g. {@code Optional<..>}, {@code Collection<..>}..). */ -

void definition(Class

expectedValueType, BeanHolder> definitionHolder); + void definition(Class

expectedValueType, + BeanHolder> definitionHolder); /** * Inspects the type of the {@link #constructorParameter()} @@ -58,7 +68,9 @@ public interface ProjectionBindingContext { * @return An optional containing a context that can be used to bind a projection * if the type of the {@link #constructorParameter()} can be bound to a multi-valued projection; * an empty optional otherwise. + * @deprecated Use {@link #containerElement()} and various bind methods of this context instead. */ + @Deprecated(since = "8.0") @Incubating Optional multi(); @@ -74,6 +86,14 @@ public interface ProjectionBindingContext { @Incubating PojoModelConstructorParameter constructorParameter(); + /** + * @return An entry point allowing to inspect the constructor parameter container element being bound to a projection. + * Returns non-empty optional only if a {@link #constructorParameter()} can be bound to a container-wrapped projection + * (be it a single-valued optional, or some multi-valued collection). + */ + @Incubating + Optional> containerElement(); + /** * @param name The name of the parameter. * @return The value provided for this parameter. @@ -135,8 +155,10 @@ default Optional paramOptional(String name) { * @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class) */ @Incubating - BeanHolder> createObjectDefinition(String fieldPath, Class projectedType, - TreeFilterDefinition filter); + default BeanHolder> createObjectDefinition(String fieldPath, Class projectedType, + TreeFilterDefinition filter) { + return createObjectDefinition( fieldPath, projectedType, filter, ProjectionCollector.nullable() ); + } /** * @param fieldPath The (relative) path to an object field in the indexed document. @@ -149,10 +171,30 @@ BeanHolder> createObjectDefinition(String * @throws SearchException If mapping the given type to a projection definition fails. * @see org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory#object(String) * @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class) + * @deprecated Use {@link #createObjectDefinition(String, Class, TreeFilterDefinition, ProjectionCollector.Provider)} instead. */ + @Deprecated(since = "8.0") @Incubating - BeanHolder>> createObjectDefinitionMulti(String fieldPath, - Class projectedType, TreeFilterDefinition filter); + default BeanHolder>> createObjectDefinitionMulti(String fieldPath, + Class projectedType, TreeFilterDefinition filter) { + return createObjectDefinition( fieldPath, projectedType, filter, ProjectionCollector.list() ); + } + + /** + * @param fieldPath The (relative) path to an object field in the indexed document. + * @param projectedType A type expected to have a corresponding projection mapping + * (e.g. using {@link org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor}). + * @param filter The filter to apply to determine which nested index field projections should be included in the projection. + * See {@link ObjectProjection#includePaths()}, {@link ObjectProjection#excludePaths()}, + * {@link ObjectProjection#includeDepth()}, ... + * @return A container-wrapped object projection definition for the given type. + * @throws SearchException If mapping the given type to a projection definition fails. + * @see org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory#object(String) + * @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class) + */ + @Incubating + BeanHolder> createObjectDefinition(String fieldPath, + Class projectedType, TreeFilterDefinition filter, ProjectionCollector.Provider collector); /** * @param projectedType A type expected to have a corresponding projection mapping @@ -177,4 +219,11 @@ BeanHolder>> createObjectDefinitionMu */ boolean isIncluded(String fieldPath); + /** + * @return An instance of a projection collector provider factory capable of supplying a collector provider + * based on a container and component types. + */ + @Incubating + ProjectionCollectorProviderFactory projectionCollectorProviderFactory(); + } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java index 5e1c43f2cdd..985fc958149 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java @@ -14,7 +14,9 @@ /** * The context returned by {@link ProjectionBindingContext#multi()}. * @see ProjectionBindingContext#multi() + * @deprecated Use {@link ProjectionBindingContext}/{@link ProjectionBindingContext#containerElement()} instead. */ +@Deprecated(since = "8.0") @Incubating public interface ProjectionBindingMultiContext { diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java index 12ec04e8bc2..febbc6c3a8a 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java @@ -9,6 +9,7 @@ import java.util.function.Function; import org.hibernate.search.engine.environment.bean.BeanHolder; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.definition.spi.ConstantProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.DistanceProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionOptionsStep; @@ -16,9 +17,9 @@ import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.mapper.pojo.logging.impl.Log; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; -import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -94,35 +95,36 @@ public DistanceProjectionBinder unit(DistanceUnit unit) { @Override public void bind(ProjectionBindingContext context) { Contracts.assertNotNullNorEmpty( parameterName, "parameterName" ); - Optional multiOptional = context.multi(); + Optional> containerElementOptional = context.containerElement(); String fieldPath = fieldPathOrFail( context ); - if ( multiOptional.isPresent() ) { - ProjectionBindingMultiContext multi = multiOptional.get(); - if ( !multi.containerElement().rawType().isAssignableFrom( Double.class ) ) { - throw log.invalidParameterTypeForDistanceProjectionInProjectionConstructor( multi.containerElement().rawType(), - "List" ); + Class containerClass; + if ( containerElementOptional.isPresent() ) { + PojoModelValue containerElement = containerElementOptional.get(); + if ( !containerElement.rawType().isAssignableFrom( Double.class ) ) { + throw log.invalidParameterTypeForDistanceProjectionInProjectionConstructor( + containerElement.rawType(), + "SomeContainer" ); } - bind( context, multi, fieldPath ); + containerClass = context.constructorParameter().rawType(); } else { if ( !context.constructorParameter().rawType().isAssignableFrom( Double.class ) ) { throw log.invalidParameterTypeForDistanceProjectionInProjectionConstructor( context.constructorParameter().rawType(), "Double" ); } - bind( context, fieldPath ); + containerClass = null; } + ProjectionCollector.Provider collector = context.projectionCollectorProviderFactory() + .projectionCollectorProvider( containerClass, Double.class ); + bind( context, fieldPath, collector ); } - private void bind(ProjectionBindingContext context, String fieldPath) { + private void bind(ProjectionBindingContext context, String fieldPath, + ProjectionCollector.Provider collector) { context.definition( Double.class, context.isIncluded( fieldPath ) - ? BeanHolder.of( new DistanceProjectionDefinition.SingleValued( fieldPath, parameterName, unit ) ) - : ConstantProjectionDefinition.nullValue() ); - } - - private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, String fieldPath) { - multi.definition( Double.class, context.isIncluded( fieldPath ) - ? BeanHolder.of( new DistanceProjectionDefinition.MultiValued( fieldPath, parameterName, unit ) ) - : ConstantProjectionDefinition.emptyList() ); + ? BeanHolder + .of( new DistanceProjectionDefinition.WrappedValued<>( fieldPath, parameterName, unit, collector ) ) + : ConstantProjectionDefinition.empty( collector ) ); } private String fieldPathOrFail(ProjectionBindingContext context) { diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java index 49fb4c69313..18abc11d9c0 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java @@ -13,9 +13,9 @@ import org.hibernate.search.engine.search.projection.definition.spi.FieldProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.mapper.pojo.logging.impl.Log; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; -import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -89,29 +89,31 @@ public FieldProjectionBinder valueModel(ValueModel valueModel) { @Override public void bind(ProjectionBindingContext context) { - Optional multiOptional = context.multi(); + Optional> containerElementOptional = context.containerElement(); String fieldPath = fieldPathOrFail( context ); - if ( multiOptional.isPresent() ) { - ProjectionBindingMultiContext multi = multiOptional.get(); - bind( context, multi, fieldPath, multi.containerElement().rawType() ); + Class containerClass; + Class containerElementClass; + if ( containerElementOptional.isPresent() ) { + PojoModelValue containerElement = containerElementOptional.get(); + containerElementClass = containerElement.rawType(); + containerClass = context.constructorParameter().rawType(); } else { - bind( context, fieldPath, context.constructorParameter().rawType() ); + containerElementClass = context.constructorParameter().rawType(); + containerClass = null; } + bind( context, fieldPath, containerClass, containerElementClass ); } - private void bind(ProjectionBindingContext context, String fieldPath, Class constructorParameterType) { - context.definition( constructorParameterType, context.isIncluded( fieldPath ) - ? BeanHolder - .of( new FieldProjectionDefinition.SingleValued<>( fieldPath, constructorParameterType, valueModel ) ) - : ConstantProjectionDefinition.nullValue() ); - } + private void bind(ProjectionBindingContext context, String fieldPath, + Class containerType, Class containerElementType) { + var collector = context.projectionCollectorProviderFactory() + .projectionCollectorProvider( containerType, containerElementType ); - private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, String fieldPath, - Class containerElementType) { - multi.definition( containerElementType, context.isIncluded( fieldPath ) - ? BeanHolder.of( new FieldProjectionDefinition.MultiValued<>( fieldPath, containerElementType, valueModel ) ) - : ConstantProjectionDefinition.emptyList() ); + context.definition( containerElementType, context.isIncluded( fieldPath ) + ? BeanHolder.of( new FieldProjectionDefinition.AccumulatedValued<>( fieldPath, containerElementType, + collector, valueModel ) ) + : ConstantProjectionDefinition.empty( collector ) ); } private String fieldPathOrFail(ProjectionBindingContext context) { diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java index b72524df6b4..b32d0a6f741 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java @@ -5,18 +5,18 @@ package org.hibernate.search.mapper.pojo.search.definition.binding.builtin; import java.lang.invoke.MethodHandles; -import java.util.List; import java.util.Optional; import java.util.function.Function; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.definition.spi.AbstractProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.mapper.pojo.logging.impl.Log; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; -import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import org.hibernate.search.util.common.spi.ToStringTreeAppender; @@ -83,21 +83,17 @@ public HighlightProjectionBinder highlighter(String highlighterName) { @Override public void bind(ProjectionBindingContext context) { String fieldPath = fieldPathOrFail( context ); - Class rawType = context.constructorParameter().rawType(); - Optional multiOptional = context.multi(); - - if ( multiOptional.isPresent() ) { - if ( !rawType.isAssignableFrom( List.class ) ) { - throw log.invalidParameterTypeForHighlightProjectionInProjectionConstructor( rawType, "List" ); - } - multiOptional.get().definition( String.class, new Multi( fieldPath, highlighterName ) ); - } - else { - if ( !rawType.isAssignableFrom( String.class ) ) { - throw log.invalidParameterTypeForHighlightProjectionInProjectionConstructor( rawType, "String" ); - } - context.definition( String.class, new Single( fieldPath, highlighterName ) ); - } + Optional> containerElementOptional = context.containerElement(); + + var collector = context.projectionCollectorProviderFactory() + .projectionCollectorProvider( + // if there's no container element, there's no container hence we are working with a "nullable" collector: + containerElementOptional.isPresent() + ? context.constructorParameter().rawType() + : null, + String.class ); + + context.definition( String.class, new Definition<>( fieldPath, highlighterName, collector ) ); } private String fieldPathOrFail(ProjectionBindingContext context) { @@ -111,13 +107,15 @@ private String fieldPathOrFail(ProjectionBindingContext context) { return paramName.get(); } - private abstract static class Definition extends AbstractProjectionDefinition { - protected final String fieldPath; - protected final String highlighterName; + private static class Definition extends AbstractProjectionDefinition { + private final String fieldPath; + private final String highlighterName; + private final ProjectionCollector.Provider collector; - private Definition(String fieldPath, String highlighterName) { + private Definition(String fieldPath, String highlighterName, ProjectionCollector.Provider collector) { this.fieldPath = fieldPath; this.highlighterName = highlighterName; + this.collector = collector; } @Override @@ -125,6 +123,14 @@ protected String type() { return "highlight"; } + @Override + public SearchProjection create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { + return factory.highlight( fieldPath ) + .highlighter( highlighterName ) + .collector( collector ) + .toProjection(); + } + @Override public void appendTo(ToStringTreeAppender appender) { super.appendTo( appender ); @@ -132,30 +138,4 @@ public void appendTo(ToStringTreeAppender appender) { appender.attribute( "highlighter", highlighterName ); } } - - private static class Single extends Definition { - - private Single(String fieldPath, String highlighterName) { - super( fieldPath, highlighterName ); - } - - @Override - public SearchProjection create(SearchProjectionFactory factory, - ProjectionDefinitionContext context) { - return factory.highlight( fieldPath ).highlighter( highlighterName ).single().toProjection(); - } - } - - private static class Multi extends Definition> { - - private Multi(String fieldPath, String highlighterName) { - super( fieldPath, highlighterName ); - } - - @Override - public SearchProjection> create(SearchProjectionFactory factory, - ProjectionDefinitionContext context) { - return factory.highlight( fieldPath ).highlighter( highlighterName ).toProjection(); - } - } } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java index 4725753f560..139934dd3a2 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java @@ -8,11 +8,12 @@ import java.util.Optional; import org.hibernate.search.engine.common.tree.TreeFilterDefinition; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.mapper.pojo.logging.impl.Log; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectProjection; +import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; -import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -91,26 +92,31 @@ public ObjectProjectionBinder filter(TreeFilterDefinition filter) { @Override public void bind(ProjectionBindingContext context) { - Optional multiOptional = context.multi(); + Optional> containerElementOptional = context.containerElement(); String fieldPath = fieldPathOrFail( context ); - if ( multiOptional.isPresent() ) { - ProjectionBindingMultiContext multi = multiOptional.get(); - bind( context, multi, fieldPath, multi.containerElement().rawType() ); + Class containerClass; + Class containerElementClass; + if ( containerElementOptional.isPresent() ) { + PojoModelValue containerElement = containerElementOptional.get(); + containerElementClass = containerElement.rawType(); + containerClass = context.constructorParameter().rawType(); } else { - bind( context, fieldPath, context.constructorParameter().rawType() ); + containerElementClass = context.constructorParameter().rawType(); + containerClass = null; } + bind( context, fieldPath, containerClass, containerElementClass ); } - private void bind(ProjectionBindingContext context, String fieldPath, Class constructorParameterType) { - context.definition( constructorParameterType, - context.createObjectDefinition( fieldPath, constructorParameterType, filter ) ); - } + private void bind(ProjectionBindingContext context, String fieldPath, Class containerType, + Class containerElementType) { + ProjectionCollector.Provider collector = context.projectionCollectorProviderFactory() + .projectionCollectorProvider( containerType, containerElementType ); - private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, - String fieldPath, Class containerElementType) { - multi.definition( containerElementType, - context.createObjectDefinitionMulti( fieldPath, containerElementType, filter ) ); + context.definition( + containerElementType, + context.createObjectDefinition( fieldPath, containerElementType, filter, collector ) + ); } private String fieldPathOrFail(ProjectionBindingContext context) { diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java index 9a969fc3590..f31414446c3 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java @@ -5,9 +5,12 @@ package org.hibernate.search.mapper.pojo.search.definition.binding.impl; import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; import java.util.function.BiFunction; import java.util.function.Function; @@ -17,6 +20,8 @@ import org.hibernate.search.engine.environment.bean.BeanHolder; import org.hibernate.search.engine.environment.bean.BeanResolver; import org.hibernate.search.engine.mapper.model.spi.MappingElement; +import org.hibernate.search.engine.search.projection.ProjectionCollector; +import org.hibernate.search.engine.search.projection.ProjectionCollectorProviderFactory; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.CompositeProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.ConstantProjectionDefinition; @@ -51,22 +56,48 @@ public class ProjectionBindingContextImpl

implements ProjectionBindingContext (mappingElement, cyclicRecursionPath) -> log.objectProjectionCyclicRecursion( mappingElement, mappingElement.eventContext(), cyclicRecursionPath ); + private static final Set CONTAINER_EXTRACTORS = Set.of( + BuiltinContainerExtractors.ARRAY_OBJECT, + BuiltinContainerExtractors.ARRAY_CHAR, + BuiltinContainerExtractors.ARRAY_BOOLEAN, + BuiltinContainerExtractors.ARRAY_BYTE, + BuiltinContainerExtractors.ARRAY_SHORT, + BuiltinContainerExtractors.ARRAY_INT, + BuiltinContainerExtractors.ARRAY_LONG, + BuiltinContainerExtractors.ARRAY_FLOAT, + BuiltinContainerExtractors.ARRAY_DOUBLE, + BuiltinContainerExtractors.COLLECTION, + BuiltinContainerExtractors.ITERABLE, + // and some single ones: + BuiltinContainerExtractors.OPTIONAL, + BuiltinContainerExtractors.OPTIONAL_DOUBLE, + BuiltinContainerExtractors.OPTIONAL_INT, + BuiltinContainerExtractors.OPTIONAL_LONG + ); + + private static final Set MULTI_EXTRACTORS = Set.of( + BuiltinContainerExtractors.COLLECTION, + BuiltinContainerExtractors.ITERABLE + ); + private final PojoMappingHelper mappingHelper; final ProjectionConstructorParameterBinder

parameterBinder; private final Map params; private final PojoTypeModel parameterTypeModel; private final PojoModelConstructorParameterRootElement

parameterRootElement; + private final ProjectionCollectorProviderFactory projectionCollectorProviderFactory; private MappingElement mappingElement; private PartialBinding

partialBinding; - public ProjectionBindingContextImpl(ProjectionConstructorParameterBinder

parameterBinder, + ProjectionBindingContextImpl(ProjectionConstructorParameterBinder

parameterBinder, Map params) { this.mappingHelper = parameterBinder.mappingHelper; this.parameterBinder = parameterBinder; this.params = params; this.parameterTypeModel = parameterBinder.parameter.typeModel(); this.parameterRootElement = parameterBinder.parameterRootElement; + this.projectionCollectorProviderFactory = new BuiltInProjectionCollectorProviderFactory(); } @Override @@ -96,34 +127,46 @@ public Optional paramOptional(String name, Class paramType) { } @Override - public void definition(Class expectedValueType, ProjectionDefinition definition) { + public void definition(Class expectedValueType, ProjectionDefinition definition) { definition( expectedValueType, BeanHolder.of( definition ) ); } @Override - public void definition(Class expectedValueType, - BeanHolder> definitionHolder) { + public void definition(Class expectedValueType, + BeanHolder> definitionHolder) { checkAndBind( definitionHolder, mappingHelper.introspector().typeModel( expectedValueType ) ); } + @SuppressWarnings("deprecation") @Override public Optional> multi() { + PojoTypeModel boundParameterElement = boundParameterElement( MULTI_EXTRACTORS ); + if ( boundParameterElement == null ) { + return Optional.empty(); + } + else { + if ( !mappingHelper.introspector().typeModel( List.class ).isSubTypeOf( parameterTypeModel.rawType() ) ) { + throw log.invalidMultiValuedParameterTypeForProjectionConstructor( parameterTypeModel ); + } + return Optional.of( new MultiContextImpl<>( boundParameterElement ) ); + } + } + + private PojoTypeModel boundParameterElement(Set extractors) { BoundContainerExtractorPath boundParameterElementPath = mappingHelper.indexModelBinder() .bindExtractorPath( parameterTypeModel, ContainerExtractorPath.defaultExtractors() ); List boundParameterElementExtractorNames = boundParameterElementPath.getExtractorPath().explicitExtractorNames(); if ( boundParameterElementExtractorNames.isEmpty() ) { - return Optional.empty(); + return null; } else { if ( boundParameterElementExtractorNames.size() > 1 - || !( BuiltinContainerExtractors.COLLECTION.equals( boundParameterElementExtractorNames.get( 0 ) ) - || BuiltinContainerExtractors.ITERABLE.equals( boundParameterElementExtractorNames.get( 0 ) ) ) - || !mappingHelper.introspector().typeModel( List.class ).isSubTypeOf( parameterTypeModel.rawType() ) ) { + || !( extractors.contains( boundParameterElementExtractorNames.get( 0 ) ) ) ) { throw log.invalidMultiValuedParameterTypeForProjectionConstructor( parameterTypeModel ); } - return Optional.of( new MultiContextImpl<>( boundParameterElementPath.getExtractedType() ) ); + return boundParameterElementPath.getExtractedType(); } } @@ -133,53 +176,48 @@ public PojoModelConstructorParameter constructorParameter() { } @Override - public BeanHolder> createObjectDefinition(String fieldPath, - Class projectedType, TreeFilterDefinition filter) { - Contracts.assertNotNull( fieldPath, "fieldPath" ); - Contracts.assertNotNull( projectedType, "projectedType" ); - Contracts.assertNotNull( filter, "filter" ); - Optional>> objectProjection = nestObjectProjection( - fieldPath, filter, - nestingContext -> { - CompositeProjectionDefinition composite = - createCompositeProjectionDefinition( projectedType, nestingContext ); - try { - return BeanHolder.ofCloseable( new ObjectProjectionDefinition.SingleValued<>( - fieldPath, composite ) ); - } - catch (RuntimeException e) { - new SuppressingCloser( e ) - .push( composite ); - throw e; - } - } - ); - return objectProjection.orElse( ConstantProjectionDefinition.nullValue() ); + public Optional> containerElement() { + return containerElementModel().map( this::getPojoModelValueElement ); + } + + private PojoModelValueElement getPojoModelValueElement(PojoTypeModel m) { + return new PojoModelValueElement<>( mappingHelper.introspector(), m ); + } + + private Optional> containerElementModel() { + PojoTypeModel boundParameterElement = boundParameterElement( CONTAINER_EXTRACTORS ); + if ( boundParameterElement == null ) { + return Optional.empty(); + } + else { + return Optional.of( boundParameterElement ); + } } @Override - public BeanHolder>> createObjectDefinitionMulti(String fieldPath, - Class projectedType, TreeFilterDefinition filter) { + public BeanHolder> createObjectDefinition(String fieldPath, + Class projectedType, TreeFilterDefinition filter, + ProjectionCollector.Provider collector) { Contracts.assertNotNull( fieldPath, "fieldPath" ); Contracts.assertNotNull( projectedType, "projectedType" ); Contracts.assertNotNull( filter, "filter" ); - Optional>>> objectProjection = nestObjectProjection( + Contracts.assertNotNull( collector, "collector" ); + Optional>> objectProjection = nestObjectProjection( fieldPath, filter, nestingContext -> { CompositeProjectionDefinition composite = createCompositeProjectionDefinition( projectedType, nestingContext ); try { - return BeanHolder.ofCloseable( new ObjectProjectionDefinition.MultiValued<>( - fieldPath, composite ) ); + return BeanHolder.ofCloseable( new ObjectProjectionDefinition.WrappedValued<>( + fieldPath, composite, collector ) ); } catch (RuntimeException e) { - new SuppressingCloser( e ) - .push( composite ); + new SuppressingCloser( e ).push( composite ); throw e; } } ); - return objectProjection.orElse( ConstantProjectionDefinition.emptyList() ); + return objectProjection.orElse( ConstantProjectionDefinition.empty( collector ) ); } private Optional nestObjectProjection(String fieldPath, TreeFilterDefinition filter, @@ -232,6 +270,11 @@ public boolean isIncluded(String fieldPath) { (prefixedRelativeName, inclusion) -> TreeNodeInclusion.INCLUDED.equals( inclusion ) ); } + @Override + public ProjectionCollectorProviderFactory projectionCollectorProviderFactory() { + return projectionCollectorProviderFactory; + } + public BeanHolder> applyBinder(ProjectionBinder binder) { try { this.mappingElement = new PojoConstructorParameterProjectionMappingElement( parameterBinder.parent.constructor, @@ -256,9 +299,17 @@ public BeanHolder> applyBinder(Proje } } - private void checkAndBind(BeanHolder> definitionHolder, + private void checkAndBind(BeanHolder> definitionHolder, PojoRawTypeModel expectedValueType) { - if ( !expectedValueType.isSubTypeOf( parameterTypeModel.rawType() ) ) { + Optional> containerElementModel = containerElementModel(); + if ( containerElementModel.isPresent() ) { + if ( !expectedValueType.isSubTypeOf( containerElementModel.get().rawType() ) ) { + throw log.invalidOutputTypeForMultiValuedProjectionDefinition( definitionHolder.get(), parameterTypeModel, + expectedValueType + ); + } + } + else if ( !expectedValueType.isSubTypeOf( parameterTypeModel.rawType() ) ) { throw log.invalidOutputTypeForProjectionDefinition( definitionHolder.get(), parameterTypeModel, expectedValueType ); } @@ -270,15 +321,13 @@ private void checkAndBind(BeanHolder> applyDefaultProjection() { - Optional.MultiContextImpl> multi = multi(); + Optional> containerElement = containerElementModel(); PojoConstructorModel constructorModelOrNull; - if ( multi.isPresent() ) { - constructorModelOrNull = parameterBinder.findProjectionConstructorOrNull( - multi.get().parameterContainerElementTypeModel.rawType() ); + if ( containerElement.isPresent() ) { + constructorModelOrNull = parameterBinder.findProjectionConstructorOrNull( containerElement.get().rawType() ); } else { - constructorModelOrNull = parameterBinder.findProjectionConstructorOrNull( - parameterTypeModel.rawType() ); + constructorModelOrNull = parameterBinder.findProjectionConstructorOrNull( parameterTypeModel.rawType() ); } Optional paramName = parameterRootElement.name(); if ( !paramName.isPresent() ) { @@ -310,6 +359,7 @@ BeanHolder> complete() { } } + @Deprecated(since = "8.0") public class MultiContextImpl implements ProjectionBindingMultiContext { public final PojoTypeModel parameterContainerElementTypeModel; private final PojoModelValue parameterContainerElementRootElement; @@ -333,23 +383,48 @@ public void definition(Class expectedValueType, } @Override - public PojoModelValue containerElement() { + public PojoModelValue containerElement() { return parameterContainerElementRootElement; } - private void checkAndBind( - BeanHolder>> definitionHolder, - PojoRawTypeModel expectedValueType) { - if ( !expectedValueType.isSubTypeOf( parameterContainerElementTypeModel.rawType() ) ) { - throw log.invalidOutputTypeForMultiValuedProjectionDefinition( definitionHolder.get(), parameterTypeModel, - expectedValueType ); - } + } - @SuppressWarnings("unchecked") // We check that P2 extends PV explicitly using reflection (see above) and we've already checked that P = List - BeanHolder> castDefinitionHolder = - (BeanHolder>) definitionHolder; + private class BuiltInProjectionCollectorProviderFactory implements ProjectionCollectorProviderFactory { + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public ProjectionCollector.Provider projectionCollectorProvider(Class containerType, + Class containerElementType) { + ProjectionCollector.Provider reference; + if ( containerType == null ) { + reference = ProjectionCollector.nullable(); + } + else if ( List.class.isAssignableFrom( containerType ) ) { + reference = ProjectionCollector.list(); + } + else if ( SortedSet.class.isAssignableFrom( containerType ) ) { + if ( !Comparable.class.isAssignableFrom( containerElementType ) ) { + throw log.cannotBindSortedSetWithNonComparableElements( + containerElementType, + mappingElement.eventContext() + ); + } + reference = ProjectionCollector.sortedSet(); + } + else if ( Set.class.isAssignableFrom( containerType ) ) { + reference = ProjectionCollector.set(); + } + else if ( containerType.isArray() ) { + reference = ProjectionCollector.array( containerElementType ); + } + else if ( Collection.class.isAssignableFrom( containerType ) || Iterable.class.isAssignableFrom( containerType ) ) { + reference = ProjectionCollector.list(); + } + else { + throw log.invalidMultiValuedParameterTypeForProjectionConstructor( parameterTypeModel ); + } - partialBinding = new PartialBinding<>( castDefinitionHolder ); + return reference; } } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/assertion/TestComparators.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/assertion/TestComparators.java index 71c7fbb1dd0..bdf3d010a82 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/assertion/TestComparators.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/assertion/TestComparators.java @@ -6,6 +6,7 @@ import java.util.Comparator; import java.util.Iterator; +import java.util.Optional; public class TestComparators { @@ -60,4 +61,40 @@ else if ( bIt.hasNext() ) { } } ); } + + public static Comparator> optional(Comparator elementComparator) { + return Comparator.nullsFirst( (a, b) -> { + if ( a.isPresent() && b.isPresent() ) { + return elementComparator.compare( a.get(), b.get() ); + } + if ( a.isEmpty() && b.isEmpty() ) { + return 0; + } + return a.isEmpty() ? -1 : 1; + } ); + } + + public static Comparator array(Comparator elementComparator) { + return Comparator.nullsFirst( (a, b) -> { + int index = 0; + int min = Math.min( a.length, b.length ); + for ( ; index < min; index++ ) { + T aElem = a[index]; + T bElem = b[index]; + int elemOrder = elementComparator.compare( aElem, bElem ); + if ( elemOrder != 0 ) { + return elemOrder; // lexicographical ordering: the first differing element dictates the order. + } + } + if ( a.length < b.length ) { + return -1; // b is a prefix of a + } + else if ( a.length > b.length ) { + return 1; // a is a prefix of b + } + else { + return 0; // a and b have the same number of elements, all equal according to the element comparator + } + } ); + } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubCompositeProjection.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubCompositeProjection.java index edcc6267744..eb270e2921b 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubCompositeProjection.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubCompositeProjection.java @@ -7,51 +7,50 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; -import java.util.List; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; class StubCompositeProjection extends StubSearchProjection

{ private final StubSearchProjection[] inners; private final ProjectionCompositor compositor; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private final boolean singleValued; protected StubCompositeProjection(StubSearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator accumulator, boolean singleValued) { + ProjectionCollector collector, boolean singleValued) { this.inners = inners; this.compositor = compositor; - this.accumulator = accumulator; + this.collector = collector; this.singleValued = singleValued; } @Override public Object extract(ProjectionHitMapper projectionHitMapper, Iterator projectionFromIndex, StubSearchProjectionContext context) { - List allInnerProjectionsFromIndex; + Iterable allInnerProjectionsFromIndex; if ( singleValued ) { Object singleValue = projectionFromIndex.next(); allInnerProjectionsFromIndex = singleValue == null ? Collections.emptyList() : Arrays.asList( singleValue ); } else { - allInnerProjectionsFromIndex = (List) projectionFromIndex.next(); + allInnerProjectionsFromIndex = (Iterable) projectionFromIndex.next(); } - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); for ( Object innerProjectionsFromIndex : allInnerProjectionsFromIndex ) { E extractedData = compositor.createInitial(); - Iterator innerProjectionFromIndex = ( (List) innerProjectionsFromIndex ).iterator(); + Iterator innerProjectionFromIndex = ( (Iterable) innerProjectionsFromIndex ).iterator(); for ( int i = 0; i < inners.length; i++ ) { Object extractedDataForInner = inners[i].extract( projectionHitMapper, innerProjectionFromIndex, context ); extractedData = compositor.set( extractedData, i, extractedDataForInner ); } - accumulated = accumulator.accumulate( accumulated, extractedData ); + accumulated = collector.accumulate( accumulated, extractedData ); } return accumulated; } @@ -60,17 +59,17 @@ public Object extract(ProjectionHitMapper projectionHitMapper, Iterator pr @SuppressWarnings("unchecked") public P transform(LoadingResult loadingResult, Object extractedData, StubSearchProjectionContext context) { A accumulated = (A) extractedData; - for ( int i = 0; i < accumulator.size( accumulated ); i++ ) { - E transformedData = accumulator.get( accumulated, i ); + for ( int i = 0; i < collector.size( accumulated ); i++ ) { + E transformedData = collector.get( accumulated, i ); // Transform in-place for ( int j = 0; j < inners.length; j++ ) { Object extractedDataForInner = compositor.get( transformedData, j ); Object transformedDataForInner = inners[j].transform( loadingResult, extractedDataForInner, context ); transformedData = compositor.set( transformedData, j, transformedDataForInner ); } - accumulated = accumulator.transform( accumulated, i, compositor.finish( transformedData ) ); + accumulated = collector.transform( accumulated, i, compositor.finish( transformedData ) ); } - return accumulator.finish( accumulated ); + return collector.finish( accumulated ); } @Override @@ -82,7 +81,7 @@ protected String typeName() { protected void toNode(StubProjectionNode.Builder self) { // Not including the compositor to facilitate assertions // self.attribute( "compositor", compositor ); - self.attribute( "accumulator", accumulator ); + self.attribute( "collector", collector ); self.attribute( "singleValued", singleValued ); for ( StubSearchProjection inner : inners ) { appendInnerNode( self, "inner", inner ); @@ -96,19 +95,19 @@ static class Builder implements CompositeProjectionBuilder { @Override public final SearchProjection

build(SearchProjection[] inners, ProjectionCompositor compositor, - ProjectionAccumulator.Provider accumulatorProvider) { + ProjectionCollector.Provider collectorProvider) { StubSearchProjection[] typedInners = new StubSearchProjection[inners.length]; for ( int i = 0; i < inners.length; i++ ) { typedInners[i] = StubSearchProjection.from( inners[i] ); } - return doBuild( typedInners, compositor, accumulatorProvider.get(), accumulatorProvider.isSingleValued() ); + return doBuild( typedInners, compositor, collectorProvider.get(), collectorProvider.isSingleValued() ); } protected SearchProjection

doBuild(StubSearchProjection[] typedInners, ProjectionCompositor compositor, - ProjectionAccumulator accumulator, boolean singleValued) { - return new StubCompositeProjection<>( typedInners, compositor, accumulator, singleValued ); + ProjectionCollector collector, boolean singleValued) { + return new StubCompositeProjection<>( typedInners, compositor, collector, singleValued ); } } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubDistanceToFieldProjection.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubDistanceToFieldProjection.java index c49279144cc..82707d8ab37 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubDistanceToFieldProjection.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubDistanceToFieldProjection.java @@ -4,9 +4,9 @@ */ package org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.projection.impl; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.DistanceToFieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; import org.hibernate.search.util.common.AssertionFailure; @@ -39,7 +39,7 @@ public void unit(DistanceUnit unit) { } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { throw new AssertionFailure( "Distance projections are not supported in the stub backend." ); } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldHighlightProjection.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldHighlightProjection.java index b3cb8fac466..ea1df494f89 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldHighlightProjection.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldHighlightProjection.java @@ -8,20 +8,23 @@ import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.dsl.spi.HighlightProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.AbstractStubSearchQueryElementFactory; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.StubSearchIndexNodeContext; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.StubSearchIndexScope; -public class StubFieldHighlightProjection extends StubSearchProjection { +public class StubFieldHighlightProjection extends StubSearchProjection { private final String fieldPath; private final String highlighterName; + private final ProjectionCollector collector; - public StubFieldHighlightProjection(String fieldPath, String highlighterName) { + public StubFieldHighlightProjection(String fieldPath, String highlighterName, + ProjectionCollector collector) { this.fieldPath = fieldPath; this.highlighterName = highlighterName; + this.collector = collector; } @Override @@ -32,9 +35,8 @@ public Object extract(ProjectionHitMapper projectionHitMapper, Iterator pr @Override @SuppressWarnings("unchecked") - public T transform(LoadingResult loadingResult, Object extractedData, - StubSearchProjectionContext context) { - return (T) extractedData; + public T transform(LoadingResult loadingResult, Object extractedData, StubSearchProjectionContext context) { + return collector.finish( (A) extractedData ); } @Override @@ -62,8 +64,8 @@ static class Builder extends HighlightProjectionBuilder { } @Override - public SearchProjection build(ProjectionAccumulator.Provider accumulatorProvider) { - return new StubFieldHighlightProjection<>( path, highlighterName ); + public SearchProjection build(ProjectionCollector.Provider collectorProvider) { + return new StubFieldHighlightProjection<>( path, highlighterName, collectorProvider.get() ); } } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldProjection.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldProjection.java index f2916f756f0..21688cebcaf 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldProjection.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubFieldProjection.java @@ -7,15 +7,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; -import java.util.List; import org.hibernate.search.engine.backend.types.converter.spi.ProjectionConverter; import org.hibernate.search.engine.search.common.ValueModel; import org.hibernate.search.engine.search.loading.spi.LoadingResult; import org.hibernate.search.engine.search.loading.spi.ProjectionHitMapper; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.FieldProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.AbstractStubSearchQueryElementFactory; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.StubSearchIndexNodeContext; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.StubSearchIndexScope; @@ -26,34 +25,34 @@ public class StubFieldProjection extends StubSearchProjection

{ private final Class fieldType; private final Class expectedType; private final ProjectionConverter converter; - private final ProjectionAccumulator accumulator; + private final ProjectionCollector collector; private final boolean singleValued; public StubFieldProjection(String fieldPath, Class fieldType, Class expectedType, ProjectionConverter converter, - ProjectionAccumulator accumulator, boolean singleValued) { + ProjectionCollector collector, boolean singleValued) { this.fieldPath = fieldPath; this.fieldType = fieldType; this.expectedType = expectedType; this.converter = converter; - this.accumulator = accumulator; + this.collector = collector; this.singleValued = singleValued; } @Override public A extract(ProjectionHitMapper projectionHitMapper, Iterator projectionFromIndex, StubSearchProjectionContext context) { - List fieldValues; + Iterable fieldValues; if ( singleValued ) { Object singleValue = projectionFromIndex.next(); fieldValues = singleValue == null ? Collections.emptyList() : Arrays.asList( singleValue ); } else { - fieldValues = (List) projectionFromIndex.next(); + fieldValues = (Iterable) projectionFromIndex.next(); } - A accumulated = accumulator.createInitial(); + A accumulated = collector.createInitial(); for ( Object fieldValue : fieldValues ) { - accumulated = accumulator.accumulate( accumulated, fieldType.cast( fieldValue ) ); + accumulated = collector.accumulate( accumulated, fieldType.cast( fieldValue ) ); } return accumulated; } @@ -63,8 +62,9 @@ public A extract(ProjectionHitMapper projectionHitMapper, Iterator project public P transform(LoadingResult loadingResult, Object extractedData, StubSearchProjectionContext context) { A accumulated = (A) extractedData; - A transformedData = accumulator.transformAll( accumulated, converter, context.fromDocumentValueConvertContext() ); - return accumulator.finish( transformedData ); + A transformedData = collector.transformAll( accumulated, converter.delegate(), + context.fromDocumentValueConvertContext() ); + return collector.finish( transformedData ); } @Override @@ -78,7 +78,7 @@ protected void toNode(StubProjectionNode.Builder self) { self.attribute( "fieldType", fieldType ); self.attribute( "expectedType", expectedType ); self.attribute( "converter", converter ); - self.attribute( "accumulator", accumulator ); + self.attribute( "collector", collector ); self.attribute( "singleValued", singleValued ); } @@ -120,9 +120,9 @@ static class Builder implements FieldProjectionBuilder { } @Override - public

SearchProjection

build(ProjectionAccumulator.Provider accumulatorProvider) { + public

SearchProjection

build(ProjectionCollector.Provider collectorProvider) { return new StubFieldProjection<>( fieldPath, valueClass, expectedType, converter, - accumulatorProvider.get(), accumulatorProvider.isSingleValued() ); + collectorProvider.get(), collectorProvider.isSingleValued() ); } } } diff --git a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubObjectProjection.java b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubObjectProjection.java index 029d4a49d04..b8e6214b032 100644 --- a/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubObjectProjection.java +++ b/util/internal/integrationtest/common/src/main/java/org/hibernate/search/util/impl/integrationtest/common/stub/backend/search/projection/impl/StubObjectProjection.java @@ -4,9 +4,9 @@ */ package org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.projection.impl; +import org.hibernate.search.engine.search.projection.ProjectionCollector; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.spi.CompositeProjectionBuilder; -import org.hibernate.search.engine.search.projection.spi.ProjectionAccumulator; import org.hibernate.search.engine.search.projection.spi.ProjectionCompositor; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.AbstractStubSearchQueryElementFactory; import org.hibernate.search.util.impl.integrationtest.common.stub.backend.search.common.impl.StubSearchIndexNodeContext; @@ -17,9 +17,9 @@ public class StubObjectProjection extends StubCompositeProjection[] inners, - ProjectionCompositor compositor, ProjectionAccumulator accumulator, + ProjectionCompositor compositor, ProjectionCollector collector, boolean singleValued) { - super( inners, compositor, accumulator, singleValued ); + super( inners, compositor, collector, singleValued ); this.objectFieldPath = objectFieldPath; } @@ -51,9 +51,9 @@ static class Builder extends StubCompositeProjection.Builder { @Override protected SearchProjection

doBuild(StubSearchProjection[] typedInners, - ProjectionCompositor compositor, ProjectionAccumulator accumulator, + ProjectionCompositor compositor, ProjectionCollector collector, boolean singleValued) { - return new StubObjectProjection<>( objectFieldPath, typedInners, compositor, accumulator, singleValued ); + return new StubObjectProjection<>( objectFieldPath, typedInners, compositor, collector, singleValued ); } } }