ExpressionIn.java

package org.microspace.table.query.sql;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.microspace.annotation.IndexType;
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 ExpressionIn<T> implements Expression<T> {

	private static final long serialVersionUID = 116424891769864966L;

	final Class<T> tableClass;
	final Class<?> columnClass;
	final String columnName;
	final String columnEsc;
	final boolean notIn;

	List<String> columnValuesString;
	List<Object> columnValues;
	LiteralValueType columnType;

	Select<T> innerSelect;
	String innerColumnName;

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

	public ExpressionIn(Class<T> tableClass, String columnName, String columnEsc) {
		this(tableClass, columnName, false, columnEsc);
	}

	public ExpressionIn(Class<T> tableClass, String columnName, boolean notIn, String columnEsc) {
		this.tableClass = tableClass;
		this.columnName = columnName;
		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.notIn = notIn;

	}

	public void setLiteralColumnValues(List<String> columnValuesString, LiteralValueType columnType) {
		this.columnType = columnType;
		this.columnValues = new LinkedList<>();
		this.columnValuesString = new LinkedList<>();
		this.columnValuesString.addAll(columnValuesString);
		for (String value : columnValuesString) {
			Object columnObject = Util.createColumnObject(value, columnClass, columnType);
			columnValues.add(columnObject);
		}
	}

	public void setInnerSelect(Select<T> innerSelect) {
		this.innerSelect = innerSelect;
		this.innerColumnName = innerSelect.getColumnNames().get(0);
	}

	public IndexedMap<Object, Entry<T>> apply(Accessor<T> accessor, IndexedMap<Object, Entry<T>> entries,
			ColumnReferences<T>[] indexedColumns, InnerSelectContext<?> innerSelectContext) {

		if (innerSelect != null) {
			if (innerSelectContext == null) {
				throw new IllegalArgumentException ("Inner Select Without a Space");
			}
			// Local inner select.
			if (innerSelectContext.getSpace() == null) {
					throw new IllegalArgumentException ("No Space Found for " + tableClass.getName());
			}
			IndexedSet<Object> res = innerSelectContext.get(innerSelect.getId());
			if (res == null) {
				throw new IllegalArgumentException ("Internal InnerSelect Stack Initialization Error");
			}
			return applyInternal(accessor, entries, indexedColumns, res.getUnderlyingSet());

		} else {
			return applyInternal(accessor, entries, indexedColumns, columnValues);
		}

	}

	private IndexedMap<Object, Entry<T>> applyInternal(Accessor<T> accessor, IndexedMap<Object, Entry<T>> entries,
			ColumnReferences<T>[] indexedColumns, Collection<Object> values) {

		IndexedSet<Object> retKeys = new IndexedSet<Object>(accessor.getPrimaryKeyGetSetPair().getIndexType());

		GetSetPair<T> pair = accessor.getGetSetPair(columnName);

		ColumnReferences<T> inf = indexedColumns[pair.getIndex()];
		if (notIn) {
			for (Object key : entries.keySet()) {
				retKeys.add(key);
			}
		}
		if (inf != null) {
			for (Object value : values) {
				IndexedSet<Object> matching = inf.getMatchingKeys(value);
				if (notIn) {
					retKeys.removeAll(matching);
				} else {
					retKeys.addAll(matching);
				}
			}
		} else {
			for (Entry<T> entry : entries.values()) {
				boolean contains = listContainsObject(pair, entry.getSpaceEntry(), values);
				if (contains) {
					if (notIn) {
						retKeys.remove(entry.getPrimaryKey());
					} else {
						retKeys.add(entry.getPrimaryKey());
					}
				}
			}
		}

		IndexedMap<Object, Entry<T>> retMap = new IndexedMap<Object, Entry<T>>(
				accessor.getPrimaryKeyGetSetPair().getIndexType());

		for (Object key : retKeys) {
			// Index may contain more values.
			Entry<T> entry = entries.get(key);
			if (entry == null) {
				continue;
			}
			retMap.put(key, entry);
		}
		return retMap;
	}

	/**
	 * Check if object matches. Inner SELECT is not supported.
	 * 
	 * @param object
	 *            is the Class Object.
	 * @param accessor
	 *            is the accessor of the object.
	 */
	public boolean match(T object, Accessor<T> accessor) {
		// Inner select IN not supported.
		if (innerSelect != null) {
			return false;
		}
		GetSetPair<T> pair = accessor.getGetSetPair(columnName);
		boolean cont = listContainsObject(pair, object, columnValues);
		return cont ^ notIn;
	}

	@SuppressWarnings("unchecked")
	private boolean listContainsObject(GetSetPair<T> pair, T object, Collection<Object> values) {
		Object value = pair.get(object);
		if (value == null)
			return false;
		for (Object literalValue : values) {

			if (pair.getIndexType() == IndexType.HASHED || !(literalValue instanceof Comparable)) {
				if (value.equals(literalValue)) {
					return true;
				}
			} else {
				@SuppressWarnings("rawtypes")
				Comparable c0 = (Comparable) value;
				@SuppressWarnings("rawtypes")
				Comparable c1 = (Comparable) literalValue;
				if (c0.compareTo(c1) == 0) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public String formatSqlQuery(SqlFormat format) {
		StringBuffer ret = new StringBuffer();
		String esc = columnEsc;
		if (format == SqlFormat.GIGASPACES) {
			esc = "`";
		}
		ret.append(esc);
		ret.append(columnName);
		ret.append(esc);
		ret.append(" ");
		if (notIn) {
			ret.append("NOT ");
		}
		ret.append("IN ");
		if (innerSelect != null) {
			ret.append("( " + innerSelect.formatSqlQuery(format) + " )");
			return ret.toString();
		}
		ret.append("( ");
		String comma = "";
		for (String s : columnValuesString) {
			ret.append(comma);
			if (columnType == LiteralValueType.STRING) {
				ret.append(Util.quote(s));
			} else {
				ret.append(s);
			}
			comma = ", ";
		}
		ret.append(" )");
		return ret.toString();
	}

}