/*
$Header: /cvsroot/xorm/xorm/src/org/xorm/query/CodeQuery.java,v 1.8 2004/03/23 23:37:49 seifertd Exp $
This file is part of XORM.
XORM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
XORM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with XORM; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.xorm.query;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import javax.jdo.Extent;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
/**
* Implementation of JDO query interface that
* executes Java code that follows a particular pattern.
*/
public class CodeQuery implements Query {
public static final String LANGUAGE = "org.xorm.query.CodeQuery";
private Class queryClass;
private PersistenceManager mgr;
private Field[] variables;
private Object candidates;
public static Query parseCodeQuery(PersistenceManager mgr, Class clazz) {
Query q = null;
CodeParser cp = null;
try {
cp = new CodeParser(clazz);
q = mgr.newQuery(clazz, cp.getFilter());
String variables = cp.getVariables();
if (variables != null) {
q.declareVariables(variables);
}
String parameters = cp.getParameters();
if (parameters != null) {
q.declareParameters(parameters);
}
} catch (Throwable e) {
// Code could not be turned into JDOQL.
q = new CodeQuery(mgr, clazz);
}
return q;
}
public CodeQuery(PersistenceManager mgr, Class queryClass) {
this.mgr = mgr;
this.queryClass = queryClass;
// Introspect to find variables
variables = queryClass.getDeclaredFields();
}
// Query interface methods
public PersistenceManager getPersistenceManager() {
return mgr;
}
public void setClass(Class clazz) {
this.queryClass = clazz;
}
public void setCandidates(Extent extent) {
this.candidates = extent;
}
public void setCandidates(Collection collection) {
this.candidates = collection;
}
private Iterator getCandidates() {
if (candidates == null) {
// do it by class
return mgr.getExtent(queryClass, false).iterator();
} else if (candidates instanceof Collection) {
return ((Collection) candidates).iterator();
}
else return ((Extent) candidates).iterator();
}
public void setFilter(String filter) { }
public void declareImports(String imports) { }
public void declareParameters(String parameters) { }
public void declareVariables(String variables) { }
public void setOrdering(String ordering) { }
public void setIgnoreCache(boolean ignoreCache) { }
public boolean getIgnoreCache() { return false; }
public void compile() { }
public void close(Object results) { }
public void closeAll() { }
public Object execute() {
return executeWithArray(new Object[0]);
}
public Object execute(Object param) {
return executeWithArray(new Object[] { param });
}
public Object execute(Object param0, Object param1) {
return executeWithArray(new Object[] { param0, param1 });
}
public Object execute(Object param0, Object param1, Object param2) {
return executeWithArray(new Object[] { param0, param1, param2 });
}
public Object executeWithMap(Map map) {
// TODO
throw new UnsupportedOperationException();
}
public Object executeWithArray(Object[] params) {
// Ensure that there is an open transaction
boolean internalTransaction = !mgr.currentTransaction().isActive();
if (internalTransaction) mgr.currentTransaction().begin();
// Determine parameter types
Class[] paramTypes = new Class [params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i] == null ? null : params[i].getClass();
}
// Find correct evaluate method using introspection... can't use getDeclaredMethod, it
// is too restrictive if any of the parameter types are interfaces or base classes and the
// actual parameter value is a class that implements or extends it. So we must resort to walking
// a list of declared methods and checking if the parameter types are compatible using isAssignableFrom
Method[] allMethods = queryClass.getDeclaredMethods(); // TODO: Use getMethods if super classes should be checked
Method evaluate = null;
for (int i = 0; i < allMethods.length; ++i) {
Method toCheck = allMethods[i];
// Not interested unless it is an evaluate(...) method ...
if (!"evaluate".equals(toCheck.getName())) {
continue;
}
// Check number of params in the method
Class[] evalParams = toCheck.getParameterTypes();
if (evalParams.length != paramTypes.length) {
// method has different number of parameters, it can't match
continue;
}
// check the parameter types against the method params and see if the method
// is compatible. THis loop will take the first method that matches, so
// the possibility remains that a "better" match exists ... how does the JVM
// resolve such issues, would be nice to implement an algorithm here that
// does a similar thing as the JVM when it resolves a "virtual" (to borrow
// C++ parlance) method call.
boolean isCompatible = true;
for (int j = 0; j < evalParams.length; ++j) {
if (!evalParams[j].isAssignableFrom(paramTypes[j])) {
isCompatible = false;
break;
}
}
if (isCompatible) {
evaluate = toCheck;
break;
}
}
/* DAS: The below won't work if the paramTypes include subclasses or implementations
Method evaluate = null;
try {
evaluate = queryClass.getDeclaredMethod("evaluate", paramTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
*/
Collection results;
if (Comparable.class.isAssignableFrom(queryClass)) {
results = new TreeSet();
} else {
results = new ArrayList();
}
if (evaluate != null) {
// Put in a mapping such for queryClass
Iterator candidates = getCandidates();
// How many variables?
Object[] current = new Object[variables.length];
List allVars = new ArrayList();
for (int i = 0; i < variables.length; i++) {
// Get all instances of variable type and add Collection to list.
allVars.add(mgr.newQuery(variables[i].getType()).execute());
}
// Now test all permutations of variables with each candidate
while (candidates.hasNext()) {
Object candidate = candidates.next();
// Now execute for each permutation...
if (recurse(current, 0, allVars.iterator(), candidate, evaluate, params)) {
results.add(candidate);
}
} // for each candidate
}
if (internalTransaction) mgr.currentTransaction().commit();
return Collections.unmodifiableCollection(results);
} // executeImpl
/** Returns true if candidate passes. */
private boolean recurse(Object[] current, int index, Iterator it, Object candidate, Method evaluate, Object[] params) {
if (!it.hasNext()) {
// All variables are set
return execute2(current, candidate, evaluate, params);
}
Collection cc = (Collection) it.next();
Iterator ccIt = cc.iterator();
while (ccIt.hasNext()) {
current[index] = ccIt.next();
if (recurse(current, index + 1, it, candidate, evaluate, params)) {
// Break out if candidate passed
return true;
}
}
return false;
}
/** Returns true if candidate passes. */
private boolean execute2(Object[] variables, Object candidate, Method evaluate, Object[] params) {
for (int i = 0; i < variables.length; i++) {
Field field = this.variables[i];
try {
field.set(candidate, variables[i]);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// Now try to evaluate
try {
return (((Boolean) evaluate.invoke(candidate, params)).booleanValue());
} catch (InvocationTargetException e) {
e.printStackTrace(); // FOR NOW
// assume false, e.g. on NullPointerException
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
} // boolean execute2()
public String toString() { return queryClass.toString(); }
} // class CodeQuery