package org.aksw.sparqlify.core.algorithms; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.aksw.jena_sparql_api.views.ExprCopy; import org.aksw.jena_sparql_api.views.ExprEvaluator; import org.aksw.jena_sparql_api.views.ExprEvaluatorPartial; import org.aksw.jena_sparql_api.views.SparqlifyConstants; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.expr.E_Conditional; import org.apache.jena.sparql.expr.Expr; import org.apache.jena.sparql.expr.ExprFunction; import org.apache.jena.sparql.expr.ExprNotComparableException; import org.apache.jena.sparql.function.FunctionRegistry; import org.apache.jena.sparql.util.ExprUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Evaluator for expressions. * Transforms SPARQL expressions so that they can be translated to SQL. * Concretely, this class attempts to remove all type constructors * from the expressions. * * * * * * If not all of an expressions' variables are bound, it tries to evaluate as much * as possible; hence the name "partial" evaluator. * * * * * * * @author Claus Stadler <cstadler@informatik.uni-leipzig.de> * */ public class ExprEvaluatorPartialOld implements ExprEvaluator { private static final Logger logger = LoggerFactory.getLogger(ExprEvaluatorPartialOld.class); private FunctionRegistry registry; /** * The transformer is called AFTER all of a functions arguments have been evaluated. * */ private ExprTransformer exprTransformer; public ExprEvaluatorPartialOld(FunctionRegistry registry, ExprTransformer exprTransformer) { this.registry = registry; this.exprTransformer = exprTransformer; } public static boolean isConstantsOnly(Iterable<Expr> exprs) { for(Expr expr : exprs) { if(!expr.isConstant()) { return false; } } return true; } public static boolean isConstantArgsOnly(ExprFunction fn) { boolean result = isConstantsOnly(fn.getArgs()); return result; } public Expr eval(ExprFunction fn, Map<Var, Expr> binding) { Expr newExpr; if(fn instanceof E_Conditional) { E_Conditional cond = (E_Conditional)fn; Expr a = eval(cond.getArg1(), binding); if(a.equals(SparqlifyConstants.nvTypeError)) { newExpr = SparqlifyConstants.nvTypeError; } else { Expr b = eval(cond.getArg2(), binding); Expr c = eval(cond.getArg3(), binding); newExpr = new E_Conditional(a, b, c); } } else { List<Expr> evaledArgs = new ArrayList<Expr>(); for(Expr arg : fn.getArgs()) { // If an argument evaluated to type error, return type error // TODO This way we can't have a conditional :/ // E.g: if(condition) then TRUE else TYPE-ERROR // So in the general case, the function definition must include // handling of arguments. For now we hard code this case here Expr evaledArg = eval(arg, binding); if(evaledArg == null) { throw new RuntimeException("Null must not happen here"); } if(evaledArg.equals(SparqlifyConstants.nvTypeError)) { return SparqlifyConstants.nvTypeError; } evaledArgs.add(evaledArg); } newExpr = ExprCopy.getInstance().copy(fn, evaledArgs); } Expr tmp = newExpr; if(exprTransformer != null && newExpr.isFunction()) { tmp = exprTransformer.transform(newExpr.getFunction()); } // If some arguments are not constant, we can't evaluate // FIXME This is not true, we could still perform a partial evaluation // What is true, though, is, that Jena throws errors when evaluating exprs with unbound vars if(tmp.isFunction() && !ExprEvaluatorPartial.isConstantArgsOnly(tmp.getFunction())) { return tmp; } // Check if the function's IRI is registered // If not, don't try to evaluate the corresponding expression Set<String> builtInOps = new HashSet<String>(Arrays.asList("<=", "<", "=", "!=", ">", ">=", "if", "&&", "||", "!", "+", "-", "*", "/")); String fnIri = org.aksw.jena_sparql_api.utils.ExprUtils.getFunctionId(fn); //fn.getFunctionIRI(); if(fnIri != null && !fnIri.isEmpty()) { if(!builtInOps.contains(fnIri) && registry.get(fnIri) == null) { return tmp; } } Expr result = tmp; try { result = ExprUtils.eval(tmp); } catch(ExprNotComparableException e) { return SparqlifyConstants.nvTypeError; } catch(Exception e) { // Failed to evaluate - use original value logger.warn("Failed to evaluate expr: " + tmp); } return result; } /* * How to best add interceptors (callbacks with transformation) for certain functions? * * e.g.: concat(foo, concat(?x...)) -> concat(foo, ?x) * lang(rdfterm(2, ?x, ?y, '')) -> ?y * * The main question is, whether to apply to callback before or after the arguments are evaluated. * * -> After makes more sense: Then we have constant folder arguments */ public Expr eval(Expr expr, Map<Var, Expr> binding) { if(expr == null) { throw new RuntimeException("Null expression should not happen"); } //System.out.println(expr); Expr result = null; if(expr.isConstant()) { //result = ConstantExpander.transform(expr); result = expr; } else if(expr.isFunction()) { ExprFunction fn = expr.getFunction(); result = eval(fn, binding); } else if(expr.isVariable()) { result = expr; if(binding != null) { Expr boundExpr = binding.get(expr.asVar()); if(boundExpr != null) { result = eval(boundExpr, null); // Do not forward the binding } } } else { throw new RuntimeException("Unknown expression type encountered: " + expr); } return result; } @Override public Expr transform(Expr expr) { Expr result = eval(expr, null); return result; } }