/**
* Speedo: an implementation of JDO compliant personality on top of JORM generic
* I/O sub-system.
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*
* Contact: speedo@objectweb.org
*
* Authors: S. Chassande-Barrioz
*
*/
package org.objectweb.speedo.query.jdo.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.jdo.JDOUserException;
import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.medor.api.Field;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.query.api.QueryTreeField;
import org.objectweb.medor.query.jorm.lib.QueryBuilder;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
/**
* @author S.Chassande-Barrioz
*/
public class SpeedoQLVariableVisitor extends SpeedoQLAbstractVisitor {
private HashSet testcontains = new HashSet();
private HashSet isEmptys = new HashSet();
/**
* qt is the built QueryTree from the current object.
*/
private QueryTree qt = null;
/**
* speedoql is the result of the filter parsing
*/
private SimpleNode speedoql = null;
private int nbNot = 0;
private List orders;
/**
* field for each defined identifiers of the query.
*/
private HashMap fields = new HashMap();
private QueryBuilder qb = new QueryBuilder();
public SpeedoQLVariableVisitor(SimpleNode speedoql,
Manager jmim,
Logger logger,
Map hparam,
Map hvar,
List orders,
String classname,
boolean includeSubClasses
) throws SpeedoException {
this.speedoql = speedoql;
this.jmiManager= jmim;
setLogger(logger);
setParams(hparam);
setVars(hvar);
setOrders(orders);
setCurrentClass(classname);
this.includeSubClasses = includeSubClasses;
startVisiting();
}
public Map getFields() {
return fields;
}
public QueryBuilder getQueryBuilder() {
return qb;
}
public QueryTree getQueryTree() {
return qb.getQueryTree();
}
public void setOrders(List orders) {
this.orders = orders;
}
/**
* The visit of the tree starts here.
* Please setup current class, params and vars hashtable before calling
* this method.
*/
public Map startVisiting() throws SpeedoException {
debug = logger != null && logger.isLoggable(BasicLevel.DEBUG);
nbNot = 0;
//Add the initial class as the first IdValue. The key is 'this'
if (curClass != null) {
logger.log(BasicLevel.DEBUG, "create a new IdValue object " +
"with the current class (" + curClass + ")");
IdValue iv = new IdValue(new String[] {curClass}, EXTENT);
iv.alias = "this";
ids.put(iv.alias, iv);
}
try {
//visit the tree
visit(speedoql);
} catch (Exception e) {
throw new SpeedoException("Error during the parsing of JDOQL:", e);
}
if (orders != null) {
for(int i=0; i<orders.size(); i++) {
String fieldName = (String) orders.get(i);
int idx = fieldName.indexOf(' ');
if (idx != -1) {
fieldName = fieldName.substring(0, idx).trim();
}
visitPath(null, fieldName);
}
}
ArrayList toTreat = new ArrayList(ids.values());
while(!toTreat.isEmpty()) {
treatIdValue((IdValue) toTreat.get(0), toTreat);
}
return fields;
}
private void treatIdValue(IdValue iv, List toTreat) throws SpeedoException {
if (iv.nameType != EXTENT) {
IdValue dependency = (IdValue) ids.get(iv.name[0]);
if (dependency == null) {
throw new SpeedoException("Dependency unresolved: " + iv.name[0]);
}
if (toTreat.contains(dependency)) {
treatIdValue(dependency, toTreat);
}
}
toTreat.remove(iv);
QueryBuilder theqb = qb;
if (iv.nameType == MEMBEROF) {
//a.b.cs.contains(x) ==> a.b.PNAME IN cs.id
String rest = mergePath(iv.name, 1, iv.name.length - 1);
theqb = new QueryBuilder(qb);
try {
theqb.define("", qb.navigate(iv.name[0]));
} catch (MedorException e) {
throw new SpeedoException(e);
}
}
try {
String n = iv.alias + "." + Field.PNAMENAME;
fields.put(n, theqb.project(iv.alias, define(theqb, iv.alias, iv.alias)));
for (int i = 0; i < iv.getDeclaredPathLength(); i++) {
String path = iv.getMergedPath(i);
if (!testcontains.contains(path) && !isEmptys.contains(path)) {
fields.put(path, theqb.project(path, define(theqb, path, null)));
}
}
} catch (Exception e) {
throw new SpeedoException("Error during the parsing of JDOQL:", e);
}
}
public QueryTreeField getField(String path) throws SpeedoException {
String[] splitted = splitPath(path);
if (params != null && params.containsKey(splitted[0])) {
if (splitted.length > 1) {
//not managed
}
} else if (vars != null && vars.containsKey(splitted[0])) {
} else { // this
if (!splitted[0].equals("this")) {
path = "this." + path;
String[] newsplitted = new String[splitted.length + 1];
newsplitted[0] = "this";
System.arraycopy(splitted, 0, newsplitted, 1, splitted.length);
splitted = newsplitted;
}
}
QueryTreeField qtf = (QueryTreeField) fields.get(path);
if (qtf == null) {
try {
qtf = qb.project(define(qb, path, null));
} catch (Exception e) {
throw new SpeedoException(e);
}
fields.put(path, qtf);
}
return qtf;
}
/**
* ********************* VISITOR METHODS ***********************************
*/
public Object visit(ASTSpeedoPrimary node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTSpeedoQL node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTPrimary node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTRelationalExpression node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTAdditiveExpression node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTUnaryExpression node, Object data) {
boolean hasNot = node.ops.size() > 0
&& ((Integer) node.ops.get(0)).intValue() == SpeedoQLConstants.NOT;
if (hasNot) {
nbNot ++;
if (debug) {
logger.log(BasicLevel.DEBUG, "remember a Not: "+ nbNot);
}
}
visit((SimpleNode) node, data);
if (hasNot && nbNot> 0) {
nbNot--;
if (debug) {
logger.log(BasicLevel.DEBUG, "forget a Not: "+ nbNot);
}
}
//logger.log(BasicLevel.DEBUG, "End of Visit UnaryExpression");
return null;
}
public Object visit(ASTCastExpression node, Object data) {
return null;
}
public Object visit(ASTArgumentList node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTLiteral node, Object data) {
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTType node, Object data) {
//voir cast , donc on s'en fout
visit((SimpleNode) node, data);
return null;
}
public Object visit(ASTQualifiedName node, Object data) {
Stack stack = (Stack) data;
String name = (String) node.value;
if (debug) {
logger.log(BasicLevel.DEBUG, "variable visitor: qualifiedname=<" + name + ">");
}
visitPath(stack, name);
if (debug) {
logger.log(BasicLevel.DEBUG, "variable visitor: qualifiedname=<" + name + ">-end");
}
return null;
}
private void visitPath(Stack stack, String path) {
String[] splitted = splitPath(path);
if (params != null && params.containsKey(splitted[0])) {
visitParameterUse(stack, splitted);
} else if (vars != null && vars.containsKey(splitted[0])) {
visitVariableUse(stack, path, splitted);
} else { // this
if (!splitted[0].equals("this")) {
path = "this." + path;
String[] newsplitted = new String[splitted.length + 1];
newsplitted[0] = "this";
System.arraycopy(splitted, 0, newsplitted, 1, splitted.length);
splitted = newsplitted;
}
visitThisUse(stack, path, splitted);
}
}
/**
* Manages a path using a parameter
* @param stack
* @param splitted
*/
private void visitParameterUse(Stack stack, String[] splitted) {
if (stack.size() > 0 && stack.peek() instanceof String) {
String setpath = (String) stack.pop();
if (debug) {
logger.log(BasicLevel.DEBUG, "The parameter '" + splitted[0]
+ "' is used in a contains expression: "
+ setpath + ".contains(" + splitted[0] + ")");
}
} else if (debug) {
logger.log(BasicLevel.DEBUG, "Use of the parameter " + splitted[0]);
}
}
/**
* Manages a path using a variable
* @param stack
* @param path
* @param splitted
*/
private void visitVariableUse(Stack stack, String path, String[] splitted) {
if (debug) {
logger.log(BasicLevel.DEBUG, "Use of the variable " + splitted[0]
+ " : " + path);
}
//fetch or create an IdValue
IdValue iv = (IdValue) ids.get(splitted[0]);
if (iv == null) {
if (debug) {
logger.log(BasicLevel.DEBUG, "Create a new IdValue for the variable " + splitted[0] + " for the path " + path);
}
iv = new IdValue();
// The definition of the variable is not yet knwon but by default
// the variable is used a simple extent (JORM_NAME)
iv.nameType = EXTENT;
iv.alias = splitted[0];
ids.put(iv.alias, iv);
} else {
if (iv.alias !=null && !iv.alias.equals(splitted[0])) {
logger.log(BasicLevel.WARN,
"Redefine a different alias for a variable, old="
+ iv.alias + ", new=" + splitted[0]);
}
iv.alias = splitted[0];
if (debug) {
logger.log(BasicLevel.DEBUG, "Use the IdValue of the variable " + splitted[0] + " by the path " + path);
}
}
if (stack.size() > 0 && stack.peek() instanceof String) {
// Define the IdValue from previous (contains)
String definition = (String) stack.pop();
if (debug) {
logger.log(BasicLevel.DEBUG, "Define the variable " + splitted[0]
+ " to " + definition);
}
if (iv.name != null) {
throw new JDOUserException("Variable '"
+ splitted[0] + "' defined several times : \n\t"
+ iv.name + "\n\t" + definition);
}
iv.nameType = ((nbNot%2)==1 ? MEMBEROF : NAVIGATION);
iv.name = splitPath(definition);
testcontains.add(definition);
}
visitThisOrVarUseEnd(stack, path, splitted);
}
private void visitThisUse(Stack stack, String path, String[] splitted) {
if (debug) {
logger.log(BasicLevel.DEBUG, "Use of this " + splitted[0]
+ " : " + path);
}
IdValue iv = (IdValue) ids.get(splitted[0]);
if (stack != null && stack.size() > 0 && stack.peek() instanceof String) {
// Define the IdValue from previous (contains)
String collectionpath = (String) stack.pop();
if (debug) {
logger.log(BasicLevel.DEBUG, "test collection membering " + path
+ " IN " + collectionpath);
}
testcontains.add(collectionpath);
}
visitThisOrVarUseEnd(stack, path, splitted);
}
/**
* manage a path starting with 'this' or a variable
* @param stack
* @param path is the path using 'this' or a varible
* @param splitted is the splitted path. The first String must be "this" or
* a varible
*/
private void visitThisOrVarUseEnd(Stack stack, String path, String[] splitted) {
int operatorId = isMethodOperator(splitted[splitted.length - 1]);
if (operatorId != -1) {
//the last element is an operator
String begin = buildStringwithout(splitted, splitted.length-1, ".");
((IdValue) ids.get(splitted[0])).addPath(begin);
switch(operatorId) {
case CONTAINS_OPERATOR:
if (debug) {
logger.log(BasicLevel.DEBUG, "contains case, path: " + begin);
}
//stock in stack a mean to find the contains stuff...
if (stack != null) {
stack.push(begin);
}
break;
case IS_EMPTY_OPERATOR:
if (debug) {
logger.log(BasicLevel.DEBUG, "isEmpty case, path: " + begin);
}
isEmptys.add(begin);
break;
}
return;
} else {
if (debug) {
logger.log(BasicLevel.DEBUG,
"Navigation case without operator, path:" + path);
}
((IdValue) ids.get(splitted[0])).addPath(path);
}
}
}