001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/query/CodeQuery.java,v 1.8 2004/03/23 23:37:49 seifertd Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.query;
021:
022: import java.lang.reflect.Field;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025:
026: import java.util.ArrayList;
027: import java.util.Collection;
028: import java.util.Collections;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.TreeSet;
033:
034: import javax.jdo.Extent;
035: import javax.jdo.PersistenceManager;
036: import javax.jdo.Query;
037:
038: /**
039: * Implementation of JDO query interface that
040: * executes Java code that follows a particular pattern.
041: */
042: public class CodeQuery implements Query {
043: public static final String LANGUAGE = "org.xorm.query.CodeQuery";
044:
045: private Class queryClass;
046: private PersistenceManager mgr;
047: private Field[] variables;
048: private Object candidates;
049:
050: public static Query parseCodeQuery(PersistenceManager mgr,
051: Class clazz) {
052: Query q = null;
053: CodeParser cp = null;
054: try {
055: cp = new CodeParser(clazz);
056: q = mgr.newQuery(clazz, cp.getFilter());
057: String variables = cp.getVariables();
058: if (variables != null) {
059: q.declareVariables(variables);
060: }
061: String parameters = cp.getParameters();
062: if (parameters != null) {
063: q.declareParameters(parameters);
064: }
065: } catch (Throwable e) {
066: // Code could not be turned into JDOQL.
067: q = new CodeQuery(mgr, clazz);
068: }
069: return q;
070: }
071:
072: public CodeQuery(PersistenceManager mgr, Class queryClass) {
073: this .mgr = mgr;
074: this .queryClass = queryClass;
075:
076: // Introspect to find variables
077: variables = queryClass.getDeclaredFields();
078: }
079:
080: // Query interface methods
081: public PersistenceManager getPersistenceManager() {
082: return mgr;
083: }
084:
085: public void setClass(Class clazz) {
086: this .queryClass = clazz;
087: }
088:
089: public void setCandidates(Extent extent) {
090: this .candidates = extent;
091: }
092:
093: public void setCandidates(Collection collection) {
094: this .candidates = collection;
095: }
096:
097: private Iterator getCandidates() {
098: if (candidates == null) {
099: // do it by class
100: return mgr.getExtent(queryClass, false).iterator();
101: } else if (candidates instanceof Collection) {
102: return ((Collection) candidates).iterator();
103: } else
104: return ((Extent) candidates).iterator();
105: }
106:
107: public void setFilter(String filter) {
108: }
109:
110: public void declareImports(String imports) {
111: }
112:
113: public void declareParameters(String parameters) {
114: }
115:
116: public void declareVariables(String variables) {
117: }
118:
119: public void setOrdering(String ordering) {
120: }
121:
122: public void setIgnoreCache(boolean ignoreCache) {
123: }
124:
125: public boolean getIgnoreCache() {
126: return false;
127: }
128:
129: public void compile() {
130: }
131:
132: public void close(Object results) {
133: }
134:
135: public void closeAll() {
136: }
137:
138: public Object execute() {
139: return executeWithArray(new Object[0]);
140: }
141:
142: public Object execute(Object param) {
143: return executeWithArray(new Object[] { param });
144: }
145:
146: public Object execute(Object param0, Object param1) {
147: return executeWithArray(new Object[] { param0, param1 });
148: }
149:
150: public Object execute(Object param0, Object param1, Object param2) {
151: return executeWithArray(new Object[] { param0, param1, param2 });
152: }
153:
154: public Object executeWithMap(Map map) {
155: // TODO
156: throw new UnsupportedOperationException();
157: }
158:
159: public Object executeWithArray(Object[] params) {
160: // Ensure that there is an open transaction
161: boolean internalTransaction = !mgr.currentTransaction()
162: .isActive();
163: if (internalTransaction)
164: mgr.currentTransaction().begin();
165:
166: // Determine parameter types
167: Class[] paramTypes = new Class[params.length];
168: for (int i = 0; i < params.length; i++) {
169: paramTypes[i] = params[i] == null ? null : params[i]
170: .getClass();
171: }
172:
173: // Find correct evaluate method using introspection... can't use getDeclaredMethod, it
174: // is too restrictive if any of the parameter types are interfaces or base classes and the
175: // actual parameter value is a class that implements or extends it. So we must resort to walking
176: // a list of declared methods and checking if the parameter types are compatible using isAssignableFrom
177: Method[] allMethods = queryClass.getDeclaredMethods(); // TODO: Use getMethods if super classes should be checked
178: Method evaluate = null;
179: for (int i = 0; i < allMethods.length; ++i) {
180: Method toCheck = allMethods[i];
181:
182: // Not interested unless it is an evaluate(...) method ...
183: if (!"evaluate".equals(toCheck.getName())) {
184: continue;
185: }
186:
187: // Check number of params in the method
188: Class[] evalParams = toCheck.getParameterTypes();
189: if (evalParams.length != paramTypes.length) {
190: // method has different number of parameters, it can't match
191: continue;
192: }
193:
194: // check the parameter types against the method params and see if the method
195: // is compatible. THis loop will take the first method that matches, so
196: // the possibility remains that a "better" match exists ... how does the JVM
197: // resolve such issues, would be nice to implement an algorithm here that
198: // does a similar thing as the JVM when it resolves a "virtual" (to borrow
199: // C++ parlance) method call.
200: boolean isCompatible = true;
201: for (int j = 0; j < evalParams.length; ++j) {
202: if (!evalParams[j].isAssignableFrom(paramTypes[j])) {
203: isCompatible = false;
204: break;
205: }
206: }
207: if (isCompatible) {
208: evaluate = toCheck;
209: break;
210: }
211: }
212:
213: /* DAS: The below won't work if the paramTypes include subclasses or implementations
214: Method evaluate = null;
215: try {
216: evaluate = queryClass.getDeclaredMethod("evaluate", paramTypes);
217: } catch (NoSuchMethodException e) {
218: e.printStackTrace();
219: } catch (SecurityException e) {
220: e.printStackTrace();
221: }
222: */
223:
224: Collection results;
225: if (Comparable.class.isAssignableFrom(queryClass)) {
226: results = new TreeSet();
227: } else {
228: results = new ArrayList();
229: }
230:
231: if (evaluate != null) {
232: // Put in a mapping such for queryClass
233: Iterator candidates = getCandidates();
234: // How many variables?
235: Object[] current = new Object[variables.length];
236: List allVars = new ArrayList();
237: for (int i = 0; i < variables.length; i++) {
238: // Get all instances of variable type and add Collection to list.
239: allVars.add(mgr.newQuery(variables[i].getType())
240: .execute());
241: }
242:
243: // Now test all permutations of variables with each candidate
244: while (candidates.hasNext()) {
245: Object candidate = candidates.next();
246:
247: // Now execute for each permutation...
248: if (recurse(current, 0, allVars.iterator(), candidate,
249: evaluate, params)) {
250: results.add(candidate);
251: }
252: } // for each candidate
253: }
254: if (internalTransaction)
255: mgr.currentTransaction().commit();
256: return Collections.unmodifiableCollection(results);
257: } // executeImpl
258:
259: /** Returns true if candidate passes. */
260: private boolean recurse(Object[] current, int index, Iterator it,
261: Object candidate, Method evaluate, Object[] params) {
262: if (!it.hasNext()) {
263: // All variables are set
264: return execute2(current, candidate, evaluate, params);
265: }
266: Collection cc = (Collection) it.next();
267: Iterator ccIt = cc.iterator();
268: while (ccIt.hasNext()) {
269: current[index] = ccIt.next();
270: if (recurse(current, index + 1, it, candidate, evaluate,
271: params)) {
272: // Break out if candidate passed
273: return true;
274: }
275: }
276: return false;
277: }
278:
279: /** Returns true if candidate passes. */
280: private boolean execute2(Object[] variables, Object candidate,
281: Method evaluate, Object[] params) {
282: for (int i = 0; i < variables.length; i++) {
283: Field field = this .variables[i];
284: try {
285: field.set(candidate, variables[i]);
286: } catch (IllegalAccessException e) {
287: e.printStackTrace();
288: }
289: }
290:
291: // Now try to evaluate
292: try {
293: return (((Boolean) evaluate.invoke(candidate, params))
294: .booleanValue());
295: } catch (InvocationTargetException e) {
296: e.printStackTrace(); // FOR NOW
297: // assume false, e.g. on NullPointerException
298: } catch (IllegalAccessException e) {
299: e.printStackTrace();
300: }
301: return false;
302: } // boolean execute2()
303:
304: public String toString() {
305: return queryClass.toString();
306: }
307: } // class CodeQuery
|