/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.bridge.util.impl;
import java.util.Calendar;
import java.util.Date;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.hibernate.search.bridge.ContainerBridge;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.spi.EncodingBridge;
import org.hibernate.search.metadata.NumericFieldSettingsDescriptor.NumericEncodingType;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* Utility class to handle numeric fields.
*
* @author Gustavo Fernandes
* @author Hardy Ferentschik
*/
public final class NumericFieldUtils {
private static final Log log = LoggerFactory.make();
private NumericFieldUtils() {
//not allowed
}
public static Query createNumericRangeQuery(String fieldName, Object from, Object to,
boolean includeLower, boolean includeUpper) {
Class<?> numericClass;
if ( from != null ) {
numericClass = from.getClass();
}
else if ( to != null ) {
numericClass = to.getClass();
}
else {
throw log.rangeQueryWithNullToAndFromValue( fieldName );
}
if ( Double.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newDoubleRange( fieldName, (Double) from, (Double) to, includeLower, includeUpper );
}
if ( Byte.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newIntRange( fieldName, ( (Byte) from ).intValue(), ( (Byte) to ).intValue(), includeLower, includeUpper );
}
if ( Short.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newIntRange( fieldName, ( (Short) from ).intValue(), ( (Short) to ).intValue(), includeLower, includeUpper );
}
if ( Long.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newLongRange( fieldName, (Long) from, (Long) to, includeLower, includeUpper );
}
if ( Integer.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newIntRange( fieldName, (Integer) from, (Integer) to, includeLower, includeUpper );
}
if ( Float.class.isAssignableFrom( numericClass ) ) {
return NumericRangeQuery.newFloatRange( fieldName, (Float) from, (Float) to, includeLower, includeUpper );
}
if ( Date.class.isAssignableFrom( numericClass ) ) {
Long fromValue = from != null ? ((Date) from).getTime() : null;
Long toValue = to != null ? ((Date) to).getTime() : null;
return NumericRangeQuery.newLongRange( fieldName, fromValue, toValue, includeLower, includeUpper );
}
if ( Calendar.class.isAssignableFrom( numericClass ) ) {
Long fromValue = from != null ? ((Calendar) from).getTime().getTime() : null;
Long toValue = to != null ? ((Calendar) to).getTime().getTime() : null;
return NumericRangeQuery.newLongRange( fieldName, fromValue, toValue, includeLower, includeUpper );
}
if ( java.time.Duration.class.isAssignableFrom( numericClass ) ) {
Long fromValue = from != null ? ( (java.time.Duration) from ).toNanos() : null;
Long toValue = to != null ? ( (java.time.Duration) to ).toNanos() : null;
return NumericRangeQuery.newLongRange( fieldName, fromValue, toValue, includeLower, includeUpper );
}
if ( java.time.Year.class.isAssignableFrom( numericClass ) ) {
Integer fromValue = from != null ? ( (java.time.Year) from ).getValue() : null;
Integer toValue = to != null ? ( (java.time.Year) to ).getValue() : null;
return NumericRangeQuery.newIntRange( fieldName, fromValue, toValue, includeLower, includeUpper );
}
if ( java.time.Instant.class.isAssignableFrom( numericClass ) ) {
Long fromValue = from != null ? ( (java.time.Instant) from ).toEpochMilli() : null;
Long toValue = to != null ? ( (java.time.Instant) to ).toEpochMilli() : null;
return NumericRangeQuery.newLongRange( fieldName, fromValue, toValue, includeLower, includeUpper );
}
throw log.numericRangeQueryWithNonNumericToAndFromValues( fieldName );
}
/**
* Will create a {@code RangeQuery} matching exactly the provided value: lower
* and upper value match, and bounds are included. This should perform
* as efficiently as a TermQuery.
*
* @param fieldName the field name the query targets
* @param value the value to match
* @return the created {@code Query}
*/
public static Query createExactMatchQuery(String fieldName, Object value) {
return createNumericRangeQuery( fieldName, value, value, true, true );
}
/**
* When the type of {@code RangeQuery} needs to be guessed among keyword based ranges or numeric based
* range queries, the parameter type defines the strategy.
*
* Note that this is currently only used by the Infinispan backend as a fallback and it should be used with a lot
* of caution as it does not take into account backend specific behaviors.
* For instance, when indexing on Elasticsearch, Dates require a keyword range query.
*
* This should match the default {@code FieldBridge} used for each type.
* @param value on Object
* @return true if the value argument is of any type which is by default indexed as a NumericField
*/
public static boolean requiresNumericRangeQuery(Object value) {
if ( value == null ) {
return false;
}
return value instanceof Double ||
value instanceof Byte ||
value instanceof Short ||
value instanceof Long ||
value instanceof Integer ||
value instanceof Float ||
value instanceof Date ||
value instanceof Calendar ||
value instanceof java.time.Instant ||
value instanceof java.time.Year ||
value instanceof java.time.Duration;
}
/**
* Indicates whether the considered {@code FieldBridge} is a numeric one.
*
* @param fieldBridge the considered {@code FieldBridge}
* @return true if the considered {@code FieldBridge} is a numeric {@code FieldBridge}
*/
public static boolean isNumericFieldBridge(FieldBridge fieldBridge) {
EncodingBridge encodingBridge = BridgeAdaptorUtils.unwrapAdaptorOnly( fieldBridge, EncodingBridge.class );
return !NumericEncodingType.UNKNOWN.equals( getNumericEncoding( encodingBridge ) );
}
/**
* Indicates whether the considered {@code FieldBridge}, or its {@link ContainerBridge#getElementBridge() element bridge},
* is a numeric one.
*
* @param fieldBridge the considered {@code FieldBridge}
* @return true if the considered {@code FieldBridge} is a numeric {@code FieldBridge}
*/
public static boolean isNumericContainerOrNumericFieldBridge(FieldBridge fieldBridge) {
EncodingBridge encodingBridge = BridgeAdaptorUtils.unwrapAdaptorAndContainer( fieldBridge, EncodingBridge.class );
return !NumericEncodingType.UNKNOWN.equals( getNumericEncoding( encodingBridge ) );
}
private static NumericEncodingType getNumericEncoding(EncodingBridge encodingBridge) {
if ( encodingBridge != null ) {
return encodingBridge.getEncodingType();
}
else {
return NumericEncodingType.UNKNOWN;
}
}
}