TemplateQuery.java
package org.microspace.table.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.microspace.annotation.IndexType;
import org.microspace.exception.IllegalOperationException;
import org.microspace.space.SimpleSpace;
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.Getter;
import org.microspace.table.column.IndexedMap;
import org.microspace.table.column.IndexedSet;
import org.microspace.util.PojoUtil;
import org.microspace.util.UniqueId;
/**
* A lightweight query.
* A query where null fields are ignored but set fields should match.
* @author Gaspar Sinai - {@literal gaspar.sinai@microspace.org}
* @version 2012-01-29
*/
public class TemplateQuery<T> implements TableQuery<T> {
private final String id;
private final T template;
private final boolean indexedOnly;
private final int maxEntries;
private final UniqueId spaceId;
/**
* An Inner class to arrange keys, In the order of the minimum matching keys
* @param <T>
*/
@SuppressWarnings("hiding")
class IndexingOrder<T> {
Integer size;
ColumnReferences<T> columnReferences;
Object property;
}
/**
* Create a query that is supposed to match non null fields.
* @param template is a POJO object that has a default constructor and Serializable fields.
*/
public TemplateQuery (T template) {
this(template, Integer.MAX_VALUE, false);
}
/**
* Create a query that is supposed to match non null fields.
* @param template is a POJO object that has a default constructor and Serializable fields.
* @param spaceId is the spaceId restriction or null.
*/
public TemplateQuery (T template, UniqueId spaceId) {
this(template, Integer.MAX_VALUE, false, spaceId);
}
/**
* Create a query that can be restricted to match indexed fields only.
*
* The matcher throws an IllegalOperationException if we encounter a non indexed value.
* Good for debugging purpose.
*
* @param template is a POJO object that has a default constructor and Serializable fields.
* @param maxEntries limits the maximum number of entries returned.
*/
public TemplateQuery (T template, int maxEntries) {
this(template, maxEntries, false, null);
}
/**
* Create a query that can be restricted to match indexed fields only.
*
* The matcher throws an IllegalOperationException if we encounter a non indexed value.
* Good for debugging purpose.
*
* @param template is a POJO object that has a default constructor and Serializable fields.
* @param maxEntries limits the maximum number of entries returned.
* @param indexedOnly is true if only indexed keys are queried. This will speed up queries.
*/
public TemplateQuery (T template, int maxEntries, boolean indexedOnly) {
this(template, maxEntries, indexedOnly, null);
}
/**
* Create a query that can be restricted to match indexed fields only.
*
* The matcher throws an IllegalOperationException if we encounter a non indexed value.
* Good for debugging purpose.
*
* @param template is a POJO object that has a default constructor and Serializable fields.
* @param maxEntries limits the maximum number of entries returned.
* @param indexedOnly is true if only indexed keys are queried. This will speed up queries.
* @param spaceId is the spaceId restriction or null.
*/
public TemplateQuery (T template, int maxEntries, boolean indexedOnly, UniqueId spaceId) {
this.template = PojoUtil.copy(template);
this.maxEntries = maxEntries;
this.indexedOnly = indexedOnly;
this.spaceId = spaceId;
if (spaceId == null) {
this.id = "TemplateQuery: " + PojoUtil.formatShort(template);
} else {
this.id = "TemplateQuery: " + spaceId.toString() + " " + PojoUtil.formatShort(template);
}
}
/**
* Obtain the underlying template object.
* @return the template object.
*/
public T getTemplate() {
return template;
}
/**
* Obtain query feature.
* @return query feature.
*/
public boolean isIndexedOnly() {
return indexedOnly;
}
/**
* {@inheritDoc}
*/
@Override
public IndexedMap<Object, Entry<T>> getMatchingEntries (Accessor<T> accessor, IndexedMap<Object, Entry<T>> entries, ColumnReferences<T> indexedColumns[]) {
IndexedSet<Object> retKeys = new IndexedSet<>(accessor.getPrimaryKeyGetSetPair().getIndexType());
IndexedMap<Object, Entry<T>> retMap = new IndexedMap<>(accessor.getPrimaryKeyGetSetPair().getIndexType());
boolean first = true;
List<IndexingOrder<?>> allRefs = new ArrayList<>();
for (int i=0; i<indexedColumns.length; i++) {
ColumnReferences<T> inf = indexedColumns[i];
if (inf == null) continue; // Not indexed.
Getter<T> getter = inf.getFieldGetter();
Object property = getter.get (template);
if (property == null || getter.isNull(property)) continue;
int size = inf.size(property);
if(size == 0){
return retMap;
}
IndexingOrder <T> indexingOrder = new IndexingOrder<>();
indexingOrder.size = size;
indexingOrder.columnReferences = inf;
indexingOrder.property = property;
allRefs.add(indexingOrder);
}
Collections.sort(allRefs, new Comparator<IndexingOrder<?>>() {
@Override
public int compare(IndexingOrder<?> o1, IndexingOrder<?> o2) {
if(o1.size !=null && o2.size!=null) {
return o1.size.compareTo(o2.size);
}
if(o1.size == null && o2.size == null){
return 0;
}
if(o1.size == null){
return -1;
}
return 1;
}
});
for(IndexingOrder<?> indexingOrder : allRefs) {
Object property = indexingOrder.property;
ColumnReferences<?> columnReference = indexingOrder.columnReferences;
if (first) {
retKeys.addAll(columnReference.getMatchingKeys(property));
first = false;
} else {
retKeys.retainAll(columnReference.getMatchingKeys(property));
}
if (retKeys.size() == 0) break;
}
// second round, check unIndexed fields.
List<GetSetPair<T>> getSet = accessor.getGetSetPairs();
for (int i=0; i<getSet.size(); i++) {
ColumnReferences<T> inf = indexedColumns[i];
if (inf != null) continue;
Getter<T> getter = getSet.get(i);
Object property = getter.get (template);
if (property == null || getter.isNull(property)) continue;
if (indexedOnly) {
throw new IllegalOperationException ("This matcher is for indexed search only.");
}
if (first) {
retKeys.addAll (getMatchingKeys(entries.keySet(), entries, property, i));
first = false;
} else {
retKeys.retainAll (getMatchingKeys(retKeys, entries, property, i));
}
if (retKeys.size() == 0) break;
}
if (first) {
retKeys.addAll(entries.keySet());
}
for (Object key : retKeys) {
retMap.put(key, entries.get(key));
}
return retMap;
}
/**
* {@inheritDoc}
*/
public List<Entry<T>> sortAndLimit (Accessor<T> accessor,
IndexedMap<Object, Entry<T>> entries) {
ArrayList<Entry<T>> matches = new ArrayList<Entry<T>> (0);
if (entries.size() == 0 || maxEntries <= 0) return matches;
matches.ensureCapacity (entries.size());
for (Object key : entries.keySet()) {
matches.add(entries.get(key));
}
Collections.sort(matches);
int size = Math.min(matches.size(), maxEntries);
ArrayList<Entry<T>> ret = new ArrayList<Entry<T>> (size);
for (int index=0; index<size; index++) {
Entry<T> entry = matches.get(index);
ret.add(entry);
}
return ret;
}
/**
* Get the matching keys of a not indexed field.
* @param from is the set to select from
* @param entries all entries
* @param property is the property to match.
* @param index is the field index.
* @return the objects that matched
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private IndexedSet<Object> getMatchingKeys (IndexedSet<Object> from, IndexedMap<Object, Entry<T>> entries, Object property, int index) {
IndexedSet<Object> retKeys = new IndexedSet<Object> (from.getIndexType());
IndexType indexType = IndexType.HASHED;
if (property instanceof Comparable) {
indexType = IndexType.SORTED;
}
for (Object key : from) {
Entry<T> entry = entries.get(key);
Object field = entry.getField(index);
if (field == null) continue;
switch (indexType){
case HASHED:
default:
if (field.equals(property)) {
retKeys.add(key);
}
break;
case SORTED:
if (((Comparable)field).compareTo((Comparable)property) == 0) {
retKeys.add(key);
}
break;
}
}
return retKeys;
}
public int getMaxEntries() {
return maxEntries;
}
@SuppressWarnings("unchecked")
public Class<T> getTableClass () {
return (Class<T>) template.getClass();
}
@Override
public String toString() {
return id;
}
@Override
public boolean match(T object, Accessor<T> accessor) {
return PojoUtil.match (template, object, accessor);
}
@Override
public String getId() {
return id;
}
/**
* {@inheritDoc}
*/
@Override
public void prepareQuery(SimpleSpace space) {
}
/**
* {@inheritDoc}
*/
@Override
public void finishQuery() {
}
@Override
public UniqueId getSpaceId() {
return spaceId;
}
}