ExpressionBoolean.java

package org.microspace.table.query.sql;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.microspace.table.Entry;
import org.microspace.table.column.Accessor;
import org.microspace.table.column.ColumnReferences;
import org.microspace.table.column.GetSetPair;
import org.microspace.table.column.IndexedMap;
import org.microspace.table.column.IndexedSet;
import org.microspace.table.query.SqlFormat;
import org.microspace.util.PojoUtil;

/**
 *
 * @author Gaspar Sinai - {@literal gaspar.sinai@microspace.org}
 * @version 2017-09-29
 * @param <T> The type of the Table.
 */
public class ExpressionBoolean<T> implements Expression<T> {

	private static final long serialVersionUID = 1L;
	
	
	final Class<T> tableClass;
	final Class<?> columnClass;
	final String columnName;
	final OperatorType operator;
	final LiteralValueType type;
	final String value;
	final Object columnObject;
	final String columnEsc;

	boolean needsBracket = false;
	
	public void setNeedsBracket (boolean needsBracket) {
		this.needsBracket = needsBracket;
	}


	public ExpressionBoolean(Class<T> tableClass, 
			OperatorType operator, String columnName, 
			LiteralValueType type, String value, String columnEsc) {
		this.tableClass = tableClass;
		this.columnEsc = columnEsc;
		Accessor<T> accessor = PojoUtil.getUnannotateAccessor(tableClass);
		GetSetPair<T> gettter = accessor.getGetSetPair(columnName);
		if (gettter == null) {
			throw new IllegalArgumentException("Unkown Column: " + columnName);
		}
		this.columnClass = PojoUtil.getColumnClass(tableClass, columnName);

		this.operator = operator;
		this.columnName = columnName;
		this.value = value;
		this.type = type;
		if (columnClass == null) {
			throw new IllegalArgumentException("Unkown Column");
		}
		if (operator != OperatorType.IS_NULL && operator != OperatorType.IS_NOT_NULL) {
			columnObject = Util.createColumnObject(value, columnClass, type);
		} else {
			columnObject = value;
		}
	}

	public IndexedMap<Object, Entry<T>> apply(Accessor<T> accessor, 
			IndexedMap<Object, Entry<T>> entries,
			ColumnReferences<T>[] indexedColumns, InnerSelectContext<?> innerSelectContext) {
		IndexedMap<Object, Entry<T>> retMap = new IndexedMap<Object, Entry<T>>(
				accessor.getPrimaryKeyGetSetPair().getIndexType());

		GetSetPair<T> getSetPair = accessor.getGetSetPair(columnName);
		int columnNumber = getSetPair.getIndex();
		ColumnReferences<T> index = indexedColumns[columnNumber];

		switch (operator) {
		case EQ: 
			if (index == null) {
				for (Entry<T> entry : entries.values()) {
					Object field = entry.getField(columnNumber);
					if (field == null)
						continue;
					if (equals(field, columnObject)) {
						retMap.put(entry.getPrimaryKey(), entry);
					}
				}
			} else {
				IndexedSet<Object> matchingSet = new IndexedSet<Object>(
						accessor.getPrimaryKeyGetSetPair().getIndexType());
				IndexedSet<Object> workingSet = entries.keySet();
				IndexedSet<Object> indexSet = index.getMatchingKeys(columnObject);
				intersection(workingSet, indexSet, matchingSet);
				for (Object key : matchingSet) {
					Entry<T> entry = entries.get(key);
					// Indexes are for the whole input.
					if (entry != null) {
						retMap.put(entry.getPrimaryKey(), entry);
					}
				}
			}
			break;
			
		case NOT_EQ: 
			if (index == null) {
				for (Entry<T> entry : entries.values()) {
					Object field = entry.getField(columnNumber);
					if (field == null || !equals(field, columnObject)) {
						retMap.put(entry.getPrimaryKey(), entry);
					}
				}
			} else {
				IndexedSet<Object> notPresentSet = new IndexedSet<Object>(
						accessor.getPrimaryKeyGetSetPair().getIndexType());
				IndexedSet<Object> workingSet = entries.keySet();
				IndexedSet<Object> indexSet = index.getMatchingKeys(columnObject);
				remove(workingSet, indexSet, notPresentSet);
				for (Object key : notPresentSet) {
					Entry<T> entry = entries.get(key);
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
			break;

		case GTH:
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				if (compare(field, columnObject) > 0) {
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
			break;
			
		case GET:
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				if (compare(field, columnObject) >= 0) {
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
			break;

		case LET:
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				if (compare(field, columnObject) <= 0) {
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
			break;

		case LTH: 
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				if (compare(field, columnObject) < 0) {
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
			break;

		case IS_NULL:
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field != null)
					continue;
				retMap.put(entry.getPrimaryKey(), entry);
			}
			break;

		case IS_NOT_NULL:
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				retMap.put(entry.getPrimaryKey(), entry);
			}
			break;

		case LIKE: 
		case NOT_LIKE:
		{
			String expr = value;
			expr = expr.toUpperCase();
			expr = expr.replace(".", "\\.");
			// TODO Add more.
			expr = expr.replace("?", ".");
			expr = expr.replace("%", ".*");
			Pattern pattern = Pattern.compile(expr);
			for (Entry<T> entry : entries.values()) {
				Object field = entry.getField(columnNumber);
				if (field == null)
					continue;
				String str = field.toString().toUpperCase();
				Matcher m = pattern.matcher(str);
				if (m.matches() == (operator == OperatorType.LIKE)) {
					retMap.put(entry.getPrimaryKey(), entry);
				}
			}
		}
			break;
		default:
			break;

		}
		return retMap;
	}
	
	public boolean match(T object, Accessor<T> accessor) {
		GetSetPair<T> getSetPair = accessor.getGetSetPair(columnName);
		Object field = getSetPair.get(object);
		if (field == null)
			return false;
		return equals(field, columnObject);
	}


	/**
	 * Parse % string as like.
	 * <p>
	 * %endswidth
	 * <p>
	 * startswith%
	 * <p>
	 * start%end
	 * <p>
	 * percent%%
	 * 
	 * @param expr
	 *            The pattern.
	 * @param str
	 *            The input.
	 * @return true if pattern matches.
	 */
	public static boolean like(String expr, String str) {
		expr = expr.toLowerCase();
		expr = expr.replace(".", "\\.");
		// ... escape any other potentially problematic characters here
		expr = expr.replace("?", ".");
		expr = expr.replace("%", ".*");
		str = str.toLowerCase();
		return str.matches(expr);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static boolean equals(Object field, Object value) {
		if (value instanceof Comparable) {
			Comparable cmp0 = (Comparable) field;
			Comparable cmp1 = (Comparable) value;
			return cmp0.compareTo(cmp1) == 0;
		}
		return field.equals(value);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static int compare(Object field, Object value) {
		if (value instanceof Comparable) {
			Comparable cmp0 = (Comparable) field;
			Comparable cmp1 = (Comparable) value;
			
			return cmp0.compareTo(cmp1);
		}
		System.err.println("Comparing " + value);
		return (field.toString().compareTo(value.toString()));
	}

	private void remove(IndexedSet<Object> from, IndexedSet<Object> present, IndexedSet<Object> target) {
		for (Object key : from) {
			if (!present.contains(key)) {
				target.add(key);
			}
		}
	}

	/**
	 * Get the intersection of two sets.
	 * 
	 * @param set0
	 *            One set.
	 * @param set1
	 *            The other set.
	 * @param target
	 *            The intersection is placed here.
	 */
	private void intersection(IndexedSet<Object> set0, IndexedSet<Object> set1, IndexedSet<Object> target) {
		if (set0.size() > set1.size()) {
			intersection(set1, set0, target);
			return;
		}
		for (Object key : set0) {
			if (set1.contains(key)) {
				target.add(key);
			}
		}
	}
	
	
	@Override
	public String formatSqlQuery (SqlFormat format) {
		String esc = columnEsc;
		if (format == SqlFormat.GIGASPACES) {
			esc = "`";
		}
		switch (type) {
		case BOOLEAN:
		case NUMBER:
			return esc + columnName + esc + " " + operator.constructSqlString(format) + " " + value;
		case NULL:
			return esc + columnName + esc + " " + operator.constructSqlString(format);
		case STRING:
			return esc + columnName + esc + " " + operator.constructSqlString(format) + " " + Util.quote(value);
		
		}
		return "";
	}
	
}