/**
* The contents of this file are subject to the Open Software License
* Version 3.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.opensource.org/licenses/osl-3.0.txt
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*/
package org.mulgara.query.filter.value;
import java.util.Collections;
import java.util.Set;
import org.apache.log4j.Logger;
import org.jrdf.graph.BlankNode;
import org.jrdf.graph.Literal;
import org.jrdf.graph.Node;
import org.jrdf.graph.URIReference;
import org.mulgara.query.QueryException;
import org.mulgara.query.Variable;
import org.mulgara.query.filter.AbstractContextOwner;
import org.mulgara.query.filter.Context;
import org.mulgara.query.filter.ContextOwner;
import org.mulgara.query.filter.RDFTerm;
/**
* Marks the use of a variable.
* This class needs a reference to the calling resolved Tuples to extract the variable
* bindings for the current row.
*
* @created Mar 7, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public class Var extends AbstractContextOwner implements ComparableExpression, ValueLiteral, NumericExpression {
/** Generated Serialization ID for RMI */
private static final long serialVersionUID = 7024038517344320135L;
/** Logger */
private static final Logger logger = Logger.getLogger(Var.class.getName());
/** The name of this variable */
private final String name;
/** An invalid index to indicate that a variable is not initialized */
private static final int NOT_INITIALIZED = -2;
/** The index of this variable in the context */
private int varIndex = NOT_INITIALIZED;
/** The owner of the current context */
private ContextOwner contextOwner = null;
/**
* Instantiate a variable.
*
* @param name The variable's name
*/
public Var(String name) {
this.name = name;
}
/** @see org.mulgara.query.filter.RDFTerm#getVariables() */
public Set<Variable> getVariables() {
return Collections.singleton(new Variable(name));
}
/**
* Tests if this variable is bound.
* @return <code>true</code> if the variable is unknown, or known and not bound.
*/
public boolean isBound() throws QueryException {
// this will set varIndex if it is not set yet
Context context = getLocalContext();
// now that varIndex is set, we can use it in the context
return context.isBound(varIndex);
}
/** {@inheritDoc} */
public Object getValue() throws QueryException {
return resolve().getValue();
}
/** {@inheritDoc} */
public Node getJRDFValue() throws QueryException {
return resolve().getJRDFValue();
}
/** {@inheritDoc} */
public Number getNumber() throws QueryException {
Object result = getValue();
if (result instanceof Number) return (Number)result;
throw new QueryException("Variable '" + name + "' did not resolve to a number. Got: " + result);
}
/**
* {@inheritDoc}
* Uses {@link #resolveComparable()} for throwing the QueryException if this variable
* does not resolve to a literal, and hence does not have a lexical form.
*/
public String getLexical() throws QueryException {
ComparableExpression e = resolveComparable();
if (e.isLiteral()) return ((ValueLiteral)e).getLexical();
// fall back to getting a string out of a complex expression
return e.getValue().toString();
}
/**@see org.mulgara.query.filter.value.ValueLiteral#getType() */
public IRI getType() throws QueryException {
ComparableExpression e = resolveComparable();
if (!e.isLiteral()) throw new QueryException("Only literals are typed");
return ((ValueLiteral)e).getType();
}
/** @see org.mulgara.query.filter.value.ValueLiteral#isSimple() */
public boolean isSimple() throws QueryException {
return ((ValueLiteral)resolve()).isSimple();
}
/** {@inheritDoc} */
public boolean isGrounded() throws QueryException { return false; }
/** {@inheritDoc} */
public boolean equals(RDFTerm v) throws QueryException {
return resolve().equals(v);
}
/** {@inheritDoc} */
public boolean greaterThan(ComparableExpression v) throws QueryException {
return resolveComparable().greaterThan(v);
}
/** {@inheritDoc} */
public boolean greaterThanEqualTo(ComparableExpression v) throws QueryException {
return resolveComparable().greaterThanEqualTo(v);
}
/** {@inheritDoc} */
public boolean lessThan(ComparableExpression v) throws QueryException {
return resolveComparable().lessThan(v);
}
/** {@inheritDoc} */
public boolean lessThanEqualTo(ComparableExpression v) throws QueryException {
return resolveComparable().lessThanEqualTo(v);
}
/**
* Resolve this variable in its current context
* @return An expression value wrapping the data bound to this point
* @throws QueryException Indicates an error getting data out of the context, or globalizing.
*/
public ComparableExpression resolveComparable() throws QueryException {
RDFTerm v = resolve();
if (!(v instanceof ComparableExpression)) throw new QueryException("Type Error: Cannot compare against a: " + v.getClass().getSimpleName());
return (ComparableExpression)v;
}
/** {@inheritDoc} */
public boolean isBlank() throws QueryException {
return resolve().isBlank();
}
/** {@inheritDoc} */
public boolean isIRI() throws QueryException {
return resolve().isIRI();
}
/**
* {@inheritDoc}
* The operation of this method is depended on the context in which it was called.
* If it is called without a context owner, then this means it was called during
* Filter construction, and we want to indicate that it is valid to treat this as a literal.
* @return <code>true</code> if there is no context, or else it calls isLiteral on the resolved value.
*/
public boolean isLiteral() throws QueryException {
return contextOwner == null ? true : resolve().isLiteral();
}
/** {@inheritDoc} */
public boolean isURI() throws QueryException {
return resolve().isURI();
}
/** {@inheritDoc} */
public boolean sameTerm(RDFTerm v) throws QueryException {
if (Var.class == v.getClass()) return resolveLocal() == ((Var)v).resolveLocal();
return resolve().sameTerm(v);
}
/**
* {@inheritDoc}
* @throws QueryException If there was an error resolving the variable, or the variable does not
* resolve to a literal.
*/
public SimpleLiteral getLang() throws QueryException {
RDFTerm term = resolve();
if (!term.isLiteral()) throw new QueryException("Cannot get a language tag on a non-literal: " + term.getClass().getSimpleName());
return ((ValueLiteral)term).getLang();
}
public boolean test(Context context) throws QueryException {
setCurrentContext(context);
RDFTerm term = resolve();
if (!term.isLiteral()) throw new QueryException("Cannot get an effective boolean value for a non-literal: " + term.getClass().getSimpleName());
return ((ComparableExpression)term).test(context);
}
/** @see org.mulgara.query.filter.RDFTerm#getContextOwner() */
public ContextOwner getContextOwner() {
return contextOwner;
}
/** @see org.mulgara.query.filter.RDFTerm#setContextOwner(org.mulgara.query.filter.ContextOwner) */
public void setContextOwner(ContextOwner owner) {
this.contextOwner = owner;
owner.addContextListener(this);
}
/**
* Resolve this variable in its current context
* @return An expression value wrapping the data bound to this point
* @throws QueryException Indicates an error getting data out of the context, or globalizing.
*/
public RDFTerm resolve() throws QueryException {
long gNode = resolveLocal();
Node node;
try {
node = getContextOwner().getCurrentContext().globalize(gNode);
} catch (QueryException qe) {
throw new QueryException("Unable to globalize variable " + name + " from id <" + gNode + ">", qe.getCause());
}
return convertToExpr(node);
}
/**
* Resolve this variable to the internal gNode.
* @return A gNode that this variable resolves to.
* @throws QueryException Indicates an error getting data out of the context.
*/
public long resolveLocal() throws QueryException {
Context localContext = getLocalContext();
if (varIndex == Context.NOT_BOUND) throw new QueryException("Resolving unbound variable: " + name);
long result = localContext.getColumnValue(varIndex);
if (result == localContext.getUnboundVal()) throw new QueryException("Resolving unbound variable: " + name);
return result;
}
/**
* Converts a node to an appropriate ExpressionValue
* @param node The node to convert
* @return A new ExpressionValue
*/
private RDFTerm convertToExpr(Node node) throws QueryException {
if (node instanceof BlankNode) return new BlankNodeValue((BlankNode)node);
if (node instanceof URIReference) return new IRI(((URIReference)node).getURI());
if (!(node instanceof Literal)) throw new QueryException("Unknown type for: " + node);
Literal l = (Literal)node;
return TypedLiteral.newLiteral(l.getLexicalForm(), l.getDatatypeURI(), l.getLanguage());
}
/**
* Return the current context, using a test context over that provided by the context owner if possible
* Updates the value of varIndex.
* @return The current context to use.
*/
private Context getLocalContext() {
Context localContext = getCurrentContext();
if (localContext == null) {
localContext = getContextOwner().getCurrentContext();
} else {
if (localContext != getContextOwner().getCurrentContext()) logger.debug("Changed context. This should be in a cloned filter.");
}
return setVarIndex(localContext);
}
/**
* Sets the varIndex if (and only if) it has not been set before.
* @param context The context to use to get the variable index.
* @return The context used, so it can be reused for the next operation.
*/
private Context setVarIndex(Context context) {
if (varIndex == NOT_INITIALIZED) varIndex = context.getInternalColumnIndex(name);
return context;
}
}