public abstract class Expression implements Criterion, WithOperator, WithProperty {

    private final Operator operator;
    private final String propertyName;
    private final AggregateProjection aggregateProjection;

    protected Expression(Operator operator, String propertyName, AggregateProjection aggregateProjection) {
        this.operator = operator;
        this.propertyName = propertyName;
        this.aggregateProjection = aggregateProjection;
    }

    protected Expression(Operator operator, String propertyName) {
        this.operator = operator;
        this.propertyName = propertyName;
        this.aggregateProjection = null;
    }

    @Override
    public Operator getOperator() {
        return operator;
    }

    @Override
    public String getPropertyName() {
        return propertyName;
    }

    public AggregateProjection getAggregateProjection() {
        return aggregateProjection;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((operator == null) ? 0 : operator.hashCode());
        result = prime * result
                + ((propertyName == null) ? 0 : propertyName.hashCode());
        result = prime * result
                + ((aggregateProjection == null) ? 0 : aggregateProjection.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Expression other = (Expression) obj;
        if (operator != other.operator)
            return false;
        if (propertyName == null) {
            if (other.propertyName != null)
                return false;
        } else if (!propertyName.equals(other.propertyName))
            return false;
        if (aggregateProjection == null) {
            if (other.aggregateProjection != null)
                return false;
        } else if (!aggregateProjection.equals(other.aggregateProjection))
            return false;
        return true;
    }

} public class AggregateProjection extends Projection {
    private String propertyName;
    private Aggregate function;

    protected AggregateProjection(Aggregate function, String propertyName) {
        super(function, propertyName);
        this.propertyName = propertyName;
        this.function = function;
    }

    public String getPropertyName(){
        return propertyName;
    }

    public AggregateProjection setPropertyName(String propertyName) {
        this.propertyName = propertyName;
        return this;
    }

    public String getName() {
        return function.symbol() + "(" + propertyName + ")";
    }
} public class GroupProjection extends Projection{

    public GroupProjection(String... propertyNames) {
        super(Aggregate.GROUP_BY, propertyNames);
    }

    @Override
    public String toString() {
        return "GroupProjection{" + getPropertyNames() + "}";
    }
} public class Projection implements Criterion, WithAggregateFunction {
    private final Aggregate function;
    private final List<String> propertyNames;

    protected Projection(Aggregate function, String... propertyNames) {
        this.function = function;
        this.propertyNames = Arrays.asList(propertyNames);
    }

    @Override
    public Aggregate getAggregate() {
        return this.function;
    }

    public List<String> getPropertyNames() {
        return this.propertyNames;
    }
} public class DataStoreException extends RuntimeException {

    private static final long serialVersionUID = -1363433871972493184L;

    public DataStoreException() {
        super();
    }

    public DataStoreException(String message) {
        super(message);
    }

    public DataStoreException(Throwable cause) {
        super(cause);
    }

    public DataStoreException(String message, Throwable cause) {
        super(message, cause);
    }

} public class PhoenixDateFormatUtil {

    public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss z";
    
    public static final String formatDate(Date date) {
        Preconditions.checkNotNull(date, "date must not be null");
        String formattedDate = new SimpleDateFormat(TIMESTAMP_FORMAT).format(date);
        StringBuffer dateFormatBuilder = new StringBuffer("TO_DATE('");
        dateFormatBuilder.append(formattedDate).append("', 'yyyy-MM-dd HH:mm:ss z')");
        return dateFormatBuilder.toString();
    }
    
} public class EntityPropertiesResolver implements PropertyResolver {

    private final EntityPropertiesMappingContext entityPropertiesMappingContext;

    public EntityPropertiesResolver(final EntityPropertiesMappingContext entityPropertiesMappingContext) {
        this.entityPropertiesMappingContext = entityPropertiesMappingContext;
    }

    @Override
    public EntityPropertyBinding resolveEntityPropertyBindingByStoreMappingName(String mappingName, Class<?> clz) {
        return entityPropertiesMappingContext.resolveEntityPropertyBindingByStoreMappingName(clz, mappingName);
    }

    @Override
    public EntityPropertyBinding resolveEntityPropertyBindingByEntityFieldName(String entityFieldName,
            Class<?> entityClass) {
        return entityPropertiesMappingContext.resolveEntityPropertyBindingByEntityFieldName(entityClass,
                entityFieldName);
    }

    @Override
    public List<String> resolveEntityMappingPropertyNames(List<String> fieldPropertyNames, Class<?> clz) {
        return entityPropertiesMappingContext.resolveEntityMappingPropertyNames(clz, fieldPropertyNames);
    }

    @Override
    public String resolve(String fieldPropertyName, Class<?> entityClass) {
        return entityPropertiesMappingContext.resolveEntityMappingPropertyName(entityClass, fieldPropertyName);
    }

    @Override
    public <T> Map<String, EntityPropertyBinding> getStoreFieldNamePropertyBindingMap(Class<T> clz) {
        return entityPropertiesMappingContext.getStoreFieldNamePropertyBindingMap(clz);
    }

    @Override
    public <T> Map<String, EntityPropertyBinding> getEntityPropertyNamePropertyBindingMap(Class<T> clz) {
        return entityPropertiesMappingContext.getEntityPropertyNamePropertyBindingMap(clz);
    }

} public class EntityPropertyBinding {

    private String name;
    private Type type;
    private String storeFieldName;
    private String nameFullPath;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public String getStoreFieldName() {
        return storeFieldName;
    }

    public void setStoreFieldName(String storeFieldName) {
        this.storeFieldName = storeFieldName;
    }

    public String getNameFullPath() {
        return nameFullPath;
    }

    public void setNameFullPath(String nameFullPath) {
        this.nameFullPath = nameFullPath;
    }

} public class EntityPropertyValueBinding {

    private final EntityPropertyBinding entityPropertyBinding;
    private int position;
    private Object value;
    
    public EntityPropertyValueBinding(EntityPropertyBinding entityPropertyBinding) {
        this.entityPropertyBinding = entityPropertyBinding;
    }
    
    public EntityPropertyBinding getEntityPropertyBinding() {
        return entityPropertyBinding;
    }
    
    public int getPosition() {
        return position;
    }
    
    public void setPosition(int position) {
        this.position = position;
    }
    
    public Object getValue() {
        return value;
    }
    
    public void setValue(Object value) {
        this.value = value;
    }
    
} public interface QuerySelect<T, R> {

    /**
     * Get the queried entity type.
     * 
     * @return the entity class being queried
     */
    public Class<T> getEntityClass();

    /**
     * Get the return entity type.
     * 
     * @return the class of the desired return type
     */
    public Class<R> getReturnType();

    /**
     * The list of properties to return. An empty collection means all properties.
     * 
     * @return the fields to be returned from the query
     */
    public List<String> getReturnFields();

    /**
     * Get the query criteria. The top level object can either be a single Criterion or a Junction of multiple nested
     * Criterion objects.
     * 
     * @return the root criterion node
     */
    public Criterion getCriteria();

    Criterion getGroupCriteria();

    /**
     * Get the Order clauses.
     * 
     * @return the ordering clauses
     */
    public Orderings getOrder();

    public List<Projection> getProjection();

    /**
     * Get the max desired results. Null signifies no maximum.
     * 
     * @return the maximum number of results or null if no maximum
     * 
     */
    public Integer getMaxResults();

    /**
     * Getter method for Query Operation type
     * 
     * @return <code>QueryOperationType</code> 
     */
    public QueryOperationType getQueryOperationType();

    /**
     * Getter method for Query Hint
     *
     * @return <code>String</code>
     */
    public String getQueryHint();

} public interface QueryUpdate<T> {

    /**
     * The list of properties to return. An empty collection means all properties.
     * 
     * @return the fields to be updated
     */
    public List<String> getSelectedFields();

    /**
     * Get the query criteria. The top level object can either be a single Criterion or a Junction of multiple nested
     * Criterion objects.
     * 
     * @return the root criterion node
     */
    public Criterion getCriteria();

    /**
     * Getter method for Query Operation type for update
     * 
     * @return <code>QueryOperationType</code> 
     */
    public QueryOperationType getQueryOperationType();

    /**
     * Get the entity, which is set to persist
     * @return T
     */
    public T getEntity();

} public interface Criterion {
} public class Ordering implements Criterion, WithProperty {

    public static enum Order {
        ASCENDING, DESCENDING
    }
    
    /**
     * When property name is <tt>null</tt> should those values be put first or last
     */
    public static enum NullOrdering {
        FIRST, LAST
    }

    private final String propertyName;
    private final Order order;
    private final NullOrdering nullOrdering;

    /**
     * Create an ordering to order results by the given property in ascending order with nulls first
     * @param propertyName the name of the property to order by
     * @return the ordering
     */
    public static Ordering asc(String propertyName) {
        return new Ordering(propertyName, Order.ASCENDING, NullOrdering.FIRST);
    }

    /**
     * Create an ordering to order results by the given property in descending order with nulls first
     * @param propertyName the name of the property to order by
     * @return the ordering
     */
    public static Ordering desc(String propertyName) {
        return new Ordering(propertyName, Order.DESCENDING, NullOrdering.FIRST);
    }
    
    /**
     * Create an ordering to order results by the given property in ascending order with nulls first
     * @param propertyName the name of the property to order by
     * @param nullOrdering should nulls be first or last
     * @return the ordering
     */
    public static Ordering asc(String propertyName, NullOrdering nullOrdering) {
        return new Ordering(propertyName, Order.ASCENDING, nullOrdering);
    }

    /**
     * Create an ordering to order results by the given property in descending order with nulls first
     * @param propertyName the name of the property to order by
     * @param nullOrdering should nulls be first or last
     * @return the ordering
     */
    public static Ordering desc(String propertyName, NullOrdering nullOrdering) {
        return new Ordering(propertyName, Order.DESCENDING, nullOrdering);
    }

    /**
     * Create an ordering
     * @param propertyName the property to order by
     * @param order the order that should be applied (ascending or descending)
     * @param nullOrdering whether nulls should be first or last
     */
    public Ordering(String propertyName, Order order, NullOrdering nullOrdering) {
        this.propertyName = propertyName;
        this.order = order;
        this.nullOrdering = nullOrdering;
    }

    @Override
    public String getPropertyName() {
        return propertyName;
    }

    /**
     * Get the order to return the sorted items in
     * @return ascending or descending
     */
    public Order getOrder() {
        return order;
    }

    /**
     * Get the ordering of nulls
     * @return whether nulls should be first or last
     */
    public NullOrdering getNullOrdering() {
    
        return nullOrdering;
    
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((order == null) ? 0 : order.hashCode());
        result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Ordering other = (Ordering) obj;
        if (order != other.order)
            return false;
        if (propertyName == null) {
            if (other.propertyName != null)
                return false;
        } else if (!propertyName.equals(other.propertyName))
            return false;
        return true;
    }

} public class Orderings {

    private final List<Ordering> orderings = new ArrayList<Ordering>();

    public Orderings() {
    }

    public Orderings(Ordering... orderings) {
        this.orderings.addAll(Arrays.asList(orderings));
    }

    public void add(Ordering ordering) {
        orderings.add(ordering);
    }

    // Discuss: defensive copy?
    public List<Ordering> get() {
        return orderings;
    }

    @Override
    public String toString() {
        return "Orderings [" + orderings + "]";
    }
} public static enum NullOrdering {
    FIRST, LAST
} public static enum Order {
    ASCENDING, DESCENDING
} public class NativeExpression implements Criterion {

    private final Class<?> expressionClass;
    private final Object expression;

    public <T> NativeExpression(Class<T> expressionClass, T expression) {
        this.expressionClass = expressionClass;
        this.expression = expression;
    }

    public Class<?> getExpressionClass() {
        return expressionClass;
    }

    public Object getExpression() {
        return expression;
    }

    @Override
    public String toString() {
        return "NativeExpression [" + expression + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((expression == null) ? 0 : expression.hashCode());
        result = prime * result
                + ((expressionClass == null) ? 0 : expressionClass.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NativeExpression other = (NativeExpression) obj;
        if (expression == null) {
            if (other.expression != null)
                return false;
        } else if (!expression.equals(other.expression))
            return false;
        if (expressionClass == null) {
            if (other.expressionClass != null)
                return false;
        } else if (!expressionClass.equals(other.expressionClass))
            return false;
        return true;
    }
} public abstract class AbstractQueryTranslator<Q, O, P> implements QueryTranslator<Q, O, P> {

    private final Class<Q> queryClass;
    private final Class<O> orderClass;
    private final PropertyResolver propertyResolver;

    public AbstractQueryTranslator(Class<Q> queryClass, Class<O> orderClass, PropertyResolver propertyResolver) {
        this.queryClass = queryClass;
        this.orderClass = orderClass;
        this.propertyResolver = propertyResolver;
    }

    protected Class<Q> getQueryClass() {
        return queryClass;
    }

    protected Class<O> getOrderClass() {
        return orderClass;
    }

    protected PropertyResolver getPropertyResolver() {
        return propertyResolver;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.eharmony.matching.seeking.translator.QueryTranslator#translate(com.eharmony.matching.seeking.query.Query)
     */
    @Override
    public <T, R> Q translate(QuerySelect<T, R> query) {
        Criterion rootCriterion = query.getCriteria();
        Class<T> entityClass = query.getEntityClass();
        return translate(rootCriterion, entityClass);
    }

    /*
     * Notes regarding the abundance of instanceof checks:
     * 
     * A case for considering it the lesser of evils: https://sites.google.com/site/steveyegge2/when-polymorphism-fails
     * 
     * If we were using Scala, we could create datastore specific Traits that would allow us to extend the Generic Query
     * and allow it produce a datastore specific query.
     * 
     * There's also a strong case for the visitor pattern. The downsides are needlessly exposing the underlying
     * Criterion types to the various implementations and a significant increase in the number of classes needed for
     * implementations.
     * 
     * If this becomes a maintainability or performance issue then we should look at replacing the current approach with
     * something like the visitor pattern.
     */
    protected <T> Q translate(Criterion c, Class<T> entityClass, AggregateProjection projection) {
        // a Criterion can be an Expression or a Junction
        if (c instanceof Expression) {
            return translate((Expression) c, entityClass, projection);
        } else if (c instanceof Junction) {
            return translate((Junction) c, entityClass);
        } else if (c instanceof GroupProjection) {
            return translate((GroupProjection) c, entityClass);
        } else if (c instanceof NativeExpression) {
            return translate((NativeExpression) c, entityClass);
        } else {
            throw unsupported(c.getClass());
        }
    }

    protected <T> Q translate(Criterion c, Class<T> entityClass) {
        return translate(c, entityClass, null);
    }

    protected abstract <T> Q translate(NativeExpression e, Class<T> entityClass);

    protected <T> Q translate(Expression e, Class<T> entityClass, AggregateProjection projection) {
        String fieldName = propertyResolver.resolve(e.getPropertyName(), entityClass);
        if (e instanceof EqualityExpression) {
            return translate((EqualityExpression) e, fieldName, projection);
        } else if (e instanceof RangeExpression) {
            return translate((RangeExpression) e, fieldName, projection);
        } else if (e instanceof SetExpression) {
            return translate((SetExpression) e, fieldName, projection);
        } else if (e instanceof UnaryExpression) {
            return translate((UnaryExpression) e, fieldName, projection);
        } else {
            throw unsupported(e.getClass());
        }
    }

    protected <T> Q translate(Projection projection, Class<T> entityClass) {
        if (projection instanceof GroupProjection) {
            List<String> propertyNames = projection.getPropertyNames();
            return translate((GroupProjection) projection,
                    propertyNames.stream()
                            .map(n -> propertyResolver.resolve(n, entityClass))
                            .toArray(String[]::new));
        } else {
            throw unsupported(projection.getClass());
        }
    }

    protected <T> Q translate(Junction j, Class<T> entityClass) {
        // a Junction can be a Conjunction (and) or a Disjunction (or)
        if (j instanceof Conjunction) {
            return translate((Conjunction) j, entityClass);
        } else if (j instanceof Disjunction) {
            return translate((Disjunction) j, entityClass);
        } else {
            throw unsupported(j.getClass());
        }
    }

    protected <T> Q translate(Conjunction j, Class<T> entityClass) {
        return and(subqueries(j, entityClass));
    }

    protected <T> Q translate(Disjunction j, Class<T> entityClass) {
        return or(subqueries(j, entityClass));
    }

    @SuppressWarnings("unchecked")
    protected <T> Q[] subqueries(Junction j, Class<T> entityClass) {
        List<Criterion> criteria = j.getCriteria();
        List<Q> translated = new ArrayList<Q>(criteria.size());
        for (Criterion c : criteria) {
            Q q = translate(c, entityClass, ((Expression) c).getAggregateProjection());
            if (q != null) {
                translated.add(q);
            }
        }
        return translated.toArray((Q[]) Array.newInstance(queryClass, translated.size()));
    }

    protected Q translate(EqualityExpression e, String fieldName, AggregateProjection projection) {
        Operator operator = e.getOperator();
        Object value = e.getValue();
        if (projection != null) {
            fieldName = (String) translate(projection, fieldName);
        }
        switch (operator) {
            case EQUAL:
                return eq(fieldName, value);
            case NOT_EQUAL:
                return ne(fieldName, value);
            case GREATER_THAN:
                return gt(fieldName, value);
            case GREATER_THAN_OR_EQUAL:
                return gte(fieldName, value);
            case LESS_THAN:
                return lt(fieldName, value);
            case LESS_THAN_OR_EQUAL:
                return lte(fieldName, value);
            case LIKE:
                return like(fieldName, value);
            case ILIKE:
                return insensitiveLike(fieldName, value);
            default:
                throw unsupported(operator, EqualityExpression.class);
        }
    }

    protected Q translate(RangeExpression e, String fieldName, AggregateProjection projection) {
        Operator operator = e.getOperator();
        Object from = e.getFrom();
        Object to = e.getTo();
        if (projection != null) {
            fieldName = (String) translate(projection, fieldName);
        }
        switch (operator) {
            case BETWEEN:
                return between(fieldName, from, to);
            default:
                throw unsupported(operator, RangeExpression.class);
        }
    }

    protected Q translate(SetExpression e, String fieldName, AggregateProjection projection) {
        Operator operator = e.getOperator();
        Object[] values = e.getValues();
        if (projection != null) {
            fieldName = (String) translate(projection, fieldName);
        }
        switch (operator) {
            case IN:
                return in(fieldName, values);
            case NOT_IN:
                return notIn(fieldName, values);
            case CONTAINS:
                return contains(fieldName, values);
            default:
                throw unsupported(operator, SetExpression.class);
        }
    }

    protected Q translate(UnaryExpression e, String fieldName, AggregateProjection projection) {
        Operator operator = e.getOperator();
        if (projection != null) {
            fieldName = (String) translate(projection, fieldName);
        }
        switch (operator) {
            case NULL:
                return isNull(fieldName);
            case NOT_NULL:
                return notNull(fieldName);
            case EMPTY:
                return isEmpty(fieldName);
            case NOT_EMPTY:
                return notEmpty(fieldName);
            default:
                throw unsupported(operator, UnaryExpression.class);
        }
    }

    protected P translate(AggregateProjection a, String fieldName) {
        Aggregate aggregate = a.getAggregate();
        switch (aggregate) {
            case AVG:
                return avg(fieldName);
            case MAX:
                return max(fieldName);
            case MIN:
                return min(fieldName);
            case COUNT:
                return count(fieldName);
            default:
                throw unsupported(aggregate, AggregateProjection.class);
        }
    }

    protected Q translate(GroupProjection g, String... fieldNames) {
        return groupBy(fieldNames);
    }


    @Override
    public <T, R> O translateOrder(QuerySelect<T, R> query) {
        List<Ordering> orderingList = query.getOrder().get();
        @SuppressWarnings("unchecked")
        O[] orders = (O[]) Array.newInstance(orderClass, orderingList.size());
        for (int i = 0; i < orders.length; i++) {
            Ordering ordering = orderingList.get(i);
            orders[i] = order(propertyResolver.resolve(ordering.getPropertyName(), query.getEntityClass()),
                    ordering);
        }
        return order(orders);
    }

    protected UnsupportedOperationException unsupported(Class<? extends Criterion> type) {
        throw new UnsupportedOperationException(type.getSimpleName() + " type not supported.");
    }

    protected UnsupportedOperationException unsupported(Operator operator, Class<? extends Expression> expressionType) {
        throw new UnsupportedOperationException(operator + " not supported for " + expressionType.getSimpleName());
    }

    protected UnsupportedOperationException unsupported(Aggregate aggregate, Class<? extends Projection> projectionType) {
        throw new UnsupportedOperationException(aggregate + " not supported for " + projectionType.getSimpleName());
    }

    protected UnsupportedOperationException unsupported(NativeExpression e) {
        throw new UnsupportedOperationException("Native Expression (" + e.getExpression() + ") of type "
                + e.getExpressionClass() + " not supported.");
    }

    /**
     * Translate an "equal" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q eq(String fieldName, Object value);

    /**
     * Translate a "not equal" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q ne(String fieldName, Object value);

    /**
     * Translate a "less than" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q lt(String fieldName, Object value);

    /**
     * Translate a "less than or equal" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q lte(String fieldName, Object value);

    /**
     * Translate a "greater than" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q gt(String fieldName, Object value);

    /**
     * Translate a "greater than or equal" expression
     *
     * @param fieldName the resolved field name
     * @param value     the reference value
     * @return Q
     */
    public abstract Q gte(String fieldName, Object value);

    /**
     * Translate a "between" expression
     *
     * @param fieldName the resolved field name
     * @param from      the lower bound value
     * @param to        the upper bound value
     * @return Q
     */
    public abstract Q between(String fieldName, Object from, Object to);

    /**
     * Translate an "in" expression
     *
     * @param fieldName the resolved field name
     * @param values    the reference values
     * @return Q
     */
    public abstract Q in(String fieldName, Object[] values);

    /**
     * Translate a "not in" expression
     *
     * @param fieldName the resolved field name
     * @param values    the reference values
     * @return Q
     */
    public abstract Q notIn(String fieldName, Object[] values);

    /**
     * Translate a "contains" expression
     *
     * @param fieldName the resolved field name
     * @param values    the reference values
     * @return Q
     */
    public abstract Q contains(String fieldName, Object[] values);

    /**
     * Translate a "is null" expression
     *
     * @param fieldName the resolved field name
     * @return Q
     */
    public abstract Q isNull(String fieldName);

    /**
     * Translate a "not null" expression
     *
     * @param fieldName the resolved field name
     * @return Q
     */
    public abstract Q notNull(String fieldName);

    /**
     * Translate a "like" expression
     *
     * @param fieldName the resolved field name
     * @param value     the value to be like
     * @return Q
     */
    public abstract Q like(String fieldName, Object value);

    /**
     * Translate an "ilike" expression
     *
     * @param fieldName the resolved field name
     * @param value     the value to be like
     * @return Q
     */
    public abstract Q insensitiveLike(String fieldName, Object value);

    /**
     * Translate a "is empty" expression
     *
     * @param fieldName the resolved field name
     * @return Q
     */
    public abstract Q isEmpty(String fieldName);

    /**
     * Translate a "is empty" expression
     *
     * @param fieldName the resolved field name
     * @return Q
     */
    public abstract Q notEmpty(String fieldName);

    /**
     * Translate an order statement
     *
     * @param fieldName the resolved field name
     * @param ordering  the ordering
     * @return O
     */
    public abstract O order(String fieldName, Ordering ordering);

    /**
     * Join multiple orderings
     *
     * @param orders O
     * @return O
     */
    public abstract O order(@SuppressWarnings("unchecked") O... orders);

    /**
     * Translate an "and" expression
     *
     * @param subqueries Q
     * @return Q
     */
    public abstract Q and(@SuppressWarnings("unchecked") Q... subqueries);

    /**
     * Translate an "or" expression
     *
     * @param subqueries Q
     * @return Q
     */
    public abstract Q or(@SuppressWarnings("unchecked") Q... subqueries);

    /**
     * Translate a "limit expression" expression
     *
     * @param value Integer number of results to return
     * @return Q
     */
    public abstract Q limit(Integer value);

    public abstract Q groupBy(String... fieldNames);

    public abstract P count(String fieldName);

    public abstract P avg(String fieldName);

    public abstract P countAll();

    public abstract P sum(String fieldName);

    public abstract P max(String fieldName);

    public abstract P min(String fieldName);

} public interface QueryTranslator<Q, O, P> {

    /**
     * Translate the generic Query
     * 
     * @param <T>
     *            entity Type
     * @param <R>
     *            return type
     * @param query
     *            QuerySelect
     * @return Q
     */
    public <T, R> Q translate(QuerySelect<T, R> query);

    /**
     * Translate the generic Orderings
     * 
     * @param <T>
     *            entity Type
     * @param <R>
     *            return type
     * @param query
     *            QuerySelect
     * @return O
     */
    public <T, R> O translateOrder(QuerySelect<T, R> query);

    /**
     * Translate the "Projections"
     * 
     * @param <T>
     *            entity Type
     * @param <R>
     *            return type
     * @param query
     *            QuerySelect
     * @return P
     */
    public <T, R> P translateProjection(QuerySelect<T, R> query);

    /**
     * Translate the generic Query
     * 
     * @param <T>
     *            entity Type
     * @param updateQuery
     *            QueryUpdate
     * @return Q
     */
    public <T> Q translate(QueryUpdate<T> updateQuery);

} public enum PhoenixHBaseAggregate implements Symbolic {

    GROUP_BY("GROUP BY"),
    COUNT("COUNT"),
    AVG("AVG"),
    SUM("SUM"),
    MAX("MAX"),
    MIN("MIN");

    private final String symbol;

    PhoenixHBaseAggregate(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String symbol() {
        return symbol;
    }


    @Override
    public String toString() {
        return symbol();
    }
}
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.eharmony.pho.query.criterion.expression.Expression;
import com.eharmony.pho.query.criterion.projection.AggregateProjection;
import com.eharmony.pho.query.criterion.projection.GroupProjection;
import com.eharmony.pho.query.criterion.projection.Projection;
import com.google.common.base.Strings;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.eharmony.pho.api.DataStoreException;
import com.eharmony.pho.hbase.util.PhoenixDateFormatUtil;
import com.eharmony.pho.mapper.EntityPropertiesResolver;
import com.eharmony.pho.mapper.EntityPropertyBinding;
import com.eharmony.pho.mapper.EntityPropertyValueBinding;
import com.eharmony.pho.query.QuerySelect;
import com.eharmony.pho.query.QueryUpdate;
import com.eharmony.pho.query.criterion.Criterion;
import com.eharmony.pho.query.criterion.Ordering;
import com.eharmony.pho.query.criterion.Orderings;
import com.eharmony.pho.query.criterion.Ordering.NullOrdering;
import com.eharmony.pho.query.criterion.Ordering.Order;
import com.eharmony.pho.query.criterion.expression.NativeExpression;
import com.eharmony.pho.translator.AbstractQueryTranslator;
import com.eharmony.pho.translator.QueryTranslator;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import static com.eharmony.pho.hbase.translator.PhoenixHBaseAggregate.*;
    public String gte(String fieldName, Object value) {
        return join(resolveMappingName(fieldName), PhoenixHBaseOperator.GREATER_THAN_OR_EQUAL, value);
    }

    @Override
    public String insensitiveLike(String fieldName, Object value) {
        return join(resolveMappingName(fieldName),
                PhoenixHBaseOperator.LIKE_CASE_INSENSITIVE,
                String.format(STRING_OPERAND_WITH_WILDCARD, value));
    }

    @Override
    public String like(String fieldName, Object value) {
        return join(resolveMappingName(fieldName),
                PhoenixHBaseOperator.LIKE,
                String.format(STRING_OPERAND_WITH_WILDCARD, value));
    }

    @Override
    public String between(String fieldName, Object from, Object to) {
        throw new UnsupportedOperationException("BETWEEN operator is not supported in phoenix hbase library...");
    }

    @Override
    public String in(String fieldName, Object[] values) {
        throw new UnsupportedOperationException("IN operator is not supported in phoenix hbase library...");
    }

    @Override
    public String notIn(String fieldName, Object[] values) {
        throw new UnsupportedOperationException("NOTIN operator is not supported in phoenix hbase library...");
    }

    @Override
    public String contains(String fieldName, Object[] values) {
        throw new UnsupportedOperationException("CONTAINS operator is not supported in phoenix hbase library...");
    }

    @Override
    public String isNull(String fieldName) {
        return join(resolveMappingName(fieldName), PhoenixHBaseOperator.IS_NULL);
    }

    @Override
    public String notNull(String fieldName) {
        return join(resolveMappingName(fieldName), PhoenixHBaseOperator.IS_NOT_NULL);
    }

    @Override
    public String isEmpty(String fieldName) {
        throw new UnsupportedOperationException("ISEMPTY operator is not supported in phoenix hbase library...");
    }

    @Override
    public String notEmpty(String fieldName) {
        throw new UnsupportedOperationException("NOTEMPTY operator is not supported in phoenix hbase library...");
    }

    @Override
    protected <T> String translate(NativeExpression e, Class<T> entityClass) {
        return e.getExpression().toString();
    }

    @Override
    public String order(String... orders) {
        return Joiner.on(", ").join(orders);
    }

    @Override
    public String order(String fieldName, Ordering ordering) {
        if (ordering == null || StringUtils.isBlank(ordering.getPropertyName())) {
            return StringUtils.EMPTY;
        }
        StringBuilder orderExpressionBuilder = new StringBuilder(fieldName.length() + ORDER_EXPRESSION_SUFFIX_MAX_LENGTH);
        orderExpressionBuilder.append(fieldName);
        Order order = ordering.getOrder();
        if (order != null) {
            orderExpressionBuilder.append(' ');
            orderExpressionBuilder
                    .append(Order.ASCENDING.equals(order) ? "ASC"
                            : "DESC");
            NullOrdering nullOrdering = ordering.getNullOrdering();
            if (nullOrdering != null) {
                orderExpressionBuilder.append(" NULLS ");
                orderExpressionBuilder.append(nullOrdering.name());
            }
        }
        return orderExpressionBuilder.toString();

    }

    @Override
    public <T, R> String translateProjection(QuerySelect<T, R> query) {
        return query.getReturnFields().size() > 0 ? Joiner.on(",").join(query.getReturnFields()) : null;
    }

    protected String join(String fieldName, Object... parts) {
        return fieldName + " " + Joiner.on(" ").join(Lists.transform(Arrays.asList(parts), toString));
    }

    protected String joinAggregateFunc(PhoenixHBaseAggregate function, String... fieldNames) {
        return function + "(" + resolveMappingNames(fieldNames) + ")";
    }

    private final Function<Object, String> toString = new Function<Object, String>() {
        @Override
        public String apply(Object o) {
            return string(o);
        }
    };

    protected String string(Object o) {
        if (o instanceof Object[]) {
            return "[" + Joiner.on(",").join(Lists.transform(Arrays.asList((Object[]) o), toString)) + "]";
        } else if (o instanceof String) {
            return "'" + sanitizeString((String) o) + "'";
        } else if (o instanceof Character) {
            logger.debug("Converting char type, value {}", o);
            return "'" + sanitizeString(o.toString()) + "'";
        } else if (o instanceof Date) {
            return PhoenixDateFormatUtil.formatDate((Date) o);