001: /**
002: * Speedo: an implementation of JDO compliant personality on top of JORM generic
003: * I/O sub-system.
004: * Copyright (C) 2001-2005 France Telecom R&D
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library 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 GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: *
021: *
022: * Authors: S.Chassande-Barrioz.
023: * Created on 3 fevr. 2005
024: *
025: */package org.objectweb.speedo.query.jdo.parser;
026:
027: import org.objectweb.jorm.api.PMapper;
028: import org.objectweb.jorm.metainfo.api.ClassRef;
029: import org.objectweb.jorm.metainfo.api.GenClassRef;
030: import org.objectweb.jorm.metainfo.api.Reference;
031: import org.objectweb.jorm.type.api.PType;
032: import org.objectweb.medor.api.Field;
033: import org.objectweb.medor.api.MedorException;
034: import org.objectweb.medor.expression.api.Expression;
035: import org.objectweb.medor.expression.api.ExpressionException;
036: import org.objectweb.medor.expression.api.Operator;
037: import org.objectweb.medor.filter.api.FieldOperand;
038: import org.objectweb.medor.filter.lib.Avg;
039: import org.objectweb.medor.filter.lib.BasicFieldOperand;
040: import org.objectweb.medor.filter.lib.Count;
041: import org.objectweb.medor.filter.lib.Max;
042: import org.objectweb.medor.filter.lib.Min;
043: import org.objectweb.medor.filter.lib.Sum;
044: import org.objectweb.medor.query.api.PropagatedField;
045: import org.objectweb.medor.query.api.QueryNode;
046: import org.objectweb.medor.query.api.QueryTree;
047: import org.objectweb.medor.query.api.QueryTreeField;
048: import org.objectweb.medor.query.jorm.lib.ClassExtent;
049: import org.objectweb.medor.query.jorm.lib.JormQueryTreeHelper;
050: import org.objectweb.medor.query.jorm.lib.PNameField;
051: import org.objectweb.medor.query.lib.Nest;
052: import org.objectweb.medor.query.lib.SelectProject;
053: import org.objectweb.speedo.api.SpeedoException;
054: import org.objectweb.speedo.mim.api.HomeItf;
055: import org.objectweb.speedo.query.jdo.JDOQueryDefinitionImpl;
056: import org.objectweb.speedo.query.jdo.JDOQueryEvalContext;
057:
058: import java.io.Serializable;
059: import java.math.BigDecimal;
060: import java.math.BigInteger;
061: import java.util.ArrayList;
062: import java.util.Date;
063: import java.util.HashMap;
064: import java.util.List;
065: import java.util.Map;
066: import java.util.Stack;
067: import java.util.StringTokenizer;
068:
069: /**
070: * This visitor parses select and group by clauses in order to build the
071: * projected field of a MEDOR query.
072: *
073: * @author S.Chassande-Barrioz
074: */
075: public class SelectGroupByVisitor {
076:
077: /**
078: * key word for the count operator
079: */
080: private static final String COUNT = "count";
081:
082: /**
083: * key word for the sum operator
084: */
085: private static final String SUM = "sum";
086:
087: /**
088: * key word for the min operator
089: */
090: private static final String MIN = "min";
091:
092: /**
093: * key word for the max operator
094: */
095: private static final String MAX = "max";
096:
097: /**
098: * key word for the avg operator
099: */
100: private static final String AVG = "avg";
101:
102: /**
103: * key word for the distrinct constraint
104: */
105: private static final String DISTINCT = "distinct";
106:
107: /**
108: * key word for the unique constraint
109: */
110: private static final String UNIQUE = "unique";
111:
112: /**
113: * list of keywords
114: */
115: private static final String[] keywords = new String[] {
116: COUNT.toLowerCase(), //0
117: COUNT.toUpperCase(), //1
118: SUM.toLowerCase(), //2
119: SUM.toUpperCase(), //3
120: MIN.toLowerCase(), //4
121: MIN.toUpperCase(), //5
122: MAX.toLowerCase(), //6
123: MAX.toUpperCase(), //7
124: AVG.toLowerCase(), //8
125: AVG.toUpperCase(), //9
126: DISTINCT.toLowerCase(), //10
127: DISTINCT.toUpperCase(), //11
128: "(", //12
129: ")", //13
130: ",", //14
131: "as", //15
132: "AS", //16
133: UNIQUE.toLowerCase(), //17
134: UNIQUE.toUpperCase() //18
135: };
136:
137: /**
138: * Retrieves the String representation of the function identifier
139: */
140: private final static String getFunctionAsString(int functionIdx) {
141: switch (functionIdx / 2) {
142: case 0:
143: return "COUNT";
144: case 1:
145: return "SUM";
146: case 2:
147: return "MIN";
148: case 3:
149: return "MAX";
150: case 4:
151: return "AVG";
152: default:
153: return "UNKNOWN";
154: }
155:
156: }
157:
158: /**
159: * SelectProjet where the simple (not aggregat operation) selected fields
160: * must be projected.
161: */
162: private QueryNode top;
163:
164: /**
165: * The query tree corresponding to the query
166: */
167: private QueryTree qt;
168:
169: /**
170: * The JORM PMapper permitting to fetch PClassMapping for prefetching
171: */
172: private PMapper mapper;
173:
174: private ClassLoader classloader;
175:
176: /**
177: * The visitor of variable used in Speedo for building the query. The
178: * visitor is used here for building paths of the select. Indeed the select
179: * clause can contains navigation path.
180: */
181: private SpeedoQLVariableVisitor sqvv;
182:
183: /**
184: * The QueryEvalContext used in Speedo for evaluate the MEDOR query. During
185: * the visit of select clause, the algorithm determines if prefetching is
186: * possible and on which class. If prefetching is possible, the
187: * PClassMapping of the prefetched class and the pname index are assigned on
188: * the #qec.
189: */
190: private JDOQueryEvalContext qec;
191:
192: /**
193: * indicate if a distinct constraint is specified in the select clause This
194: * field is filled after the call of the parse(String) method.
195: */
196: private boolean unique = false;
197:
198: /**
199: * indicate if a distinct constraint is specified in the select clause This
200: * field is filled after the call of the parse(String) method.
201: */
202: private boolean distinct = false;
203:
204: /**
205: * indicate if the select clause contains aggregat operator(s) This field is
206: * filled after the call of the parse(String) method.
207: */
208: private boolean hasAggregate = false;
209:
210: /**
211: * contains the MEDOR object permitting to build the projection part of the
212: * query. MEDOR object type can be one of the following: - Field : it means
213: * the selected part is a simple navigation (this, this.a.f1, this.a.myB,
214: * ...) - Expression: it means the selected part is a calculated field such
215: * as aggregate function (AVG(this.a.f1), this.f1 + this.f2, ...).
216: *
217: * This field is filled after the call of the parse(String) method.
218: */
219: private ArrayList selectfields = new ArrayList();
220:
221: /**
222: * contains the alias name (String) of the projected fields. The content of
223: * this field is linked to the #selectfields field. A null value in the list
224: * means the corresponding selected fields (same index) has no alias.
225: *
226: * This field is filled after the call of the parse(String) method.
227: */
228: private ArrayList aliases = new ArrayList();
229:
230: /**
231: * contains the list of grouped fields. The grouped fields are the one used
232: * in an aggregat operator.
233: *
234: * This field is filled after the call of the parse(String) method.
235: */
236: private ArrayList groupedFields = new ArrayList();
237:
238: /**
239: * contains the list of fields specified in the group by clause.
240: *
241: * This field is filled after the call of the parse(String) method.
242: */
243: private ArrayList groupByFields = new ArrayList();
244:
245: private ArrayList usedFields = new ArrayList();
246:
247: private ArrayList selectFieldTypes = new ArrayList();
248:
249: /**
250: * @param qt
251: * @param sp
252: * @param mapper
253: * @param sqvv
254: * @param qd
255: * @param qec
256: */
257: public SelectGroupByVisitor(SelectProject sp, QueryTree qt,
258: PMapper mapper, SpeedoQLVariableVisitor sqvv,
259: JDOQueryEvalContext qec, ClassLoader classloader) {
260: super ();
261: this .qt = qt;
262: this .top = sp;
263: this .mapper = mapper;
264: this .sqvv = sqvv;
265: this .qec = qec;
266: this .classloader = classloader;
267: }
268:
269: public SelectGroupByVisitor(SelectProject sp, QueryTree qt,
270: SpeedoQLVariableVisitor sqvv, ClassLoader classloader) {
271: this (sp, qt, null, sqvv, null, classloader);
272: }
273:
274: public Class[] getSelectFieldTypes() {
275: return (Class[]) selectFieldTypes
276: .toArray(new Class[selectFieldTypes.size()]);
277: }
278:
279: /**
280: * Visit the select and Groupby clause
281: * @param qd is the definition of the query
282: */
283: public void visit(JDOQueryDefinitionImpl qd) throws SpeedoException {
284: try {
285: _visit(qd);
286: } catch (ExpressionException e) {
287: throw new SpeedoException(e);
288: } catch (MedorException e) {
289: throw new SpeedoException(e);
290: }
291: }
292:
293: /**
294: * Visit the select and Groupby clause
295: * @param qd is the definition of the query
296: */
297: private void _visit(JDOQueryDefinitionImpl qd)
298: throws SpeedoException, MedorException, ExpressionException {
299: String select = qd.getResult();
300: String groupby = qd.getGrouping();
301: boolean withPrefetch = qd.withPrefetch();
302: if (select == null || select.length() == 0) {
303: select = "this";
304: }
305: parseSelect(select);
306: parseGroupBy(groupby);
307:
308: //Prefetching or not
309: if (!hasAggregate //not aggregation
310: && withPrefetch //Prefetching asked
311: && selectfields.size() == 1 //only one part in the select
312: ) {
313: addPrefetchField(qd);
314: }
315: if (hasAggregate) {
316: top = addAggregateNode();
317: }
318: top.setDistinct(distinct);
319: projectFields();
320: if (qec != null) {
321: qec.query = top;
322: }
323: }
324:
325: /**
326: * Add the prefetch field to the current query tree
327: * @param qd is the definition of the query
328: */
329: private void addPrefetchField(JDOQueryDefinitionImpl qd)
330: throws MedorException {
331: Field f = ((PropagatedField) selectfields.get(0))
332: .getOriginFields()[0];
333: if (f instanceof PNameField) {
334: PNameField pnf = (PNameField) f;
335: if (pnf.isClassPName() && !pnf.isInGenClass()) {
336: //Add prefeched fields
337: ClassExtent ce = (ClassExtent) pnf.getQueryTree();
338: String prefetchedClassName = ce.getJormName();
339: HomeItf sh = (HomeItf) mapper
340: .lookup(prefetchedClassName);
341: if (sh.getPrefetchOnQuery()) {
342: qec.pcm = sh;
343: JormQueryTreeHelper.addPrefetchFields(ce, qt, top,
344: qd.getIncludeSubClasses());
345: qec.pnIndex = top.getTupleStructure().getSize() + 1;
346: }
347: }//else the selected field is a class identifier
348: } //else the selected field is not an identifier or a reference
349: }
350:
351: /**
352: * @return an aggregate node for aggregate operators. the returned node must
353: * become the root the query tree.
354: */
355: private Nest addAggregateNode() throws SpeedoException,
356: MedorException {
357: Map old2newFields = new HashMap();
358: //project used fields on the QueryNode #top
359: for (int i = 0; i < usedFields.size(); i++) {
360: QueryTreeField qtf = (QueryTreeField) usedFields.get(i);
361: Field newf = top.addPropagatedField(qtf.getName(), qtf
362: .getType(), new QueryTreeField[] { qtf });
363: old2newFields.put(qtf, newf);
364: }
365: //replace the use of old fields in 'grouped' by the new projected
366: replaceFieldsInList(groupedFields, old2newFields);
367: //replace the use of old fields in 'groupby' by the new projected
368: replaceFieldsInList(groupByFields, old2newFields);
369: //replace the use of old fields in 'selected' by the new projected
370: replaceFieldsInList(selectfields, old2newFields);
371: return new Nest((QueryTreeField[]) groupedFields
372: .toArray(new QueryTreeField[groupedFields.size()]),
373: "grouped_fields", (QueryTreeField[]) groupByFields
374: .toArray(new QueryTreeField[groupByFields
375: .size()]), "aggregate_node");
376: //The nest becomes the top node, selected field will add on it
377: }
378:
379: /**
380: * Projects the fields defined in the select clause on the top node.
381: */
382: private void projectFields() throws SpeedoException,
383: MedorException, ExpressionException {
384: //project field on the #top query node
385: for (int i = 0; i < selectfields.size(); i++) {
386: Object o = selectfields.get(i);
387: Field fieldOnTop = null;
388: String fieldName = (String) aliases.get(i);
389: if (o instanceof Expression) {
390: if (fieldName == null || fieldName.length() == 0) {
391: fieldName = "field_" + i;
392: }
393: Expression e = (Expression) o;
394: e.compileExpression();
395: fieldOnTop = top.addCalculatedField(fieldName, e
396: .getType(), e);
397: } else if (o instanceof Field) {
398: Field f = (Field) o;
399: if (fieldName == null || fieldName.length() == 0) {
400: fieldName = f.getName();
401: }
402: if (!groupByFields.contains(f) || (top instanceof Nest)) {
403: fieldOnTop = top
404: .addPropagatedField(
405: fieldName,
406: f.getType(),
407: new QueryTreeField[] { (QueryTreeField) f });
408: } else {
409: String fn = top.getName();
410: if (fn == null || fn.length() == 0) {
411: fn = "";
412: } else {
413: fn += ".";
414: }
415: fn += f.getName();
416: fieldOnTop = top.getTupleStructure().getField(fn);
417: }
418: }
419: //Compute the field type
420: selectFieldTypes.add(getFieldClass(fieldOnTop));
421: }
422: }
423:
424: /**
425: * Computes the type of a field.
426: */
427: private Class getFieldClass(Field field) throws SpeedoException {
428: PType ptype = field.getType();
429: String className = null;
430: switch (ptype.getTypeCode()) {
431: case PType.TYPECODE_BIGDECIMAL:
432: return BigDecimal.class;
433: case PType.TYPECODE_BIGINTEGER:
434: return BigInteger.class;
435: case PType.TYPECODE_BOOLEAN:
436: case PType.TYPECODE_OBJBOOLEAN:
437: return Boolean.class;
438: case PType.TYPECODE_BYTE:
439: case PType.TYPECODE_OBJBYTE:
440: return Byte.class;
441: case PType.TYPECODE_BYTEARRAY:
442: return Byte[].class;
443: case PType.TYPECODE_CHAR:
444: case PType.TYPECODE_OBJCHAR:
445: return Character.class;
446: case PType.TYPECODE_CHARARRAY:
447: return Character[].class;
448: case PType.TYPECODE_DATE:
449: return Date.class;
450: case PType.TYPECODE_DOUBLE:
451: case PType.TYPECODE_OBJDOUBLE:
452: return Double.class;
453: case PType.TYPECODE_FLOAT:
454: case PType.TYPECODE_OBJFLOAT:
455: return Float.class;
456: case PType.TYPECODE_INT:
457: case PType.TYPECODE_OBJINT:
458: return Integer.class;
459: case PType.TYPECODE_LONG:
460: case PType.TYPECODE_OBJLONG:
461: return Long.class;
462: case PType.TYPECODE_SERIALIZED:
463: return Serializable.class;
464: case PType.TYPECODE_SHORT:
465: case PType.TYPECODE_OBJSHORT:
466: return Integer.class;
467: case PType.TYPECODE_STRING:
468: return String.class;
469:
470: case PType.TYPECODE_REFERENCE:
471: if (field instanceof PropagatedField) {
472: Field f = ((PropagatedField) field).getOriginFields()[0];
473: if (f instanceof PNameField) {
474: PNameField pnf = (PNameField) f;
475: if (pnf.isClassPName()) {
476: if (pnf.isInGenClass()) {
477: //identifier of the genclass
478: className = pnf.getGenClassRef()
479: .getGenClassId();
480: } else {
481: //identifier of a class
482: className = pnf.getMetaObjectClass()
483: .getFQName();
484: }
485: } else {
486: Reference ref = pnf.getReference();
487: if (ref instanceof ClassRef) {
488: //reference to a class
489: className = ((ClassRef) ref).getMOClass()
490: .getFQName();
491: } else if (ref instanceof GenClassRef) {
492: //reference to a genclass
493: className = ((GenClassRef) ref)
494: .getGenClassId();
495: }
496: }
497: }
498: }
499: break;
500: default:
501: className = ptype.getJavaName();
502: }
503: if (className == null) {
504: throw new SpeedoException("Type '" + ptype.getJavaName()
505: + "' not found for projected field:"
506: + field.getName());
507: } else {
508: try {
509: return classloader.loadClass(className);
510: } catch (ClassNotFoundException e) {
511: throw new SpeedoException("Type '" + className
512: + "' not found for projected field:"
513: + field.getName(), e);
514: }
515: }
516: }
517:
518: /**
519: * Replaces the list elements by the associated value from the map.
520: * If an element of the list is an Expression (MEDOR), the use of Fields
521: * in the Expression is replaced.
522: * @param list
523: * is the list containg elements to replace
524: * @param old2newElement
525: * is the table associating the old element (to replace) to the
526: * new element.
527: */
528: private void replaceFieldsInList(ArrayList list, Map old2newElement)
529: throws SpeedoException {
530: for (int i = 0; i < list.size(); i++) {
531: Object old = list.get(i);
532: Object neo = old2newElement.get(old);
533: if (neo != null && old != neo) {
534: list.set(i, neo);
535: } else if (old instanceof Expression) {
536: replaceFieldsInExpression((Expression) old,
537: old2newElement);
538: }
539: }
540: }
541:
542: /**
543: * Replaces field usage in expression.
544: * @param e is the expression to treat
545: * @param old2newElement
546: * is the table associating the old element (to replace) to the
547: * new element.
548: * @throws SpeedoException
549: */
550: private void replaceFieldsInExpression(Expression e,
551: Map old2newElement) throws SpeedoException {
552: if (e instanceof FieldOperand) {
553: FieldOperand fo = (FieldOperand) e;
554: Field old = fo.getField();
555: Field neo = (Field) old2newElement.get(old);
556: if (neo != null && old != neo) {
557: ((FieldOperand) e).setField(neo);
558: }
559: } else if (e instanceof Operator) {
560: Operator operator = (Operator) e;
561: for (int i = 0; i < operator.getOperandNumber(); i++) {
562: replaceFieldsInExpression(operator.getExpression(i),
563: old2newElement);
564: }
565: }
566: }
567:
568: /**
569: * Parses a select clause (with or without 'SELECT' keyword).
570: *
571: * @see #selectfields, #aliases, #groupedFields, #distinct, #hasAggregate .
572: * It
573: * @param select
574: * @throws SpeedoException
575: */
576: private void parseSelect(String select) throws SpeedoException {
577: StringTokenizer st = new StringTokenizer(select, " (,)", true);
578: Stack stack = new Stack();
579: while (st.hasMoreTokens()) {
580: String token = st.nextToken().trim();
581: if (token.length() == 0) {
582: continue;
583: }
584: if (token.equalsIgnoreCase("select")) {
585: continue;
586: }
587: int keywordIdx = isKeyword(token);
588: if (keywordIdx < 0) {
589: parseFieldExpression(token, stack);
590: } else if (keywordIdx < 10) {
591: //Aggregate operator
592: stack.push(new Integer(keywordIdx));
593: } else if (keywordIdx == 10 || keywordIdx == 11) {
594: //distinct
595: if (stack.size() > 0) {
596: //the distinct is used in aggregate operator
597: stack.push(new Boolean(true));
598: } else {
599: distinct = true;
600: }
601: } else if (keywordIdx == 17 || keywordIdx == 18) {
602: //unique
603: unique = true;
604: } else if (keywordIdx == 14) {
605: parseComma(stack);
606: } else if (keywordIdx == 12) {
607: //(
608: stack.push(new Integer(keywordIdx));
609: } else if (keywordIdx == 13) {
610: //)
611: parseRightParenthesis(stack);
612: }
613: }
614: if (stack.size() == 1) {
615: //There is only one element in the select clause
616: selectfields.add(stack.pop());
617: aliases.add(null);
618: }
619: }
620:
621: private void parseFieldExpression(String token, Stack stack)
622: throws SpeedoException {
623: //field expression
624: if (token.equals("*")) {
625: token = "this";
626: }
627: Field f = sqvv.getField(token);
628: if (!usedFields.contains(f)) {
629: usedFields.add(f);
630: }
631: stack.push(f);
632: }
633:
634: private void parseComma(Stack stack) throws SpeedoException {
635: if (stack.size() > 0) {
636: Object o1 = stack.pop();
637: if (stack.size() > 1) {
638: //with an alias
639: Object o2 = stack.pop();
640: if (stack.size() > 2) {
641: //not the end of the selected field
642: stack.push(o2);
643: stack.push(o1);
644: } else if (!(o1 instanceof String)
645: || !(o2 instanceof Expression)) {
646: throw new SpeedoException(
647: "Malformed selected field "
648: + (selectfields.size() + 1));
649: } else {
650: selectfields.add(o2);
651: aliases.add(o1);
652: }
653: } else {
654: //without an alias
655: selectfields.add(o1);
656: aliases.add(null);
657: }
658: } else {
659: throw new SpeedoException(
660: "Empty definition of the selected field "
661: + (selectfields.size() + 1));
662: }
663: }
664:
665: private void parseRightParenthesis(Stack stack)
666: throws SpeedoException {
667: ArrayList operands = new ArrayList();
668: boolean isOperand = true;
669: Integer functionIdx = null;
670: //pop all operand until the '(' and the function identifier
671: // (Integer)
672: boolean distinctOnOperand = false;
673: do {
674: Object o = stack.pop();
675: isOperand = !new Integer(12).equals(o);
676: if (isOperand) {
677: //register the operand
678: operands.add(o);
679: } else {
680: if (stack.peek() instanceof Boolean) {
681: distinctOnOperand = ((Boolean) stack.pop())
682: .booleanValue();
683: }
684: // fetch the function identifier
685: functionIdx = (Integer) stack.pop();
686: }
687: } while (isOperand);
688: Expression e;
689: //Verify the operand number
690: if (functionIdx.intValue() > 0 && functionIdx.intValue() < 10) {
691: if (operands.size() != 1) {
692: throw new SpeedoException(
693: "Bad number of operand for the function "
694: + getFunctionAsString(functionIdx
695: .intValue())
696: + ", expected 1 operand, found "
697: + operands.size());
698: }
699: }
700: //instanciate the medor Operator corresponding to the function
701: Object operand = operands.get(0);
702: if (operand instanceof Expression) {
703: e = (Expression) operand;
704: } else if (operand instanceof Field) {
705: e = new BasicFieldOperand((Field) operand);
706: } else {
707: throw new SpeedoException("Unexpect operand: " + operand);
708: }
709: getFieldsFromExpression(e, groupedFields);
710: hasAggregate = true;
711: switch (functionIdx.intValue() / 2) {
712: case 0:
713: e = new Count(e, distinctOnOperand);
714: break;
715: case 1:
716: e = new Sum(e, distinctOnOperand);
717: break;
718: case 2:
719: e = new Min(e, distinctOnOperand);
720: break;
721: case 3:
722: e = new Max(e, distinctOnOperand);
723: break;
724: case 4:
725: e = new Avg(e, distinctOnOperand);
726: break;
727: default:
728: throw new SpeedoException("Unknown function identifier: "
729: + functionIdx.intValue());
730: }
731: stack.push(e);
732: }
733:
734: /**
735: * Parses a groupby clause (with or without 'GROUP BY' keyword).
736: *
737: * @see #selectfields, #aliases, #groupedFields, #distinct, #hasAggregate .
738: * It
739: * @param select
740: * @throws SpeedoException
741: */
742: private void parseGroupBy(String groupby) throws SpeedoException {
743: if (groupby == null || groupby.length() == 0) {
744: return;
745: }
746: StringTokenizer st = new StringTokenizer(groupby, ",", false);
747: while (st.hasMoreTokens()) {
748: groupByFields.add(sqvv.getField(st.nextToken().trim()));
749: }
750: for (int i = 0; i < groupByFields.size(); i++) {
751: Field f = (Field) groupByFields.get(i);
752: if (!usedFields.contains(f)) {
753: usedFields.add(f);
754: }
755: }
756: }
757:
758: /**
759: * Parses an expression and register the list of Fiefd used in the
760: * expression.
761: *
762: * @param e
763: * is the expression to parse
764: * @param result
765: * is the list to fill with Field used in the expression
766: */
767: private void getFieldsFromExpression(Expression e, List result) {
768: if (e instanceof FieldOperand) {
769: Field f = ((FieldOperand) e).getField();
770: if (!result.contains(f)) {
771: result.add(f);
772: }
773: } else if (e instanceof Operator) {
774: Operator operator = (Operator) e;
775: for (int i = 0; i < operator.getOperandNumber(); i++) {
776: getFieldsFromExpression(operator.getExpression(i),
777: result);
778: }
779: }
780: }
781:
782: private int isKeyword(String str) {
783: if (str == null || str.length() == 0) {
784: return -1;
785: }
786: for (int i = 0; i < keywords.length; i++) {
787: if (keywords[i].equals(str)) {
788: return i;
789: }
790: }
791: return -1;
792: }
793: }
|