/*
* Copyright 2008 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.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.Filter;
import org.mulgara.resolver.spi.QueryEvaluationContext;
import org.mulgara.resolver.spi.TuplesContext;
import org.mulgara.store.tuples.AbstractTuples;
/**
* Filtering operation. This class wraps another Tuples, removing those elements that don't
* pass the filter.
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public class FilteredTuples extends AbstractTuples implements ContextOwner {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(FilteredTuples.class.getName());
/** The inner tuples to filter. */
protected Tuples unfiltered;
/** The filter to apply. */
protected Filter filter;
/** 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>();
/**
* Configure a tuples for filtering.
*
* @param unfiltered The original tuples.
* @param filter The filter to apply.
* @param queryContext The context to evaluate the tuples in.
* @throws IllegalArgumentException If the <var>unfiltered</var> tuples is null.
*/
FilteredTuples(Tuples unfiltered, Filter filter, QueryEvaluationContext queryContext) throws IllegalArgumentException {
// store the operands
this.filter = filter;
this.unfiltered = (Tuples)unfiltered.clone();
this.context = new TuplesContext(this.unfiltered, queryContext.getResolverSession());
filter.setContextOwner(this);
setVariables(this.unfiltered.getVariables());
}
/** {@inheritDoc} */
public long getColumnValue(int column) throws TuplesException {
return unfiltered.getColumnValue(column);
}
/** {@inheritDoc} */
public long getRawColumnValue(int column) throws TuplesException {
return unfiltered.getColumnValue(column);
}
/** {@inheritDoc} */
public long getRowUpperBound() throws TuplesException {
return unfiltered.getRowUpperBound();
}
/** {@inheritDoc} */
public long getRowExpectedCount() throws TuplesException {
return (long)(unfiltered.getRowExpectedCount() * getMatchRatio());
}
/** {@inheritDoc} */
@Override
public boolean isEmpty() throws TuplesException {
return unfiltered.isEmpty();
}
/** {@inheritDoc} */
public boolean isColumnEverUnbound(int column) throws TuplesException {
return unfiltered.isColumnEverUnbound(column);
}
/** {@inheritDoc} */
public Variable[] getVariables() {
return unfiltered.getVariables();
}
/** {@inheritDoc} */
public int getColumnIndex(Variable variable) throws TuplesException {
return unfiltered.getColumnIndex(variable);
}
/** {@inheritDoc} */
public boolean isMaterialized() {
return false;
}
/** {@inheritDoc} */
public boolean hasNoDuplicates() throws TuplesException {
return unfiltered.hasNoDuplicates();
}
/** {@inheritDoc} */
public RowComparator getComparator() {
return unfiltered.getComparator();
}
/** {@inheritDoc} */
public List<Tuples> getOperands() {
return Collections.unmodifiableList(Arrays.asList(new Tuples[] {unfiltered}));
}
/** {@inheritDoc} */
public boolean isUnconstrained() throws TuplesException {
return unfiltered.isUnconstrained();
}
/** {@inheritDoc} */
public void renameVariables(Constraint constraint) {
unfiltered.renameVariables(constraint);
}
/** {@inheritDoc} */
public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
unfiltered.beforeFirst(prefix, suffixTruncation);
}
/**
* @return {@inheritDoc}
* @throws TuplesException {@inheritDoc}
*/
public boolean next() throws TuplesException {
do {
// move to the next on the unfiltered
boolean currentNext = unfiltered.next();
// Short-circuit execution if this tuples' cursor is after the last row
if (!currentNext) return false;
// check if the filter passes the current row on the unfiltered
} while (!testFilter());
return true;
}
/** {@inheritDoc} */
public void close() throws TuplesException {
unfiltered.close();
}
/** @return {@inheritDoc} */
public Object clone() {
FilteredTuples cloned = (FilteredTuples)super.clone();
// Clone the mutable fields as well
cloned.unfiltered = (Tuples)unfiltered.clone();
cloned.context = new TuplesContext(cloned.unfiltered, 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);
}
/**
* Tests a filter using the current context.
* @return The test result.
* @throws QueryException If there was an error accessing data needed for the test.
*/
private boolean testFilter() {
// re-root the filter expression to this Tuples
filter.setContextOwner(this);
try {
return filter.test(context);
} catch (QueryException qe) {
return false;
}
}
/**
* 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);
}
/**
* The expected ratio for matching on the filter. This value should update over time.
* TODO: calculate this value, update it over time, and record it against the filter pattern.
* @return A value between 1.0 (100% match) and 0.0 (no match).
*/
private double getMatchRatio() {
return 0.5;
}
}