/*
* This file is part of the GeoLatte project.
*
* GeoLatte 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 3 of the License, or
* (at your option) any later version.
*
* GeoLatte 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 GeoLatte. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2010 - 2010 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.common.cql.hibernate;
import org.geolatte.common.reflection.EntityClassReader;
import org.geolatte.common.cql.AbstractBuilder;
import org.geolatte.common.cql.node.*;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import java.util.*;
/**
* <p>
* Treewalker that builds a Hibernate Criteria based on a given CQL AST. Use as follows:
* <pre>
* {@code
* HibernateCriteriaBuilder builder = new HibernateCriteriaBuilder(clazz); // builder for the given class
* tree.apply(builder); // with tree, the root element of the AST as returned by the parser.
* }
* </pre>
*
* </p>
* <p>
* <i>Creation-Date</i>: 31-May-2010<br>
* <i>Creation-Time</i>: 09:47:51<br>
* </p>
*
* @author Bert Vanhooff
* @author <a href="http://www.qmino.com">Qmino bvba</a>
* @since SDK1.5
*/
class HibernateCriteriaBuilder extends AbstractBuilder {
// The criteria that is built incrementally by walking the tree
private DetachedCriteria criteria;
EntityClassReader reader;
// A map of all translated nodes as they are visited depth first.
private HashMap<Node, Criterion> translatedExpressions = new HashMap<Node, Criterion>();
public HibernateCriteriaBuilder(Class clazz) {
criteria = DetachedCriteria.forClass(clazz);
reader = EntityClassReader.getClassReaderFor(clazz);
}
public DetachedCriteria getCriteria() {
return criteria;
}
@Override
public void caseStart(Start node) {
node.getPExpr().apply(this);
criteria.add(translatedExpressions.get(node.getPExpr()));
}
@Override
public void outAAndExpr(AAndExpr node) {
translatedExpressions.put(node, Restrictions.and(translatedExpressions.get(node.getLeft()), translatedExpressions.get(node.getRight())));
}
@Override
public void outAOrExpr(AOrExpr node) {
translatedExpressions.put(node, Restrictions.or(translatedExpressions.get(node.getLeft()), translatedExpressions.get(node.getRight())));
}
@Override
public void outANotExpr(ANotExpr node) {
translatedExpressions.put(node, Restrictions.not(translatedExpressions.get(node.getExpr())));
}
@Override
public void outAGtExpr(AGtExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.gt(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outAGteExpr(AGteExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.ge(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outALtExpr(ALtExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.lt(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outALteExpr(ALteExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.le(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outAEqExpr(AEqExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.eq(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outANeqExpr(ANeqExpr node) {
String propertyAlias = createAlias(node.getLeft());
translatedExpressions.put(node, Restrictions.ne(propertyAlias, reader.parseAsPropertyType(translatedLiterals.get(node.getRight()).toString(), getPropertyPath(node.getLeft()))));
}
@Override
public void outALikeExpr(ALikeExpr node) {
String propertyAlias = createAlias(node.getLeft());
EscapingLikeExpression likeExpression = new EscapingLikeExpression(propertyAlias, translatedLiterals.get(node.getRight()).toString());
translatedExpressions.put(node, likeExpression);
}
@Override
public void outANotLikeExpr(ANotLikeExpr node) {
String propertyAlias = createAlias(node.getLeft());
EscapingLikeExpression likeExpression = new EscapingLikeExpression(propertyAlias, translatedLiterals.get(node.getRight()).toString());
translatedExpressions.put(node, Restrictions.not(likeExpression));
}
@Override
public void outAIlikeExpr(AIlikeExpr node) {
String propertyAlias = createAlias(node.getLeft());
EscapingLikeExpression likeExpression = new EscapingLikeExpression(propertyAlias, translatedLiterals.get(node.getRight()).toString(), true);
translatedExpressions.put(node, likeExpression);
}
@Override
public void outANotIlikeExpr(ANotIlikeExpr node) {
String propertyAlias = createAlias(node.getLeft());
EscapingLikeExpression likeExpression = new EscapingLikeExpression(propertyAlias, translatedLiterals.get(node.getRight()).toString(), true);
translatedExpressions.put(node, Restrictions.not(likeExpression));
}
@Override
public void outAIsNullExpr(AIsNullExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, Restrictions.isNull(propertyAlias));
}
@Override
public void outAIsNotNullExpr(AIsNotNullExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, Restrictions.isNotNull(propertyAlias));
}
@Override
public void outAExistsExpr(AExistsExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, new PropertyExistsCriterion(propertyAlias) );
}
@Override
public void outADoesNotExistExpr(ADoesNotExistExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, new PropertyDoesNotExistCriterion(propertyAlias) );
}
@Override
public void outABeforeExpr(ABeforeExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, Restrictions.lt(propertyAlias, parseDate(node.getDateTime().toString().trim())));
}
@Override
public void outAAfterExpr(AAfterExpr node) {
String propertyAlias = createAlias(node.getAttr());
translatedExpressions.put(node, Restrictions.gt(propertyAlias, parseDate(node.getDateTime().toString().trim())));
}
@Override
public void outADuringExpr(ADuringExpr node) {
PTimespanLiteral timespan = node.getTimeSpan();
Criterion greaterThan;
Criterion lowerThan;
if (timespan instanceof AFromToTimespanLiteral ) {
AFromToTimespanLiteral fromToTimespan = (AFromToTimespanLiteral)timespan;
greaterThan = Restrictions.gt(node.getAttr().toString().trim(), parseDate(fromToTimespan.getFrom().getText().trim()));
lowerThan = Restrictions.lt(node.getAttr().toString().trim(), parseDate(fromToTimespan.getTo().getText().trim()));
}
else if (timespan instanceof AFromDurationTimespanLiteral) {
AFromDurationTimespanLiteral fromDurationTimespan = (AFromDurationTimespanLiteral)timespan;
Date fromDate = parseDate(fromDurationTimespan.getFrom().getText().trim());
Duration duration = (Duration)translatedLiterals.get(fromDurationTimespan.getDuration());
// Calculate to 'to' date
Calendar calendar = Calendar.getInstance();
calendar.setTime(fromDate);
calendar.add(Calendar.YEAR, duration.getYears());
calendar.add(Calendar.MONTH, duration.getMonths());
calendar.add(Calendar.DATE, duration.getDays());
calendar.add(Calendar.HOUR, duration.getHours());
calendar.add(Calendar.MINUTE, duration.getMinutes());
calendar.add(Calendar.SECOND, duration.getSeconds());
Date toDate = calendar.getTime();
greaterThan = Restrictions.gt(node.getAttr().toString().trim(), fromDate);
lowerThan = Restrictions.lt(node.getAttr().toString().trim(), toDate);
} else { // if (timespan instanceof ADurationToTimespanLiteral)
greaterThan = null;
lowerThan = null;
}
Criterion combined = Restrictions.and(greaterThan, lowerThan);
translatedExpressions.put(node, combined);
}
// List of property paths for which aliasses are created.
Map<String, String> createdAliasses = new HashMap<String, String>();
/**
* Creates an alias for the property's parent if necessary (the property is compound)
* E.g. for a property path "address.street.number", an alias is created for "address.street", which can be used to
* access the number property.
*
* @param attr The attribute/property node to create an alias for.
* @return The aliased Property name
*/
private String createAlias(PAttr attr) {
// This is a simple property -> should not create an alias
if (attr instanceof AIdAttr)
return ((AIdAttr) attr).getIdentifier().getText().trim();
// In the other case, we have a compound property -> must create an alias (or reuse an existing one) and return the alias name to the property path
// Loop through all parts and create aliasses along the way.
List<String> propertyParts = getPropertyParts(attr);
String currentPropertyPath = ""; // the current property path
String currentAlias = "";
for (int i = 0; i < propertyParts.size() - 1; i++) { // last part is the final property itself.. must not create an alias for that one
String currentPropertyPart = propertyParts.get(i);
currentPropertyPath += currentPropertyPart;
if (createdAliasses.containsKey(currentPropertyPath)) // already have an alias
currentAlias = createdAliasses.get(currentPropertyPath);
else {
String newAlias = currentAlias + currentPropertyPart + "01";
criteria = criteria.createAlias((currentAlias.length() == 0 ? "" :(currentAlias + ".")) + currentPropertyPart, newAlias);
createdAliasses.put(currentPropertyPath, newAlias);
currentAlias = newAlias;
}
currentPropertyPath += ".";
}
String lastPropertyPart = propertyParts.get(propertyParts.size()-1); // Last (missing) part of the property path.
return currentAlias.length() == 0 ? lastPropertyPart : currentAlias + "." + lastPropertyPart;
}
}