package com.venky.swf.sql; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import com.venky.core.collections.SequenceSet; import com.venky.core.string.StringUtil; import com.venky.core.util.ObjectUtil; import com.venky.swf.db.Database; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.reflection.ModelReflector; import com.venky.swf.db.table.BindVariable; import com.venky.swf.db.table.ModelInvocationHandler; import com.venky.swf.db.table.Record; import com.venky.swf.exceptions.MultiException; public class Expression { String columnName = null; List<BindVariable> values = null ; Operator op = null; public static final int CHUNK_SIZE = 30; public static Expression createExpression(String pool,String columnName, Operator op, Object... values){ List<List<Object>> chunks = getValueChunks(Arrays.asList(values)); Expression e = new Expression(pool,Conjunction.OR); for (List<Object> chunk : chunks){ e.add(new Expression(pool,columnName,op,chunk.toArray())); } return e; } public static List<List<Object>> getValueChunks(List<Object> values){ List<List<Object>> chunks = new ArrayList<List<Object>>(); chunks.add(new ArrayList<Object>()); for (Object bv: values){ List<Object> aChunk = chunks.get(chunks.size()-1); if (aChunk.size() >= CHUNK_SIZE){ aChunk = new ArrayList<Object>(); chunks.add(aChunk); } if (bv != null){ aChunk.add(bv); }else { if (!aChunk.isEmpty()){ chunks.add(new ArrayList<Object>()); } chunks.add(new ArrayList<Object>()); } } return chunks; } String pool = null; @SafeVarargs public <T> Expression(String pool,String columnName,Operator op, T... values){ this.columnName = columnName; this.op = op ; this.values = new SequenceSet<BindVariable>(); this.pool = pool; try { for (int i = 0 ; i < values.length ; i ++ ){ if (values[i] instanceof BindVariable) { this.values.add((BindVariable)values[i]); }else { this.values.add(new BindVariable(pool,values[i])); } } }catch (NullPointerException ex){ MultiException mex = new MultiException("NPE found while creating expression for " + columnName + op.toString() ); mex.add(ex); throw mex; } setFinalized(true); } Conjunction conjunction = null; public Expression(String pool,Conjunction conjunction){ this.pool = pool; this.conjunction = conjunction; this.values = new ArrayList<BindVariable>(); } private boolean finalized = false; private boolean isFinalized(){ return finalized; } private void setFinalized(boolean finalized){ this.finalized = finalized; } private void ensureModifiable(){ if (isFinalized()){ throw new ExpressionFinalizedException(); } } public static class ExpressionFinalizedException extends RuntimeException { /** * */ private static final long serialVersionUID = -1865905730160016333L; } private List<Expression> connected = new ArrayList<Expression>(); private Expression parent = null; public int getNumChildExpressions(){ return connected.size(); } public Expression getParent() { return parent; } public void setParent(Expression parent) { this.parent = parent; } public Expression add(Expression expression){ ensureModifiable(); expression.setParent(this); connected.add(expression); addValues(expression.getValues()); return this; } private void addValues(List<BindVariable> values){ ensureModifiable(); this.values.addAll(values); if (parent != null){ parent.addValues(values); } } private String realSQL = null; public String getRealSQL(){ if (realSQL != null){ return realSQL; } StringBuilder builder = new StringBuilder(getParameterizedSQL()); List<BindVariable> parameters = getValues(); int index = builder.indexOf("?"); int p = 0; while (index >= 0) { BindVariable parameter = parameters.get(p); String pStr = StringUtil.valueOf(parameter.getValue()) ; if (Database.getJdbcTypeHelper(pool).getTypeRef(parameter.getJdbcType()).isQuotedWhenUnbounded()){ pStr = "'" + pStr + "'"; } builder.replace(index, index+1, pStr); p+=1; index = builder.indexOf("?",index+pStr.length()); } String sql = builder.toString(); if (isFinalized()){ realSQL = sql; } return sql; } private String parameterizedSQL = null ; public String getParameterizedSQL(){ if (parameterizedSQL != null){ return parameterizedSQL; } StringBuilder builder = new StringBuilder(); if (conjunction == null){ builder.append(columnName); builder.append(" "); if (values == null || values.isEmpty()){ if (op == Operator.EQ || op == Operator.IN){ builder.append(" IS NULL "); }else { builder.append(" IS NOT NULL "); } }else { builder.append(op.toString()); if (op.isMultiValued()){ builder.append(" ( "); } for (int i = 0 ; i < values.size() ; i++){ if (i != 0){ //To handle In clause. builder.append(","); } builder.append(" ? "); } if (op.isMultiValued()){ builder.append(" ) "); } } }else if (!connected.isEmpty()){ boolean multipleExpressionsConnected = connected.size() > 1; Iterator<Expression> i = connected.iterator(); while(i.hasNext()){ Expression expression = i.next(); if (!expression.isEmpty()){ if (builder.length() > 0){ builder.append(" "); builder.append(conjunction); builder.append(" "); } builder.append(expression.getParameterizedSQL()); } } if (builder.length() > 0 && multipleExpressionsConnected){ //avoid frivolous brackets builder.insert(0,"( "); builder.append(" )"); } } String sql = builder.toString(); if (isFinalized()){ parameterizedSQL = sql; } return sql; } public List<BindVariable> getValues(){ return Collections.unmodifiableList(values); } public boolean isEmpty(){ boolean empty = false; if (conjunction != null){ empty = true; for (Iterator<Expression> i = connected.iterator();i.hasNext() && empty ; ){ empty = i.next().isEmpty(); } } return empty; } private Integer hashValue = null; @Override public int hashCode(){ if (hashValue == null){ setFinalized(true); hashValue = getRealSQL().hashCode(); } return hashValue; } public boolean equals(Object other){ if (other == null){ return false; } if (!(other instanceof Expression)){ return false; } Expression e = (Expression) other; setFinalized(true); e.setFinalized(true); if (hashCode() != e.hashCode()){ return false; }else { return getRealSQL().equals(e.getRealSQL()); } } private Object get(Object record, String columnName){ boolean isModelProxyObject = Proxy.isProxyClass(record.getClass()) && (record instanceof Model); Object value = null; if (isModelProxyObject){ Model m = (Model)record; ModelInvocationHandler h = (ModelInvocationHandler) Proxy.getInvocationHandler(m); String fieldName = columnName ; if (!h.getReflector().getFields().contains(fieldName)) { fieldName = h.getReflector().getFieldName(columnName); if (fieldName == null){ throw new IllegalArgumentException(columnName + " is neither a column nor field" ); } } value = h.getReflector().get(m,fieldName); }else if (Record.class.isInstance(record)){ value = ((Record)record).get(columnName); }else { throw new RuntimeException("Don't know how to get column value from object of type " + record.getClass() + " for column " + columnName); } return value; } @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean eval(Object record){ if (conjunction == null){ Object value = get(record,columnName); if (value == null){ if (values.isEmpty() || values.contains(null)){ return (op == Operator.EQ || op == Operator.IN); }else { return op == Operator.NE; } }else if (values.isEmpty()){ if (op == Operator.EQ || op == Operator.IN){ return false; }else if (op == Operator.NE){ return true; } } //value not null if (values.size() == 1){ Object v = values.get(0).getValue(); if (v == null){ return false; } if (v.getClass() != value.getClass()){ value = Database.getJdbcTypeHelper(pool).getTypeRef(v.getClass()).getTypeConverter().valueOf(value); //Compare Apples and apples not apples and oranges. } if (op == Operator.EQ){ return ObjectUtil.equals(v,value); }else if (value instanceof Comparable){ if (op == Operator.GE){ return ((Comparable)value).compareTo(v) >= 0; }else if (op == Operator.GT){ return ((Comparable)value).compareTo(v) > 0; }else if (op == Operator.LE){ return ((Comparable)value).compareTo(v) <= 0; }else if (op == Operator.LT){ return ((Comparable)value).compareTo(v) < 0; }else if (op == Operator.NE){ return ((Comparable)value).compareTo(v) != 0; } } if (op == Operator.LK && value instanceof String && v instanceof String){ return ((String)value).matches(((String)v).replace("%", ".*")); } } if (op == Operator.IN){ if (values.contains(new BindVariable(pool,value))){ return true; } } }else if (conjunction == Conjunction.OR){ boolean ret = connected.isEmpty(); for (Iterator<Expression> i = connected.iterator(); !ret && i.hasNext() ;){ Expression e = i.next(); ret = ret || e.eval(record); } return ret; }else if (conjunction == Conjunction.AND){ boolean ret = true; for (Iterator<Expression> i = connected.iterator(); ret && i.hasNext() ;){ Expression e = i.next(); ret = ret && e.eval(record); } return ret; } return false; } public <M extends Model> String toLucene(Class<M> modelClass) { StringBuilder builder = new StringBuilder(); if (conjunction == null){ if (ModelReflector.instance(modelClass).getIndexedColumns().contains(columnName) || "ID".equals(columnName)){ if (values == null || values.isEmpty()){ if (op == Operator.EQ || op == Operator.IN){ builder.append(" ( "); builder.append(columnName); builder.append(":NULL "); builder.append(" ) "); }/*else { builder.append("NOT "); builder.append(columnName); builder.append(":NULL "); }*/ }else { builder.append(" ( "); for (int i = 0 ; i < values.size() ; i++){ if (i != 0){ builder.append(" OR "); } builder.append(columnName); builder.append(":"); builder.append(values.get(i).getValue()); } builder.append(" ) "); } } }else if (!connected.isEmpty()){ int numExpressionsConnected = 0 ; Iterator<Expression> i = connected.iterator(); while(i.hasNext()){ Expression expression = i.next(); String luceneString = expression.toLucene(modelClass); if (!ObjectUtil.isVoid(luceneString)){ if (builder.length() > 0){ builder.append(" "); builder.append(conjunction); builder.append(" "); } builder.append(luceneString); numExpressionsConnected ++; } } if (builder.length() > 0 && numExpressionsConnected > 1){ //avoid frivolous brackets builder.insert(0,"( "); builder.append(" )"); } } return builder.toString(); } }