/* * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.hibernate.search.query.collector.impl; import org.apache.lucene.search.Collector; import org.hibernate.search.ProjectionConstants; import org.hibernate.search.bridge.TwoWayStringBridge; import org.hibernate.search.bridge.builtin.StringBridge; import org.hibernate.search.query.fieldcache.impl.FieldCacheLoadingType; import org.hibernate.search.query.fieldcache.impl.FieldLoadingStrategy; /** * Every search needs a fresh instance of a Collector, still for * each field the same name and type are going to be used. * So reuse a {@code FieldCollectorFactory} for each field, to create * {@code Collector} instances as needed. * * @author Sanne Grinovero <sanne@hibernate.org> (C) 2011 Red Hat Inc. */ public class FieldCacheCollectorFactory { public static final FieldCacheCollectorFactory CLASS_TYPE_FIELD_CACHE_COLLECTOR_FACTORY = new FieldCacheCollectorFactory( ProjectionConstants.OBJECT_CLASS, FieldCacheLoadingType.STRING, new StringBridge() ); /** * when the Query is going to collect more than this amount of * documents, it seems an array based collector is better suited. * Below that, we save some memory by using a Map keyed on the array index. * TODO: make this configurable? */ private static final int DEFAULT_IMPLEMENTATION_SWITCH_THRESHOLD = 100; private final String fieldName; private final FieldCacheLoadingType type; private final int implementationSwitchThreshold; private final TwoWayStringBridge twoWayStringBridge; public FieldCacheCollectorFactory(String fieldName, FieldCacheLoadingType type, TwoWayStringBridge twoWayStringBridge) { this( fieldName, type, twoWayStringBridge, DEFAULT_IMPLEMENTATION_SWITCH_THRESHOLD ); } public FieldCacheCollectorFactory(String fieldName, FieldCacheLoadingType type, TwoWayStringBridge twoWayStringBridge, int implementationSwitchThreshold) { if ( fieldName == null ) { throw new IllegalArgumentException( "fieldName is mandatory" ); } if ( type == null ) { throw new IllegalArgumentException( "type is mandatory" ); } this.fieldName = fieldName; this.type = type; this.twoWayStringBridge = twoWayStringBridge; this.implementationSwitchThreshold = implementationSwitchThreshold; } public FieldCacheCollector createFieldCollector(Collector collector, int totalMaxDocs, int expectedMatchesCount) { FieldCacheCollector fieldCollector = createDefaultFieldCollector( collector, totalMaxDocs, expectedMatchesCount, type.createLoadingStrategy( fieldName ) ); if ( twoWayStringBridge != null ) { return new TwoWayTransformingFieldCacheCollector( fieldCollector, twoWayStringBridge ); } else { return fieldCollector; } } /** * There are two possible implementations of {@code FieldCacheCollector}, * one is more efficient for large and one for small results. * Here we try to guesstimate the most appropriate implementation, * which doesn't depend on the type but on the estimated result size * (so it's a per-query decision) * * @param collector the collector to delegate to * @param totalMaxDocs the maximum document count * @param expectedMatchesCount the expected matching document count * @param loadingStrategy the default loading strategy * * @return the most suitable implementation of {@code FieldCacheCollector} depending on the expected count of * collected documents */ private FieldCacheCollector createDefaultFieldCollector( Collector collector, int totalMaxDocs, int expectedMatchesCount, FieldLoadingStrategy loadingStrategy) { if ( expectedMatchesCount > implementationSwitchThreshold ) { return new BigArrayFieldCacheCollectorImpl( collector, loadingStrategy, new String[totalMaxDocs] ); } else { return new MapFieldCacheCollectorImpl( collector, loadingStrategy ); } } // HashCode and Equals are used to detect same kind of FieldCollectorFactory applied on different indexed classes * @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ( ( fieldName == null ) ? 0 : fieldName.hashCode() ); result = prime * result + ( ( type == null ) ? 0 : type.hashCode() ); return result; } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } FieldCacheCollectorFactory other = (FieldCacheCollectorFactory) obj; if ( fieldName == null ) { if ( other.fieldName != null ) { return false; } } else if ( !fieldName.equals( other.fieldName ) ) { return false; } if ( type != other.type ) { return false; } return true; } }