/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.openjpa.persistence.criteria;

import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaBuilder.Trimspec;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Subquery;

import org.apache.openjpa.kernel.exps.ExpressionFactory;
import org.apache.openjpa.kernel.exps.Literal;
import org.apache.openjpa.kernel.exps.Value;
import org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.persistence.criteria.CriteriaExpressionVisitor.TraversalStyle;
import org.apache.openjpa.persistence.meta.Types;

/**
 * Expressions according to JPA 2.0.
 *
 * A facade to OpenJPA kernel expressions to enforce stronger typing.
 *
 * @author Pinaki Poddar
 * @author Fay Wang
 *
 * @since 2.0.0
 *
 */
class Expressions {
    static final String OPEN_BRACE = "(";
    static final String CLOSE_BRACE = ")";
    static final String COMMA = ",";

    /**
     * Convert the given Criteria expression to a corresponding kernel value
     * using the given ExpressionFactory.
     * Handles null expression.
     */
     static Value toValue(ExpressionImpl<?> e, ExpressionFactory factory, CriteriaQueryImpl<?> q) {
        return (e == null) ? factory.getNull() : e.toValue(factory, q);
    }

     static void setImplicitTypes(Value v1, Value v2, Class<?> expected, CriteriaQueryImpl<?> q) {
         JPQLExpressionBuilder.setImplicitTypes(v1, v2, expected, q.getMetamodel(),
             q.getParameterTypes(), q.toString());
     }

     /**
      * Visits the given expression and the given children recursively.
      * The order of traversal depends on the parent and is determined by the visitor.
      */
     static void acceptVisit(CriteriaExpressionVisitor visitor, CriteriaExpression parent, Expression<?>...exprs) {
         if (parent == null)
             return;
         TraversalStyle traversal = visitor.getTraversalStyle(parent);
         switch (traversal) {
         case INFIX :
             if (exprs == null || exprs.length == 0) {
                 visitor.enter(parent);
                 visitor.exit(parent);
                 return;
             }
             for (int i = 0; i < exprs.length; i++) {
                 ExpressionImpl<?> e = (ExpressionImpl<?>)exprs[i];
                 if (e != null) e.acceptVisit(visitor);
                 if (i + 1 != exprs.length) {
                     visitor.enter(parent);
                     visitor.exit(parent);
                 }
             }
             break;
         case POSTFIX:
             visitChildren(visitor,exprs);
             visitor.enter(parent);
             visitor.exit(parent);
             break;
         case PREFIX :
             visitor.enter(parent);
             visitor.exit(parent);
             visitChildren(visitor,exprs);
             break;
         case FUNCTION:
             visitor.enter(parent);
             visitChildren(visitor, exprs);
             visitor.exit(parent);
             break;
         }
     }

     static void visitChildren(CriteriaExpressionVisitor visitor, Expression<?>...exprs) {
         for (int i = 0; exprs != null && i < exprs.length; i++) {
             ExpressionImpl<?> e = (ExpressionImpl<?>)exprs[i];
             if (e != null) e.acceptVisit(visitor);
         }
     }

     /**
      * Renders the given expressions as a list of values separated by the given connector.
      */
     static StringBuilder asValue(AliasContext q, Expression<?>[] exps, String connector) {
         StringBuilder buffer = new StringBuilder();
         if (exps == null) return buffer;
         for (int i = 0; i < exps.length; i++) {
             buffer.append(((ExpressionImpl<?>)exps[i]).asValue(q));
             if (i+1 != exps.length) {
                 buffer.append(connector);
             }
         }
         return buffer;
     }

     /**
      * Renders the given arguments as a list of values separated by the given connector.
      */
     static StringBuilder asValue(AliasContext q, Object...params) {
         StringBuilder buffer = new StringBuilder();
         if (params == null) return buffer;
         for (int i = 0; i < params.length; i++) {
             Object o = params[i];
             if (o == null) {
                 if (i+1 < params.length && params[i+1].equals(COMMA)) {
                     i++;
                 }
                 continue;
             }
             if (o instanceof CriteriaExpression) {
                 buffer.append(((CriteriaExpression)o).asValue(q));
             } else {
                 buffer.append(o);
             }
         }
         return buffer;
     }

     /**
      * Return a list that is either empty (if the given list is null) or a list
      * whose mutation do not impact the original list.
      */
     static <X> List<X> returnCopy(List<X> list) {
         return list == null ? new ArrayList<>() : new CopyOnWriteArrayList<>(list);
     }

     /**
      * Return a set that is either empty (if the given set is null) or a set
      * whose mutation do not impact the original list.
      */
     static <X> Set<X> returnCopy(Set<X> set) {
         return set == null ? new HashSet<>() : new CopyOnWriteArraySet<>(set);
     }

     static org.apache.openjpa.kernel.exps.Expression and(ExpressionFactory factory,
             org.apache.openjpa.kernel.exps.Expression e1, org.apache.openjpa.kernel.exps.Expression e2) {
             return e1 == null ? e2 : e2 == null ? e1 : factory.and(e1, e2);
     }



     /**
     * Unary Functional Expression applies a unary function on a input operand Expression.
     *
     * @param <X> the type of the resultant expression
     */
    public abstract static class UnaryFunctionalExpression<X> extends ExpressionImpl<X> {
        protected final ExpressionImpl<?> e;
        /**
         * Supply the resultant type and input operand expression.
         */
        public UnaryFunctionalExpression(Class<X> t, Expression<?> e) {
            super(t);
            this.e  = (ExpressionImpl<?>)e;
        }

        public UnaryFunctionalExpression(Expression<X> e) {
            this((Class<X>)e.getJavaType(), e);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e);
        }
    }

    /**
     * Binary Functional Expression applies a binary function on a pair of input Expression.
     *
     * @param <X> the type of the resultant expression
     */
    public abstract static class BinarayFunctionalExpression<X> extends ExpressionImpl<X>{
        protected final ExpressionImpl<?> e1;
        protected final ExpressionImpl<?> e2;

        /**
         * Supply the resultant type and pair of input operand expressions.
         */
        public BinarayFunctionalExpression(Class<X> t, Expression<?> x, Expression<?> y) {
            super(t);
            e1 = (ExpressionImpl<?>)x;
            e2 = (ExpressionImpl<?>)y;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e1, e2);
        }
    }

    /**
     * Functional Expression applies a function on a list of input Expressions.
     *
     * @param <X> the type of the resultant expression
     */
    public abstract static class FunctionalExpression<X> extends ExpressionImpl<X> {
        protected final ExpressionImpl<?>[] args;

        /**
         * Supply the resultant type and list of input operand expressions.
         */
        public FunctionalExpression(Class<X> t, Expression<?>... args) {
            super(t);
            int len = args == null ? 0 : args.length;
            this.args = new ExpressionImpl<?>[len];
            for (int i = 0; args != null && i < args.length; i++) {
                this.args[i] = (ExpressionImpl<?>)args[i];
            }
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, args);
        }
    }

    /**
     * Binary Logical Expression applies a function on a pair of input Expression to generate a Predicate
     * i.e. an expression whose resultant type is Boolean.
     *
     */
   public static abstract class BinaryLogicalExpression extends PredicateImpl {
        protected final ExpressionImpl<?> e1;
        protected final ExpressionImpl<?> e2;

        public BinaryLogicalExpression(Expression<?> x, Expression<?> y) {
            super();
            e1 = (ExpressionImpl<?>)x;
            e2 = (ExpressionImpl<?>)y;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e1, e2);
        }
    }


    public static class Abs<X> extends UnaryFunctionalExpression<X> {
        public  Abs(Expression<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.abs(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "ABS", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Ceiling<X> extends UnaryFunctionalExpression<X> {
        public  Ceiling(Expression<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.ceiling(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "CEILING", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Exponential extends UnaryFunctionalExpression<Double> {
        public  Exponential(Expression<? extends Number> x) {
            super(Double.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.exp(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "EXP", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Floor<X> extends UnaryFunctionalExpression<X> {
        public  Floor(Expression<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.floor(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "FLOOR", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class NaturalLogarithm extends UnaryFunctionalExpression<Double> {
        public  NaturalLogarithm(Expression<? extends Number> x) {
            super(Double.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.ln(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "LN", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Sign extends UnaryFunctionalExpression<Integer> {
        public  Sign(Expression<? extends Number> x) {
            super(Integer.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.sign(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "SIGN", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Power<X, Y extends Number> extends BinarayFunctionalExpression<Double> {
        public Power(Expression<X> x, Expression<Y> y) {
            super(double.class, x, y);
        }
        public Power(Expression<X> x, Y y) {
            this(x,new Constant<>(y));
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.power(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "POWER", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE);
        }
    }

    public static class Round<X> extends BinarayFunctionalExpression<X> {
        public Round(Expression<?> x, Expression<?> y) {
            super(((Class<X>) x.getJavaType()), x, y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.round(
                Expressions.toValue(e1, factory, q), 
                Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "ROUND", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE);
        }
    }

    public static class Count extends UnaryFunctionalExpression<Long> {
        private boolean _distinct;
        public  Count(Expression<?> x) {
            this(x, false);
        }

        public  Count(Expression<?> x, boolean distinct) {
            super(Long.class, x);
            _distinct = distinct;

        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value v = Expressions.toValue(e, factory, q);
            return _distinct ? factory.count(factory.distinct(v)) : factory.count(v);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "COUNT", OPEN_BRACE, _distinct ? "DISTINCT"+OPEN_BRACE : "",
                e, _distinct ? CLOSE_BRACE : "", CLOSE_BRACE);
        }
    }

    public static class Avg extends UnaryFunctionalExpression<Double> {
        public  Avg(Expression<?> x) {
            super(Double.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.avg(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "AVG", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Sqrt extends UnaryFunctionalExpression<Double> {
        public  Sqrt(Expression<? extends Number> x) {
            super(Double.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.sqrt(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "SQRT", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Max<X> extends UnaryFunctionalExpression<X> {
        public  Max(Expression<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.max(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "MAX", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Min<X> extends UnaryFunctionalExpression<X> {
        public  Min(Expression<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.min(Expressions.toValue(e, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "MIN", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Size extends UnaryFunctionalExpression<Integer> {
        public  Size(Expression<? extends Collection<?>> x) {
            super(Integer.class, x);
        }

        public  Size(Collection<?> x) {
            this(new Constant<Collection<?>>(x));
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value val = Expressions.toValue(e, factory, q);
            Value result;
            if (val instanceof Literal && ((Literal)val).getParseType() == Literal.TYPE_COLLECTION)
                result = factory.newLiteral(((Collection)((Literal)val).getValue()).size(),
                    Literal.TYPE_NUMBER);
            else
                result = factory.size(val);
            result.setImplicitType(Integer.class);
            return result;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "SIZE", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class DatabaseFunction<T> extends FunctionalExpression<T> {
        private final String functionName;
        private final Class<T> resultType;

        public  DatabaseFunction(String name, Class<T> resultType, Expression<?>... exps) {
            super(resultType, exps);
            functionName = name;
            this.resultType = resultType;
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.newFunction(functionName, getJavaType(),
                new Expressions.ListArgument(resultType, args).toValue(factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, functionName, OPEN_BRACE, Expressions.asValue(q, args, COMMA), CLOSE_BRACE);
        }
    }


    public static class Type<X extends Class> extends UnaryFunctionalExpression<X> {
        public Type(PathImpl<?, ?> path) {
            super((Class)Class.class, path);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.type(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "TYPE", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Cast<B> extends UnaryFunctionalExpression<B> {
        public Cast(Expression<?> x, Class<B> b) {
            super(b, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.cast(Expressions.toValue(e, factory, q), getJavaType());
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, OPEN_BRACE, getJavaType().getSimpleName(), CLOSE_BRACE, e);
        }
    }

    public static class Concat extends BinarayFunctionalExpression<String> {
        public Concat(Expression<String> x, Expression<String> y) {
            super(String.class, x, y);
        }

        public Concat(Expression<String> x, String y) {
            this(x, new Constant<>(y));
        }

        public Concat(String x, Expression<String> y) {
            this(new Constant<>(x), y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.concat(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "CONCAT", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE);
        }
    }

    public static class Substring extends UnaryFunctionalExpression<String> {
        private ExpressionImpl<Integer> from;
        private ExpressionImpl<Integer> len;

        public Substring(Expression<String> s, Expression<Integer> from, Expression<Integer> len) {
            super(String.class, s);
            this.from = (ExpressionImpl<Integer>)from;
            this.len  = (ExpressionImpl<Integer>)len;
        }

        public Substring(Expression<String> s, Expression<Integer> from) {
            this(s, (ExpressionImpl<Integer>)from, null);
        }

        public Substring(Expression<String> s) {
            this(s, (Expression<Integer>)null, (Expression<Integer>)null);
        }

        public Substring(Expression<String> s, Integer from) {
            this(s, new Constant<>(from), null);
        }

        public Substring(Expression<String> s, Integer from, Integer len) {
            this(s, new Constant<>(from), new Constant<>(len));
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return JPQLExpressionBuilder.convertSubstringArguments(factory,
                Expressions.toValue(e, factory, q),
                from == null ? null : from.toValue(factory, q),
                len == null ? null : len.toValue(factory, q));
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, from, len);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "SUBSTRING", OPEN_BRACE, e, COMMA, from, COMMA, len, CLOSE_BRACE);
        }
    }

    public static class Locate extends ExpressionImpl<Integer> {
        private ExpressionImpl<String> pattern;
        private ExpressionImpl<Integer> from;
        private ExpressionImpl<String> path;

        public Locate(Expression<String> path, Expression<String> pattern, Expression<Integer> from) {
            super(Integer.class);
            this.path = (ExpressionImpl<String>)path;
            this.pattern = (ExpressionImpl<String>)pattern;
            this.from = (ExpressionImpl<Integer>)from;
        }

        public Locate(Expression<String> path, Expression<String> pattern) {
            this(path, pattern, null);
         }

        public Locate(Expression<String> path, String pattern) {
            this(path, new Constant<>(pattern), null);
        }

        public Locate(String path, Expression<String> pattern) {
            this(new Constant<>(path), pattern, null);
        }

        public Locate(Expression<String> path, String pattern, int from) {
            this(path, new Constant<>(pattern), new Constant<>(from));
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value locateSearch = path.toValue(factory, q);
            Value locateFromIndex = (from == null ? null : Expressions.toValue(from, factory, q));
            Value locatePath = Expressions.toValue(pattern, factory, q);

            return factory.indexOf(locateSearch,
                locateFromIndex == null ? locatePath
                    : factory.newArgumentList(locatePath, locateFromIndex));
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, pattern, from, path);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "LOCATE", OPEN_BRACE, pattern, COMMA, path, CLOSE_BRACE);
        }
    }

    public static class Trim extends BinarayFunctionalExpression<String> {
        static Expression<Character> defaultTrim = new Constant<>(Character.class, ' ');
        static Trimspec defaultSpec = Trimspec.BOTH;
        private Trimspec ts;

        public Trim(Expression<String> x, Expression<Character> y, Trimspec ts) {
            super(String.class, x, y);
            this.ts = ts;
        }

        public Trim(Expression<String> x, Expression<Character> y) {
            this(x, y, defaultSpec);
        }

        public Trim(Expression<String> x) {
            this(x, defaultTrim, defaultSpec);
        }

        public Trim(Expression<String> x, Character t) {
            this(x, new Constant<>(Character.class, t), defaultSpec);
        }

        public Trim(Expression<String> x, Character t, Trimspec ts) {
            this(x, new Constant<>(Character.class, t), ts);
        }

        public Trim(Expression<String> x, Trimspec ts) {
            this(x, defaultTrim, ts);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Boolean spec = null;
            if (ts != null) {
                switch (ts) {
                case LEADING  : spec = true;  break;
                case TRAILING : spec = false; break;
                case BOTH     : spec = null;  break;
                }
            }
            Character t = (Character)((Constant<Character>)e2).arg;
            Constant<String> e2 = new Constant<>(String.class, t.toString());
            return factory.trim(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q), spec);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "TRIM", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE);
        }
    }

    public static class Sum<N extends Number> extends BinarayFunctionalExpression<N> {
        public Sum(Expression<? extends Number> x, Expression<? extends Number> y) {
            super((Class<N>)x.getJavaType(), x, y);
        }

        public Sum(Expression<? extends Number> x) {
            this(x, (Expression<? extends Number>)null);
        }

        public Sum(Expression<? extends Number> x, Number y) {
            this(x, new Constant<>(Number.class, y));
        }

        public Sum(Number x, Expression<? extends Number> y) {
            this(new Constant<>(Number.class, x), y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value= (e2 == null)
            ?   factory.sum(Expressions.toValue(e1, factory, q))
            :   factory.add(
                   Expressions.toValue(e1, factory, q),
                   Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return e2 == null
               ? Expressions.asValue(q, "SUM", OPEN_BRACE, e1, CLOSE_BRACE)
               : Expressions.asValue(q, e1, " + ", e2);
        }
     }

    public static class Product<N extends Number> extends BinarayFunctionalExpression<N> {
        public Product(Expression<? extends Number> x, Expression<? extends Number> y) {
            super((Class<N>)x.getJavaType(), x, y);
        }

        public Product(Expression<? extends Number> x, Number y) {
            this(x, new Constant<>(Number.class, y));
        }

        public Product(Number x, Expression<? extends Number> y) {
            this(new Constant<>(Number.class, x), y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.multiply(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " * " ,e2);
        }
    }

    public static class Diff<N extends Number> extends BinarayFunctionalExpression<N> {
        public Diff(Expression<? extends Number> x, Expression<? extends Number> y) {
            super((Class<N>)x.getJavaType(), x, y);
        }

        public Diff(Expression<? extends Number> x, Number y) {
            this(x, new Constant<>(Number.class, y));
        }

        public Diff(Number x, Expression<? extends Number> y) {
            this(new Constant<>(Number.class, x), y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.subtract(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " - " ,e2);
        }
    }


    public static class Quotient<N extends Number> extends BinarayFunctionalExpression<N> {
        public Quotient(Expression<? extends Number> x, Expression<? extends Number> y) {
            super((Class<N>)x.getJavaType(), x, y);
        }

        public Quotient(Expression<? extends Number> x, Number y) {
            this(x, new Constant<>(y));
        }

        public Quotient(Number x, Expression<? extends Number> y) {
            this(new Constant<>(x), y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.divide(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, "%" ,e2);
        }
    }

    public static class Mod extends BinarayFunctionalExpression<Integer> {
        public  Mod(Expression<Integer> x, Expression<Integer> y) {
            super(Integer.class, x,y);
        }
        public  Mod(Expression<Integer> x, Integer y) {
            this(x,new Constant<>(Integer.class, y));
        }
        public  Mod(Integer x, Expression<Integer> y) {
            this(new Constant<>(Integer.class, x),y);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value = factory.mod(
                Expressions.toValue(e1, factory, q),
                Expressions.toValue(e2, factory, q));
            value.setImplicitType(getJavaType());
            return value;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "MOD", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE);
        }
    }

    public static class CurrentDate extends ExpressionImpl<java.sql.Date> {
        public  CurrentDate() {
            super(java.sql.Date.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentDate(getJavaType());
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("CURRENT_DATE");
        }
    }

    public static class CurrentTime extends ExpressionImpl<java.sql.Time> {
        public  CurrentTime() {
            super(java.sql.Time.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentTime(getJavaType());
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("CURRENT_TIME");
        }
    }

    public static class CurrentTimestamp extends ExpressionImpl<java.sql.Timestamp> {
        public  CurrentTimestamp() {
            super(java.sql.Timestamp.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentTimestamp(getJavaType());
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("CURRENT_TIMESTAMP");
        }
    }

    public static class CurrentLocalDateTime extends ExpressionImpl<java.time.LocalDateTime> {
        public CurrentLocalDateTime() {
            super(java.time.LocalDateTime.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentLocalDateTime(LocalDateTime.class);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("LOCAL DATETIME");
        }
    }

    public static class CurrentLocalDate extends ExpressionImpl<java.time.LocalDate> {
        public CurrentLocalDate() {
            super(java.time.LocalDate.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentLocalDateTime(LocalDate.class);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("LOCAL DATE");
        }
    }

    public static class CurrentLocalTime extends ExpressionImpl<java.time.LocalTime> {
        public CurrentLocalTime() {
            super(java.time.LocalTime.class);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.getCurrentLocalDateTime(LocalTime.class);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return new StringBuilder("LOCAL TIME");
        }
    }

    public static class Equal extends BinaryLogicalExpression {
        public <X,Y> Equal(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> Equal(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new NotEqual(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.equal(val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " = ", e2);
        }
    }

    public static class NotEqual extends BinaryLogicalExpression {
        public <X,Y> NotEqual(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> NotEqual(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new Equal(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.notEqual(val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " <> ", e2);
        }
    }

    public static class GreaterThan extends BinaryLogicalExpression {
        public <X,Y> GreaterThan(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> GreaterThan(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new LessThanEqual(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.greaterThan(val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " > ", e2);
        }
    }

    public static class GreaterThanEqual extends BinaryLogicalExpression {
        public <X,Y> GreaterThanEqual(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> GreaterThanEqual(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new LessThan(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.greaterThanEqual(val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " >= ", e2);
        }
    }

    public static class LessThan extends BinaryLogicalExpression {
        public <X,Y> LessThan(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> LessThan(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new GreaterThanEqual(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.lessThan(val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " < ", e2);
        }
    }

    public static class LessThanEqual extends BinaryLogicalExpression {
        public <X,Y> LessThanEqual(Expression<X> x, Expression<Y> y) {
            super(x,y);
        }

        public <X> LessThanEqual(Expression<X> x, Object y) {
            this(x, new Constant(y));
        }

        @Override
        public PredicateImpl not() {
            return new GreaterThan(e1, e2).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val1 = Expressions.toValue(e1, factory, q);
            Value val2 = Expressions.toValue(e2, factory, q);
            Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
            return factory.lessThanEqual(val1, val2);
        }


        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e1, " <= ", e2);
        }
    }

    public static class Between<Y extends Comparable<Y>> extends PredicateImpl.And {
        private final ExpressionImpl<? extends Y> e;
        private final ExpressionImpl<? extends Y> v1;
        private final ExpressionImpl<? extends Y> v2;

        public Between(Expression<? extends Y> v, Expression<? extends Y> x, Expression<? extends Y> y) {
            super(new GreaterThanEqual(v,x), new LessThanEqual(v,y));
            e = (ExpressionImpl<? extends Y>)v;
            v1 = (ExpressionImpl<? extends Y>)x;
            v2 = (ExpressionImpl<? extends Y>)y;
        }

        public Between(Expression<? extends Y> v, Y x, Y y) {
            this(v, new Constant<>(x), new Constant<>(y));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e, " BETWEEN ", v1, " AND ", v2);
        }
    }

    public static class Constant<X> extends ExpressionImpl<X> {
        public final Object arg;
        public Constant(Class<X> t, X x) {
            super(t);
            this.arg = x;
        }

        public Constant(X x) {
            this(x == null ? null : (Class<X>)x.getClass(), x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Object value = arg;
            Class<?> literalClass = getJavaType();
            if (arg instanceof ParameterExpressionImpl) {
                return ((ParameterExpressionImpl)arg).toValue(factory, q);
            }
            int literalType = Literal.TYPE_UNKNOWN;
            if (Number.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_NUMBER;
            } else if (Boolean.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_BOOLEAN;
            } else if (String.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_STRING;
            } else if (Enum.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_ENUM;
            } else if (Class.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_CLASS;
                Literal lit = factory.newTypeLiteral(value, Literal.TYPE_CLASS);
                ClassMetaData can = ((Types.Entity<X>)q.getRoot().getModel()).meta;
                Class<?> candidate = can.getDescribedType();
                if (candidate.isAssignableFrom((Class)value)) {
                   lit.setMetaData(q.getMetamodel().getRepository().getMetaData((Class<?>)value, null, true));
                } else {
                    lit.setMetaData(can);
                }
                return lit;
            } else if (Collection.class.isAssignableFrom(literalClass)) {
                literalType = Literal.TYPE_COLLECTION;
            }
            return factory.newLiteral(value, literalType);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, arg instanceof Expression ? ((Expression)arg) : null);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            if (arg == null)
                return new StringBuilder("NULL");
            Class<?> literalClass = getJavaType();
            if (arg instanceof ParameterExpressionImpl) {
                return ((ParameterExpressionImpl<?>)arg).asValue(q);
            } else if (Number.class.isAssignableFrom(literalClass)) {
                return new StringBuilder(arg.toString());
            } else if (Boolean.class.isAssignableFrom(literalClass)) {
                return new StringBuilder(arg.toString());
            } else if (String.class.isAssignableFrom(literalClass)) {
                return new StringBuilder("'").append(arg.toString()).append("'");
            } else if (Enum.class.isAssignableFrom(literalClass)) {
                return new StringBuilder(arg.toString());
            } else if (Class.class.isAssignableFrom(literalClass)) {
                return new StringBuilder(((Class)arg).getSimpleName());
            } else if (Collection.class.isAssignableFrom(literalClass)) {
                return new StringBuilder(((Collection)arg).toString());
            }
            return new StringBuilder(arg.toString());
        }
    }

    public static class IsEmpty extends PredicateImpl {
        final ExpressionImpl<?> collection;
        public IsEmpty(Expression<?> collection) {
            super();
            this.collection = (ExpressionImpl<?>)collection;
        }

        @Override
        public PredicateImpl not() {
            return new IsNotEmpty(collection).markNegated();
        }

        @Override
        Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return Expressions.toValue(collection, factory, q);
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val = Expressions.toValue(collection, factory, q);
            return factory.isEmpty(val);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, collection);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, collection, " IS EMPTY");
        }
    }

    public static class IsNotEmpty extends PredicateImpl {
        final ExpressionImpl<?> collection;
        public IsNotEmpty(Expression<?> collection) {
            super();
            this.collection = (ExpressionImpl<?>)collection;
        }

        @Override
        public PredicateImpl not() {
            return new IsEmpty(collection).markNegated();
        }

        @Override
        Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return Expressions.toValue(collection, factory, q);
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(ExpressionFactory factory, CriteriaQueryImpl<?> q){
            Value val = Expressions.toValue(collection, factory, q);
            // factory.isNotEmpty() not used to match JPQL
            return factory.not(factory.isEmpty(val));
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, collection);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, collection, " IS NOT EMPTY");
        }
    }


    public static class Index extends UnaryFunctionalExpression<Integer> {
        public Index(Joins.List<?,?> e) {
            super(Integer.class, e);
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value v = Expressions.toValue(e, factory, q);
            ClassMetaData meta = ((PathImpl<?,?>)e)._member.fmd.getElement().getTypeMetaData();
            v.setMetaData(meta);
            return factory.index(v);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "INDEX", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class IsMember<E> extends PredicateImpl {
        final ExpressionImpl<E> element;
        final ExpressionImpl<?> collection;

        public IsMember(Expression<E> element, Expression<?> collection) {
            super();
            this.element = (ExpressionImpl<E>)element;
            this.collection = (ExpressionImpl<?>)collection;
        }

        public IsMember(E element, Expression<?> collection) {
            this(new Constant<>(element), collection);
        }

        @Override
        public org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            org.apache.openjpa.kernel.exps.Expression contains = factory.contains(
                Expressions.toValue(collection, factory, q),
                Expressions.toValue(element, factory, q));
            return contains;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, collection, element);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, element, "MEMBER OF ", collection);
        }
    }

    public static class Like extends PredicateImpl {
        public static final String MATCH_MULTICHAR  = "%";
        public static final String MATCH_SINGLECHAR = "_";

        final ExpressionImpl<String> str;
        final ExpressionImpl<String> pattern;
        final ExpressionImpl<Character> escapeChar;

        public Like(Expression<String> x, Expression<String> pattern, Expression<Character> escapeChar) {
            super();
            this.str = (ExpressionImpl<String>)x;
            this.pattern = (ExpressionImpl<String>)pattern;
            this.escapeChar = (ExpressionImpl<Character>)escapeChar;
        }

        public Like(Expression<String> x, Expression<String> pat, char esc) {
            this(x, pat, new Constant<>(Character.class, esc));
        }

        public Like(Expression<String> x, Expression<String> pattern) {
            this(x, pattern, null);
        }

        public Like(Expression<String> x, String pattern) {
            this(x, new Constant<>(pattern), null);
        }

        public Like(Expression<String> x, String pat,
            Expression<Character> esc) {
            this(x, new Constant<>(pat), esc);
        }

        public Like(Expression<String> x, String pat,  Character esc) {
            this(x, new Constant<>(pat), new Constant<>(esc));
        }

        @Override
        public org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            String escapeStr = escapeChar == null ? null :
                ((Character)((Literal)Expressions.toValue(
                    escapeChar, factory, q)).getValue()).toString();

            return factory.matches(
                Expressions.toValue(str, factory, q),
                Expressions.toValue(pattern, factory, q),
                MATCH_SINGLECHAR, MATCH_MULTICHAR, escapeStr);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, str, pattern, escapeChar);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, str, " LIKE ", pattern);
        }
    }

    public static class Coalesce<T> extends ExpressionImpl<T> implements CriteriaBuilder.Coalesce<T> {
        private final List<Expression<? extends T>> values = new ArrayList<>();

        public Coalesce(Class<T> cls) {
            super(cls);
        }

        @Override
        public Coalesce<T> value(T value) {
            values.add(new Constant<>(value));
            return this;
        }

        @Override
        public Coalesce<T> value(Expression<? extends T> value) {
            values.add(value);
            return this;
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value[] vs = new Value[values.size()];
            int i = 0;
            for (Expression<?> e : values)
                vs[i++] = Expressions.toValue((ExpressionImpl<?>)e, factory, q);
            return factory.coalesceExpression(vs);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, values.toArray(new ExpressionImpl[values.size()]));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "COALESCE", OPEN_BRACE, Expressions.asValue(q, values == null
                    ? null : values.toArray(new Expression<?>[values.size()]), COMMA), CLOSE_BRACE);
        }
    }

    public static class Nullif<T> extends ExpressionImpl<T> {
        private Expression<T> val1;
        private Expression<?> val2;

        public Nullif(Expression<T> x, Expression<?> y) {
            super((Class<T>)x.getJavaType());
            val1 = x;
            val2 = y;
        }

        public Nullif(Expression<T> x, T y) {
            this(x, new Constant<>(y));
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value value1 = Expressions.toValue((ExpressionImpl<?>)val1, factory, q);
            Value value2 = Expressions.toValue((ExpressionImpl<?>)val2, factory, q);
            return factory.nullIfExpression(value1, value2);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, val1, val2);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "NULLIF", OPEN_BRACE, val1, COMMA, val2, CLOSE_BRACE);
        }
    }

    public static class IsNull extends PredicateImpl {
        final ExpressionImpl<?> e;
        public IsNull(ExpressionImpl<?> e) {
            super();
            this.e = e;
        }

        @Override
        public PredicateImpl not() {
            return new IsNotNull(e).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.equal(
                Expressions.toValue(e, factory, q),
                factory.getNull());
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, e);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e, " IS NULL");
        }
    }

    public static class IsNotNull extends PredicateImpl {
        final ExpressionImpl<?> e;
        public IsNotNull(ExpressionImpl<?> e) {
            super();
            this.e = e;
        }

        @Override
        public PredicateImpl not() {
            return new IsNull(e).markNegated();
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.notEqual(
                Expressions.toValue(e, factory, q),
                factory.getNull());
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, e);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, e, " IS NOT NULL");
        }
    }


    public static class In<T> extends PredicateImpl.Or implements CriteriaBuilder.In<T> {
        final ExpressionImpl<T> e;
        public In(Expression<?> e) {
            super();
            this.e = (ExpressionImpl<T>)e;
        }

        @Override
        public Expression<T> getExpression() {
            return e;
        }

        @Override
        public In<T> value(T value) {
            add(new Expressions.Equal(e,value));
            return this;
        }

        @Override
        public In<T> value(Expression<? extends T> value) {
            add(new Expressions.Equal(e,value));
            return this;
        }

        @Override
        public PredicateImpl not() {
            In<T> notIn = new In<>(e);
            notIn.markNegated();
            for (Predicate e : _exps) {
                notIn.add(e);
            }
            return notIn;
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            org.apache.openjpa.kernel.exps.Expression inExpr = null;
            if (_exps.size() == 1) {
                Expressions.Equal e = (Expressions.Equal)_exps.get(0);
                ExpressionImpl<?> e2 = e.e2;
                ExpressionImpl<?> e1 = e.e1;

                Class<?> e1JavaType = e1.getJavaType();
                Class<?> e2JavaType = e2.getJavaType();

                // array and Collection
                if (BindableParameter.class.isInstance(e2) && BindableParameter.class.cast(e2).value() != null &&
                    ((e2JavaType.isArray() && e2JavaType.getComponentType().equals(e1JavaType))
                    || (Class.class.isInstance(e2JavaType) ||
                        (ParameterizedType.class.isInstance(e2JavaType)
                            && ParameterizedType.class.cast(e2JavaType).getActualTypeArguments().length > 0
                            && e1JavaType.equals(ParameterizedType.class.cast(e2JavaType).getActualTypeArguments()[0]))
                       ))) {
                    final BindableParameter bp = BindableParameter.class.cast(e2);
                    final Object value = bp.value();

                    _exps.clear();
                    if (value == null) {
                        add(new Expressions.Equal(e1, null));
                    } else if (value.getClass().isArray()) {
                        final int len = Array.getLength(value);
                        for (int i = 0; i < len; i++) {
                            add(new Expressions.Equal(e1, Array.get(value, i)));
                        }
                    } else if (Collection.class.isInstance(value)) {
                        for (final Object item : Collection.class.cast(value)) {
                            add(new Expressions.Equal(e1, item));
                        }
                    }
                } else {
                    // normal case
                    Value val2 = Expressions.toValue(e2, factory, q);
                    if (!(val2 instanceof Literal)) {
                        Value val1 = Expressions.toValue(e1, factory, q);
                        Expressions.setImplicitTypes(val1, val2, e1.getJavaType(), q);
                        inExpr = factory.contains(val2, val1);
                        return isNegated() ? factory.not(inExpr) : inExpr;
                    } else if (((Literal)val2).getParseType() == Literal.TYPE_COLLECTION) {
                        Collection coll = (Collection)((Literal)val2).getValue();
                        _exps.clear();
                        for (Object v : coll) {
                            add(new Expressions.Equal(e1,v));
                        }
                    }
                }
            }
            inExpr = super.toKernelExpression(factory, q);
            IsNotNull notNull = new Expressions.IsNotNull(e);

            return factory.and(inExpr, notNull.toKernelExpression(factory, q));
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            super.acceptVisit(visitor);
            Expressions.acceptVisit(visitor, this, e);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            StringBuilder buffer = Expressions.asValue(q, e, " IN ", OPEN_BRACE);
            for (int i = 0; i < _exps.size(); i++) {
                buffer.append(((Equal)_exps.get(i)).e2.asValue(q)).append(i+1 == _exps.size() ? CLOSE_BRACE : COMMA);
            }
            return buffer;
        }
    }

    public static class Case<T> extends ExpressionImpl<T> implements CriteriaBuilder.Case<T> {
        private final List<Expression<? extends T>> thens = new ArrayList<>();
        private final List<Expression<Boolean>> whens = new ArrayList<>();
        private Expression<? extends T> otherwise;

        public Case(Class<T> cls) {
            super(cls);
        }

        @Override
        public Case<T> when(Expression<Boolean> when, Expression<? extends T> then) {
            whens.add(when);
            thens.add(then);
            return this;
        }

        @Override
        public Case<T> when(Expression<Boolean> when, T then) {
            return when(when, new Expressions.Constant<>(then));
        }

        @Override
        public Case<T> otherwise(Expression<? extends T> otherwise) {
            this.otherwise = otherwise;
            return this;
        }

        @Override
        public Case<T> otherwise(T otherwise) {
            return otherwise(new Expressions.Constant<>(otherwise));
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            int size = whens.size();
            org.apache.openjpa.kernel.exps.Expression[] exps = new org.apache.openjpa.kernel.exps.Expression[size];
            for (int i = 0; i < size; i++) {
                org.apache.openjpa.kernel.exps.Expression expr =
                    ((ExpressionImpl<?>)whens.get(i)).toKernelExpression(factory, q);
                Value action = Expressions.toValue((ExpressionImpl<?>)thens.get(i), factory, q);
                exps[i] = factory.whenCondition(expr, action);
            }

            Value other = Expressions.toValue((ExpressionImpl<?>)otherwise, factory, q);
            return factory.generalCaseExpression(exps, other);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            visitor.enter(this);
            for (int i = 0; i < whens.size(); i++) {
                Expressions.visitChildren(visitor, whens.get(i));
                Expressions.visitChildren(visitor, thens.get(i));
            }
            Expressions.visitChildren(visitor, otherwise);
            visitor.exit(this);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            StringBuilder buffer = new StringBuilder("CASE ");
            int size = whens.size();
            for (int i = 0; i < size; i++) {
                buffer.append(Expressions.asValue(q, " WHEN ", whens.get(i), " THEN ", thens.get(i)));
            }
            buffer.append(Expressions.asValue(q, " ELSE ", otherwise, " END"));
            return buffer;
        }
    }

    public static class SimpleCase<C, R> extends ExpressionImpl<R> implements CriteriaBuilder.SimpleCase<C,R> {
        private final List<Expression<? extends R>> thens = new ArrayList<>();
        private final List<Expression<C>> whens = new ArrayList<>();
        private Expression<? extends R> otherwise;
        private Expression<C> caseOperand;

        public SimpleCase(Class<R> cls) {
            super(cls);
        }

        public SimpleCase(Expression<C> expr) {
            super(null);
            this.caseOperand = expr;
        }

        @Override
        public Expression<C> getExpression() {
            return caseOperand;
        }

        public SimpleCase<C,R> when(Expression<? extends C> when, Expression<? extends R> then) {
            whens.add((Expression<C>) when);
            thens.add(then);
            return this;
        }

        @Override
        public SimpleCase<C, R> when(Expression<? extends C> condition, R result) {
            whens.add((Expression<C>) condition);
            thens.add(new Constant<>(result));
            return this;
        }

        @Override
        public SimpleCase<C,R> when(C when, Expression<? extends R> then) {
            return when(new Constant<>(when), then);
        }

        @Override
        public SimpleCase<C,R> when(C when, R then) {
            return when(when, new Expressions.Constant<>(then));
        }

        @Override
        public SimpleCase<C,R> otherwise(Expression<? extends R> otherwise) {
            this.otherwise = otherwise;
            return this;
        }

        @Override
        public SimpleCase<C,R> otherwise(R otherwise) {
            return otherwise(new Expressions.Constant<>(otherwise));
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            Value caseOperandExpr = Expressions.toValue((ExpressionImpl<?>)caseOperand, factory, q);
            int size = whens.size();
            org.apache.openjpa.kernel.exps.Expression[] exps = new org.apache.openjpa.kernel.exps.Expression[size];
            for (int i = 0; i < size; i++) {
                Value when = Expressions.toValue((ExpressionImpl<C>)whens.get(i), factory, q);
                Value action = Expressions.toValue((ExpressionImpl<?>)thens.get(i), factory, q);
                exps[i] = factory.whenScalar(when, action);
            }

            Value other = Expressions.toValue((ExpressionImpl<?>)otherwise, factory, q);
            return factory.simpleCaseExpression(caseOperandExpr, exps, other);
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            visitor.enter(this);
            Expressions.visitChildren(visitor, caseOperand);
            for (int i = 0; i < whens.size(); i++) {
                Expressions.visitChildren(visitor, whens.get(i));
                Expressions.visitChildren(visitor, thens.get(i));
            }
            Expressions.visitChildren(visitor, otherwise);
            visitor.exit(this);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            StringBuilder buffer = new StringBuilder("CASE ");
            int size = whens.size();
            for (int i = 0; i < size; i++) {
                buffer.append(Expressions.asValue(q, " WHEN ", whens.get(i), " THEN ", thens.get(i)));
            }
            buffer.append(Expressions.asValue(q, " ELSE ", otherwise, " END"));
            return buffer;
        }
    }

    public static class Lower extends UnaryFunctionalExpression<String> {
        public Lower(Expression<String> x) {
            super(String.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.toLowerCase(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "LOWER", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Upper extends UnaryFunctionalExpression<String> {
        public Upper(Expression<String> x) {
            super(String.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.toUpperCase(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "UPPER", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Length extends UnaryFunctionalExpression<Integer> {
        public Length(Expression<String> x) {
            super(Integer.class, x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.stringLength(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "LENGTH", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static abstract class SubqueryPredicate<X> extends PredicateImpl {
        final SubqueryImpl<X> e;

        public SubqueryPredicate(Subquery<X> x) {
            super();
            e = (SubqueryImpl<X>)x;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e);
        }
    }

    public static abstract class SubqueryExpression<X> extends ExpressionImpl<X> {
        final SubqueryImpl<X> e;

        public SubqueryExpression(Subquery<X> x) {
            super((Class<X>)x.getJavaType());
            e = (SubqueryImpl<X>)x;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e);
        }
    }

    public static class Exists<X> extends SubqueryPredicate<X> {
        public Exists(Subquery<X> x) {
            super(x);
        }

        @Override
        org.apache.openjpa.kernel.exps.Expression toKernelExpression(
            ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            org.apache.openjpa.kernel.exps.Expression exists =
                factory.isNotEmpty(Expressions.toValue(e, factory, q));
            return exists;
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, " EXISTS", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class All<X> extends SubqueryExpression<X> {
        public All(Subquery<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.all(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "ALL", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Any<X> extends SubqueryExpression<X> {
        public Any(Subquery<X> x) {
            super(x);
        }

        @Override
        public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.any(Expressions.toValue(e, factory, q));
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "ANY", OPEN_BRACE, e, CLOSE_BRACE);
        }
    }

    public static class Not extends PredicateImpl {
        protected final ExpressionImpl<Boolean> e;
        public Not(Expression<Boolean> ne) {
            super();
            e = (ExpressionImpl<Boolean>)ne;
        }

        @Override
        public org.apache.openjpa.kernel.exps.Expression toKernelExpression(
          ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            return factory.not(e.toKernelExpression(factory, q));
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, e);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return Expressions.asValue(q, "NOT ", e);
        }
    }

    public static class CastAs<Y> extends ExpressionImpl<Y> {
        protected final ExpressionImpl<?> actual;
        public CastAs(Class<Y> cast, ExpressionImpl<?> actual) {
            super(cast);
            this.actual = actual;
        }

        @Override
        public org.apache.openjpa.kernel.exps.Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            org.apache.openjpa.kernel.exps.Value e = actual.toValue(factory, q);
            e.setImplicitType(getJavaType());
            return e;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, actual);
        }

        @Override
        public StringBuilder asValue(AliasContext q) {
            return actual.asValue(q);
        }
    }

    /**
     * An expression that is composed of one or more expressions.
     *
     * @param <T>
     */
    public static class ListArgument<T> extends ExpressionImpl<T> {
        private final ExpressionImpl<?>[] _args;
        public ListArgument(Class<T> cls, ExpressionImpl<?>... args) {
            super(cls);
            _args = args;
        }

        @Override
        public org.apache.openjpa.kernel.exps.Arguments toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
            org.apache.openjpa.kernel.exps.Value[] kvs = new org.apache.openjpa.kernel.exps.Value[_args.length];
            int i = 0;
            for (ExpressionImpl<?> arg : _args) {
                kvs[i++] = arg.toValue(factory, q);
            }
            org.apache.openjpa.kernel.exps.Arguments e = factory.newArgumentList(kvs);
            e.setImplicitType(getJavaType());
            return e;
        }

        @Override
        public void acceptVisit(CriteriaExpressionVisitor visitor) {
            Expressions.acceptVisit(visitor, this, _args);
        }
    }
}
