/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* 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.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s):
* DefinablePrefixAnnotation contributed by Netymon Pty Ltd on behalf of
* The Australian Commonwealth Government under contract 4500507038.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
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.TuplesException;
import org.mulgara.query.Variable;
/**
* Logical disjunction. The append is performed by iterating through the
* operands, presenting to the output the lowest of them. It requires that the
* operands be union-compatible (i.e. matching variable lists) and have a shared
* sort order. The "magical" variable used from the Graph can be renamed to be
* made union compatible with the renameVariables method.
*
* @created 2003-02-10
*
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/01/05 04:59:10 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2003 <A href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</A>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class OrderedAppend extends AbstractTuples {
/**
* Logger.
*/
private final static Logger logger =
Logger.getLogger(OrderedAppend.class.getName());
/**
* The propositions to conjoin.
*/
protected final Tuples[] operands;
/**
* The return value of the {@link #beforeFirst} method.
*/
private boolean beforeFirst;
/**
* Description of the Field
*/
private final BitSet incompleteBitSet = new BitSet();
/**
* Description of the Field
*/
private int nextOperand;
/**
* Description of the Field
*/
private final RowComparator rowComparator = new DefaultRowComparator();
/**
* Prefix set by {@link #beforeFirst} and used by {@link #next}.
*/
private long[] prefix;
private boolean prefixDefinable;
/**
* Conjoin a list of propositions.
*
* @param operands the propositions to conjoin; the order affects efficiency,
* but not the logical value of the result
* @throws IllegalArgumentException if <var>operands</var> is
* <code>null</code>
* @throws TuplesException EXCEPTION TO DO
*/
public OrderedAppend(Tuples[] operands) throws TuplesException {
// Validate "operands" parameter
if (operands == null) {
throw new IllegalArgumentException("Null \"operands\" parameter");
}
// Initialize fields
this.operands = clone(operands);
if (operands.length == 0) {
setVariables(new Variable[] {});
} else {
setVariables(operands[0].getVariables());
}
prefixDefinable = true;
for (int i = 0; i < operands.length; i++) {
prefixDefinable = prefixDefinable &&
(operands[i].getAnnotation(DefinablePrefixAnnotation.class) != null);
}
}
/**
* Copy constructor. Used for cloning.
* @param orderedAppend Original object to clone.
*/
private OrderedAppend(OrderedAppend orderedAppend) {
operands = clone(orderedAppend.operands);
setVariables(orderedAppend.getVariables());
prefix = orderedAppend.prefix;
}
/**
* Gets the ColumnValue attribute of the OrderedAppend object
*
* @param column PARAMETER TO DO
* @return The ColumnValue value
* @throws TuplesException EXCEPTION TO DO
*/
public long getColumnValue(int column) throws TuplesException {
if (nextOperand == -1) {
throw new TuplesException("No row");
}
if ((column < 0) || (column >= getNumberOfVariables())) {
throw new TuplesException("Invalid column: " + column);
}
return operands[nextOperand].getColumnValue(column);
}
/**
* Gets the RowCount attribute of the OrderedAppend object
*
* @return The RowCount value
* @throws TuplesException EXCEPTION TO DO
*/
public long getRowCount() throws TuplesException {
rowCount = 0;
for (int i = 0; i < operands.length; i++) {
rowCount += operands[i].getRowCount();
if (rowCount < 0)
return Long.MAX_VALUE;
}
return rowCount;
}
public long getRowUpperBound() throws TuplesException {
long bound = 0;
for (int i = 0; i < operands.length; i++) {
bound += operands[i].getRowUpperBound();
if (bound < 0)
return Long.MAX_VALUE;
}
return bound;
}
public long getRowExpectedCount() throws TuplesException {
long bound = 0;
for (int i = 0; i < operands.length; i++) {
bound += operands[i].getRowExpectedCount();
if (bound < 0) return Long.MAX_VALUE;
}
return bound;
}
public boolean isEmpty() throws TuplesException {
for (Tuples op : operands) {
if (!op.isEmpty()) return false;
}
return true;
}
/**
* A column is unbound if it's unbound in any of the operands.
*/
public boolean isColumnEverUnbound(int column) throws TuplesException {
for (int i = 0; i < operands.length; i++) {
if (operands[i].isColumnEverUnbound(column)) {
return true;
}
}
return false;
}
public boolean hasNoDuplicates() {
return false;
}
/**
* @return whether any operand is unconstrained
* @throws TuplesException EXCEPTION TO DO
*/
public boolean isUnconstrained() throws TuplesException {
for (int i = 0; i < operands.length; i++) {
if (operands[i].isUnconstrained()) {
return true;
}
}
return false;
}
public List<Tuples> getOperands() {
return Arrays.asList(operands);
}
//
// Methods implementing Tuples
//
/**
* Move the cursor to before the first row, optionally with a specifies list
* of leading column values.
*
* @param prefix only iterate through product terms with the specified leading
* column values ({@link #NO_PREFIX} should be passed if all prefix
* values are desired
* @param suffixTruncation the number of trailing rows to ignore when
* determining whether a row is distinct
* @throws IllegalArgumentException if <var>prefix</var> is <code>null</code>
* @throws TuplesException EXCEPTION TO DO
*/
public void beforeFirst(long[] prefix, int suffixTruncation)
throws TuplesException {
for (int i = 0; i < operands.length; i++) {
operands[i].beforeFirst(prefix, suffixTruncation);
incompleteBitSet.set(i);
}
beforeFirst = true;
nextOperand = -1;
this.prefix = prefix;
}
/**
* METHOD TO DO
*
* @throws TuplesException EXCEPTION TO DO
*/
public void close() throws TuplesException {
close(operands);
}
/**
* Move to the next row satisfying the current prefix and suffix truncation.
* If no such row exists, return <code>false<code> and the current row
* becomes unspecified. The current row is unspecified when a tuples instance
* is created. To specify the current row, the {@link #beforeFirst()} or
* {@link #beforeFirst(long[], int)} methods must be invoked
*
* @return whether a subsequent row with the specified prefix exists
* @throws IllegalStateException if the current row is unspecified.
* @throws TuplesException EXCEPTION TO DO
*/
public boolean next() throws TuplesException {
if (beforeFirst) {
beforeFirst = false;
for (int i = 0; i < operands.length; i++) {
if (!operands[i].next()) {
incompleteBitSet.clear(i);
}
}
}
else if (nextOperand == -1) {
return false;
}
else {
if (!operands[nextOperand].next()) {
incompleteBitSet.clear(nextOperand);
}
}
nextOperand = seekLowOperand();
return nextOperand != -1;
}
/**
* Renames the variables which label the tuples if they have the "magic" names
* such as "Subject", "Predicate", "Object" and "Meta". This includes the
* operands of an ordered append.
*
* Sets the the current
*
* @param constraint PARAMETER TO DO
*/
public void renameVariables(Constraint constraint) {
//super.renameVariables(constraint);
for (int index = 0; index < operands.length; index++) {
if (logger.isDebugEnabled()) {
logger.debug("!! Renaming tuples index: " + index);
}
operands[index].renameVariables(constraint);
}
if (logger.isDebugEnabled()) {
logger.debug("!! Tuples after rename: " + this.toString());
}
}
/**
* Returns a copy of the internal variables.
*
* @return a copy of the internal variables.
*/
public Variable[] getVariables() {
// Container for our variables
ArrayList<Variable> variablesList = new ArrayList<Variable>();
// Iterate through the tuples objects and get the variables for each
for (int index = 0; index < operands.length; index++) {
// Get the array of variables for the current tuples object
Variable[] variableArray = operands[index].getVariables();
// We need to prevent duplicate variables so iterate through the new array
// and remove duplicates
for (int i = 0; i < variableArray.length; i++) {
if (!variablesList.contains(variableArray[i])) {
// If we don't have the variable already, add it
variablesList.add(variableArray[i]);
}
}
}
// Convert the list to an array
Variable[] newVariables = new Variable[variablesList.size()];
newVariables = variablesList.toArray(newVariables);
return newVariables;
}
public Annotation getAnnotation(Class<? extends Annotation> annotation) {
if (annotation == DefinablePrefixAnnotation.class && prefixDefinable) {
return new DefinablePrefixAnnotation() {
public void definePrefix(Set<Variable> boundVars) throws TuplesException {
for (int i = 0; i < operands.length; i++) {
DefinablePrefixAnnotation annotation =
(DefinablePrefixAnnotation)operands[i].getAnnotation(DefinablePrefixAnnotation.class);
// Note: this should also probably check variable orderings, but this is deferred until
// we are doing this more generally. See bug report logged in mulgara tracking system:
// http://mulgara.org/jira/browse/MGR-15
annotation.definePrefix(boundVars);
}
}
};
} else {
return null;
}
}
/**
* METHOD TO DO
*
* @return RETURNED VALUE TO DO
*/
public Object clone() {
return new OrderedAppend(this);
}
/**
* @return the index of the lowest operand, or -1 if no operand has further
* tuples
* @throws TuplesException EXCEPTION TO DO
*/
private int seekLowOperand() throws TuplesException {
// Find the first incomplete operand
int lowOperand = incompleteBitSet.nextSetBit(0);
if (lowOperand == -1) {
return -1;
}
// Find the lowest incomplete operand
for (int i = lowOperand + 1; i < operands.length; i++) {
if (incompleteBitSet.get(i) &&
(rowComparator.compare(operands[lowOperand], operands[i]) > 0)) {
lowOperand = i;
}
}
return lowOperand;
}
}