001: /**
002: * Speedo: an implementation of JDO compliant personality on top of JORM generic
003: * I/O sub-system.
004: * Copyright (C) 2001-2004 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: * Contact: speedo@objectweb.org
023: *
024: * Authors: S. Chassande-Barrioz
025: *
026: */package org.objectweb.speedo.query.jdo.parser;
027:
028: import java.util.ArrayList;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Stack;
034:
035: import javax.jdo.JDOUserException;
036:
037: import org.objectweb.jorm.metainfo.api.Manager;
038: import org.objectweb.medor.api.Field;
039: import org.objectweb.medor.api.MedorException;
040: import org.objectweb.medor.query.api.QueryTree;
041: import org.objectweb.medor.query.api.QueryTreeField;
042: import org.objectweb.medor.query.jorm.lib.QueryBuilder;
043: import org.objectweb.speedo.api.SpeedoException;
044: import org.objectweb.util.monolog.api.BasicLevel;
045: import org.objectweb.util.monolog.api.Logger;
046:
047: /**
048: * @author S.Chassande-Barrioz
049: */
050: public class SpeedoQLVariableVisitor extends SpeedoQLAbstractVisitor {
051:
052: private HashSet testcontains = new HashSet();
053: private HashSet isEmptys = new HashSet();
054:
055: /**
056: * qt is the built QueryTree from the current object.
057: */
058: private QueryTree qt = null;
059:
060: /**
061: * speedoql is the result of the filter parsing
062: */
063: private SimpleNode speedoql = null;
064:
065: private int nbNot = 0;
066:
067: private List orders;
068:
069: /**
070: * field for each defined identifiers of the query.
071: */
072: private HashMap fields = new HashMap();
073:
074: private QueryBuilder qb = new QueryBuilder();
075:
076: public SpeedoQLVariableVisitor(SimpleNode speedoql, Manager jmim,
077: Logger logger, Map hparam, Map hvar, List orders,
078: String classname, boolean includeSubClasses)
079: throws SpeedoException {
080: this .speedoql = speedoql;
081: this .jmiManager = jmim;
082: setLogger(logger);
083: setParams(hparam);
084: setVars(hvar);
085: setOrders(orders);
086: setCurrentClass(classname);
087: this .includeSubClasses = includeSubClasses;
088: startVisiting();
089: }
090:
091: public Map getFields() {
092: return fields;
093: }
094:
095: public QueryBuilder getQueryBuilder() {
096: return qb;
097: }
098:
099: public QueryTree getQueryTree() {
100: return qb.getQueryTree();
101: }
102:
103: public void setOrders(List orders) {
104: this .orders = orders;
105: }
106:
107: /**
108: * The visit of the tree starts here.
109: * Please setup current class, params and vars hashtable before calling
110: * this method.
111: */
112: public Map startVisiting() throws SpeedoException {
113: debug = logger != null && logger.isLoggable(BasicLevel.DEBUG);
114: nbNot = 0;
115:
116: //Add the initial class as the first IdValue. The key is 'this'
117: if (curClass != null) {
118: logger.log(BasicLevel.DEBUG, "create a new IdValue object "
119: + "with the current class (" + curClass + ")");
120: IdValue iv = new IdValue(new String[] { curClass }, EXTENT);
121: iv.alias = "this";
122: ids.put(iv.alias, iv);
123: }
124: try {
125: //visit the tree
126: visit(speedoql);
127: } catch (Exception e) {
128: throw new SpeedoException(
129: "Error during the parsing of JDOQL:", e);
130: }
131: if (orders != null) {
132: for (int i = 0; i < orders.size(); i++) {
133: String fieldName = (String) orders.get(i);
134: int idx = fieldName.indexOf(' ');
135: if (idx != -1) {
136: fieldName = fieldName.substring(0, idx).trim();
137: }
138: visitPath(null, fieldName);
139: }
140: }
141: ArrayList toTreat = new ArrayList(ids.values());
142: while (!toTreat.isEmpty()) {
143: treatIdValue((IdValue) toTreat.get(0), toTreat);
144: }
145: return fields;
146: }
147:
148: private void treatIdValue(IdValue iv, List toTreat)
149: throws SpeedoException {
150: if (iv.nameType != EXTENT) {
151: IdValue dependency = (IdValue) ids.get(iv.name[0]);
152: if (dependency == null) {
153: throw new SpeedoException("Dependency unresolved: "
154: + iv.name[0]);
155: }
156: if (toTreat.contains(dependency)) {
157: treatIdValue(dependency, toTreat);
158: }
159: }
160: toTreat.remove(iv);
161:
162: QueryBuilder theqb = qb;
163: if (iv.nameType == MEMBEROF) {
164: //a.b.cs.contains(x) ==> a.b.PNAME IN cs.id
165: String rest = mergePath(iv.name, 1, iv.name.length - 1);
166: theqb = new QueryBuilder(qb);
167: try {
168: theqb.define("", qb.navigate(iv.name[0]));
169: } catch (MedorException e) {
170: throw new SpeedoException(e);
171: }
172: }
173:
174: try {
175: String n = iv.alias + "." + Field.PNAMENAME;
176: fields.put(n, theqb.project(iv.alias, define(theqb,
177: iv.alias, iv.alias)));
178: for (int i = 0; i < iv.getDeclaredPathLength(); i++) {
179: String path = iv.getMergedPath(i);
180: if (!testcontains.contains(path)
181: && !isEmptys.contains(path)) {
182: fields.put(path, theqb.project(path, define(theqb,
183: path, null)));
184: }
185: }
186: } catch (Exception e) {
187: throw new SpeedoException(
188: "Error during the parsing of JDOQL:", e);
189: }
190: }
191:
192: public QueryTreeField getField(String path) throws SpeedoException {
193: String[] splitted = splitPath(path);
194: if (params != null && params.containsKey(splitted[0])) {
195: if (splitted.length > 1) {
196: //not managed
197: }
198: } else if (vars != null && vars.containsKey(splitted[0])) {
199: } else { // this
200: if (!splitted[0].equals("this")) {
201: path = "this." + path;
202: String[] newsplitted = new String[splitted.length + 1];
203: newsplitted[0] = "this";
204: System.arraycopy(splitted, 0, newsplitted, 1,
205: splitted.length);
206: splitted = newsplitted;
207: }
208: }
209: QueryTreeField qtf = (QueryTreeField) fields.get(path);
210: if (qtf == null) {
211: try {
212: qtf = qb.project(define(qb, path, null));
213: } catch (Exception e) {
214: throw new SpeedoException(e);
215: }
216: fields.put(path, qtf);
217: }
218: return qtf;
219: }
220:
221: /**
222: * ********************* VISITOR METHODS ***********************************
223: */
224:
225: public Object visit(ASTSpeedoPrimary node, Object data) {
226: visit((SimpleNode) node, data);
227: return null;
228: }
229:
230: public Object visit(ASTSpeedoQL node, Object data) {
231: visit((SimpleNode) node, data);
232: return null;
233: }
234:
235: public Object visit(ASTPrimary node, Object data) {
236: visit((SimpleNode) node, data);
237: return null;
238: }
239:
240: public Object visit(ASTRelationalExpression node, Object data) {
241: visit((SimpleNode) node, data);
242: return null;
243: }
244:
245: public Object visit(ASTAdditiveExpression node, Object data) {
246:
247: visit((SimpleNode) node, data);
248: return null;
249: }
250:
251: public Object visit(ASTUnaryExpression node, Object data) {
252: boolean hasNot = node.ops.size() > 0
253: && ((Integer) node.ops.get(0)).intValue() == SpeedoQLConstants.NOT;
254: if (hasNot) {
255: nbNot++;
256: if (debug) {
257: logger
258: .log(BasicLevel.DEBUG, "remember a Not: "
259: + nbNot);
260: }
261: }
262: visit((SimpleNode) node, data);
263: if (hasNot && nbNot > 0) {
264: nbNot--;
265: if (debug) {
266: logger.log(BasicLevel.DEBUG, "forget a Not: " + nbNot);
267: }
268: }
269: //logger.log(BasicLevel.DEBUG, "End of Visit UnaryExpression");
270: return null;
271: }
272:
273: public Object visit(ASTCastExpression node, Object data) {
274: return null;
275: }
276:
277: public Object visit(ASTArgumentList node, Object data) {
278: visit((SimpleNode) node, data);
279: return null;
280: }
281:
282: public Object visit(ASTLiteral node, Object data) {
283: visit((SimpleNode) node, data);
284: return null;
285: }
286:
287: public Object visit(ASTType node, Object data) {
288: //voir cast , donc on s'en fout
289: visit((SimpleNode) node, data);
290: return null;
291: }
292:
293: public Object visit(ASTQualifiedName node, Object data) {
294: Stack stack = (Stack) data;
295: String name = (String) node.value;
296: if (debug) {
297: logger.log(BasicLevel.DEBUG,
298: "variable visitor: qualifiedname=<" + name + ">");
299: }
300: visitPath(stack, name);
301: if (debug) {
302: logger.log(BasicLevel.DEBUG,
303: "variable visitor: qualifiedname=<" + name
304: + ">-end");
305: }
306: return null;
307: }
308:
309: private void visitPath(Stack stack, String path) {
310: String[] splitted = splitPath(path);
311: if (params != null && params.containsKey(splitted[0])) {
312: visitParameterUse(stack, splitted);
313: } else if (vars != null && vars.containsKey(splitted[0])) {
314: visitVariableUse(stack, path, splitted);
315: } else { // this
316: if (!splitted[0].equals("this")) {
317: path = "this." + path;
318: String[] newsplitted = new String[splitted.length + 1];
319: newsplitted[0] = "this";
320: System.arraycopy(splitted, 0, newsplitted, 1,
321: splitted.length);
322: splitted = newsplitted;
323: }
324: visitThisUse(stack, path, splitted);
325: }
326: }
327:
328: /**
329: * Manages a path using a parameter
330: * @param stack
331: * @param splitted
332: */
333: private void visitParameterUse(Stack stack, String[] splitted) {
334: if (stack.size() > 0 && stack.peek() instanceof String) {
335: String setpath = (String) stack.pop();
336: if (debug) {
337: logger.log(BasicLevel.DEBUG, "The parameter '"
338: + splitted[0]
339: + "' is used in a contains expression: "
340: + setpath + ".contains(" + splitted[0] + ")");
341: }
342: } else if (debug) {
343: logger.log(BasicLevel.DEBUG, "Use of the parameter "
344: + splitted[0]);
345: }
346: }
347:
348: /**
349: * Manages a path using a variable
350: * @param stack
351: * @param path
352: * @param splitted
353: */
354: private void visitVariableUse(Stack stack, String path,
355: String[] splitted) {
356: if (debug) {
357: logger.log(BasicLevel.DEBUG, "Use of the variable "
358: + splitted[0] + " : " + path);
359: }
360: //fetch or create an IdValue
361: IdValue iv = (IdValue) ids.get(splitted[0]);
362: if (iv == null) {
363: if (debug) {
364: logger
365: .log(BasicLevel.DEBUG,
366: "Create a new IdValue for the variable "
367: + splitted[0]
368: + " for the path " + path);
369: }
370: iv = new IdValue();
371: // The definition of the variable is not yet knwon but by default
372: // the variable is used a simple extent (JORM_NAME)
373: iv.nameType = EXTENT;
374: iv.alias = splitted[0];
375: ids.put(iv.alias, iv);
376: } else {
377: if (iv.alias != null && !iv.alias.equals(splitted[0])) {
378: logger.log(BasicLevel.WARN,
379: "Redefine a different alias for a variable, old="
380: + iv.alias + ", new=" + splitted[0]);
381: }
382: iv.alias = splitted[0];
383: if (debug) {
384: logger.log(BasicLevel.DEBUG,
385: "Use the IdValue of the variable "
386: + splitted[0] + " by the path " + path);
387: }
388: }
389: if (stack.size() > 0 && stack.peek() instanceof String) {
390: // Define the IdValue from previous (contains)
391: String definition = (String) stack.pop();
392: if (debug) {
393: logger.log(BasicLevel.DEBUG, "Define the variable "
394: + splitted[0] + " to " + definition);
395: }
396: if (iv.name != null) {
397: throw new JDOUserException("Variable '" + splitted[0]
398: + "' defined several times : \n\t" + iv.name
399: + "\n\t" + definition);
400: }
401: iv.nameType = ((nbNot % 2) == 1 ? MEMBEROF : NAVIGATION);
402: iv.name = splitPath(definition);
403: testcontains.add(definition);
404: }
405: visitThisOrVarUseEnd(stack, path, splitted);
406: }
407:
408: private void visitThisUse(Stack stack, String path,
409: String[] splitted) {
410: if (debug) {
411: logger.log(BasicLevel.DEBUG, "Use of this " + splitted[0]
412: + " : " + path);
413: }
414: IdValue iv = (IdValue) ids.get(splitted[0]);
415: if (stack != null && stack.size() > 0
416: && stack.peek() instanceof String) {
417: // Define the IdValue from previous (contains)
418: String collectionpath = (String) stack.pop();
419: if (debug) {
420: logger.log(BasicLevel.DEBUG,
421: "test collection membering " + path + " IN "
422: + collectionpath);
423: }
424: testcontains.add(collectionpath);
425: }
426: visitThisOrVarUseEnd(stack, path, splitted);
427:
428: }
429:
430: /**
431: * manage a path starting with 'this' or a variable
432: * @param stack
433: * @param path is the path using 'this' or a varible
434: * @param splitted is the splitted path. The first String must be "this" or
435: * a varible
436: */
437: private void visitThisOrVarUseEnd(Stack stack, String path,
438: String[] splitted) {
439: int operatorId = isMethodOperator(splitted[splitted.length - 1]);
440: if (operatorId != -1) {
441: //the last element is an operator
442: String begin = buildStringwithout(splitted,
443: splitted.length - 1, ".");
444: ((IdValue) ids.get(splitted[0])).addPath(begin);
445: switch (operatorId) {
446: case CONTAINS_OPERATOR:
447: if (debug) {
448: logger.log(BasicLevel.DEBUG,
449: "contains case, path: " + begin);
450: }
451: //stock in stack a mean to find the contains stuff...
452: if (stack != null) {
453: stack.push(begin);
454: }
455: break;
456: case IS_EMPTY_OPERATOR:
457: if (debug) {
458: logger.log(BasicLevel.DEBUG, "isEmpty case, path: "
459: + begin);
460: }
461: isEmptys.add(begin);
462: break;
463: }
464: return;
465: } else {
466: if (debug) {
467: logger.log(BasicLevel.DEBUG,
468: "Navigation case without operator, path:"
469: + path);
470: }
471: ((IdValue) ids.get(splitted[0])).addPath(path);
472: }
473: }
474:
475: }
|