package de.linwave.gtm;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import org.odbms.FUNCTION;
import org.odbms.OP;
import org.odbms.ObjectContainer;
import org.odbms.ObjectSet;
import org.odbms.Predicate;
import org.odbms.Query;
import de.linwave.gtm.query.ObjectSetImpl;
import de.linwave.gtm.sort.AbstractSorter;
import de.linwave.gtm.sort.SORT;
import de.linwave.gtm.sort.SortItem;
public class QueryImpl<E> implements Query
{
static final ObjectContainer db = GTM.getInstance();
static final GTM gtm = GTM.getGTMInstance();
protected Class<?> clasz;
private List<Constraint> constrains = new ArrayList<Constraint>();
private boolean executed;
private boolean remote;
/**
*
*/
QueryImpl() {
}
public QueryImpl(Class<?> clasz) {
this(clasz, false);
}
/**
*
* @param clasz
*/
public QueryImpl(Class<?> clasz, boolean remote) {
this.clasz = clasz;
this.remote = remote;
constrain(new Constraint(clasz));
}
/**
*
* @param <E>
* @param predicate
*/
<E> QueryImpl(Predicate<E> predicate) {
Method m = getFilterMethod(predicate);
clasz = getExtentTypeFromGenericParameter(predicate);
constrain(new Constraint(null, clasz, predicate));
}
/**
*
* @param <E>
* @param predicate
* @param comparator
*/
<E> QueryImpl(Predicate<E> predicate, Comparator<E> comparator) {
clasz = getExtentTypeFromGenericParameter(predicate);
constrain(new Constraint(null, clasz, predicate, comparator));
}
/**
*
*/
public <E> ObjectSet<E> execute()
{
if (isRemote()) {
System.out.println("***** REMOTE ******");
return null;
}
executed = true;
// Reorder Constraints
// First the indexed fields
List<Constraint> ordered = new ArrayList<Constraint>();
for (Constraint constrain : constrains) {
if (constrain.field != null && IndexUtils.isIndexed(clasz, constrain.field) && constrain.operator == OP.EQUALS) {
ordered.add(constrain);
}
}
for (Constraint constrain : constrains) {
if (constrain.field != null && !ordered.contains(constrain) && IndexUtils.isIndexed(clasz, constrain.field) && constrain.operator == OP.STARTS_WITH) {
ordered.add(constrain);
}
}
// Indexed field != STARTS_WITH or EQUALS
for (Constraint constrain : constrains) {
if (constrain.field != null && !ordered.contains(constrain) && IndexUtils.isIndexed(clasz, constrain.field)) {
ordered.add(constrain);
}
}
// Now the un-indexed fields
for (Constraint constrain : constrains) {
if (constrain.field != null && constrain.operator != null && !ordered.contains(constrain)) {
ordered.add(constrain);
}
}
// Now with Predicates
for (Constraint constrain : constrains) {
if (constrain.predicate != null && !ordered.contains(constrain)) {
ordered.add(constrain);
}
}
// Now the SORT-ONLY constrains
for (Constraint constrain : constrains) {
if (constrain.sort != null && !ordered.contains(constrain)) {
ordered.add(constrain);
}
}
// Now the rest if no other constraints before
if (ordered.isEmpty()) {
for (Constraint constrain : constrains) {
if (!ordered.contains(constrain)) {
ordered.add(constrain);
}
}
}
// The constrains are now ordered
constrains = ordered;
// Connect the OUT to IN queues
AbstractQueue<Long> q = null;
// Check if we need a PriorityQueue (i.e. sortBy, native query with comparator)
boolean haveComparator = false;
for (Constraint constrain : constrains) {
if (constrain.getComparator() != null) {
haveComparator = true;
}
}
// Start the constrain threads
for (Constraint constraint : constrains) {
// Connect the OUT to IN queue of the next constraint and execute
constraint.queueIN = q;
constraint.execute();
q = constraint.queueOUT;
}
// Check if we have to wait for the last queue to complete
boolean wait = false;
wait = remote ? true : false;
// Wait for query to complete
// getLastConstraint().waitForCompleted();
// Sort resulting queueOUT?
for (Constraint constraint : constrains) {
if (constraint.sort != null) {
wait = true;
break;
}
}
if (wait) {
getLastConstraint().waitForCompleted();
}
for (Constraint constraint : constrains) {
if (constraint.sort != null) {
AbstractQueue<Long> lastQueue = getLastConstraint().getQueueOUT();
FieldInfo fi = ObjectTraverser.getFieldInfo(clasz, constraint.field);
Object[] arr = new Object[lastQueue.size() - 1];
int idx = 0;
for (Long oid : lastQueue) {
if (oid != GTM.END_OF_QUEUE) {
String globalName = GlobalName.buildGlobalName(clasz, oid, constraint.field);
SortItem item = new SortItem(oid, fi.typeHandler.get(gtm.GET(globalName)));
arr[idx++] = item;
}
}
// Sort the array
AbstractSorter sorter = fi.typeHandler.getSorter();
sorter.setSortDirection(constraint.sort);
sorter.setLexicalSort(constraint.lexicalSort);
Arrays.sort(arr, sorter);
// Rebuild the queue with the sorted array
AbstractQueue<Long> queue = new LinkedBlockingQueue<Long>();
for (int i = 0; i < arr.length; i++) {
queue.add(((SortItem) arr[i]).oid);
}
queue.add(GTM.END_OF_QUEUE);
getLastConstraint().queueOUT = queue;
}
}
//
if (haveComparator) {
ObjectSetImpl objSetImpl = new ObjectSetImpl<E>(clasz, getLastConstraint());
Constraint constraint = getLastConstraint();
constraint.waitForCompleted();
Object[] arr = new Object[constraint.getQueueOUT().size() - 1];
int idx = 0;
AbstractQueue<Long> ql = constraint.getQueueOUT();
// for (Long oid : constraint.getQueueOUT()) {
for (Long oid : ql) {
if (oid != GTM.END_OF_QUEUE) {
arr[idx++] = (E) db.getByID(clasz, oid);
}
}
Arrays.sort(arr, constraint.getComparator());
Field oidField = ObjectTraverser.getFieldInfo(clasz, GTM.OID).field;
AbstractQueue<Long> newOutQueue = new LinkedBlockingQueue<Long>();
for (int i = 0; i < arr.length; i++) {
E item = (E) arr[i];
try {
Long oid = (Long) oidField.get(item);
newOutQueue.add(oid);
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
newOutQueue.add(GTM.END_OF_QUEUE);
constraint.setQueueOUT(newOutQueue);
return new ObjectSetImpl<E>(clasz, constraint);
} else {
return getObjectSet();
}
}
/**
*
* @param <E>
* @return
*/
public <E> ObjectSet<E> getObjectSet()
{
return new ObjectSetImpl<E>(clasz, getLastConstraint());
}
/**
*
*/
public Constraint constrain(Class<?> clasz)
{
this.clasz = clasz;
Constraint constraint = new Constraint(clasz);
constrain(constraint);
return constraint;
}
/**
*
* @param constraint
* @return
*/
public Constraint constrain(Constraint constraint)
{
constrains.add(constraint);
return constraint;
}
/**
* Sort ascending with default lexicalSort (the, The)
*
* @param field
* @return
*/
public Constraint sortBy(String field)
{
return sortBy(field, true);
}
public Constraint sortBy(String field, boolean lexicalSort)
{
AbstractQueue<Long> queue = getLastConstraint().queueOUT;
Constraint c = new Constraint(queue, clasz, field, SORT.ASCENDING, lexicalSort);
constrains.add(c);
return c;
}
/**
* Sort descending with default lexicalSort (the, The)
*/
public Constraint sortByDescending(String field)
{
return sortByDescending(field, true);
}
/**
* Sort by field
*
* @param field
* @return
*/
public Constraint sortByDescending(String field, boolean lexicalSort)
{
AbstractQueue<Long> queue = getLastConstraint().queueOUT;
Constraint c = new Constraint(queue, clasz, field, SORT.DESCENDING, lexicalSort);
constrains.add(c);
return c;
}
/**
*
* @param field
* @param operator
* @param value
* @return
*/
public Constraint constrain(String field, OP operator, Object... value)
{
return constrain(field, operator, null, value);
}
/**
*
* @param field
* @param operator
* @param function
* @param value
* @return
*/
public Constraint constrain(String field, OP operator, FUNCTION function, Object... value)
{
AbstractQueue<Long> queue = getLastConstraint().queueOUT;
Constraint c = new Constraint(queue, clasz, field, operator, function, value);
constrains.add(c);
return c;
}
/**
*
* @return
*/
public List<Constraint> getConstraints()
{
return constrains;
}
/**
*
* @param p
*/
public <E> ObjectSet<E> AND(Query p)
{
QueryImpl qi = (QueryImpl) p;
waitForQueue(this);
waitForQueue(qi);
AbstractQueue<Long> q1 = (AbstractQueue<Long>) getOUTQueue();
AbstractQueue<Long> q2 = (AbstractQueue<Long>) qi.getOUTQueue();
q1.retainAll(q2);
return getObjectSet();
}
/**
*
* @param p
*/
public <E> ObjectSet<E> OR(Query p)
{
QueryImpl qi = (QueryImpl) p;
waitForQueue(this);
waitForQueue(qi);
AbstractQueue<Long> q1 = (AbstractQueue<Long>) getOUTQueue();
AbstractQueue<Long> q2 = (AbstractQueue<Long>) qi.getOUTQueue();
AbstractQueue<Long> newQueue = new LinkedBlockingQueue<Long>();
if (q1.size() > q2.size()) {
newQueue.addAll(q1);
addToQueue(newQueue, q2);
} else {
newQueue.addAll(q2);
addToQueue(newQueue, q1);
}
return getObjectSet();
}
/**
* Add a long to the current queueOUT if it doesn't exists.
*
* @param l
* @return true if the long was added, false otherwise
*/
private void addToQueue(AbstractQueue<Long> destination, AbstractQueue<Long> source)
{
int i = 0, j = 0;
for (Long oid : source) {
i++;
if (!destination.contains(oid)) {
destination.add(oid);
j++;
}
}
while (destination.remove(GTM.END_OF_QUEUE)) {
;
}
destination.add(GTM.END_OF_QUEUE);
setOUTQueue(destination);
}
/**
*
* @return
*/
protected AbstractQueue<Long> getOUTQueue()
{
Constraint c = getLastConstraint();
return c.queueOUT;
}
/**
*
* @return
*/
protected Constraint getLastConstraint()
{
return constrains.get(constrains.size() - 1);
}
/**
* Overwrite the queue from the FIRST constraint in an AND or OR operation
*
* @param q
*/
public void setOUTQueue(AbstractQueue<Long> q)
{
getLastConstraint().queueOUT = q;
}
/**
*
* @param query
*/
private void waitForQueue(QueryImpl query)
{
int waits = 0;
if (!query.executed) {
query.execute();
}
Constraint constraint = query.getLastConstraint();
while (!constraint.isCompleted()) {
waits++;
GTMUtils.sleep(25);
}
}
// =================== Predicate stuff ========================
private Class<E> getExtentTypeFromGenericParameter(Predicate predicate)
{
Class<E> extentType = (Class<E>) getFilterMethod(predicate).getParameterTypes()[0];
try {
Type genericType = ((ParameterizedType) predicate.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
if ((genericType instanceof Class) && (extentType.isAssignableFrom((Class) genericType))) {
extentType = (Class<E>) genericType;
}
} catch (RuntimeException e) {
}
return extentType;
}
/**
*
* @return
*/
private Method getFilterMethod(Predicate predicate)
{
Method method = null;
Method[] methods = predicate.getClass().getMethods();
for (int methodIdx = 0; methodIdx < methods.length; methodIdx++) {
method = methods[methodIdx];
if ((!method.getName().equals("match")) || method.getParameterTypes().length != 1) {
continue;
}
String targetName = method.getParameterTypes()[0].getName();
if (!"java.lang.Object".equals(targetName)) {
break;
}
}
if (method == null) {
throw new IllegalArgumentException("Invalid predicate.");
}
return method;
}
// =================== Predicate stuff ========================
/**
*
*/
public void printConstraintInfo()
{
System.out.println("---------- Constraints ----------");
for (Constraint constrain : getConstraints()) {
System.out.println(constrain);
}
}
public boolean isRemote()
{
return remote;
}
}