/*
* 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): N/A.
*
* [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.Cursor;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.store.statement.StatementStore;
/**
* Implement most of the convenience methods of the {@link Tuples} interface.
*
* @created 2003-01-09
*
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
*
* @version $Revision: 1.10 $
*
* @modified $Date: 2005/05/16 11:07:10 $
*
* @maintenanceAuthor $Author: amuys $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2001-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 abstract class AbstractTuples implements Tuples {
/** Logger. */
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(AbstractTuples.class.getName());
/** Empty variable array. */
private final static Variable[] emptyVariables = new Variable[] {};
/** Description of the Field */
@SuppressWarnings("unused")
private final static long[] NO_PREFIX = new long[] {};
/** Description of the Field */
private final static RowComparator defaultRowComparator = new DefaultRowComparator();
/** The variable names of each column. */
private Variable[] variables = null;
/** Cache the row count when calculated. */
protected long rowCount = -1;
protected int rowCardinality = -1;
//
// Convenience methods
//
/**
* @param tuples an array of {@link Tuples} to be cloned
* @return the array of clones
*/
protected static Tuples[] clone(Tuples[] tuples) {
Tuples[] clonedTuples = new Tuples[tuples.length];
for (int i = 0; i < tuples.length; i++) {
clonedTuples[i] = (Tuples) tuples[i].clone();
}
return clonedTuples;
}
/**
* @param tuples an array of {@link Tuples} to be closed
* @throws TuplesException EXCEPTION TO DO
*/
protected static void close(Tuples[] tuples) throws TuplesException {
for (int i = 0; i < tuples.length; i++) {
tuples[i].close();
}
}
/**
* Convert the {@link Tuples#isColumnEverUnbound} results of a
* <var>tuples</var> into an array.
*
* @param tuples an instance to copy the
* {@link Tuples#isColumnEverUnbound} values from
* @return an array indexed by column number containing flags for whether
* the corresponding columns are ever unbound
*/
public static boolean[] columnEverUnboundArray(Tuples tuples)
throws TuplesException {
boolean[] columnEverUnbound = new boolean[tuples.getNumberOfVariables()];
for (int i = 0; i < columnEverUnbound.length; i++) {
columnEverUnbound[i] = tuples.isColumnEverUnbound(i);
}
return columnEverUnbound;
}
/**
* Generate a legible representation of an array of <code>long</code>-valued
* node numbers.
*
* If the array passed is <code>null</code>, this generates the
* {@link String} <code>"NULL"</code>. Otherwise, it generates a
* space-separated sequence of numbers in square brackets. The value
* {@link Tuples#UNBOUND} is represented as a wildcard asterisk rather than
* a number.
*
* @param longArray the array to format, possibly <code>null</code>
* @return a legible representation of the <var>longArray</var>
*/
public static String toString(long[] longArray) {
if (longArray == null) {
return "NULL";
}
StringBuffer buffer = new StringBuffer("L[");
for (int i = 0; i < longArray.length; i++) {
buffer.append(" ");
if (longArray[i] == Tuples.UNBOUND) {
buffer.append("*");
}
else {
buffer.append(longArray[i]);
}
}
buffer.append(" ]");
return buffer.toString();
}
public static String toString(int[] intArray) {
if (intArray == null) {
return "NULL";
}
StringBuffer buffer = new StringBuffer("I[");
for (int i = 0; i < intArray.length; i++) {
buffer.append(" ");
buffer.append(intArray[i]);
}
buffer.append(" ]");
return buffer.toString();
}
public static String toString(boolean[] boolArray) {
if (boolArray == null) {
return "NULL";
}
StringBuffer buffer = new StringBuffer("B[");
for (int i = 0; i < boolArray.length; i++) {
buffer.append(" ");
buffer.append(boolArray[i]);
}
buffer.append(" ]");
return buffer.toString();
}
public static String toString(Variable[] varArray) {
if (varArray == null) {
return "NULL";
}
StringBuffer buffer = new StringBuffer("V[");
for (int i = 0; i < varArray.length; i++) {
buffer.append(" ");
buffer.append(varArray[i].toString());
}
buffer.append(" ]");
return buffer.toString();
}
//
// Methods implementing Tuples
//
/**
* Gets the ColumnValue attribute of the AbstractTuples object
*
* @param column The column offset to get data from
* @return The ColumnValue value
* @throws TuplesException If there was a Tuples specific error accessing the data.
*/
public abstract long getColumnValue(int column) throws TuplesException;
/**
* Gets the raw (unfiltered) ColumnValue attribute of the AbstractTuples object.
* By default this returns the normal column value.
*
* @param column The column offset to get data from
* @return The column value as a gNode
* @throws TuplesException If there was a Tuples specific error accessing the data.
*/
public long getRawColumnValue(int column) throws TuplesException {
return getColumnValue(column);
}
/**
* Returns a copy of the internal variables.
*
* @return a copy of the internal variables.
*/
public Variable[] getVariables() {
Variable[] newVariables = null;
if (variables != null) {
newVariables = new Variable[variables.length];
System.arraycopy(variables, 0, newVariables, 0, variables.length);
} else {
newVariables = emptyVariables;
}
return newVariables;
}
/**
* Returns the number of variables in a tuples.
*
* @return the number of variables in a tuples.
*/
public int getNumberOfVariables() {
int noVars = 0;
if (variables != null) {
noVars = variables.length;
}
return noVars;
}
/**
* Gets the RowCount attribute of the AbstractTuples object
*
* @return The RowCount value
* @throws TuplesException EXCEPTION TO DO
*/
public long getRowCount() throws TuplesException {
if (rowCount != -1) {
return rowCount;
} else if (isMaterialized()) {
return getRowUpperBound(); // Upper bound is accurate if tuples is materialized.
}
Tuples temp = (Tuples)this.clone();
rowCount = 0;
temp.beforeFirst();
while (temp.next()) {
rowCount++;
}
temp.close();
return rowCount;
}
public abstract long getRowUpperBound() throws TuplesException;
public abstract long getRowExpectedCount() throws TuplesException;
public int getRowCardinality() throws TuplesException {
if (rowCardinality != -1) return rowCardinality;
if (rowCount > 1) {
rowCardinality = Cursor.MANY;
} else {
switch ((int) rowCount) {
case 0:
rowCardinality = Cursor.ZERO;
break;
case 1:
rowCardinality = Cursor.ONE;
break;
case -1:
Tuples temp = (Tuples)this.clone();
temp.beforeFirst();
if (!temp.next()) {
rowCount = 0;
rowCardinality = Cursor.ZERO;
} else if (!temp.next()) {
rowCount = 1;
rowCardinality = Cursor.ONE;
} else {
rowCardinality = Cursor.MANY;
}
temp.close();
break;
default:
throw new TuplesException("Illegal rowCount " + rowCount);
}
}
return rowCardinality;
}
/**
* Returns true if the number of rows is zero.
*
* @return true if the number of rows is zero.
* @throws TuplesException EXCEPTION TO DO
*/
public boolean isEmpty() throws TuplesException {
return getRowCardinality() == ZERO;
}
/**
* Gets the ColumnIndex attribute of the AbstractTuples object
*
* @param variable PARAMETER TO DO
* @return The ColumnIndex value
* @throws TuplesException EXCEPTION TO DO
*/
public int getColumnIndex(Variable variable) throws TuplesException {
if (variable == null) {
throw new IllegalArgumentException("Null \"variable\" parameter");
}
Variable[] variables = getVariables();
for (int i = 0; i < variables.length; i++) {
if (variables[i].equals(variable)) return i;
}
throw new TuplesException("No such variable " + variable + " in tuples " +
Arrays.asList(variables) + " (" + getClass() + ")");
}
public abstract boolean isColumnEverUnbound(int column) throws TuplesException;
/**
* Gets the Materialized attribute of the AbstractTuples object
*
* @return The Materialized value
*/
public boolean isMaterialized() {
return false;
}
/**
* Gets the Unconstrained attribute of the AbstractTuples object
*
* @return The Unconstrained value
* @throws TuplesException EXCEPTION TO DO
*/
public boolean isUnconstrained() throws TuplesException {
return (getNumberOfVariables() == 0) && (getRowCardinality() > Cursor.ZERO);
}
/**
* @return <code>null</code>, indicating unordered {@link Tuples}
*/
public RowComparator getComparator() {
return (getNumberOfVariables() == 0) ? defaultRowComparator : null;
}
//
// Methods implementing Tuples interface
//
/**
* 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 abstract void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException;
/**
* Convenience method for the usual case of wanting to reset a tuples to
* iterate through every single element. Equivalent to {@link
* #beforeFirst(long[], int)}<code>({@link #NO_PREFIX}, 0)</code>.
*
* @throws TuplesException EXCEPTION TO DO
*/
public void beforeFirst() throws TuplesException {
beforeFirst(Tuples.NO_PREFIX, 0);
}
/**
* 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 abstract boolean next() throws TuplesException;
/**
* METHOD TO DO
*
* @throws TuplesException EXCEPTION TO DO
*/
public abstract void close() throws TuplesException;
/**
* Renames the variables which label the tuples if they have the "magic" names
* such as "Subject", "Predicate", "Object" and "Meta".
*
* @param constraint PARAMETER TO DO
*/
public void renameVariables(Constraint constraint) {
if (variables != null) {
loop:for (int i = 0; i < variables.length; ++i) {
Variable v = variables[i];
for (int j = 0; j < 4; ++j) {
// v will be a reference to one of the objects in Graph.VARIABLES[].
if (v.equals(StatementStore.VARIABLES[j])) {
// The array obtained from getVariables() is modifiable.
variables[i] = (Variable) constraint.getElement(j);
continue loop;
}
}
throw new Error("Unexpected variable: " + v);
}
}
}
/**
* Clone this tuples.
*
* @return A new instance euqivalent to the current Tuples.
*/
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(getClass() + " doesn't support clone, which it must", e);
}
}
/**
* Tests equality of the given object.
*
* @param o the object to compare.
* @return if the object is equal by row number, variables and row.
*/
public boolean equals(Object o) {
return (o instanceof Tuples) && equals(this, (Tuples)o);
}
/**
* Added to match {@link #equals(Object)}.
* Builds code based on content.
*/
public int hashCode() {
return TuplesOperations.hashCode(this);
}
/**
* Tests if two tuples are identical
* @param first The first tuples to test.
* @param second The second tuples to test.
* @return true if both tuples compare equal by row number, variables and by row.
*/
public static boolean equals(Tuples first, Tuples second) {
boolean isEqual = false;
if (first == second) return true;
if (first == null || second == null) return false;
try {
// Ensure that the row count is the same
if (first.getRowCount() == second.getRowCount()) {
// Ensure that the variable lists are equal
if (Arrays.asList(first.getVariables()).equals(
Arrays.asList(second.getVariables()))) {
// Clone tuples to be compared
Tuples t1 = (Tuples) first.clone();
Tuples t2 = (Tuples) second.clone();
TuplesException te = null;
try {
// Put them at the start.
t1.beforeFirst();
t2.beforeFirst();
boolean finished = false;
boolean tuplesEqual = true;
// Repeat until there are no more rows or we find an unequal row.
while (!finished) {
// Assume that if t1 has next so does t2.
finished = !t1.next();
t2.next();
// If we're not finished compare the row.
if (!finished) {
// Check if the elements in both rows are equal.
for (int variableIndex = 0;
variableIndex < t1.getNumberOfVariables();
variableIndex++) {
// If they're not equal quit the loop and set tuplesEqual to
// false.
if (t1.getColumnValue(variableIndex) !=
t2.getColumnValue(variableIndex)) {
tuplesEqual = false;
finished = true;
}
}
}
}
isEqual = tuplesEqual;
} catch (TuplesException e) {
te = e;
} finally {
try {
t1.close();
} catch (TuplesException e) {
if (te == null) te = e;
} finally {
try {
t2.close();
} catch (TuplesException e2) {
if (te == null) te = e2;
}
}
}
if (te != null) throw te;
}
}
} catch (TuplesException ex) {
throw new RuntimeException(ex.toString(), ex);
}
return isEqual;
}
/**
* Output the contents of this tuples in a string.
*
* @return The string representing the tuples.
*/
public String toString() {
return SimpleTuplesFormat.format(this);
}
/**
* Sets the internal representation of the variables from the list given.
*
* @param variableList the list containing variables.
*/
protected void setVariables(List<Variable> variableList) {
variables = variableList.toArray(new Variable[variableList.size()]);
}
/**
* Sets the internal representation of the variables from an existing array.
*
* @param variableArray the array containing variables.
*/
protected void setVariables(Variable[] variableArray) {
variables = variableArray;
}
public Annotation getAnnotation(Class<? extends Annotation> annotationClass) throws TuplesException {
return null;
}
}