package com.bigdata.rdf.sparql.ast;
import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import org.openrdf.model.URI;
import com.bigdata.bop.BOp;
import com.bigdata.bop.IValueExpression;
import com.bigdata.bop.NV;
/**
* AST node for anything which is neither a constant nor a variable, including
* math operators, string functions, etc. The {@link FunctionNode} arguments are
* the ordered list {@link ValueExpressionNode}s that are the arguments to the
* SPARQL function.
*
* @author <a href="mailto:mrpersonick@users.sourceforge.net">Mike Personick</a>
* @version $Id$
*/
public class FunctionNode extends ValueExpressionNode {
/**
*
*/
private static final long serialVersionUID = 1L;
interface Annotations extends ValueExpressionNode.Annotations {
/**
* The function URI from the {@link FunctionRegistry}.
*/
String FUNCTION_URI = FunctionNode.class.getName() + ".functionURI";
/**
* The scalar values needed to construct the {@link IValueExpression}.
* This is a {@link Serializable} {@link Map} with {@link String} keys
* and {@link Object} values.
*/
String SCALAR_VALS = FunctionNode.class.getName() + ".scalarVals";
}
/**
* Construct a function node in the AST.
*
* @param functionURI
* the function URI. see {@link FunctionRegistry}
* @param scalarValues
* One or more scalar values that are passed to the function
* @param args
* the arguments to the function.
*
* @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/672">
* Occasional error on BSBM Explore query</a>
*/
public FunctionNode(//
final URI functionURI,
final Map<String,Object> scalarValues,
final ValueExpressionNode... args) {
super(args, NV.asMap(new NV[] {
new NV(Annotations.SCALAR_VALS, scalarValues),
new NV(Annotations.FUNCTION_URI, functionURI), }));
if (functionURI == null)
throw new IllegalArgumentException();
// scalarValues MAY be null.
// super(args, null/*anns*/);
//
// super.setProperty(Annotations.SCALAR_VALS, scalarValues);
//
// super.setProperty(Annotations.FUNCTION_URI, functionURI);
}
/**
* Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}.
*/
public FunctionNode(final FunctionNode op) {
super(op);
}
/**
* Required shallow copy constructor.
*/
public FunctionNode(final BOp[] args, final Map<String, Object> anns) {
super(args, anns);
}
public URI getFunctionURI() {
return (URI) getRequiredProperty(Annotations.FUNCTION_URI);
}
/**
* Returns an unmodifiable map because if the scalar values are modified, we
* need to clear the value expression cache. This is handled correctly by
* {@link #setScalarValues(Map)}.
*/
public Map<String, Object> getScalarValues() {
@SuppressWarnings("unchecked")
final Map<String, Object> scalarValues = (Map<String, Object>) getProperty(Annotations.SCALAR_VALS);
if (scalarValues == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(scalarValues);
}
/**
* Overridden to clear the cached value expression.
*/
@Override
public void invalidate() {
super.clearProperty(Annotations.VALUE_EXPR);
super.invalidate();
}
/**
* Return <code>true</code> iff the {@link FunctionNode} makes use of a
* {@link FunctionRegistry#BOUND} operator.
*
* @return <code>true</code>iff it uses <code>BOUND()</code>
*
* TODO Unit test.
*/
public boolean isBound() {
if (FunctionRegistry.BOUND.equals(getFunctionURI()))
return true;
final int arity = arity();
for (int i = 0; i < arity; i++) {
final BOp child = get(i);
if (child instanceof FunctionNode) {
if (!((FunctionNode) child).isBound()) {
return true;
}
}
}
return false;
}
/*
* Factory methods.
*/
/**
* Return <code>t1 AND t2</code>.
*/
static public FunctionNode AND(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.AND, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 OR t2</code>.
*/
static public FunctionNode OR(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.OR, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 OR t2</code>.
*/
static public FunctionNode NOT(final ValueExpressionNode inner) {
return new FunctionNode(FunctionRegistry.NOT, null/* scalarValues */,
new ValueExpressionNode[] { inner });
}
/**
* Return <code>t1 + t2</code> (aka ADD).
*/
static public FunctionNode add(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.ADD, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 - t2</code> (aka SUBTRACT).
*/
static public FunctionNode subtract(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.SUBTRACT, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>sameTerm(t1,t2)</code> (aka EQ).
*/
static public FunctionNode sameTerm(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.SAME_TERM, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 = t2</code> (aka EQ aka RDFTERM-EQUALS).
*/
static public FunctionNode EQ(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.EQ, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 != t2</code>
*/
static public FunctionNode NE(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.NE, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 < t2</code>
*/
static public FunctionNode LT(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.LT, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 > t2</code>
*/
static public FunctionNode GT(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.GT, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 <= t2</code>
*/
static public FunctionNode LE(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.LE, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>t1 >= t2</code>
*/
static public FunctionNode GE(final ValueExpressionNode t1,
final ValueExpressionNode t2) {
return new FunctionNode(FunctionRegistry.GE, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Return <code>min(v1,v2)</code>
*/
static public FunctionNode MIN(
final ValueExpressionNode v1, final ValueExpressionNode v2) {
return new FunctionNode(FunctionRegistry.MIN, null/* scalarValues */,
new ValueExpressionNode[] { v1, v2 });
}
/**
* Return <code>max(v1,v2)</code>
*/
static public FunctionNode MAX(
final ValueExpressionNode v1, final ValueExpressionNode v2) {
return new FunctionNode(FunctionRegistry.MAX, null/* scalarValues */,
new ValueExpressionNode[] { v1, v2 });
}
/**
* Return a binary function <code>op(t1,t2)</code>
*/
static public FunctionNode binary(final URI uri, final TermNode t1,
final TermNode t2) {
return new FunctionNode(uri, null/* scalarValues */,
new ValueExpressionNode[] { t1, t2 });
}
/**
* Provides a pretty print representation with recursive descent.
*/
@Override
public String toString(int i) {
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
final Integer bopId = (Integer) getProperty(Annotations.BOP_ID);
if (bopId != null) {
sb.append("[" + bopId + "]");
}
sb.append("(");
int nwritten = 0;
final Iterator<BOp> itr = argIterator();
while(itr.hasNext()) {
final BOp t = itr.next();
if (nwritten > 0)
sb.append(',');
if (t == null) {
sb.append("<null>");
} else {
sb.append(((IValueExpressionNode)t).toString(i+1));
}
nwritten++;
}
sb.append(")");
annotationsToString(sb, i);
return sb.toString();
}
}