blob: fc1ff41fe5d88e19d41952118c95b31e7723c9ee [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.sse.builders;
import java.util.* ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.query.Query ;
import org.apache.jena.query.SortCondition ;
import org.apache.jena.sparql.algebra.Op ;
import org.apache.jena.sparql.algebra.Table ;
import org.apache.jena.sparql.algebra.op.* ;
import org.apache.jena.sparql.core.* ;
import org.apache.jena.sparql.expr.Expr ;
import org.apache.jena.sparql.expr.ExprAggregator ;
import org.apache.jena.sparql.expr.ExprList ;
import org.apache.jena.sparql.pfunction.PropFuncArg ;
import org.apache.jena.sparql.sse.Item ;
import org.apache.jena.sparql.sse.ItemList ;
import org.apache.jena.sparql.sse.Tags ;
public class BuilderOp
{
// It's easier to have a object than have all statics because the
// order of statics matters (the dispatch table gets initialized to
// tag/null because the buildXYZ are null at that point).
// which forces the code structure unnaturally.
private static BuilderOp builderOp = new BuilderOp() ;
public static Op build(Item item)
{
if (item.isNode() )
BuilderLib.broken(item, "Attempt to build op structure from a plain node") ;
if (item.isSymbol() )
BuilderLib.broken(item, "Attempt to build op structure from a bare symbol") ;
if (!item.isTagged())
BuilderLib.broken(item, "Attempt to build op structure from a non-tagged item") ;
return builderOp.build(item.getList()) ;
}
protected Map<String, Build> dispatch = new HashMap<>() ;
public BuilderOp()
{
addBuild(Tags.tagBGP, buildBGP) ;
addBuild(Tags.tagQuadPattern, buildQuadPattern) ;
addBuild(Tags.tagQuadBlock, buildQuadBlock) ;
addBuild(Tags.tagTriple, buildTriple) ;
addBuild(Tags.tagQuad, buildQuad) ;
addBuild(Tags.tagTriplePath, buildTriplePath) ;
addBuild(Tags.tagFilter, buildFilter) ;
addBuild(Tags.tagGraph, buildGraph) ;
addBuild(Tags.tagService, buildService) ;
addBuild(Tags.tagProc, buildProcedure) ;
addBuild(Tags.tagPropFunc, buildPropertyFunction) ;
addBuild(Tags.tagJoin, buildJoin) ;
addBuild(Tags.tagSequence, buildSequence) ;
addBuild(Tags.tagDisjunction, buildDisjunction) ;
addBuild(Tags.tagLeftJoin, buildLeftJoin) ;
addBuild(Tags.tagDiff, buildDiff) ;
addBuild(Tags.tagMinus, buildMinus) ;
addBuild(Tags.tagUnion, buildUnion) ;
addBuild(Tags.tagDatasetNames, buildDatasetNames) ;
addBuild(Tags.tagConditional, buildConditional) ;
addBuild(Tags.tagToList, buildToList) ;
addBuild(Tags.tagGroupBy, buildGroupBy) ;
addBuild(Tags.tagOrderBy, buildOrderBy) ;
addBuild(Tags.tagTopN, buildTopN) ;
addBuild(Tags.tagProject, buildProject) ;
addBuild(Tags.tagDistinct, buildDistinct) ;
addBuild(Tags.tagReduced, buildReduced) ;
addBuild(Tags.tagAssign, buildAssign) ;
addBuild(Tags.tagExtend, buildExtend) ;
addBuild(Tags.symAssign, buildAssign) ;
addBuild(Tags.tagFind, buildFind) ;
addBuild(Tags.tagSlice, buildSlice) ;
addBuild(Tags.tagTable, buildTable) ;
addBuild(Tags.tagNull, buildNull) ;
addBuild(Tags.tagLabel, buildLabel) ;
}
public static void add(String tag, Build builder)
{
builderOp.addBuild(tag, builder) ;
}
public static void remove(String tag)
{
builderOp.removeBuild(tag) ;
}
public static boolean contains(String tag)
{
return builderOp.containsBuild(tag) ;
}
// The main recursive build operation.
private Op build(ItemList list)
{
Item head = list.get(0) ;
String tag = head.getSymbol() ;
Build bob = findBuild(tag) ;
if ( bob != null )
return bob.make(list) ;
else
BuilderLib.broken(head, "Unrecognized algebra operation: "+tag) ;
return null ;
}
public static BasicPattern buildBGP(Item item)
{
if ( ! item.isTagged(Tags.tagBGP) )
BuilderLib.broken(item, "Not a basic graph pattern") ;
if ( ! item.isList() )
BuilderLib.broken(item, "Not a list for a basic graph pattern") ;
ItemList list = item.getList() ;
return buildBGP(list) ;
}
private static BasicPattern buildBGP(ItemList list)
{
// Skips the tag.
BasicPattern triples = new BasicPattern() ;
for ( int i = 1 ; i < list.size() ; i++ )
{
Item item = list.get(i) ;
if ( ! item.isList() )
BuilderLib.broken(item, "Not a triple structure") ;
Triple t = BuilderGraph.buildTriple(item.getList()) ;
triples.add(t) ;
}
return triples ;
}
protected Op build(ItemList list, int idx)
{
return build(list.get(idx).getList()) ;
}
// <<<< ---- Coordinate these
// Lowercase on insertion?
protected void addBuild(String tag, Build builder)
{
dispatch.put(tag, builder) ;
}
protected void removeBuild(String tag)
{
dispatch.remove(tag) ;
}
protected boolean containsBuild(String tag)
{
return findBuild(tag) != null ;
}
protected Build findBuild(String str)
{
for ( String key : dispatch.keySet() )
{
if ( str.equalsIgnoreCase( key ) )
{
return dispatch.get( key );
}
}
return null ;
}
// >>>> ----
static public interface Build { Op make(ItemList list) ; }
// Not static. The initialization through the singleton would not work
// (static initialization order - these operations would need to go
// before the singelton.
// Or assign null and create object on first call but that breaks add/remove
final protected Build buildTable = new Build()
{
@Override
public Op make(ItemList list)
{
Item t = Item.createList(list) ;
Table table = BuilderTable.build(t) ;
return OpTable.create(table) ;
}
} ;
final protected Build buildBGP = new Build()
{
@Override
public Op make(ItemList list)
{
BasicPattern triples = buildBGP(list) ;
return new OpBGP(triples) ;
}
} ;
final protected Build buildQuadPattern = new Build()
{
@Override
public Op make(ItemList list)
{
Node g = null ;
BasicPattern bp = new BasicPattern() ;
for ( int i = 1 ; i < list.size() ; i++ )
{
Item item = list.get(i) ;
if ( ! item.isList() )
BuilderLib.broken(item, "Not a quad structure") ;
Quad q = BuilderGraph.buildQuad(item.getList()) ;
if ( g == null )
g = q.getGraph() ;
else
{
if ( ! g.equals(q.getGraph()) )
BuilderLib.broken(item, "Quad has different graph node in quadapttern: "+q) ;
}
bp.add(q.asTriple()) ;
}
OpQuadPattern op = new OpQuadPattern(g, bp) ;
return op ;
}
} ;
final protected Build buildQuadBlock = new Build()
{
@Override
public Op make(ItemList list)
{
Node g = null ;
QuadPattern qp = new QuadPattern() ;
for ( int i = 1 ; i < list.size() ; i++ )
{
Item item = list.get(i) ;
if ( ! item.isList() )
BuilderLib.broken(item, "Not a quad structure") ;
Quad q = BuilderGraph.buildQuad(item.getList()) ;
qp.add(q) ;
}
OpQuadBlock op = new OpQuadBlock(qp) ;
return op ;
}
} ;
final protected Build buildTriple = new Build(){
@Override
public Op make(ItemList list)
{
Triple t = BuilderGraph.buildTriple(list) ;
return new OpTriple(t) ;
}} ;
final protected Build buildQuad = new Build(){
@Override
public Op make(ItemList list)
{
Quad q = BuilderGraph.buildQuad(list) ;
return new OpQuad(q) ;
}} ;
final protected Build buildTriplePath = new Build(){
@Override
public Op make(ItemList list)
{
TriplePath tp = BuilderPath.buildTriplePath(list) ;
return new OpPath(tp) ;
}} ;
final protected Build buildFilter = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "Malformed filter") ;
Item itemExpr = list.get(1) ;
Item itemOp = list.get(2) ;
Op op = build(itemOp.getList()) ;
ExprList exprList = BuilderExpr.buildExprOrExprList(itemExpr) ;
return OpFilter.filterDirect(exprList, op) ;
}
} ;
final protected Build buildJoin = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "Join") ;
Op left = build(list, 1) ;
Op right = build(list, 2) ;
Op op = OpJoin.create(left, right) ;
return op ;
}
} ;
// Add all the operations from the list to the OpN
final private void addOps(OpN op, ItemList list)
{
for ( int i = 1 ; i < list.size() ; i++ )
{
Op sub = build(list, i) ;
op.add(sub) ;
}
}
final protected Build buildSequence = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLengthAtLeast(2, list, "Sequence") ;
OpSequence op = OpSequence.create() ;
addOps(op, list) ;
return op ;
}
} ;
final protected Build buildDisjunction = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLengthAtLeast(2, list, "Disjunction") ;
OpDisjunction op = OpDisjunction.create() ;
addOps(op, list) ;
return op ;
}
} ;
final protected Build buildLeftJoin = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, 4, list, "leftjoin: wanted 2 or 3 arguments") ;
Op left = build(list, 1) ;
Op right = build(list, 2) ;
ExprList expr = null ;
if ( list.size() == 4 )
{
Item exprItem = list.get(3) ;
// Allow empty
if ( exprItem.isList() && exprItem.getList().isEmpty() )
{}
else
expr = BuilderExpr.buildExprOrExprList(exprItem) ;
}
Op op = OpLeftJoin.create(left, right, expr) ;
return op ;
}
} ;
final protected Build buildDiff = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, 4, list, "diff: wanted 2 arguments") ;
Op left = build(list, 1) ;
Op right = build(list, 2) ;
Op op = OpDiff.create(left, right) ;
return op ;
}
} ;
final protected Build buildMinus = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, 4, list, "minus: wanted 2 arguments") ;
Op left = build(list, 1) ;
Op right = build(list, 2) ;
Op op = OpMinus.create(left, right) ;
return op ;
}
} ;
final protected Build buildUnion = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "union") ;
Op left = build(list, 1) ;
Op right = build(list, 2) ;
Op op = new OpUnion(left, right) ;
return op ;
}
} ;
final protected Build buildDatasetNames = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, list, Tags.tagDatasetNames) ;
Node n = BuilderNode.buildNode(list.get(1)) ;
return new OpDatasetNames(n) ;
}
} ;
final protected Build buildConditional = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, 3, list, "condition") ;
Op left = build(list, 1) ;
// No second argument means unit.
Op right = OpTable.unit() ;
if ( list.size() != 2 )
right = build(list, 2) ;
Op op = new OpConditional(left, right) ;
return op ;
}
} ;
final protected Build buildGraph = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "graph") ;
Node graph = BuilderNode.buildNode(list.get(1)) ;
Op sub = build(list, 2) ;
return new OpGraph(graph, sub) ;
}
} ;
final protected Build buildService = new Build()
{
@Override
public Op make(ItemList list)
{
boolean silent = false ;
BuilderLib.checkLength(3, 4, list, "service") ;
list = list.cdr() ;
if ( list.size() == 3 )
{
if ( !list.car().isSymbol() )
BuilderLib.broken(list, "Expected a keyword") ;
if ( ! list.car().getSymbol().equalsIgnoreCase("SILENT") )
BuilderLib.broken(list, "Service: Expected SILENT") ;
silent = true ;
list = list.cdr() ;
}
Node service = BuilderNode.buildNode(list.car()) ;
if ( ! service.isURI() && ! service.isVariable() )
BuilderLib.broken(list, "Service must provide a URI or variable") ;
list = list.cdr() ;
Op sub = build(list, 0) ;
return new OpService(service, sub, silent) ;
}
} ;
final protected Build buildProcedure = new Build()
{
@Override
public Op make(ItemList list)
{
// (proc <foo> (args) form)
BuilderLib.checkLength(4, list, "proc") ;
Node procId = BuilderNode.buildNode(list.get(1)) ;
if ( ! procId.isURI() )
BuilderLib.broken(list, "Procedure name must be a URI") ;
ExprList args = BuilderExpr.buildExprOrExprList(list.get(2)) ;
Op sub = build(list, 3) ;
return new OpProcedure(procId, args, sub) ;
}
} ;
final protected Build buildPropertyFunction = new Build()
{
@Override
public Op make(ItemList list)
{
// (proc <foo> (subject args) (object args) form)
BuilderLib.checkLength(5, list, "propfunc") ;
Node property = BuilderNode.buildNode(list.get(1)) ;
if ( ! property.isURI() )
BuilderLib.broken(list, "Property function name must be a URI") ;
PropFuncArg subjArg = readPropFuncArg(list.get(2)) ;
PropFuncArg objArg = readPropFuncArg(list.get(3)) ;
Op sub = build(list, 4) ;
return new OpPropFunc(property, subjArg, objArg, sub) ;
}
} ;
static final private PropFuncArg readPropFuncArg(Item item)
{
if ( item.isNode() )
return new PropFuncArg(BuilderNode.buildNode(item)) ;
if ( item.isList() )
return new PropFuncArg(BuilderNode.buildNodeList(item)) ;
BuilderLib.broken(item, "Expected a property function argument (node or list of nodes") ;
return null ;
}
final protected Build buildToList = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, list, "tolist") ;
Op sub = build(list, 1) ;
Op op = new OpList(sub) ;
return op ;
}
} ;
final protected Build buildGroupBy = new Build()
{
// See buildProject
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, 4, list, "Group") ;
// GroupBy
VarExprList vars = BuilderExpr.buildNamedExprList(list.get(1).getList()) ;
List<ExprAggregator> aggregators = new ArrayList<>() ;
if ( list.size() == 4 )
{
// Aggregations : assume that the exprs are legal.
VarExprList y = BuilderExpr.buildNamedExprList(list.get(2).getList()) ;
// Aggregations need to know the name of the variable they are associated with
// (so it can be set by the aggregation calculation)
// Bind aggregation to variable
// Remember to process in order that VarExprList keeps the variables.
for ( Var aggVar : y.getVars() )
{
Expr e = y.getExpr(aggVar) ;
if ( ! ( e instanceof ExprAggregator ) )
BuilderLib.broken(list, "Not a aggregate expression: "+e) ;
ExprAggregator eAgg = (ExprAggregator)e ;
eAgg.setVar(aggVar) ;
aggregators.add(eAgg) ;
}
}
Op sub = build(list, list.size()-1) ;
Op op = OpGroup.create(sub,vars, aggregators) ;
return op ;
}
} ;
final protected Build buildOrderBy = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "Order") ;
ItemList conditions = list.get(1).getList() ;
// Maybe tagged (asc, desc or a raw expression)
List<SortCondition> x = new ArrayList<>() ;
for ( int i = 0 ; i < conditions.size() ; i++ )
{
//int direction = Query.ORDER_DEFAULT ;
Item item = conditions.get(i) ;
SortCondition sc = scBuilder(item) ;
x.add(sc) ;
}
Op sub = build(list, 2) ;
Op op = new OpOrder(sub, x) ;
return op ;
}
} ;
SortCondition scBuilder(Item item)
{
int direction = Query.ORDER_DEFAULT ;
if ( item.isTagged("asc") || item.isTagged("desc") )
{
BuilderLib.checkList(item) ;
BuilderLib.checkLength(2, item.getList(), "Direction corrupt") ;
if ( item.isTagged("asc") )
direction = Query.ORDER_ASCENDING ;
else
direction = Query.ORDER_DESCENDING ;
item = item.getList().get(1) ;
}
Expr expr = BuilderExpr.buildExpr(item) ;
if ( expr.isVariable() )
return new SortCondition(expr.getExprVar().asVar(), direction) ;
else
return new SortCondition(expr, direction) ;
}
final protected Build buildTopN = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, Tags.tagTopN) ;
int N = BuilderNode.buildInt(list.get(1).getList(), 0, -1) ;
ItemList conditions = list.get(1).getList().cdr() ;
// Maybe tagged (asc, desc or a raw expression)
List<SortCondition> x = new ArrayList<>() ;
for ( int i = 0 ; i < conditions.size() ; i++ )
{
//int direction = Query.ORDER_DEFAULT ;
Item item = conditions.get(i) ;
SortCondition sc = scBuilder(item) ;
x.add(sc) ;
}
Op sub = build(list, 2) ;
Op op = new OpTopN(sub, N, x) ;
return op ;
}
} ;
final protected Build buildProject = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "project") ;
Item item1 = list.get(1);
List<Var> x = null;
if ( item1.isList() ) {
x = BuilderNode.buildVars(list.get(1).getList()) ;
} else if ( list.get(1).isVar() ) {
Var var = BuilderNode.buildVar(item1);
x = Collections.singletonList(var);
} else
BuilderLib.broken("Not a list of variable for project: "+list.get(1)) ;
Op sub = build(list, 2) ;
return new OpProject(sub, x) ;
}
} ;
final protected Build buildDistinct = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, list, "distinct") ;
Op sub = build(list, 1) ;
return OpDistinct.create(sub) ;
}
} ;
final protected Build buildReduced = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, list, "reduced") ;
Op sub = build(list, 1) ;
return OpReduced.create(sub) ;
}
} ;
final protected Build buildAssign = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(3, list, "assign") ;
VarExprList x = BuilderExpr.buildNamedExprOrExprList(list.get(1)) ;
Op sub ;
if ( list.size() == 2 )
sub = OpTable.unit() ;
else
sub = build(list, 2) ;
return OpAssign.create(sub, x) ;
}
} ;
final protected Build buildExtend = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, 3, list, "extend") ;
VarExprList x = BuilderExpr.buildNamedExprOrExprList(list.get(1)) ;
Op sub ;
if ( list.size() == 2 )
sub = OpTable.unit() ;
else
sub = build(list, 2) ;
return OpExtend.create(sub, x) ;
}
} ;
final protected Build buildFind = list -> {
BuilderLib.checkLength(3, list, "find") ;
// Var
Item item1 = list.get(1); // var
Var var = BuilderNode.buildVar(item1);
// Triple
Item tItem = list.get(2);
BuilderLib.checkList(tItem);
Triple triple = BuilderGraph.buildTriple(tItem.getList()) ;
return new OpFind(triple, var);
};
final protected Build buildSlice = list -> {
BuilderLib.checkLength(4, list, "slice") ;
long start = BuilderNode.buildLong(list, 1, -1) ;
long length = BuilderNode.buildLong(list, 2, -1) ;
if ( start == -1 )
start = Query.NOLIMIT ;
if ( length == -1 )
length = Query.NOLIMIT ;
Op sub = build(list, 3) ;
return new OpSlice(sub, start, length) ;
};
final protected Build buildNull = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(1, list, Tags.tagNull) ;
return OpNull.create() ;
}
} ;
final protected Build buildLabel = new Build()
{
@Override
public Op make(ItemList list)
{
BuilderLib.checkLength(2, 3, list, Tags.tagLabel) ;
Item label = list.get(1) ;
Object str = null ;
if ( label.isSymbol() )
str = label.getSymbol() ;
else if ( label.isNode() )
{
if ( label.getNode().isLiteral() )
{
if ( label.getNode().getLiteralLanguage() == null ||
label.getNode().getLiteralLanguage().equals("") )
str = label.getNode().getLiteralLexicalForm() ;
}
else
str = label.getNode() ;
}
else
BuilderLib.broken("No a symbol or a node") ;
if ( str == null )
str = label.toString() ;
Op op = null ;
if ( list.size() == 3 )
op = build(list, 2) ;
return OpLabel.create(str, op) ;
// if ( op == null )
// return new OpLabel(str) ;
// else
// return new OpLabel(str , op) ;
}
} ;
}