All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.blazebit.persistence.criteria.impl.BlazeCriteriaBuilderImpl Maven / Gradle / Ivy

There is a newer version: 1.6.12
Show newest version
/*
 * Copyright 2014 - 2021 Blazebit.
 *
 * 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 com.blazebit.persistence.criteria.impl;

import com.blazebit.persistence.ConfigurationProperties;
import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.criteria.BlazeCollectionJoin;
import com.blazebit.persistence.criteria.BlazeCriteriaBuilder;
import com.blazebit.persistence.criteria.BlazeCriteriaDelete;
import com.blazebit.persistence.criteria.BlazeCriteriaQuery;
import com.blazebit.persistence.criteria.BlazeCriteriaUpdate;
import com.blazebit.persistence.criteria.BlazeExpression;
import com.blazebit.persistence.criteria.BlazeJoin;
import com.blazebit.persistence.criteria.BlazeListJoin;
import com.blazebit.persistence.criteria.BlazeMapJoin;
import com.blazebit.persistence.criteria.BlazeOrder;
import com.blazebit.persistence.criteria.BlazePath;
import com.blazebit.persistence.criteria.BlazeRoot;
import com.blazebit.persistence.criteria.BlazeSetJoin;
import com.blazebit.persistence.criteria.impl.expression.AbstractExpression;
import com.blazebit.persistence.criteria.impl.expression.AbstractPredicate;
import com.blazebit.persistence.criteria.impl.expression.BetweenPredicate;
import com.blazebit.persistence.criteria.impl.expression.BinaryArithmeticExpression;
import com.blazebit.persistence.criteria.impl.expression.BooleanExpressionPredicate;
import com.blazebit.persistence.criteria.impl.expression.BooleanLiteralPredicate;
import com.blazebit.persistence.criteria.impl.expression.ComparisonPredicate;
import com.blazebit.persistence.criteria.impl.expression.ComparisonPredicate.ComparisonOperator;
import com.blazebit.persistence.criteria.impl.expression.CompoundPredicate;
import com.blazebit.persistence.criteria.impl.expression.CompoundSelectionImpl;
import com.blazebit.persistence.criteria.impl.expression.ExistsPredicate;
import com.blazebit.persistence.criteria.impl.expression.GeneralCaseExpression;
import com.blazebit.persistence.criteria.impl.expression.InPredicate;
import com.blazebit.persistence.criteria.impl.expression.IsEmptyPredicate;
import com.blazebit.persistence.criteria.impl.expression.IsNullPredicate;
import com.blazebit.persistence.criteria.impl.expression.LikePredicate;
import com.blazebit.persistence.criteria.impl.expression.LiteralExpression;
import com.blazebit.persistence.criteria.impl.expression.MemberOfPredicate;
import com.blazebit.persistence.criteria.impl.expression.NotPredicate;
import com.blazebit.persistence.criteria.impl.expression.NullLiteralExpression;
import com.blazebit.persistence.criteria.impl.expression.ParameterExpressionImpl;
import com.blazebit.persistence.criteria.impl.expression.QuantifiableSubqueryExpression;
import com.blazebit.persistence.criteria.impl.expression.SimpleCaseExpression;
import com.blazebit.persistence.criteria.impl.expression.UnaryMinusExpression;
import com.blazebit.persistence.criteria.impl.expression.function.AbsFunction;
import com.blazebit.persistence.criteria.impl.expression.function.AggregationFunction;
import com.blazebit.persistence.criteria.impl.expression.function.CoalesceFunction;
import com.blazebit.persistence.criteria.impl.expression.function.ConcatFunction;
import com.blazebit.persistence.criteria.impl.expression.function.CurrentDateFunction;
import com.blazebit.persistence.criteria.impl.expression.function.CurrentTimeFunction;
import com.blazebit.persistence.criteria.impl.expression.function.CurrentTimestampFunction;
import com.blazebit.persistence.criteria.impl.expression.function.FunctionFunction;
import com.blazebit.persistence.criteria.impl.expression.function.LengthFunction;
import com.blazebit.persistence.criteria.impl.expression.function.LocateFunction;
import com.blazebit.persistence.criteria.impl.expression.function.LowerFunction;
import com.blazebit.persistence.criteria.impl.expression.function.NullifFunction;
import com.blazebit.persistence.criteria.impl.expression.function.SizeFunction;
import com.blazebit.persistence.criteria.impl.expression.function.SqrtFunction;
import com.blazebit.persistence.criteria.impl.expression.function.SubstringFunction;
import com.blazebit.persistence.criteria.impl.expression.function.TrimFunction;
import com.blazebit.persistence.criteria.impl.expression.function.UpperFunction;
import com.blazebit.persistence.criteria.impl.path.AbstractJoin;
import com.blazebit.persistence.criteria.impl.path.AbstractPath;
import com.blazebit.persistence.criteria.impl.path.CollectionAttributeJoin;
import com.blazebit.persistence.criteria.impl.path.ListAttributeJoin;
import com.blazebit.persistence.criteria.impl.path.MapAttributeJoin;
import com.blazebit.persistence.criteria.impl.path.PluralAttributePath;
import com.blazebit.persistence.criteria.impl.path.RootImpl;
import com.blazebit.persistence.criteria.impl.path.SetAttributeJoin;
import com.blazebit.persistence.criteria.impl.support.CriteriaBuilderSupport;
import com.blazebit.persistence.parser.EntityMetamodel;

import javax.persistence.Tuple;
import javax.persistence.criteria.CollectionJoin;
import javax.persistence.criteria.CompoundSelection;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Predicate.BooleanOperator;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.criteria.SetJoin;
import javax.persistence.criteria.Subquery;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Christian Beikov
 * @since 1.2.0
 */
public class BlazeCriteriaBuilderImpl implements BlazeCriteriaBuilder, CriteriaBuilderSupport, Serializable {

    private static final long serialVersionUID = 1L;

    private final EntityMetamodel metamodel;
    private final CriteriaBuilderFactory cbf;
    private final boolean negationWrapper;
    private final boolean valueAsParameter;

    public BlazeCriteriaBuilderImpl(CriteriaBuilderFactory cbf) {
        this.metamodel = cbf.getService(EntityMetamodel.class);
        this.cbf = cbf;
        String negationWrapper = cbf.getProperty(ConfigurationProperties.CRITERIA_NEGATION_WRAPPER);
        this.negationWrapper = negationWrapper == null || negationWrapper.isEmpty() || Boolean.parseBoolean(negationWrapper);
        String valueAsParameter = cbf.getProperty(ConfigurationProperties.CRITERIA_VALUE_AS_PARAMETER);
        this.valueAsParameter = valueAsParameter == null || valueAsParameter.isEmpty() || Boolean.parseBoolean(valueAsParameter);
    }

    public EntityMetamodel getEntityMetamodel() {
        return metamodel;
    }

    public CriteriaBuilderFactory getCriteriaBuilderFactory() {
        return cbf;
    }

    public  AbstractPredicate negate(T predicate) {
        if (negationWrapper) {
            return new NotPredicate(this, predicate);
        } else {
            return predicate.copyNegated();
        }
    }

    @Override
    public BlazeCriteriaQuery createQuery() {
        return new BlazeCriteriaQueryImpl(this, Object.class);
    }

    @Override
    public  BlazeCriteriaQuery createQuery(Class resultClass) {
        return new BlazeCriteriaQueryImpl(this, resultClass);
    }

    @Override
    public BlazeCriteriaQuery createTupleQuery() {
        return new BlazeCriteriaQueryImpl(this, Tuple.class);
    }

    @Override
    public  BlazeCriteriaUpdate createCriteriaUpdate(Class targetEntity) {
        return new BlazeCriteriaUpdateImpl(this, targetEntity, null);
    }

    @Override
    public  BlazeCriteriaUpdate createCriteriaUpdate(Class targetEntity, String alias) {
        return new BlazeCriteriaUpdateImpl(this, targetEntity, alias);
    }

    @Override
    public  BlazeCriteriaDelete createCriteriaDelete(Class targetEntity) {
        return new BlazeCriteriaDeleteImpl(this, targetEntity, null);
    }

    @Override
    public  BlazeCriteriaDelete createCriteriaDelete(Class targetEntity, String alias) {
        return new BlazeCriteriaDeleteImpl(this, targetEntity, alias);
    }

    /**********************
     * Selection stuff
     **********************/

    private List> wrapSelectionItems(List> selections) {
        List> copy = null;

        for (int i = 0; i < selections.size(); i++) {
            Selection selection = selections.get(i);
            if (selection.isCompoundSelection()) {
                // TODO: rethink if this restriction shouldn't be relaxed
                if (selection.getJavaType().isArray()) {
                    throw new IllegalArgumentException("Illegal array selection in multiselect selections");
                }
                if (Tuple.class.isAssignableFrom(selection.getJavaType())) {
                    throw new IllegalArgumentException("Illegal tuple selection in multiselect selections");
                }
            }

            Selection copySelection = wrapSelection(selection);
            if (copy != null) {
                copy.add(copySelection);
            } else if (copySelection != selection) {
                copy = new ArrayList>();
                for (int j = 0; j < i; j++) {
                    copy.add(selections.get(j));
                }
                copy.add(copySelection);
            }
        }

        if (copy != null) {
            return copy;
        }
        return selections;
    }

    @SuppressWarnings({"unchecked"})
    public  Selection wrapSelection(Selection selection) {
        if (selection instanceof Predicate) {
            return (Selection) selectCase().when((Predicate) selection, literal(true)).otherwise(literal(false));
        }

        return selection;
    }

    public  BlazeExpression nullValue(Class javaType) {
        if (valueAsParameter) {
            return new ParameterExpressionImpl<>(this, javaType, (T) null);
        } else {
            return new NullLiteralExpression<>(this, javaType);
        }
    }

    public  BlazeExpression value(T value) {
        checkValue(value);
        if (valueAsParameter) {
            return new ParameterExpressionImpl(this, value);
        } else {
            return new LiteralExpression(this, value);
        }
    }

    @Override
    public CompoundSelection tuple(Selection... selections) {
        return tuple(Arrays.asList(selections));
    }

    public CompoundSelection tuple(List> selections) {
        return new CompoundSelectionImpl(this, Tuple.class, wrapSelectionItems(selections));
    }

    @Override
    public CompoundSelection array(Selection... selections) {
        return array(Arrays.asList(selections));
    }

    public CompoundSelection array(List> selections) {
        return array(Object[].class, selections);
    }

    public  CompoundSelection array(Class type, List> selections) {
        return new CompoundSelectionImpl(this, type, wrapSelectionItems(selections));
    }

    @Override
    public  CompoundSelection construct(Class result, Selection... selections) {
        return construct(result, Arrays.asList(selections));
    }

    public  CompoundSelection construct(Class result, List> selections) {
        return new CompoundSelectionImpl(this, result, wrapSelectionItems(selections));
    }

    /**********************
     * Order by stuff
     **********************/

    @Override
    public BlazeOrder asc(Expression x) {
        return new OrderImpl(x, true, false);
    }

    @Override
    public BlazeOrder desc(Expression x) {
        return new OrderImpl(x, false, false);
    }

    @Override
    public BlazeOrder asc(Expression x, boolean nullsFirst) {
        return new OrderImpl(x, true, nullsFirst);
    }

    @Override
    public BlazeOrder desc(Expression x, boolean nullsFirst) {
        return new OrderImpl(x, false, nullsFirst);
    }

    /**********************
     * Predicates
     **********************/

    public Predicate wrap(Expression expression) {
        if (expression instanceof Predicate) {
            return (Predicate) expression;
        } else if (expression instanceof AbstractPath) {
            return equal(expression, literal(Boolean.TRUE));
        } else {
            return new BooleanExpressionPredicate(this, false, expression);
        }
    }

    @Override
    public Predicate not(Expression expression) {
        return wrap(expression).not();
    }

    @Override
    public Predicate and(Expression x, Expression y) {
        return new CompoundPredicate(this, BooleanOperator.AND, x, y);
    }

    @Override
    public Predicate or(Expression x, Expression y) {
        return new CompoundPredicate(this, BooleanOperator.OR, x, y);
    }

    @Override
    public Predicate and(Predicate... restrictions) {
        return new CompoundPredicate(this, BooleanOperator.AND, restrictions);
    }

    @Override
    public Predicate or(Predicate... restrictions) {
        return new CompoundPredicate(this, BooleanOperator.OR, restrictions);
    }

    @Override
    public Predicate conjunction() {
        return new CompoundPredicate(this, BooleanOperator.AND);
    }

    @Override
    public Predicate disjunction() {
        return new CompoundPredicate(this, BooleanOperator.OR);
    }

    @Override
    public Predicate isTrue(Expression expression) {
        if (CompoundPredicate.class.isInstance(expression)) {
            final CompoundPredicate predicate = (CompoundPredicate) expression;
            if (predicate.getExpressions().size() == 0) {
                return new BooleanLiteralPredicate(this, predicate.getOperator() == BooleanOperator.AND);
            }
            return predicate;
        } else if (Predicate.class.isInstance(expression)) {
            return (Predicate) expression;
        }
        return equal(expression, literal(Boolean.TRUE));
    }

    @Override
    public Predicate isFalse(Expression expression) {
        if (CompoundPredicate.class.isInstance(expression)) {
            final CompoundPredicate predicate = (CompoundPredicate) expression;
            if (predicate.getExpressions().size() == 0) {
                return new BooleanLiteralPredicate(this, predicate.getOperator() == BooleanOperator.OR);
            }
            return predicate.not();
        } else if (Predicate.class.isInstance(expression)) {
            final Predicate predicate = (Predicate) expression;
            return predicate.not();
        }
        return equal(expression, literal(Boolean.FALSE));
    }

    @Override
    public Predicate isNull(Expression x) {
        return new IsNullPredicate(this, false, x);
    }

    @Override
    public Predicate isNotNull(Expression x) {
        return new IsNullPredicate(this, true, x);
    }

    @Override
    public Predicate equal(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.EQUAL, x, y);
    }

    @Override
    public Predicate notEqual(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.NOT_EQUAL, x, y);
    }

    @Override
    public Predicate equal(Expression x, Object y) {
        return new ComparisonPredicate(this, ComparisonOperator.EQUAL, x, value(y));
    }

    @Override
    public Predicate notEqual(Expression x, Object y) {
        return new ComparisonPredicate(this, ComparisonOperator.NOT_EQUAL, x, value(y));
    }

    @Override
    public > Predicate greaterThan(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN, x, y);
    }

    @Override
    public > Predicate lessThan(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN, x, y);
    }

    @Override
    public > Predicate greaterThanOrEqualTo(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN_OR_EQUAL, x, y);
    }

    @Override
    public > Predicate lessThanOrEqualTo(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN_OR_EQUAL, x, y);
    }

    @Override
    public > Predicate greaterThan(Expression x, Y y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN, x, value(y));
    }

    @Override
    public > Predicate lessThan(Expression x, Y y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN, x, value(y));
    }

    @Override
    public > Predicate greaterThanOrEqualTo(Expression x, Y y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN_OR_EQUAL, x, value(y));
    }

    @Override
    public > Predicate lessThanOrEqualTo(Expression x, Y y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN_OR_EQUAL, x, value(y));
    }

    @Override
    public Predicate gt(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN, x, y);
    }

    @Override
    public Predicate lt(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN, x, y);
    }

    @Override
    public Predicate ge(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN_OR_EQUAL, x, y);
    }

    @Override
    public Predicate le(Expression x, Expression y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN_OR_EQUAL, x, y);
    }

    @Override
    public Predicate gt(Expression x, Number y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN, x, value(y));
    }

    @Override
    public Predicate lt(Expression x, Number y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN, x, value(y));
    }

    @Override
    public Predicate ge(Expression x, Number y) {
        return new ComparisonPredicate(this, ComparisonOperator.GREATER_THAN_OR_EQUAL, x, value(y));
    }

    @Override
    public Predicate le(Expression x, Number y) {
        return new ComparisonPredicate(this, ComparisonOperator.LESS_THAN_OR_EQUAL, x, value(y));
    }

    @Override
    public > Predicate between(Expression expression, Y lowerBound, Y upperBound) {
        return new BetweenPredicate(this, false, expression, value(lowerBound), value(upperBound));
    }

    @Override
    public > Predicate between(Expression expression, Expression lowerBound, Expression upperBound) {
        return new BetweenPredicate(this, false, expression, lowerBound, upperBound);
    }

    @Override
    public  In in(Expression expression) {
        return new InPredicate(this, expression);
    }

    @Override
    public Predicate like(Expression matchExpression, Expression pattern) {
        return new LikePredicate(this, false, matchExpression, pattern);
    }

    @Override
    public Predicate like(Expression matchExpression, Expression pattern, Expression escapeCharacter) {
        return new LikePredicate(this, false, matchExpression, pattern, escapeCharacter);
    }

    @Override
    public Predicate like(Expression matchExpression, Expression pattern, char escapeCharacter) {
        return new LikePredicate(this, false, matchExpression, pattern, value(escapeCharacter));
    }

    @Override
    public Predicate like(Expression matchExpression, String pattern) {
        return new LikePredicate(this, false, matchExpression, value(pattern));
    }

    @Override
    public Predicate like(Expression matchExpression, String pattern, Expression escapeCharacter) {
        return new LikePredicate(this, false, matchExpression, value(pattern), escapeCharacter);
    }

    @Override
    public Predicate like(Expression matchExpression, String pattern, char escapeCharacter) {
        return new LikePredicate(this, false, matchExpression, value(pattern), value(escapeCharacter));
    }

    @Override
    public Predicate notLike(Expression matchExpression, Expression pattern) {
        return new LikePredicate(this, true, matchExpression, pattern);
    }

    @Override
    public Predicate notLike(Expression matchExpression, Expression pattern, Expression escapeCharacter) {
        return new LikePredicate(this, true, matchExpression, pattern, escapeCharacter);
    }

    @Override
    public Predicate notLike(Expression matchExpression, Expression pattern, char escapeCharacter) {
        return new LikePredicate(this, true, matchExpression, pattern, value(escapeCharacter));
    }

    @Override
    public Predicate notLike(Expression matchExpression, String pattern) {
        return new LikePredicate(this, true, matchExpression, value(pattern));
    }

    @Override
    public Predicate notLike(Expression matchExpression, String pattern, Expression escapeCharacter) {
        return new LikePredicate(this, true, matchExpression, value(pattern), escapeCharacter);
    }

    @Override
    public Predicate notLike(Expression matchExpression, String pattern, char escapeCharacter) {
        return new LikePredicate(this, true, matchExpression, value(pattern), value(escapeCharacter));
    }

    /**********************
     * Parameter and literal
     **********************/

    @Override
    public  ParameterExpression parameter(Class paramClass) {
        return new ParameterExpressionImpl(this, paramClass);
    }

    @Override
    public  ParameterExpression parameter(Class paramClass, String name) {
        return new ParameterExpressionImpl(this, paramClass, name);
    }

    @Override
    public  AbstractExpression literal(T value) {
        checkValue(value);
        return new LiteralExpression(this, value);
    }

    @Override
    public  AbstractExpression nullLiteral(Class resultClass) {
        return new NullLiteralExpression(this, resultClass);
    }

    /**********************
     * Aggregate functions
     **********************/

    @Override
    public  Expression avg(Expression x) {
        return new AggregationFunction.AVG(this, x);
    }

    @Override
    public  Expression sum(Expression x) {
        return new AggregationFunction.SUM(this, x);
    }

    @Override
    public Expression sumAsLong(Expression x) {
        return new AggregationFunction.SUM(this, x, Long.class);
    }

    @Override
    public Expression sumAsDouble(Expression x) {
        return new AggregationFunction.SUM(this, x, Double.class);
    }

    @Override
    public  Expression max(Expression x) {
        return new AggregationFunction.MAX(this, x);
    }

    @Override
    public  Expression min(Expression x) {
        return new AggregationFunction.MIN(this, x);
    }

    @Override
    public > Expression greatest(Expression x) {
        return new AggregationFunction.GREATEST<>(this, x);
    }

    @Override
    public > Expression least(Expression x) {
        return new AggregationFunction.LEAST<>(this, x);
    }

    @Override
    public Expression count(Expression x) {
        return new AggregationFunction.COUNT(this, x, false);
    }

    @Override
    public Expression countDistinct(Expression x) {
        return new AggregationFunction.COUNT(this, x, true);
    }

    /**********************
     * Other functions
     **********************/

    @Override
    public  Expression function(String name, Class returnType, Expression... arguments) {
        return new FunctionFunction(this, returnType, name, arguments);
    }

    @Override
    public  Expression abs(Expression expression) {
        return new AbsFunction(this, expression);
    }

    @Override
    public Expression sqrt(Expression expression) {
        return new SqrtFunction(this, expression);
    }

    @Override
    public Expression currentDate() {
        return new CurrentDateFunction(this);
    }

    @Override
    public Expression currentTimestamp() {
        return new CurrentTimestampFunction(this);
    }

    @Override
    public Expression currentTime() {
        return new CurrentTimeFunction(this);
    }

    @Override
    public Expression substring(Expression value, Expression start) {
        return new SubstringFunction(this, value, start);
    }

    @Override
    public Expression substring(Expression value, int start) {
        return new SubstringFunction(this, value, value(start));
    }

    @Override
    public Expression substring(Expression value, Expression start, Expression length) {
        return new SubstringFunction(this, value, start, length);
    }

    @Override
    public Expression substring(Expression value, int start, int length) {
        return new SubstringFunction(this, value, value(start), value(length));
    }

    @Override
    public Expression trim(Expression trimSource) {
        return new TrimFunction(this, trimSource);
    }

    @Override
    public Expression trim(Trimspec trimspec, Expression trimSource) {
        return new TrimFunction(this, trimspec, trimSource);
    }

    @Override
    public Expression trim(Expression trimCharacter, Expression trimSource) {
        return new TrimFunction(this, trimCharacter, trimSource);
    }

    @Override
    public Expression trim(Trimspec trimspec, Expression trimCharacter, Expression trimSource) {
        return new TrimFunction(this, trimspec, trimCharacter, trimSource);
    }

    @Override
    public Expression trim(char trimCharacter, Expression trimSource) {
        return new TrimFunction(this, value(trimCharacter), trimSource);
    }

    @Override
    public Expression trim(Trimspec trimspec, char trimCharacter, Expression trimSource) {
        return new TrimFunction(this, trimspec, value(trimCharacter), trimSource);
    }

    @Override
    public Expression lower(Expression value) {
        return new LowerFunction(this, value);
    }

    @Override
    public Expression upper(Expression value) {
        return new UpperFunction(this, value);
    }

    @Override
    public Expression length(Expression value) {
        return new LengthFunction(this, value);
    }

    @Override
    public Expression locate(Expression string, Expression pattern) {
        return new LocateFunction(this, pattern, string);
    }

    @Override
    public Expression locate(Expression string, Expression pattern, Expression start) {
        return new LocateFunction(this, pattern, string, start);
    }

    @Override
    public Expression locate(Expression string, String pattern) {
        return new LocateFunction(this, value(pattern), string);
    }

    @Override
    public Expression locate(Expression string, String pattern, int start) {
        return new LocateFunction(this, value(pattern), string, value(start));
    }

    @Override
    public Expression concat(Expression string1, Expression string2) {
        return new ConcatFunction(this, string1, string2);
    }

    @Override
    public Expression concat(Expression string1, String string2) {
        return new ConcatFunction(this, string1, value(string2));
    }

    @Override
    public Expression concat(String string1, Expression string2) {
        return new ConcatFunction(this, value(string1), string2);
    }

    /**********************
     * Arithmetic operations
     **********************/

    @Override
    public  Expression neg(Expression expression) {
        return new UnaryMinusExpression(this, expression);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression sum(Expression expression1, Expression expression2) {
        checkExpression(expression1);
        checkExpression(expression2);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression1.getJavaType(), expression2.getJavaType());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.ADD, expression1, expression2);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression prod(Expression expression1, Expression expression2) {
        checkExpression(expression1);
        checkExpression(expression2);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression1.getJavaType(), expression2.getJavaType());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.MULTIPLY, expression1, expression2);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression diff(Expression expression1, Expression expression2) {
        checkExpression(expression1);
        checkExpression(expression2);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression1.getJavaType(), expression2.getJavaType());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.SUBTRACT, expression1, expression2);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression sum(Expression expression, N value) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression.getJavaType(), value.getClass());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.ADD, expression, value(value));
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression prod(Expression expression, N value) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression.getJavaType(), value.getClass());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.MULTIPLY, expression, value(value));
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression diff(Expression expression, N value) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression.getJavaType(), value.getClass());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.SUBTRACT, expression, value(value));
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression sum(N value, Expression expression) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(value.getClass(), expression.getJavaType());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.ADD, value(value), expression);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression prod(N value, Expression expression) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(value.getClass(), expression.getJavaType());
        return (BinaryArithmeticExpression) new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.MULTIPLY, value(value), expression);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public  Expression diff(N value, Expression expression) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(value.getClass(), expression.getJavaType());
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.SUBTRACT, value(value), expression);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Expression quot(Expression expression1, Expression expression2) {
        checkExpression(expression1);
        checkExpression(expression2);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression1.getJavaType(), expression2.getJavaType(), true);
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.DIVIDE, expression1, expression2);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Expression quot(Expression expression, Number value) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(expression.getJavaType(), value.getClass(), true);
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.DIVIDE, expression, value(value));
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Expression quot(Number value, Expression expression) {
        checkValue(value);
        checkExpression(expression);
        final Class resultType = BinaryArithmeticExpression.determineResultType(value.getClass(), expression.getJavaType(), true);
        return new BinaryArithmeticExpression(this, resultType, BinaryArithmeticExpression.Operation.DIVIDE, value(value), expression);
    }

    @Override
    public Expression mod(Expression expression1, Expression expression2) {
        checkExpression(expression1);
        checkExpression(expression2);
        return new BinaryArithmeticExpression(this, Integer.class, BinaryArithmeticExpression.Operation.MOD, expression1, expression2);
    }

    @Override
    public Expression mod(Expression expression, Integer value) {
        checkValue(value);
        checkExpression(expression);
        return new BinaryArithmeticExpression(this, Integer.class, BinaryArithmeticExpression.Operation.MOD, expression, value(value));
    }

    @Override
    public Expression mod(Integer value, Expression expression) {
        checkValue(value);
        checkExpression(expression);
        return new BinaryArithmeticExpression(this, Integer.class, BinaryArithmeticExpression.Operation.MOD, value(value), expression);
    }

    private void checkValue(Object value) {
        if (value == null) {
            throw new IllegalArgumentException("null value not allowed!");
        }
    }

    private void checkExpression(Expression expression) {
        if (expression == null) {
            throw new IllegalArgumentException("null expression not allowed!");
        }
    }

    /**********************
     * Casting functions
     **********************/

    @Override
    public Expression toLong(Expression expression) {
        return ((AbstractExpression) expression).asLong();
    }

    @Override
    public Expression toInteger(Expression expression) {
        return ((AbstractExpression) expression).asInteger();
    }

    public Expression toFloat(Expression expression) {
        return ((AbstractExpression) expression).asFloat();
    }

    @Override
    public Expression toDouble(Expression expression) {
        return ((AbstractExpression) expression).asDouble();
    }

    @Override
    public Expression toBigDecimal(Expression expression) {
        return ((AbstractExpression) expression).asBigDecimal();
    }

    @Override
    public Expression toBigInteger(Expression expression) {
        return ((AbstractExpression) expression).asBigInteger();
    }

    @Override
    public Expression toString(Expression characterExpression) {
        return ((AbstractExpression) characterExpression).asString();
    }

    /**********************
     * Subquery expressions
     **********************/

    @Override
    public Predicate exists(Subquery subquery) {
        return new ExistsPredicate(this, subquery);
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public  Expression all(Subquery subquery) {
        return new QuantifiableSubqueryExpression(this, (Class) subquery.getJavaType(), subquery, QuantifiableSubqueryExpression.Quantor.ALL);
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public  Expression some(Subquery subquery) {
        return new QuantifiableSubqueryExpression(this, (Class) subquery.getJavaType(), subquery, QuantifiableSubqueryExpression.Quantor.SOME);
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public  Expression any(Subquery subquery) {
        return new QuantifiableSubqueryExpression(this, (Class) subquery.getJavaType(), subquery, QuantifiableSubqueryExpression.Quantor.ANY);
    }

    /**********************
     * Case-When stuff
     **********************/

    @Override
    public  Expression coalesce(Expression exp1, Expression exp2) {
        return coalesce((Class) null, exp1, exp2);
    }

    public  Expression coalesce(Class type, Expression exp1, Expression exp2) {
        return new CoalesceFunction(this, type).value(exp1).value(exp2);
    }

    @Override
    public  Expression coalesce(Expression exp1, Y value) {
        return coalesce((Class) null, exp1, value(value));
    }

    public  Expression coalesce(Class type, Expression exp1, Y value) {
        return new CoalesceFunction(this, type).value(exp1).value(value);
    }

    @Override
    public  Coalesce coalesce() {
        return coalesce((Class) null);
    }

    public  Coalesce coalesce(Class type) {
        return new CoalesceFunction(this, type);
    }

    @Override
    public  Expression nullif(Expression exp1, Expression exp2) {
        return nullif(null, exp1, exp2);
    }

    public  Expression nullif(Class type, Expression exp1, Expression exp2) {
        return new NullifFunction(this, type, exp1, exp2);
    }

    @Override
    public  Expression nullif(Expression exp1, Y value) {
        return nullif(null, exp1, value(value));
    }

    public  Expression nullif(Class type, Expression exp1, Y value) {
        return new NullifFunction(this, type, exp1, value(value));
    }

    @Override
    public  SimpleCase selectCase(Expression expression) {
        // JDK 9 complains without the cast
        return /*(SimpleCase) */selectCase((Class) null, expression);
    }

    public  SimpleCase selectCase(Class type, Expression expression) {
        return new SimpleCaseExpression(this, type, expression);
    }

    @Override
    public  Case selectCase() {
        return selectCase((Class) null);
    }

    public  Case selectCase(Class type) {
        return new GeneralCaseExpression(this, type);
    }

    /**********************
     * Collection functions
     **********************/

    @Override
    public > Expression size(C c) {
        int size = c == null ? 0 : c.size();
        return new LiteralExpression(this, Integer.class, size);
    }

    @Override
    public > Expression mapSize(C c) {
        int size = c == null ? 0 : c.size();
        return new LiteralExpression(this, Integer.class, size);
    }

    @Override
    public > Expression size(Expression exp) {
        if (LiteralExpression.class.isInstance(exp)) {
            return size(((LiteralExpression) exp).getLiteral());
        } else if (PluralAttributePath.class.isInstance(exp)) {
            return new SizeFunction(this, (PluralAttributePath) exp);
        }
        throw illegalCollection(exp);
    }

    @Override
    public > Expression mapSize(Expression exp) {
        if (LiteralExpression.class.isInstance(exp)) {
            return mapSize(((LiteralExpression) exp).getLiteral());
        } else if (PluralAttributePath.class.isInstance(exp)) {
            return new SizeFunction(this, (PluralAttributePath) exp);
        }
        throw illegalCollection(exp);
    }

    @Override
    public > Expression> values(M map) {
        return new LiteralExpression>(this, map.values());
    }

    @Override
    public > Expression> keys(M map) {
        return new LiteralExpression>(this, map.keySet());
    }

    @Override
    public > Predicate isEmpty(Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new IsEmptyPredicate(this, false, (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isMapEmpty(Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new IsEmptyPredicate(this, false, (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isNotEmpty(Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new IsEmptyPredicate(this, true, (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isMapNotEmpty(Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new IsEmptyPredicate(this, true, (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isMember(E e, Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new MemberOfPredicate(this, false, value(e), (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isNotMember(E e, Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new MemberOfPredicate(this, true, value(e), (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isMember(Expression elementExpression, Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new MemberOfPredicate(this, false, elementExpression, (PluralAttributePath) collectionExpression);
    }

    @Override
    public > Predicate isNotMember(Expression elementExpression, Expression collectionExpression) {
        if (!(collectionExpression instanceof PluralAttributePath)) {
            throw illegalCollection(collectionExpression);
        }
        return new MemberOfPredicate(this, true, elementExpression, (PluralAttributePath) collectionExpression);
    }

    private RuntimeException illegalCollection(Expression expression) {
        return new IllegalArgumentException("Illegal expression type! Expected plural expression but got: " + expression.getClass().getName());
    }

    /****************************
     * Treat support for JPA 2.1
     ***************************/

    @Override
    public  BlazeJoin treat(Join join, Class type) {
        return ((AbstractJoin) join).treatJoin(type);
    }

    @Override
    public  BlazeCollectionJoin treat(CollectionJoin join, Class type) {
        return ((CollectionAttributeJoin) join).treatJoin(type);
    }

    @Override
    public  BlazeSetJoin treat(SetJoin join, Class type) {
        return ((SetAttributeJoin) join).treatJoin(type);
    }

    @Override
    public  BlazeListJoin treat(ListJoin join, Class type) {
        return ((ListAttributeJoin) join).treatJoin(type);
    }

    @Override
    public  BlazeMapJoin treat(MapJoin join, Class type) {
        return ((MapAttributeJoin) join).treatJoin(type);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  BlazePath treat(Path path, Class type) {
        if (path instanceof AbstractJoin) {
            return ((AbstractJoin) path).treatJoin(type);
        }
        return ((AbstractPath) path).treatAs(type);
    }

    @Override
    public  BlazeRoot treat(Root root, Class type) {
        return ((RootImpl) root).treatAs(type);
    }

    @Override
    public  BlazeJoin treat(BlazeJoin join, Class type) {
        return treat((Join) join, type);
    }

    @Override
    public  BlazeCollectionJoin treat(BlazeCollectionJoin join, Class type) {
        return treat((CollectionJoin) join, type);
    }

    @Override
    public  BlazeSetJoin treat(BlazeSetJoin join, Class type) {
        return treat((SetJoin) join, type);
    }

    @Override
    public  BlazeListJoin treat(BlazeListJoin join, Class type) {
        return treat((ListJoin) join, type);
    }

    @Override
    public  BlazeMapJoin treat(BlazeMapJoin join, Class type) {
        return treat((MapJoin) join, type);
    }

    @Override
    public  BlazePath treat(BlazePath path, Class type) {
        return treat((Path) path, type);
    }

    @Override
    public  BlazeRoot treat(BlazeRoot root, Class type) {
        return treat((Root) root, type);
    }
}