blob: 2759af58587857ad70b1e6498aa21c30f60fa51c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.sparql.expr;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;
import org.apache.jena.sparql.ARQInternalErrorException ;
import org.apache.jena.sparql.engine.binding.Binding ;
import org.apache.jena.sparql.function.FunctionEnv ;
import org.apache.jena.sparql.sse.Tags ;
import org.apache.jena.sparql.util.Context ;
/**
* ARQ extension to SPARQL which provides for dynamic function invocation
*/
public class E_Call extends ExprFunctionN
{
private static final String symbol = Tags.tagCall ;
private Map<String,Expr> functionCache = new HashMap<>();
private Expr identExpr;
private List<Expr> argExprs;
public E_Call(ExprList args)
{
this(symbol, args) ;
}
protected E_Call(String sym, ExprList args)
{
super(sym, args) ;
if (args.size() == 0) {
identExpr = null;
} else {
identExpr = args.get(0);
argExprs = args.getList().subList(1,args.size()) ;
}
}
@Override
public NodeValue evalSpecial(Binding binding, FunctionEnv env)
{
//No argument returns unbound
if (identExpr == null) throw new ExprEvalException("CALL() has no arguments");
//One/More arguments means invoke a function dynamically
NodeValue func = identExpr.eval(binding, env);
if (func == null) throw new ExprEvalException("CALL: Function identifier unbound");
if (func.isIRI()) {
Expr e = buildFunction(func.getNode().getURI(), argExprs, env.getContext());
if (e == null)
throw new ExprEvalException("CALL: Function identifier <" + func.getNode().getURI() + "> does not identify a known function");
//Calling this may throw an error which we will just let bubble up
return e.eval(binding, env);
} else {
throw new ExprEvalException("CALL: Function identifier not an IRI");
}
}
@Override
public Expr copy(ExprList newArgs) { return new E_Call(newArgs) ; }
@Override
public NodeValue eval(List<NodeValue> args, FunctionEnv env) {
// Instead of evalSpecial, we can rely on the machinery to evaluate the arguments to CALL first.
// This precludes special forms for CALL first argument.
// This code here is not usually called - evalSpecial is more general and is the main code path,
NodeValue func = args.get(0) ;
if (func == null) throw new ExprEvalException("CALL: Function identifier unbound");
if (func.isIRI()) {
ExprList a = new ExprList() ;
for ( int i = 1 ; i < args.size() ; i++ )
a.add(args.get(i)) ;
//Expr e = null ;
Expr e = new E_Function(func.getNode().getURI(), a);
//Calling this may throw an error which we will just let bubble up
return e.eval(null, env) ;
} else
throw new ExprEvalException("CALL: Function identifier not an IRI");
}
@Override
public NodeValue eval(List<NodeValue> args) {
// eval(List, FunctionEnv) should be called.
throw new ARQInternalErrorException();
}
/**
* Returns the expr representing the dynamic function to be invoked
* <p>
* Uses caching wherever possible to avoid
* </p>
*/
private Expr buildFunction(String functionIRI, List<Expr> args, Context cxt)
{
//Use our cached version of the expression wherever possible
if (functionCache.containsKey(functionIRI)) {
return functionCache.get(functionIRI);
}
//Otherwise generate a new function and cache it
try
{
E_Function e = new E_Function(functionIRI, new ExprList(args));
e.buildFunction(cxt);
functionCache.put(functionIRI, e);
return e;
} catch (Throwable e) {
//Anything goes wrong in creating the function cache a null so we don't retry every time we see this IRI
functionCache.put(functionIRI, null);
return null;
}
}
}