/*
* Copyright 2009 Fedora Commons, Inc.
*
* Licensed 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.mulgara.store.tuples;
// Java 2 standard packages
import java.util.*;
// Third party packages
import org.apache.log4j.*;
// Locally written packages
import org.jrdf.graph.Node;
import org.mulgara.query.Constraint;
import org.mulgara.query.QueryException;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.query.filter.Context;
import org.mulgara.query.filter.ContextOwner;
import org.mulgara.query.filter.RDFTerm;
import org.mulgara.resolver.spi.QueryEvaluationContext;
import org.mulgara.resolver.spi.TuplesContext;
import org.mulgara.store.statement.StatementStore;
import org.mulgara.store.tuples.AbstractTuples;
/**
* Variable binding operation. This class wraps another Tuples, binding a variable based on what
* the context provided by that Tuples.
*
* @created July 1, 2009
* @author Paula Gearon
* @copyright © 2009 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public class LetTuples extends AbstractTuples implements ContextOwner {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(LetTuples.class.getName());
/** The inner tuples to filter. */
protected Tuples innerTuples;
/** The expression to bind the variable to. */
protected RDFTerm expr;
/** The variable to be bound by the expression. */
protected Variable var;
/** The tuples context */
protected TuplesContext context;
/** A list of context owners that this owner provides the context for. */
private List<ContextOwner> contextListeners = new ArrayList<ContextOwner>();
/** A convenience for remembering the last column in this tuples. */
private int lastCol;
/**
* Configure a tuples for binding a variable.
*
* @param innerTuples The original tuples.
* @param var the variable to bind.
* @param expr The expression to bind the variable to.
* @param queryContext The context to evaluate the tuples in.
* @throws IllegalArgumentException If the <var>innerTuples</var> is null.
*/
LetTuples(Tuples innerTuples, Variable var, RDFTerm expr, QueryEvaluationContext queryContext) throws IllegalArgumentException {
// store the operands
this.var = var;
this.expr = expr;
this.innerTuples = (Tuples)innerTuples.clone();
this.context = new TuplesContext(this.innerTuples, queryContext.getResolverSession());
expr.setContextOwner(this);
// duplicate the inner variables, and add the new variable to it
Variable[] innerVars = this.innerTuples.getVariables();
this.lastCol = innerVars.length;
Variable[] vars = new Variable[this.lastCol + 1];
for (int i = 0; i < innerVars.length; i++) {
if (var.equals(innerVars[i])) throw new IllegalArgumentException("Variable ?" + var + " is already bound");
vars[i] = innerVars[i];
}
vars[innerVars.length] = var;
setVariables(vars);
}
/** {@inheritDoc} */
public long getColumnValue(int column) throws TuplesException {
if (column < lastCol) return innerTuples.getColumnValue(column);
// re-root the expression to this Tuples
expr.setContextOwner(this);
try {
Node val = expr.getJRDFValue();
return context.localize(val);
} catch (QueryException e) {
return UNBOUND;
}
}
/** {@inheritDoc} */
public long getRawColumnValue(int column) throws TuplesException {
if (column < lastCol) return innerTuples.getRawColumnValue(column);
// re-root the expression to this Tuples
expr.setContextOwner(this);
try {
Node val = expr.getJRDFValue();
return context.localize(val);
} catch (QueryException e) {
return UNBOUND;
}
}
/** {@inheritDoc} */
public long getRowUpperBound() throws TuplesException {
return innerTuples.getRowUpperBound();
}
/** {@inheritDoc} */
public long getRowExpectedCount() throws TuplesException {
return innerTuples.getRowExpectedCount();
}
/** {@inheritDoc} */
public boolean isEmpty() throws TuplesException {
return innerTuples.isEmpty();
}
/** {@inheritDoc} */
public boolean isColumnEverUnbound(int column) throws TuplesException {
return column == lastCol || innerTuples.isColumnEverUnbound(column);
}
/** {@inheritDoc} */
public Variable[] getVariables() {
return super.getVariables();
}
/** {@inheritDoc} */
public int getColumnIndex(Variable variable) throws TuplesException {
return variable.equals(var) ? lastCol : innerTuples.getColumnIndex(variable);
}
/** {@inheritDoc} */
public boolean isMaterialized() {
return innerTuples.isMaterialized();
}
/** {@inheritDoc} */
public boolean hasNoDuplicates() throws TuplesException {
return innerTuples.hasNoDuplicates();
}
/** {@inheritDoc} */
public RowComparator getComparator() {
return innerTuples.getComparator();
}
/** {@inheritDoc} */
public List<Tuples> getOperands() {
return Collections.unmodifiableList(Arrays.asList(new Tuples[] {innerTuples}));
}
/** {@inheritDoc} */
public boolean isUnconstrained() throws TuplesException {
return innerTuples.isUnconstrained();
}
/** {@inheritDoc} */
public void renameVariables(Constraint constraint) {
innerTuples.renameVariables(constraint);
for (int i = 0; i < StatementStore.VARIABLES.length; i++) {
if (var.equals(StatementStore.VARIABLES[i])) {
var = (Variable)constraint.getElement(i);
break;
}
}
}
/**
* {@inheritDoc}
* We are not going to extend this operation to localized values.
*/
public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
innerTuples.beforeFirst(prefix, suffixTruncation);
}
/**
* @return {@inheritDoc}
* @throws TuplesException {@inheritDoc}
*/
public boolean next() throws TuplesException {
return innerTuples.next();
}
/** {@inheritDoc} */
public void close() throws TuplesException {
innerTuples.close();
}
/** @return {@inheritDoc} */
public Object clone() {
LetTuples cloned = (LetTuples)super.clone();
// Clone the mutable fields as well
cloned.innerTuples = (Tuples)innerTuples.clone();
cloned.context = new TuplesContext(cloned.innerTuples, context);
return cloned;
}
/**
* Tells a filter what the current context is.
* @see org.mulgara.query.filter.ContextOwner#getCurrentContext()
*/
public Context getCurrentContext() {
return context;
}
/**
* Allows the context to be set manually. This is not expected.
* @see org.mulgara.query.filter.ContextOwner#setCurrentContext(org.mulgara.query.filter.Context)
*/
public void setCurrentContext(Context context) {
if (!(context instanceof TuplesContext)) throw new IllegalArgumentException("FilteredTuples can only accept a TuplesContext.");
this.context = (TuplesContext)context;
for (ContextOwner l: contextListeners) l.setCurrentContext(context);
}
/**
* This provides a context, and does not need to refer to a parent.
* @see org.mulgara.query.filter.ContextOwner#getContextOwner()
*/
public ContextOwner getContextOwner() {
throw new IllegalStateException("Should never be asking for the context owner of a Tuples");
}
/**
* The owner of the context for a Tuples is never needed, since it is always provided by the Tuples.
* @see org.mulgara.query.filter.ContextOwner#setContextOwner(org.mulgara.query.filter.ContextOwner)
*/
public void setContextOwner(ContextOwner owner) {
}
/**
* Adds a context owner as a listener so that it will be updated with its context
* when this owner gets updated.
* @param l The context owner to register.
*/
public void addContextListener(ContextOwner l) {
contextListeners.add(l);
}
}