package org.triiskelion.tinyspring.dao; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Created with IntelliJ IDEA. * User: Sebastian MA * Date: August 21, 2014 * Time: 11:17 */ public class TinyPredicate { /** * Operator of a simple predicate. Available only if the predicate is simple */ private final Operator operator; protected PredicateType predicateType = PredicateType.SIMPLE; protected List<TinyPredicate> predicateList = new ArrayList<>(); protected String column; /** * the parameter values of the predicate. Available only if the predicate is simple */ protected List<Object> values = new ArrayList<>(); /** * Indicates if the predicate is valid. Available only if the predicate is simple */ protected boolean isValid = true; protected boolean empty = false; //////////////////////////////////////////////////////////////////////////////////////// // construction methods //////////////////////////////////////////////////////////////////////////////////////// /** * Creates an empty conjunction predicate with specific type * * @param type * type of the predicate */ protected TinyPredicate(PredicateType type) { this.predicateType = type; operator = null; } /** * Creates a simple predicate * * @param alias * @param column * @param operator * @param values */ // protected TinyPredicate(String alias, String column, Operator operator, Object... values) { // // isValid = validateParameters(operator, values); // empty = !isValid; // // if(StringUtils.isBlank(alias)) { // alias = TinyQuery.TABLE_ALIAS; // } // // this.operator = operator; // this.column = String.format("%s.%s", alias, column); // Lists.addAll(this.values, values); // } /** * Creates a simple predicate * * @param alias * @param column * @param operator * @param values */ protected TinyPredicate(String alias, String column, Operator operator, List values) { isValid = validateParameters(operator, values); empty = !isValid; if(StringUtils.isBlank(alias)) { alias = TinyQuery.TABLE_ALIAS; } this.operator = operator; this.column = String.format("%s.%s", alias, column); this.values.addAll(values); } private static TinyPredicate createPredicate(PredicateType type) { return new TinyPredicate(type); } //////////////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////////////// public TinyPredicate and(TinyPredicate other) { if(other == null) { return this; } return and(this, other); } public TinyPredicate or(TinyPredicate other) { if(other == null) { return this; } return or(this, other); } public TinyPredicate negate() { return not(this); } //////////////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////////////// public static TinyPredicate isNull(String column) { return isNull(null, column); } public static TinyPredicate isNull(String alias, String column) { return new TinyPredicate(alias, column, Operator.isNull, listOf()); } public static TinyPredicate isNotNull(String column) { return isNotNull(null, column); } public static TinyPredicate isNotNull(String alias, String column) { return new TinyPredicate(alias, column, Operator.isNotNull, listOf()); } /** * @param column * @param value * * @return */ public static TinyPredicate equal(String column, Object value) { return equal(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate equal(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.equal, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate notEqual(String column, Object value) { return notEqual(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate notEqual(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.notEqual, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate lessThan(String column, Object value) { return lessThan(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate lessThan(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.lessThan, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate lessThanOrEqual(String column, Object value) { return lessThanOrEqual(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate lessThanOrEqual(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.lessThanOrEqual, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate greaterThan(String column, Object value) { return greaterThan(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate greaterThan(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.greaterThan, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate greaterThanOrEqual(String column, Object value) { return greaterThanOrEqual(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate greaterThanOrEqual(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.greaterThanOrEqual, listOf(value)); } /** * @param column * @param value * * @return */ public static TinyPredicate like(String column, Object value) { return like(null, column, value); } /** * @param alias * JPQL table alias * @param column * JPQL column name * @param value * value object * ignored if value is null. * * @return */ public static TinyPredicate like(String alias, String column, Object value) { return new TinyPredicate(alias, column, Operator.like, listOf(value)); } /** * @param column * * @return */ public static TinyPredicate between(String column, Object startValue, Object endValue) { return between(null, column, startValue, endValue); } /** * @param alias * JPQL table alias * @param column * JPQL column name * * @return */ public static TinyPredicate between(String alias, String column, Object startValue, Object endValue) { return new TinyPredicate(alias, column, Operator.between, listOf(startValue, endValue)); } /** * @param column * * @return */ public static TinyPredicate in(String column, List values) { return in(null, column, values); } /** * @param alias * JPQL table alias * @param column * JPQL column name * * @return */ public static TinyPredicate in(String alias, String column, List values) { return new TinyPredicate(alias, column, Operator.in, values); } public static TinyPredicate in(String column, Object... values) { return in(null, column, values); } public static TinyPredicate in(String alias, String column, Object... values) { return new TinyPredicate(alias, column, Operator.in, Arrays.asList(values)); } /** * Concatenate the predicates with AND * * @param predicates * Nullable * * @return */ public static TinyPredicate and(TinyPredicate... predicates) { if(predicates == null || predicates.length == 0) { throw new IllegalArgumentException("operation 'AND' requires at least 1 predicate"); } if(predicates.length == 1) { return predicates[0]; } TinyPredicate result = TinyPredicate.createPredicate(PredicateType.AND); for(TinyPredicate p : predicates) { result.empty |= p.empty; if(p.predicateType == PredicateType.AND) { result.predicateList.addAll(p.predicateList); } else { result.predicateList.add(p); } } return result; } /** * conjunct all the predicates with OR * * @param predicates * * @return */ public static TinyPredicate or(TinyPredicate... predicates) { if(predicates == null || predicates.length == 0) { throw new IllegalArgumentException("operation 'OR' requires at least 1 predicate"); } if(predicates.length == 1) { return predicates[0]; } TinyPredicate result = TinyPredicate.createPredicate(PredicateType.OR); for(TinyPredicate p : predicates) { result.empty |= p.empty; if(p.predicateType == PredicateType.OR) { result.predicateList.addAll(p.predicateList); } else { result.predicateList.add(p); } } return result; } public static TinyPredicate not(TinyPredicate predicate) { if(predicate == null) { throw new IllegalArgumentException("operation 'NOT' requires a predicate"); } TinyPredicate result = TinyPredicate.createPredicate(PredicateType.NOT); result.empty = predicate.empty; result.predicateList.add(predicate); return result; } /** * Validates the values of the expression. * * @param type * type of the operation * @param values * values for parameters * * @return */ protected static boolean validateParameters(Operator type, List values) { switch(type) { case isNull: case isNotNull: return true; case equal: case notEqual: case lessThan: case lessThanOrEqual: case greaterThan: case greaterThanOrEqual: case like: return values != null && values.size() >= 1 && values.get(0) != null; case between: return values != null && values.size() >= 2 && values.get(0) != null && values.get(1) != null; case in: boolean result = values != null && values.size() >= 1; if(values != null) { for(Object obj : values) { result &= obj != null; } } return result; default: throw new IllegalArgumentException("Unknown operator"); } } public String createExpression(TinyQuery query) { StringBuilder buffer = new StringBuilder(column); switch(operator) { case isNull: buffer.append(" IS NULL "); break; case isNotNull: buffer.append(" IS NOT NULL "); break; case equal: buffer.append("="); break; case notEqual: buffer.append("<>"); break; case lessThan: buffer.append("<"); break; case lessThanOrEqual: buffer.append("<="); break; case greaterThan: buffer.append(">"); break; case greaterThanOrEqual: buffer.append(">="); break; case like: buffer.append(" LIKE "); break; case between: buffer.append(" BETWEEN"); break; case in: buffer.append(" IN "); break; default: throw new IllegalArgumentException("Unknown operator"); } switch(operator) { // zero parameter case isNull: case isNotNull: break; // 1 parameter case equal: case notEqual: case lessThan: case lessThanOrEqual: case greaterThan: case greaterThanOrEqual: case like: String valueHolder = column.replace(".", "_") + "_" + query.index; query.namedParameters.put(valueHolder, values.get(0)); query.index++; buffer.append(":").append(valueHolder); break; // 2 parameters case between: String valueHolder0 = column.replace(".", "_") + "_" + query.index + "_0"; String valueHolder1 = column.replace(".", "_") + "_" + query.index + "_1"; query.namedParameters.put(valueHolder0, values.get(0)); query.namedParameters.put(valueHolder1, values.get(1)); query.index = query.index + 1; buffer.append(":").append(valueHolder0).append(" AND :").append(valueHolder1); break; // infinite parameters case in: List<String> list = new ArrayList<>(); for(Object value : values) { valueHolder = column.replace(".", "_") + "_" + query.index + "_" + values .indexOf(value); list.add(":" + valueHolder); query.namedParameters.put(valueHolder, value); } query.index++; buffer.append("(").append(StringUtils.join(list, ",")).append(")"); break; default: throw new IllegalArgumentException("Unknown operator: " + operator); } return buffer.toString(); } public enum Operator { isNull, isNotNull, equal, notEqual, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, between, in, like, } /** * A predicate is considered simple if it does not contain logical conjunction like AND, OR, * NOT. * * @return TRUE if the predicate is simple, otherwise FALSE. */ public boolean isSimple() { return predicateType == PredicateType.SIMPLE; } public String toString() { return String.format("Predicate[%s]", column != null ? column : predicateType); } /** * @param parameter * nullable * * @return */ public static String likeParameterContains(String parameter) { return parameter == null ? null : "%" + parameter + "%"; } public static List listOf(Object... objects) { List result = new ArrayList(); Collections.addAll(result, objects); return result; } }