001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/query/BoundExpression.java,v 1.26 2003/07/09 01:22:50 wbiggs 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.util.ArrayList;
023: import java.util.Collection;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.Map;
027: import java.util.StringTokenizer;
028:
029: import javax.jdo.JDOHelper;
030: import javax.jdo.JDOUserException;
031: import javax.jdo.PersistenceManager;
032: import javax.jdo.spi.PersistenceCapable;
033:
034: import org.xorm.ClassMapping;
035: import org.xorm.InterfaceInvocationHandler;
036: import org.xorm.ModelMapping;
037: import org.xorm.RelationshipMapping;
038: import org.xorm.XORM;
039: import org.xorm.I15d;
040: import org.xorm.datastore.Column;
041: import org.xorm.datastore.Table;
042: import org.xorm.util.FieldDescriptor;
043:
044: /**
045: * Represents a Query where parameters have been bound to specific
046: * values and mapped against the datastore. This object bridges the
047: * gap between the object query model (Expression) and the data query
048: * model (Selector). The task of a DatastoreDriver is to take a
049: * Selector and convert it to a native query representation.
050: *
051: * Currently only a limited set of Expressions can be transformed
052: * to Selectors.
053: *
054: * Note that once bound, a BoundExpression does not require any knowledge
055: * of JDO interfaces.
056: */
057: public class BoundExpression extends QueryContext implements
058: ExpressionVisitor, I15d {
059: private QueryLanguage query;
060: private ModelMapping modelMapping;
061:
062: // This gets cached after first call to getSelector()
063: private Selector top;
064: private Selector selector;
065: private HashMap varToSelector = new HashMap();
066:
067: private Column lastColumn;
068: private RelationshipMapping lastRelationship;
069: private ClassMapping mapping;
070: private Object lastValue;
071: private boolean valueMode;
072:
073: /**
074: * Creates a new BoundExpression for the given query.
075: */
076: public BoundExpression(QueryLanguage query, PersistenceManager mgr) {
077: super (query.getCandidateClass());
078: this .query = query;
079: this .modelMapping = XORM.getModelMapping(mgr);
080: }
081:
082: private Selector.Ordering[] getOrdering() {
083: // Convert QueryOrdering[] to Selector.Ordering[]
084: QueryOrdering[] fieldOrdering = query.getOrdering();
085: if (fieldOrdering == null)
086: return null;
087: Selector.Ordering[] ordering = new Selector.Ordering[fieldOrdering.length];
088: for (int i = 0; i < fieldOrdering.length; i++) {
089: String field = fieldOrdering[i].getField();
090: Column column = null;
091:
092: // Handle a field of a field of a field of a...
093: Class currentClass = getCandidateClass();
094: StringTokenizer tok = new StringTokenizer(field, ".");
095:
096: String fieldToken;
097: FieldDescriptor fd;
098: Selector firstSelector = null, currentSelector = null;
099:
100: while (modelMapping.isManagedType(currentClass)) {
101: ClassMapping currentClassMapping = modelMapping
102: .getClassMapping(currentClass);
103:
104: Selector nextSelector = new Selector(
105: currentClassMapping.getTable(), null);
106: if (currentSelector != null) {
107: Condition condition = new SimpleCondition(column,
108: Operator.EQUAL, nextSelector);
109: currentSelector.setCondition(condition);
110: } else {
111: firstSelector = nextSelector;
112: }
113: currentSelector = nextSelector;
114:
115: if (!tok.hasMoreTokens()) {
116: throw new JDOUserException(I18N.msg("E_ordering"));
117: }
118: fieldToken = tok.nextToken();
119: fd = currentClassMapping.getFieldDescriptor(fieldToken);
120: if (fd == null) {
121: throw new JDOUserException(I18N.msg(
122: "E_unmapped_field", fieldToken));
123: }
124: column = currentClassMapping.getColumn(fieldToken);
125: currentClass = fd.type;
126: }
127:
128: top.merge(firstSelector, Operator.ANDC);
129:
130: // Ok, now that we jumped through all the field dereferencing
131: // hoops, let's set the column we're actually ordering on.
132: ordering[i] = new Selector.Ordering(column,
133: fieldOrdering[i].getOrder());
134: }
135: return ordering;
136: }
137:
138: public void bindParameter(int index, Object value) {
139: if (query instanceof AbstractQueryLanguage) {
140: AbstractQueryLanguage aql = (AbstractQueryLanguage) query;
141:
142: String name = (String) aql.getParameterNames().get(index);
143: Class clazz = aql.getParameterType(name);
144: if (value == null) {
145: if (clazz.isPrimitive()) {
146: throw new JDOUserException(I18N
147: .msg("E_null_primitive"));
148: }
149: bindParameter(name, value);
150: } else {
151: if (clazz.isInstance(value)
152: || isWrappedInstance(value, clazz)) {
153: bindParameter(name, value);
154: } else {
155: throw new JDOUserException(I18N
156: .msg("E_param_type", value.getClass()
157: .getName(), clazz.getName()));
158: }
159: }
160: }
161: }
162:
163: private static boolean isWrappedInstance(Object value, Class clazz) {
164: if (!clazz.isPrimitive()) {
165: return false;
166: }
167:
168: if (clazz.equals(Integer.TYPE)) {
169: return value instanceof Integer;
170: } else if (clazz.equals(Short.TYPE)) {
171: return value instanceof Short;
172: } else if (clazz.equals(Long.TYPE)) {
173: return value instanceof Long;
174: } else if (clazz.equals(Boolean.TYPE)) {
175: return value instanceof Boolean;
176: } else if (clazz.equals(Byte.TYPE)) {
177: return value instanceof Byte;
178: } else if (clazz.equals(Float.TYPE)) {
179: return value instanceof Float;
180: } else if (clazz.equals(Double.TYPE)) {
181: return value instanceof Double;
182: } else if (clazz.equals(Character.TYPE)) {
183: return value instanceof Character;
184: } else {
185: // Never gets here
186: return false;
187: }
188: }
189:
190: /**
191: * Creates or retrieves the Selector tree for this query.
192: */
193: public Selector getSelector() {
194: if (top == null) {
195: if (query instanceof DataQuery) {
196: top = makeSelector((DataQuery) query);
197: } else {
198: //System.out.println("Expression in: " + query.getExpression());
199: getSelector(query.getExpression());
200: }
201: top.setOrdering(getOrdering());
202: }
203: //System.out.println("Selector out; " + top);
204: return top;
205: }
206:
207: // Convert DataQuery to Selector
208: private Selector makeSelector(DataQuery dataQuery) {
209: Class targetClass = dataQuery.getCandidateClass();
210: ClassMapping mapping = modelMapping
211: .getClassMapping(targetClass);
212: Table table = mapping.getTable();
213: Condition dataCondition = dataQuery.getCondition();
214: if (dataCondition instanceof RawCondition) {
215: // Resolve parameters
216: dataCondition = makeRawCondition((RawCondition) dataCondition);
217: }
218: return new Selector(table, dataCondition);
219: }
220:
221: private RawCondition makeRawCondition(RawCondition condition) {
222: String where2 = condition.getRawQuery();
223: int pos = 0;
224: while ((pos = where2.indexOf('{', pos)) != -1) {
225: int pos2 = where2.indexOf('}', pos + 1);
226: if (pos2 == -1)
227: break;
228: String name = where2.substring(pos + 1, pos2);
229: boolean contains = hasParameter(name);
230: Object operand = resolveParameter(name);
231:
232: /*
233: * Ignore {xxx} if xxx has no value this is to allow JDBC escape
234: * sequences to be passed through. Maybe XORM should use a
235: * different delimiter than {} so it doesn't conflict with JDBC...
236: */
237: if (!contains) {
238: pos++;
239: continue;
240: }
241:
242: where2 = where2.substring(0, pos)
243: + condition.convertOperand(operand)
244: + where2.substring(pos2 + 1);
245: pos = pos2 + 1;
246: }
247: return new RawCondition(condition.getTables(), where2);
248: }
249:
250: // Called internally
251: private Selector getSelector(Expression expression) {
252: mapping = modelMapping.getClassMapping(query
253: .getCandidateClass());
254: Table table = mapping.getTable();
255: top = new Selector(table, null);
256: selector = top;
257: expression.accept(this );
258:
259: // Deal with boolean standalones
260: while (expression instanceof Expression.Not) {
261: expression = ((Expression.Not) expression).getOperand();
262: }
263:
264: if ((expression instanceof Expression.FieldAccess)
265: && (expression.getType().equals(Boolean.TYPE))) {
266: selector.setCondition(new SimpleCondition(lastColumn,
267: Operator.EQUAL, Boolean.TRUE));
268: }
269:
270: //System.out.println("getSelector(" + expression + ")\nreturns: " + top);
271: return top;
272: }
273:
274: public boolean visitAnd(Expression.And exp) {
275: visitAndOrImpl(exp, Operator.ANDC);
276: return true;
277: }
278:
279: public boolean visitConditionalAnd(Expression.ConditionalAnd exp) {
280: visitAndOrImpl(exp, Operator.ANDC);
281: return true;
282: }
283:
284: public boolean visitConditionalOr(Expression.ConditionalOr exp) {
285: visitAndOrImpl(exp, Operator.ORC);
286: return true;
287: }
288:
289: private void visitAndOrImpl(Expression.Comparison exp,
290: Operator operator) {
291: Selector orig = top;
292:
293: // Visit lhs
294: Selector lhs = getSelector(exp.getLHS());
295:
296: // Visit rhs
297: Selector rhs = getSelector(exp.getRHS());
298:
299: // Merge left and right
300: if (lhs != null) {
301: lhs.merge(rhs, operator);
302: orig.setCondition(lhs.getCondition());
303: } else {
304: orig.setCondition(rhs.getCondition());
305: }
306: top = orig;
307: //System.out.println("returning with top: " + top);
308: }
309:
310: public boolean visitComparison(Expression.Comparison exp) {
311: Expression left = exp.getLHS();
312: Expression right = exp.getRHS();
313:
314: // Check for some cheats
315: // x.indexOf(y) != -1 --> x.strstr(y)
316: // x.indexOf(y) >= 0 --> same (for you old-school hackers)
317: if ((left instanceof Expression.MethodCall)
318: && (right instanceof Expression.Constant)
319: && "indexOf".equals(((Expression.MethodCall) left)
320: .getName())
321: && (((exp.operator() == Operator.NOT_EQUAL) && new Integer(
322: -1).equals(right.evaluate(this ))) || ((exp
323: .operator() == Operator.GTE) && new Integer(0)
324: .equals(right.evaluate(this ))))) {
325: Expression.MethodCall lmc = (Expression.MethodCall) left;
326: Expression exp2 = new Expression.MethodCall(lmc.getOwner(),
327: "strstr", lmc.getParameters(), Boolean.TYPE);
328: exp2.accept(this );
329: return true;
330: }
331:
332: // Visit the field
333: exp.getLHS().accept(this );
334:
335: // Get the operator
336: Operator operator = exp.operator();
337:
338: // Get the value
339: valueMode = true;
340: exp.getRHS().accept(this );
341: // Convert Objects to their IDs
342: if (lastValue instanceof PersistenceCapable) {
343: lastValue = XORM.extractPrimaryKey(JDOHelper
344: .getObjectId(lastValue));
345: }
346: valueMode = false;
347:
348: SimpleCondition sc = new SimpleCondition(lastColumn, operator,
349: lastValue);
350: selector.setCondition(sc);
351: return true;
352: }
353:
354: public boolean visitFieldAccess(Expression.FieldAccess exp) {
355: //System.out.println("Visit fieldAccess: " + exp.toString());
356: if (exp == Expression.FieldAccess.THIS) {
357: mapping = modelMapping.getClassMapping(query
358: .getCandidateClass());
359: lastRelationship = null;
360: lastColumn = mapping.getTable().getPrimaryKey();
361: } else {
362: // Recurse up the chain to "this"
363: Expression owner = exp.getOwner();
364: owner.accept(this );
365:
366: if (valueMode) {
367: // Introspect on lastValue (which should be a JDO object)
368: InterfaceInvocationHandler handler = InterfaceInvocationHandler
369: .getHandler(lastValue);
370: Class returnType = exp.getType();
371: ClassMapping returnTypeMapping = null;
372: if (ClassMapping.isUserType(returnType)) {
373: returnTypeMapping = modelMapping
374: .getClassMapping(returnType);
375: }
376: lastValue = handler.invokeGet(exp.getName(),
377: returnTypeMapping, exp.getType());
378: } else {
379: // If we're hanging from another field, interpolate
380: // the table join
381: if (lastRelationship != null) {
382: checkJoin(owner);
383: }
384:
385: lastColumn = mapping.getColumn(exp.getName());
386: lastRelationship = mapping.getRelationship(exp
387: .getName());
388: }
389: }
390: return true;
391: }
392:
393: private void checkJoin(Expression owner) {
394: if (lastRelationship != null) {
395: // Ex. x.y; lastColumn = x
396: // generate x_id = x.x_id
397: ClassMapping nextMapping = modelMapping
398: .getClassMapping(owner.getType());
399: Selector se = new Selector(nextMapping.getTable(), null);
400: SimpleCondition join = new SimpleCondition(
401: mapping.getColumn(((Expression.Symbolic) owner)
402: .getName()), Operator.EQUAL, se);
403: selector.setCondition(join);
404: selector = se;
405: mapping = nextMapping;
406: }
407: }
408:
409: public boolean visitVariable(Expression.Variable exp) {
410: mapping = modelMapping.getClassMapping(exp.getType());
411: selector = (Selector) varToSelector.get(exp.getName());
412: top = (Selector) selector.clone();
413: lastRelationship = null;
414:
415: //System.out.println("top is now: " + top);
416: // TODO This may not be correct if the table is touched
417: // previously/elsewhere in the query
418: selector = top.findSelector(mapping.getTable());
419: //System.out.println("selector is now: " + selector);
420: return true;
421: }
422:
423: public boolean visitMethodCall(Expression.MethodCall exp) {
424: String name = exp.getName();
425: // Read the field
426: exp.getOwner().accept(this );
427: // Do we need a join?
428: if (!"contains".equals(name) && !"isEmpty".equals(name)) {
429: checkJoin(exp.getOwner());
430: }
431:
432: Column column = lastColumn;
433: // Read the value
434: Object param = null;
435: Expression operand = null;
436: if (!"isEmpty".equals(name)) {
437: operand = exp.getParameters()[0];
438: if ((operand instanceof Expression.Constant)
439: || (operand instanceof Expression.Parameter)) {
440: param = operand.evaluate(this );
441: }
442: }
443: if ("contains".equals(name)) {
444: Expression owner = exp.getOwner();
445:
446: if ((owner instanceof Expression.Parameter)
447: && (operand instanceof Expression)) {
448: mapping = modelMapping.getClassMapping(query
449: .getCandidateClass());
450: Table table = mapping.getTable();
451: top = new Selector(table, null);
452: selector = top;
453: operand.accept(this );
454: selector.setCondition(new SimpleCondition(lastColumn,
455: Operator.IN, collectionToIDs((Collection) owner
456: .evaluate(this ))));
457: return true;
458: }
459:
460: String field = ((Expression.Member) owner).getName();
461: RelationshipMapping rm = mapping.getRelationship(field);
462: ClassMapping targetMapping = modelMapping
463: .getClassMapping(rm.getSource().getElementClass());
464: Selector s = null;
465: Selector old = selector;
466: if (operand instanceof Expression.Variable) {
467: // This field is really the variable, using targetMapping
468: selector = new Selector(targetMapping.getTable(), null);
469: } else if (operand instanceof Expression.Parameter) {
470: if (param != null) {
471: param = XORM.extractPrimaryKey(JDOHelper
472: .getObjectId(param));
473: }
474: selector = new Selector(rm.getTarget().getColumn()
475: .getTable(), new SimpleCondition(rm.getTarget()
476: .getColumn(), Operator.EQUAL, param));
477: selector.setJoinColumn(rm.getTarget().getColumn());
478:
479: // If we have a parameter on a MToN mapping,
480: // we can skip the target table
481: selector.setJoinColumn(rm.getSource().getColumn());
482: old.setCondition(new SimpleCondition(mapping.getTable()
483: .getPrimaryKey(), Operator.CONTAINS, selector));
484: return true;
485: }
486: if (rm.isMToN()) {
487: s = new Selector(rm.getTarget().getColumn().getTable(),
488: new SimpleCondition(rm.getTarget().getColumn(),
489: Operator.EQUAL, selector));
490: } else {
491: s = selector;
492: }
493: s.setJoinColumn(rm.getSource().getColumn());
494: old.setCondition(new SimpleCondition(mapping.getTable()
495: .getPrimaryKey(), Operator.CONTAINS, s));
496: if (operand instanceof Expression.Variable) {
497: varToSelector.put(((Expression.Variable) operand)
498: .getName(), top);
499: top = null;
500: }
501: } else if ("startsWith".equals(name)) {
502: selector.setCondition(new SimpleCondition(column,
503: Operator.STARTS_WITH, param));
504: } else if ("endsWith".equals(name)) {
505: selector.setCondition(new SimpleCondition(column,
506: Operator.ENDS_WITH, param));
507: } else if ("strstr".equals(name)) {
508: selector.setCondition(new SimpleCondition(column,
509: Operator.STR_CONTAINS, param));
510: } else if ("isEmpty".equals(name)) {
511: // model.model_id where model_id = model_platform
512: // .model_id where model_id is null
513: String field = ((Expression.Member) exp.getOwner())
514: .getName();
515: RelationshipMapping rm = mapping.getRelationship(field);
516: Selector s = new Selector(rm.getSource().getColumn()
517: .getTable(), new SimpleCondition(rm.getSource()
518: .getColumn(), Operator.EQUAL, null));
519: s.setJoinColumn(rm.getSource().getColumn());
520: s.setOuterJoin(true);
521: selector.setCondition(new SimpleCondition(selector
522: .getTable().getPrimaryKey(), Operator.CONTAINS, s));
523: }
524: return true;
525: }
526:
527: public boolean visitParameter(Expression.Parameter exp) {
528: //System.out.println("Visit parameter: " + exp.toString());
529: lastValue = resolveParameter(exp.getName());
530: lastRelationship = null;
531: return true;
532: }
533:
534: public boolean visitConstant(Expression.Constant exp) {
535: lastValue = exp.getValue();
536: return true;
537: }
538:
539: public boolean visitNot(Expression.Not exp) {
540: exp.getOperand().accept(this );
541: Condition c = selector.getCondition();
542: if (c != null) {
543: c.setInverted();
544: }
545: return true;
546: }
547:
548: public boolean visitUnary(Expression.Unary exp) {
549: return false;
550: }
551:
552: private Collection collectionToIDs(Collection input) {
553: Collection output = new ArrayList(input.size());
554: Iterator it = input.iterator();
555: while (it.hasNext()) {
556: Object obj = it.next();
557: if (obj instanceof PersistenceCapable) {
558: obj = XORM
559: .extractPrimaryKey(JDOHelper.getObjectId(obj));
560: }
561: output.add(obj);
562: }
563: return output;
564: }
565: }
|