package com.mysema.rdfbean.sesame;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.annotation.Nullable;
import org.openrdf.model.Literal;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.query.algebra.*;
import org.openrdf.query.algebra.Order;
import org.openrdf.query.algebra.Compare.CompareOp;
import org.openrdf.query.algebra.MathExpr.MathOp;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.JoinExpression;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.types.*;
import com.mysema.rdfbean.model.*;
import com.mysema.rdfbean.query.VarNameIterator;
import com.mysema.rdfbean.xsd.ConverterRegistryImpl;
/**
* @author tiwe
*
*/
public class SesameRDFVisitor implements RDFVisitor<Object, QueryMetadata> {
private static final ValueConstant EMPTY_STRING = new ValueConstant(new LiteralImpl("^$"));
private static final ValueConstant CASE_INSENSITIVE = new ValueConstant(new LiteralImpl("i"));
private static final Map<Operator<?>, CompareOp> COMPARE_OPS = new HashMap<Operator<?>, CompareOp>();
private static final Map<Operator<?>, MathOp> MATH_OPS = new HashMap<Operator<?>, MathOp>();
private static final Map<Operator<?>, String> FUNCTION_OPS = new HashMap<Operator<?>, String>();
static {
SesameFunctions.init();
COMPARE_OPS.put(Ops.EQ, CompareOp.EQ);
COMPARE_OPS.put(Ops.NE, CompareOp.NE);
COMPARE_OPS.put(Ops.LT, CompareOp.LT);
COMPARE_OPS.put(Ops.LOE, CompareOp.LE);
COMPARE_OPS.put(Ops.GT, CompareOp.GT);
COMPARE_OPS.put(Ops.GOE, CompareOp.GE);
MATH_OPS.put(Ops.ADD, MathOp.PLUS);
MATH_OPS.put(Ops.SUB, MathOp.MINUS);
MATH_OPS.put(Ops.MULT, MathOp.MULTIPLY);
MATH_OPS.put(Ops.DIV, MathOp.DIVIDE);
// string
FUNCTION_OPS.put(Ops.TRIM, "functions:trim");
FUNCTION_OPS.put(Ops.UPPER, "functions:upper");
FUNCTION_OPS.put(Ops.LOWER, "functions:lower");
FUNCTION_OPS.put(Ops.CONCAT, "functions:concat");
FUNCTION_OPS.put(Ops.SUBSTR_1ARG, "functions:substring");
FUNCTION_OPS.put(Ops.SUBSTR_2ARGS, "functions:substring2");
FUNCTION_OPS.put(Ops.CHAR_AT, "functions:charAt");
FUNCTION_OPS.put(Ops.STARTS_WITH, "functions:startsWith");
FUNCTION_OPS.put(Ops.ENDS_WITH, "functions:endsWith");
FUNCTION_OPS.put(Ops.STARTS_WITH_IC, "functions:startsWithIc");
FUNCTION_OPS.put(Ops.ENDS_WITH_IC, "functions:endsWithIc");
FUNCTION_OPS.put(Ops.STRING_CONTAINS, "functions:stringContains");
FUNCTION_OPS.put(Ops.STRING_CONTAINS_IC, "functions:stringContainsIc");
FUNCTION_OPS.put(Ops.EQ_IGNORE_CASE, "functions:equalsIgnoreCase");
FUNCTION_OPS.put(Ops.STRING_LENGTH, "functions:stringLength");
FUNCTION_OPS.put(Ops.INDEX_OF, "functions:indexOf");
FUNCTION_OPS.put(Ops.INDEX_OF_2ARGS, "functions:indexOf2");
FUNCTION_OPS.put(Ops.LIKE, "functions:like");
FUNCTION_OPS.put(Ops.LIKE_ESCAPE, "functions:like");
// FUNCTION_OPS.put(Ops.StringOps.SPACE, "functions:space");
FUNCTION_OPS.put(Ops.StringOps.LOCATE, "functions:locate");
FUNCTION_OPS.put(Ops.StringOps.LOCATE2, "functions:locate2");
// math
FUNCTION_OPS.put(Ops.MathOps.CEIL, "functions:ceil");
FUNCTION_OPS.put(Ops.MathOps.FLOOR, "functions:floor");
FUNCTION_OPS.put(Ops.MathOps.SQRT, "functions:sqrt");
FUNCTION_OPS.put(Ops.MathOps.ABS, "functions:abs");
FUNCTION_OPS.put(Ops.MOD, "functions:modulo");
// date / time
FUNCTION_OPS.put(Ops.DateTimeOps.YEAR, "functions:year");
FUNCTION_OPS.put(Ops.DateTimeOps.YEAR_MONTH, "functions:yearMonth");
FUNCTION_OPS.put(Ops.DateTimeOps.MONTH, "functions:month");
FUNCTION_OPS.put(Ops.DateTimeOps.WEEK, "functions:week");
FUNCTION_OPS.put(Ops.DateTimeOps.DAY_OF_WEEK, "functions:dayOfWeek");
FUNCTION_OPS.put(Ops.DateTimeOps.DAY_OF_MONTH, "functions:dayOfMonth");
FUNCTION_OPS.put(Ops.DateTimeOps.DAY_OF_YEAR, "functions:dayOfYear");
FUNCTION_OPS.put(Ops.DateTimeOps.HOUR, "functions:hour");
FUNCTION_OPS.put(Ops.DateTimeOps.MINUTE, "functions:minute");
FUNCTION_OPS.put(Ops.DateTimeOps.SECOND, "functions:second");
FUNCTION_OPS.put(Ops.DateTimeOps.MILLISECOND, "functions:millisecond");
// other
FUNCTION_OPS.put(Ops.COALESCE, "functions:coalesce");
FUNCTION_OPS.put(Ops.NULLIF, "functions:nullif");
}
private final SesameDialect dialect;
private final Map<Path<?>, Var> pathToVar = new HashMap<Path<?>, Var>();
private final Map<ParamExpression<?>, Var> paramToVar = new HashMap<ParamExpression<?>, Var>();
private final Map<Object, Var> constantToVar = new HashMap<Object, Var>();
private final VarNameIterator varNames = new VarNameIterator("__v");
private final VarNameIterator extNames = new VarNameIterator("__e");
private final Stack<Var> graphs = new Stack<Var>();
public SesameRDFVisitor(SesameDialect dialect) {
this.dialect = dialect;
}
private Var toVar(Expression<?> expr, QueryMetadata md) {
return (Var) expr.accept(this, md);
}
private TupleExpr toTuple(Expression<?> expr, QueryMetadata md) {
return (TupleExpr) expr.accept(this, md);
}
@Nullable
private ValueExpr toValue(Expression<?> expr, QueryMetadata md) {
return (ValueExpr) expr.accept(this, md);
}
@SuppressWarnings("unchecked")
@Override
public TupleExpr visit(QueryMetadata md, QueryLanguage<?, ?> queryType) {
List<Constant<UID>> fromUIDs = new ArrayList<Constant<UID>>();
for (JoinExpression join : md.getJoins()) {
fromUIDs.add((Constant<UID>) join.getTarget());
}
// where
TupleExpr tuple;
if (fromUIDs.isEmpty()) {
tuple = toTuple(md.getWhere(), md);
} else if (fromUIDs.size() == 1) {
graphs.push(visit(fromUIDs.get(0), md));
tuple = toTuple(md.getWhere(), md);
graphs.pop();
} else {
QUID g = new QUID("__g");
graphs.push(visit(g, md));
BooleanBuilder b = new BooleanBuilder();
for (Constant<UID> co : fromUIDs) {
b.or(g.eq(co));
}
tuple = filter(toTuple(md.getWhere(), md), b.getValue(), md);
graphs.pop();
}
if (queryType == QueryLanguage.BOOLEAN) {
return tuple;
}
// order
if (!md.getOrderBy().isEmpty()) {
List<OrderElem> orderElements = new ArrayList<OrderElem>();
for (OrderSpecifier<?> os : md.getOrderBy()) {
orderElements.add(new OrderElem(toValue(os.getTarget(), md), os.isAscending()));
}
tuple = new Order(tuple, orderElements);
}
// TODO : group by
// TODO : having
// projection
ProjectionElemList projection = new ProjectionElemList();
List<ProjectionElemList> projectionElements = new ArrayList<ProjectionElemList>();
List<ExtensionElem> extensions = new ArrayList<ExtensionElem>();
if (queryType == QueryLanguage.TUPLE) {
for (Expression<?> expr : md.getProjection()) {
ValueExpr val = toValue(expr, md);
if (val instanceof Var) {
projection.addElement(new ProjectionElem(((Var) val).getName()));
} else {
String extLabel = extNames.next();
projection.addElement(new ProjectionElem(extLabel));
extensions.add(new ExtensionElem(val, extLabel));
}
}
} else {
for (Expression<?> expr : md.getProjection()) {
Stack<Block> blocks = new Stack<Block>();
blocks.addAll((List) md.getProjection());
while (!blocks.isEmpty()) {
Block bl = blocks.pop();
// TODO : shorten
if (bl instanceof PatternBlock) {
PatternBlock pa = (PatternBlock) expr;
ProjectionElemList p = new ProjectionElemList();
ValueExpr subject = toValue(pa.getSubject(), md);
if (subject instanceof Var) {
Var v = (Var) subject;
p.addElement(new ProjectionElem(v.getName(), "subject"));
if (v.getValue() != null) {
extensions.add(new ExtensionElem(subject, v.getName()));
}
} else {
String extLabel = extNames.next();
projection.addElement(new ProjectionElem(extLabel, "subject"));
extensions.add(new ExtensionElem(subject, extLabel));
}
ValueExpr predicate = toValue(pa.getPredicate(), md);
if (predicate instanceof Var) {
Var v = (Var) predicate;
p.addElement(new ProjectionElem(v.getName(), "predicate"));
if (v.getValue() != null) {
extensions.add(new ExtensionElem(predicate, v.getName()));
}
} else {
String extLabel = extNames.next();
projection.addElement(new ProjectionElem(extLabel, "predicate"));
extensions.add(new ExtensionElem(predicate, extLabel));
}
ValueExpr object = toValue(pa.getObject(), md);
if (object instanceof Var) {
Var v = (Var) object;
p.addElement(new ProjectionElem(v.getName(), "object"));
if (v.getValue() != null) {
extensions.add(new ExtensionElem(object, v.getName()));
}
} else {
String extLabel = extNames.next();
projection.addElement(new ProjectionElem(extLabel, "object"));
extensions.add(new ExtensionElem(object, extLabel));
}
projectionElements.add(p);
} else {
blocks.addAll(((GroupBlock) bl).getBlocks());
}
}
}
}
if (!extensions.isEmpty()) {
tuple = new Extension(tuple, extensions);
}
if (!projection.getElements().isEmpty()) {
tuple = new Projection(tuple, projection);
} else if (!projectionElements.isEmpty()) {
tuple = new MultiProjection(tuple, projectionElements);
}
// limit / offset
QueryModifiers modifiers = md.getModifiers();
if (modifiers.isRestricting()) {
Long limit = modifiers.getLimit();
Long offset = modifiers.getOffset();
tuple = new Slice(
tuple,
offset != null ? offset.intValue() : 0,
limit != null ? limit.intValue() : -1);
}
// distinct
if (md.isDistinct()) {
tuple = new Distinct(tuple);
}
return tuple;
}
@Override
public Union visit(UnionBlock expr, QueryMetadata md) {
List<TupleExpr> tuples = new ArrayList<TupleExpr>(expr.getBlocks().size());
for (Block block : expr.getBlocks()) {
tuples.add(toTuple(block, md));
}
Union rv = new Union(tuples.get(0), tuples.get(1));
for (int i = 2; i < tuples.size(); i++) {
rv = new Union(rv, tuples.get(i));
}
return rv;
}
@Override
public TupleExpr visit(GroupBlock expr, QueryMetadata md) {
return visit((ContainerBlock) expr, md);
}
@Override
public TupleExpr visit(GraphBlock expr, QueryMetadata md) {
graphs.push(toVar(expr.getContext(), md));
TupleExpr rv = visit((ContainerBlock) expr, md);
graphs.pop();
return rv;
}
@Override
public TupleExpr visit(OptionalBlock expr, QueryMetadata md) {
return visit((ContainerBlock) expr, md);
}
private TupleExpr visit(ContainerBlock expr, QueryMetadata md) {
TupleExpr rv = merge(expr.getBlocks(), md);
if (expr.getFilters() != null) {
rv = filter(rv, expr.getFilters(), md);
}
return rv;
}
private TupleExpr merge(List<Block> blocks, QueryMetadata md) {
List<TupleExpr> tuples = new ArrayList<TupleExpr>(blocks.size());
boolean asLeftJoin = false;
for (Block block : blocks) {
if (block instanceof OptionalBlock) {
if (!tuples.isEmpty()) {
TupleExpr right = toTuple(block, md);
LeftJoin lj = new LeftJoin(tuples.size() == 1 ? tuples.get(0) : join(tuples), right);
tuples = new ArrayList<TupleExpr>();
tuples.add(lj);
} else {
asLeftJoin = true;
tuples.add(toTuple(block, md));
}
} else if (asLeftJoin) {
LeftJoin lj = new LeftJoin(toTuple(block, md), tuples.get(0));
tuples = new ArrayList<TupleExpr>();
tuples.add(lj);
asLeftJoin = false;
} else {
tuples.add(toTuple(block, md));
}
}
return join(tuples);
}
private TupleExpr join(List<TupleExpr> tuples) {
if (tuples.size() > 1) {
Join rv = new Join(tuples.get(0), tuples.get(1));
for (int i = 2; i < tuples.size(); i++) {
rv = new Join(rv, tuples.get(i));
}
return rv;
} else {
return tuples.get(0);
}
}
private TupleExpr filter(TupleExpr tuple, Predicate expr, QueryMetadata md) {
ValueExpr filter = toValue(expr, md);
if (filter != null) {
return new Filter(tuple, filter);
} else {
return tuple;
}
}
@Override
public TupleExpr visit(PatternBlock expr, QueryMetadata md) {
Var subject = toVar(expr.getSubject(), md);
Var predicate = toVar(expr.getPredicate(), md);
Var object = toVar(expr.getObject(), md);
StatementPattern pattern;
if (expr.getContext() != null) {
pattern = new StatementPattern(subject, predicate, object, toVar(expr.getContext(), md));
} else if (!graphs.isEmpty()) {
pattern = new StatementPattern(subject, predicate, object, graphs.peek());
} else {
pattern = new StatementPattern(subject, predicate, object);
}
// datatype inference (string typed literal can be replaced with
// untyped) via union
if (object.getValue() != null
&& object.getValue() instanceof Literal
&& XMLSchema.STRING.equals(((Literal) object.getValue()).getDatatype())) {
Var object2 = new Var(object.getName(), dialect.getLiteral(new LIT(object.getValue().stringValue(), RDF.text)));
return new Union(pattern, new StatementPattern(subject, predicate, object2, pattern.getContextVar()));
} else {
return pattern;
}
}
@Override
public Var visit(Constant<?> expr, QueryMetadata md) {
Var var = constantToVar.get(expr);
if (NODE.class.isAssignableFrom(expr.getType())) {
var = new Var(varNames.next(), dialect.getNode((NODE) expr.getConstant()));
} else if (expr.getType().equals(String.class)) {
var = new Var(varNames.next(), dialect.getNode(new LIT(expr.getConstant().toString())));
} else {
UID datatype = ConverterRegistryImpl.DEFAULT.getDatatype(expr.getType());
String value = ConverterRegistryImpl.DEFAULT.toString(expr.getConstant());
var = new Var(varNames.next(), dialect.getNode(new LIT(value, datatype)));
}
return var;
}
@Override
public Object visit(TemplateExpression<?> expr, QueryMetadata md) {
throw new UnsupportedOperationException();
}
@Override
public Object visit(FactoryExpression<?> expr, QueryMetadata md) {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
@Override
public ValueExpr visit(Operation<?> expr, QueryMetadata md) {
Operator<?> op = expr.getOperator();
if (op == Ops.AND) {
return new And(toValue(expr.getArg(0), md), toValue(expr.getArg(1), md));
} else if (op == Ops.OR) {
return new Or(toValue(expr.getArg(0), md), toValue(expr.getArg(1), md));
} else if (op == Ops.IN) {
// expand IN to OR/EQ
BooleanBuilder builder = new BooleanBuilder();
for (Object o : ((Constant<Collection>) expr.getArg(1)).getConstant()) {
builder.or(ExpressionUtils.eqConst((Expression) expr.getArg(0), o));
}
return (ValueExpr) builder.getValue().accept(this, md);
} else if (op == Ops.NOT) {
return new Not(toValue(expr.getArg(0), md));
} else if (COMPARE_OPS.containsKey(op)) {
if (expr.getArg(1) instanceof SubQueryExpression<?>) {
return new CompareAll(toValue(expr.getArg(0), md), toTuple(expr.getArg(1), md), COMPARE_OPS.get(op));
} else {
return new Compare(toValue(expr.getArg(0), md), toValue(expr.getArg(1), md), COMPARE_OPS.get(op));
}
} else if (MATH_OPS.containsKey(op)) {
return new MathExpr(toValue(expr.getArg(0), md), toValue(expr.getArg(1), md), MATH_OPS.get(op));
} else if (op == Ops.NEGATE) {
return new MathExpr(toValue(expr.getArg(0), md), toValue(new ConstantImpl<LIT>(new LIT("-1", XSD.intType)), md), MathOp.MULTIPLY);
} else if (op == Ops.NEGATE) {
return new MathExpr(toValue(expr.getArg(0), md), toValue(ConstantImpl.create(-1), md), MathOp.MULTIPLY);
} else if (op == Ops.MATCHES) {
return new Regex(new Str(toValue(expr.getArg(0), md)), new Str(toValue(expr.getArg(1), md)), null);
} else if (op == Ops.MATCHES_IC) {
return new Regex(new Str(toValue(expr.getArg(0), md)), new Str(toValue(expr.getArg(1), md)), CASE_INSENSITIVE);
} else if (op == Ops.STRING_IS_EMPTY) {
return new Regex(new Str(toValue(expr.getArg(0), md)), EMPTY_STRING, null);
} else if (op == Ops.IS_NULL) {
return new Not(new Bound(toVar(expr.getArg(0), md)));
} else if (op == Ops.IS_NOT_NULL) {
return new Bound(toVar(expr.getArg(0), md));
} else if (op == Ops.EXISTS) {
return new Exists(toTuple(expr.getArg(0), md));
// }else if (op == Ops.DELEGATE){
// return toValue(expr.getArg(0), md);
} else if (op == Ops.STRING_CAST) {
return new Str(toValue(expr.getArg(0), md));
} else if (op == Ops.NUMCAST) {
return new FunctionCall(toVar(expr.getArg(1), md).getValue().stringValue(), toValue(expr.getArg(0), md));
} else if (FUNCTION_OPS.containsKey(op)) {
List<ValueExpr> args = new ArrayList<ValueExpr>(expr.getArgs().size());
for (Expression<?> e : expr.getArgs()) {
args.add(toValue(e, md));
}
return new FunctionCall(FUNCTION_OPS.get(op), args);
} else {
throw new IllegalArgumentException(expr.toString());
}
}
@Override
public Var visit(Path<?> expr, QueryMetadata md) {
Var var = pathToVar.get(expr);
if (var == null) {
var = new Var(expr.toString());
pathToVar.put(expr, var);
}
return var;
}
@SuppressWarnings("unchecked")
@Override
public TupleExpr visit(SubQueryExpression<?> expr, QueryMetadata md) {
for (Map.Entry<ParamExpression<?>, Object> entry : md.getParams().entrySet()) {
expr.getMetadata().setParam((ParamExpression) entry.getKey(), entry.getValue());
}
return visit(expr.getMetadata(), QueryLanguage.TUPLE);
}
@Override
public Var visit(ParamExpression<?> expr, QueryMetadata md) {
Var var = paramToVar.get(expr);
if (var == null) {
var = new Var(expr.getName());
if (md.getParams().containsKey(expr)) {
var.setValue(dialect.getNode((NODE) md.getParams().get(expr)));
}
paramToVar.put(expr, var);
}
return var;
}
}