/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.horizondb.db.parser.builders;
import static java.lang.String.format;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import io.horizondb.db.HorizonDBException;
import io.horizondb.db.parser.BadHqlGrammarException;
import io.horizondb.model.ErrorCodes;
import io.horizondb.model.core.Field;
import io.horizondb.model.core.Predicate;
import io.horizondb.model.core.predicates.Operator;
import io.horizondb.model.core.predicates.Predicates;
import io.horizondb.model.schema.TimeSeriesDefinition;
/**
* Factory methods for <code>PredicateBuilder</code>.
*/
public final class PredicateBuilders {
/**
* Builder for NOOP predicates.
*/
private static final PredicateBuilder NOOP_BUILDER = new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) {
return Predicates.noop();
}
};
/**
* Creates a builder for a NOOP predicate.
*
* @return a builder for an NOOP predicate
*/
public static PredicateBuilder noop() {
return NOOP_BUILDER;
}
/**
* Creates a builder for an AND predicate.
*
* @param left the builder for the left predicate
* @param right the builder for the right predicate
* @return a builder for an AND predicate
*/
public static PredicateBuilder and(final PredicateBuilder left,
final PredicateBuilder right) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.and(left.build(definition), right.build(definition));
}
};
}
/**
* Creates a builder for an OR predicate.
*
* @param left the builder for the left predicate
* @param right the builder for the right predicate
* @return a builder for an OR predicate
*/
public static PredicateBuilder or(final PredicateBuilder left,
final PredicateBuilder right) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.or(left.build(definition), right.build(definition));
}
};
}
/**
* Creates a builder for an IN predicate.
*
* @param fieldName the name of the field
* @param values the values
* @return a builder for a IN predicate
*/
public static PredicateBuilder in(final String fieldName,
final List<String> values) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.in(fieldName, getFields(definition, fieldName, values));
}
};
}
/**
* Creates a builder for a NOT IN predicate.
*
* @param fieldName the name of the field
* @param min the minimum value of the closed range
* @param max the maximum value of the closed range
* @return a builder for a NOT IN predicate
*/
public static PredicateBuilder notIn(final String fieldName,
final List<String> values) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.notIn(fieldName, getFields(definition, fieldName, values));
}
};
}
/**
* Creates a builder for a BETWEEN predicate.
*
* @param fieldName the name of the field
* @param min the minimum value of the closed range
* @param max the maximum value of the closed range
* @return a builder for a BETWEEN predicate
*/
public static PredicateBuilder between(final String fieldName,
final String min,
final String max) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.between(fieldName,
getField(definition, fieldName, min),
getField(definition, fieldName, max));
}
};
}
/**
* Creates a builder for a NOT BETWEEN predicate.
*
* @param fieldName the name of the field
* @param min the minimum value of the closed range
* @param max the maximum value of the closed range
* @return a builder for a NOT BETWEEN predicate
*/
public static PredicateBuilder notBetween(final String fieldName,
final String min,
final String max) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.notBetween(fieldName,
getField(definition, fieldName, min),
getField(definition, fieldName, max));
}
};
}
/**
* Creates a builder for a simple a predicate.
*
* @param fieldName the name of the field
* @param operator the operator
* @param value the value to which the value of the field must be compared
* @return a simple a predicate builder
*/
public static PredicateBuilder simplePredicate(final String fieldName,
final Operator operator,
final String value) {
return new PredicateBuilder() {
@Override
public Predicate build(TimeSeriesDefinition definition) throws HorizonDBException {
return Predicates.simplePredicate(fieldName,
operator,
getField(definition, fieldName, value));
}
};
}
/**
* Returns the field with the specified name and the specified value.
*
* @param definition the time series definition
* @param fieldName the field name
* @param value the field value
* @return the field with the specified name and the specified value
* @throws HorizonDBException if the field cannot be created
*/
private static Field getField(TimeSeriesDefinition definition,
String fieldName,
String value) throws HorizonDBException {
return setFieldValue(definition, newField(definition, fieldName), value);
}
/**
* Returns the fields with specified values.
*
* @param definition the time series definition
* @param fieldName the field name
* @param values the field values
* @return the fields with specified values
* @throws HorizonDBException if the fields cannot be created
*/
private static SortedSet<Field> getFields(TimeSeriesDefinition definition,
String fieldName,
List<String> values) throws HorizonDBException {
Field prototype = newField(definition, fieldName);
SortedSet<Field> fields = new TreeSet<>();
for (int i = 0, m = values.size(); i < m; i++) {
fields.add(setFieldValue(definition, prototype.newInstance(), values.get(i)));
}
return fields;
}
/**
* Sets the value of the specified field.
*
* @param definition the time series definition
* @param field the field
* @param value the value as <code>String</code>
* @return the field with the value set
* @throws BadHqlGrammarException if the value is invalid for the specified field
*/
private static Field setFieldValue(TimeSeriesDefinition definition, Field field, String value) throws BadHqlGrammarException {
try {
return field.setValueFromString(definition.getTimeZone(), value);
} catch (NumberFormatException e) {
throw new BadHqlGrammarException(format("The value %s cannot be converted into a number", value));
} catch (IllegalArgumentException e) {
throw new BadHqlGrammarException(e.getMessage());
}
}
/**
* Creates a new instance of the field with the specified name.
*
* @param definition the time series definition
* @param fieldName the field name
* @return a new instance of the field with the specified name.
* @throws HorizonDBException if no field with the specified name exists
*/
private static Field newField(TimeSeriesDefinition definition, String fieldName) throws HorizonDBException {
Field field = definition.newField(fieldName);
if (field == null)
{
throw new HorizonDBException(ErrorCodes.INVALID_QUERY, format("Unknown field: %s", fieldName));
}
return field;
}
/**
* The class must not be instantiated.
*/
private PredicateBuilders() {
}
}