/*
* 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.statement.xa;
import java.io.*;
import java.nio.*;
// Java 2 standard packages
import java.util.*;
// Third party packages
import org.apache.log4j.Logger;
// Locally written packages
import org.mulgara.query.*;
import org.mulgara.store.nodepool.*;
import org.mulgara.store.statement.*;
import org.mulgara.store.tuples.StoreTuples;
import org.mulgara.store.tuples.TuplesOperations;
import org.mulgara.store.xa.AbstractBlockFile;
import org.mulgara.store.xa.Block;
import org.mulgara.store.xa.BlockFile;
import org.mulgara.store.xa.LockFile;
import org.mulgara.store.xa.PersistableMetaRoot;
import org.mulgara.store.xa.SimpleXAResourceException;
import org.mulgara.store.xa.XAStatementStore;
import org.mulgara.store.xa.XAUtils;
import org.mulgara.util.Constants;
/**
* An implementation of {@link StatementStore}.
*
* @created 2001-10-12
*
* @author David Makepeace
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/02/22 08:16:34 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright ©2001-2004 <a href="http://www.pisoftware.com/">Plugged In
* Software Pty Ltd</a>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public final class XAStatementStoreImpl implements XAStatementStore {
/**
* Logger.
*/
private final static Logger logger = Logger.getLogger(XAStatementStoreImpl.class);
final static boolean RELEASE_NODE_LISTENERS_ENABLED = false;
final static long NONE = NodePool.NONE;
/**
* Description of the Field
*/
final static int TI_0123 = 0;
/**
* Description of the Field
*/
final static int TI_1203 = 1;
/**
* Description of the Field
*/
final static int TI_2013 = 2;
/**
* Description of the Field
*/
final static int TI_3012 = 3;
/**
* Description of the Field
*/
final static int TI_3120 = 4;
/**
* Description of the Field
*/
final static int TI_3201 = 5;
/**
* Description of the Field
*/
final static int NR_INDEXES = 6;
/**
* Description of the Field
*/
private final static int[][] orders = {
{0, 1, 2, 3},
{1, 2, 0, 3},
{2, 0, 1, 3},
{3, 0, 1, 2},
{3, 1, 2, 0},
{3, 2, 0, 1}
};
private final static int[] selectIndex = {
/* XXXX */ TI_0123,
/* XXX0 */ TI_0123,
/* XX1X */ TI_1203,
/* XX10 */ TI_0123,
/* X2XX */ TI_2013,
/* X2X0 */ TI_2013,
/* X21X */ TI_1203,
/* X210 */ TI_0123,
/* 3XXX */ TI_3012,
/* 3XX0 */ TI_3012,
/* 3X1X */ TI_3120,
/* 3X10 */ TI_3012,
/* 32XX */ TI_3201,
/* 32X0 */ TI_3201,
/* 321X */ TI_3120,
/* 3210 */ TI_0123
};
/**
* Description of the Field
*/
private final static int FILE_MAGIC = 0xa5e7f2e1;
/**
* Description of the Field
*/
private final static int FILE_VERSION = 8;
/**
* Index of the file magic number within each of the two on-disk metaroots.
*/
private final static int IDX_MAGIC = 0;
/**
* Index of the file version number within each of the two on-disk metaroots.
*/
private final static int IDX_VERSION = 1;
/**
* Index of the valid flag (in ints) within each of the two on-disk metaroots.
*/
private final static int IDX_VALID = 2;
/**
* The index of the phase number in the on-disk phase.
*/
private final static int IDX_PHASE_NUMBER = 3;
/**
* The size of the header of a metaroot in ints.
*/
private final static int HEADER_SIZE_INTS = 4;
/**
* The size of the header of a metaroot in longs.
*/
private final static int HEADER_SIZE_LONGS = (HEADER_SIZE_INTS + 1) / 2;
/**
* The size of a metaroot in longs.
*/
private final static int METAROOT_SIZE = HEADER_SIZE_LONGS +
Phase.RECORD_SIZE;
/**
* The number of metaroots in the metaroot file.
*/
private final static int NR_METAROOTS = 2;
/**
* Description of the Field
*/
private final static int MASK0 = 1;
/**
* Description of the Field
*/
private final static int MASK1 = 2;
/**
* Description of the Field
*/
private final static int MASK2 = 4;
/**
* Description of the Field
*/
private final static int MASK3 = 8;
/**
* The name of the triple store which forms the base name for the graph files.
*/
private String fileName;
/**
* The LockFile that protects the graph from being opened twice.
*/
private LockFile lockFile;
/**
* The BlockFile for the node pool metaroot file.
*/
private BlockFile metarootFile = null;
/**
* The metaroot blocks of the metaroot file.
*/
private Block[] metarootBlocks = new Block[NR_METAROOTS];
/**
* Description of the Field
*/
private boolean wrongFileVersion = false;
/**
* Description of the Field
*/
private TripleAVLFile[] tripleAVLFiles = new TripleAVLFile[NR_INDEXES];
/**
* Description of the Field
*/
private Phase currentPhase = null;
/**
* Determines if modifications can be performed without creating a new
* (in-memory) phase. If dirty is false and the current phase is in use (by
* unclosed Tupleses) then a new phase must be created to protect the existing
* Tupleses before any further modifications are made.
*/
private boolean dirty = true;
/**
* Description of the Field
*/
private int phaseIndex = 0;
/**
* Description of the Field
*/
private int phaseNumber = 0;
/**
* Description of the Field
*/
private Phase.Token committedPhaseToken = null;
private Object committedPhaseLock = new Object();
/**
* Description of the Field
*/
private Phase.Token recordingPhaseToken = null;
/**
* Description of the Field
*/
private boolean prepared = false;
/**
* Description of the Field
*/
private List<ReleaseNodeListener> releaseNodeListeners = new ArrayList<ReleaseNodeListener>();
//private XANodePoolImpl nodePool = null;
/**
* CONSTRUCTOR XAGraphImpl TO DO
*
* @param fileName PARAMETER TO DO
* @throws IOException EXCEPTION TO DO
*/
public XAStatementStoreImpl(String fileName) throws IOException {
this.fileName = fileName;
lockFile = LockFile.createLockFile(fileName + ".g.lock");
try {
// Check that the metaroot file was created with a compatible version
// of the triplestore.
RandomAccessFile metarootRAF = null;
try {
metarootRAF = new RandomAccessFile(fileName + ".g", "r");
if (metarootRAF.length() >= 2 * Constants.SIZEOF_INT) {
int fileMagic = metarootRAF.readInt();
int fileVersion = metarootRAF.readInt();
if (AbstractBlockFile.byteOrder != ByteOrder.BIG_ENDIAN) {
fileMagic = XAUtils.bswap(fileMagic);
fileVersion = XAUtils.bswap(fileVersion);
}
wrongFileVersion =
fileMagic != FILE_MAGIC || fileVersion != FILE_VERSION;
} else {
wrongFileVersion = false;
}
} catch (FileNotFoundException ex) {
wrongFileVersion = false;
} finally {
if (metarootRAF != null) {
metarootRAF.close();
}
}
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFiles[i] = new TripleAVLFile(
fileName + ".g_" + orders[i][0] + orders[i][1] + orders[i][2] + orders[i][3],
orders[i]
);
}
} catch (IOException ex) {
try {
close();
} catch (StatementStoreException ex2) {
logger.info("Exception closing failed XAStatementStoreImpl", ex2);
}
throw ex;
}
}
/**
* Returns <code>true</code> if there are no triples in the graph
*
* @return <code>true</code> if there are no triples in the graph
*/
public synchronized boolean isEmpty() {
checkInitialized();
return currentPhase.isEmpty();
}
/**
* Returns a count of the number of triples in the graph
*
* @return a count of the number of triples in the graph
*/
public synchronized long getNrTriples() {
checkInitialized();
return currentPhase.getNrTriples();
}
/**
* Gets the PhaseNumber attribute of the XAGraphImpl object
*
* @return The PhaseNumber value
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized int getPhaseNumber() throws SimpleXAResourceException {
checkInitialized();
return phaseNumber;
}
/**
* Adds a feature to the ReleaseNodeListener attribute of the XAGraphImpl
* object
*
* @param l The feature to be added to the ReleaseNodeListener attribute
*/
public synchronized void addReleaseNodeListener(ReleaseNodeListener l) {
if (!releaseNodeListeners.contains(l)) {
releaseNodeListeners.add(l);
}
}
/**
* METHOD TO DO
*
* @param l PARAMETER TO DO
*/
public synchronized void removeReleaseNodeListener(ReleaseNodeListener l) {
releaseNodeListeners.remove(l);
}
/**
* Adds a new triple to the graph if it doesn't already exist.
*
* @param node0 the first element of the new triple
* @param node1 the second element of the new triple
* @param node2 the third element of the new triple
* @param node3 the fourth element of the new triple
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized void addTriple(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
checkInitialized();
if (
node0 < NodePool.MIN_NODE ||
node1 < NodePool.MIN_NODE ||
node2 < NodePool.MIN_NODE ||
node3 < NodePool.MIN_NODE
) {
throw new StatementStoreException(
"Attempt to add a triple with node number out of range: " +
node0 + " " + node1 + " " + node2 + " " + node3
);
}
if (!dirty && currentPhase.isInUse()) {
try {
new Phase();
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
}
currentPhase.addTriple(node0, node1, node2, node3);
}
/**
* Removes all triples matching the given specification.
*
* @param node0 the value for the first element of the triples
* @param node1 the value for the second element of the triples
* @param node2 the value for the third element of the triples
* @param node3 the value for the fourth element of the triples
* @throws StatementStoreException if something exceptional happens
*/
public synchronized void removeTriples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
checkInitialized();
if (node0 != NONE && node1 != NONE && node2 != NONE && node3 != NONE) {
if (!dirty && currentPhase.isInUse()) {
try {
new Phase();
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
}
// Remove the triple.
currentPhase.removeTriple(node0, node1, node2, node3);
} else {
// Find all the tuples matching the specification and remove them.
StoreTuples tuples = currentPhase.findTuples(node0, node1, node2, node3);
try {
try {
if (!tuples.isEmpty()) {
// There is at least one triple to remove so protect the
// Tuples as we make changes to the triplestore.
try {
new Phase();
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
long[] triple = new long[] { node0, node1, node2, node3 };
int[] columnMap = tuples.getColumnOrder();
int nrColumns = columnMap.length;
tuples.beforeFirst();
while (tuples.next()) {
// Copy the row data over to the triple.
for (int col = 0; col < nrColumns; ++col) {
triple[columnMap[col]] = tuples.getColumnValue(col);
}
currentPhase.removeTriple(
triple[0], triple[1], triple[2], triple[3]
);
}
}
} finally {
tuples.close();
}
} catch (TuplesException ex) {
throw new StatementStoreException(
"Exception while iterating over temporary Tuples.", ex
);
}
}
}
/**
* Finds triples matching the given specification.
*
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A set of all the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized StoreTuples findTuples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
checkInitialized();
dirty = false;
return currentPhase.findTuples(node0, node1, node2, node3);
}
/**
* Finds triples matching the given specification and index mask.
*
* @param mask The mask of the index to use. This is only allowable for 3 variables
* and a given graph.
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A set of all the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized StoreTuples findTuples(
int mask, long node0, long node1, long node2, long node3
) throws StatementStoreException {
checkInitialized();
dirty = false;
if (!checkMask(mask, node0, node1, node2, node3)) throw new StatementStoreException("Bad explicit index selection for given node pattern.");
return currentPhase.findTuples(mask, node0, node1, node2, node3);
}
/**
* Tests a mask for consistency against the nodes it will be used to find.
* @param mask The mask to test.
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return <code>true</code> if the mask is consistent with the given nodes.
*/
private static boolean checkMask(int mask, long node0, long node1, long node2, long node3) {
if (node0 != NONE && 0 == (mask & MASK0)) return false;
if (node1 != NONE && 0 == (mask & MASK1)) return false;
if (node2 != NONE && 0 == (mask & MASK2)) return false;
if (node3 != NONE && 0 == (mask & MASK3)) return false;
return true;
}
/**
* Returns a StoreTuples which contains all triples in the store. The
* parameters provide a hint about how the StoreTuples will be used. This
* information is used to select the index from which the StoreTuples will be
* obtained.
*
* @param node0Bound specifies that node0 will be bound
* @param node1Bound specifies that node1 will be bound
* @param node2Bound specifies that node2 will be bound
* @param node3Bound specifies that node3 will be bound
* @return the {@link StoreTuples}
* @throws StatementStoreException if something exceptional happens
*/
public synchronized StoreTuples findTuples(
boolean node0Bound, boolean node1Bound, boolean node2Bound,
boolean node3Bound
) throws StatementStoreException {
checkInitialized();
dirty = false;
return currentPhase.findTuples(node0Bound, node1Bound, node2Bound, node3Bound);
}
/**
* Returns <code>true</code> if any triples match the given specification.
* Allows wild cards StatementStore.NONE for any of the node numbers.
*
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return <code>true</code> if any matching triples exist in the graph.
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized boolean existsTriples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
checkInitialized();
return currentPhase.existsTriples(node0, node1, node2, node3);
}
public XAStatementStore newReadOnlyStatementStore() {
return new ReadOnlyGraph();
}
public XAStatementStore newWritableStatementStore() {
return this;
}
/**
* Close all files, removing empty space from the ends as required.
*
* @throws StatementStoreException if an error occurs while truncating,
* flushing or closing one of the three files.
*/
public synchronized void close() throws StatementStoreException {
try {
unmap();
} finally {
IOException savedEx = null;
for (int i = 0; i < NR_INDEXES; ++i) {
try {
if (tripleAVLFiles[i] != null) {
tripleAVLFiles[i].close();
}
} catch (IOException ex) {
if (savedEx == null) savedEx = ex;
}
}
if (metarootFile != null) {
try {
metarootFile.close();
} catch (IOException ex) {
if (savedEx == null) savedEx = ex;
}
}
try {
if (lockFile != null) {
lockFile.release();
lockFile = null;
}
} catch (Exception e) {
// only unchecked exceptions may arrive here
// Existing I/O exceptions to take priority
if (savedEx == null) throw new StatementStoreException("Error releasing lock file", e);
}
if (savedEx != null) {
throw new StatementStoreException("I/O error closing graph.", savedEx);
}
}
}
/**
* Close this graph, if it is currently open, and remove all files associated
* with it.
*
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized void delete() throws StatementStoreException {
currentPhase = null;
try {
unmap();
} finally {
IOException savedEx = null;
for (int i = 0; i < NR_INDEXES; ++i) {
try {
if (tripleAVLFiles[i] != null) tripleAVLFiles[i].delete();
} catch (IOException ex) {
savedEx = ex;
}
}
if (metarootFile != null) {
try {
metarootFile.delete();
} catch (IOException ex) {
savedEx = ex;
}
}
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFiles[i] = null;
}
metarootFile = null;
try {
if (lockFile != null) {
lockFile.release();
lockFile = null;
}
} catch (Exception e) {
// only unchecked exceptions may arrive here
// Existing I/O exceptions to take priority
if (savedEx == null) throw new StatementStoreException("Error releasing lock file", e);
}
if (savedEx != null) {
throw new StatementStoreException("I/O error deleting graph.", savedEx);
}
}
}
protected void finalize() throws Throwable {
// close the statement store if it has not already been closed explicitly.
try {
close();
} catch (Throwable t) {
logger.warn(
"Exception in finalize while trying to close the statement store.", t
);
} finally {
super.finalize();
}
}
/**
* METHOD TO DO
*/
public void release() {
// NO-OP
if (logger.isDebugEnabled()) {
logger.debug("Release " + this.getClass() + ":" + System.identityHashCode(this));
}
}
/**
* METHOD TO DO
*/
public void refresh() {
if (logger.isDebugEnabled()) {
logger.debug("Refresh " + this.getClass() + ":" + System.identityHashCode(this));
}
// NO-OP
}
//
// Methods from SimpleXAResource.
//
/**
* METHOD TO DO
*
* @param phaseNumber PARAMETER TO DO
* @throws IOException EXCEPTION TO DO
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void clear(int phaseNumber)
throws IOException, SimpleXAResourceException
{
if (logger.isDebugEnabled()) {
logger.debug("Clear(" + phaseNumber + ") " +
this.getClass() + ":" + System.identityHashCode(this));
}
if (currentPhase != null) {
throw new IllegalStateException("Graph already has a current phase.");
}
openMetarootFile(true);
synchronized (committedPhaseLock) {
committedPhaseToken = new Phase().use();
}
this.phaseNumber = phaseNumber;
phaseIndex = 1;
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFiles[i].clear();
}
new Phase();
}
/**
* METHOD TO DO
*
* @throws IOException EXCEPTION TO DO
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void clear() throws IOException, SimpleXAResourceException {
if (logger.isDebugEnabled()) {
logger.debug("Clear " + this.getClass() + ":" + System.identityHashCode(this));
}
if (currentPhase == null) {
clear(0);
}
// TODO - should throw an exception if clear() is called after any other
// operations are performed. Calling clear() multiple times should be
// permitted.
}
/**
* METHOD TO DO
*
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void prepare() throws SimpleXAResourceException {
if (logger.isDebugEnabled()) {
logger.debug("Prepare " + this.getClass() + ":" + System.identityHashCode(this));
}
checkInitialized();
if (prepared) {
// prepare already performed.
throw new SimpleXAResourceException("prepare() called twice.");
}
try {
// Perform a prepare.
recordingPhaseToken = currentPhase.use();
Phase recordingPhase = currentPhase;
new Phase();
// Ensure that all data associated with the phase is on disk.
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFiles[i].force();
}
// Write the metaroot.
int newPhaseIndex = 1 - phaseIndex;
int newPhaseNumber = phaseNumber + 1;
Block block = metarootBlocks[newPhaseIndex];
block.putInt(IDX_VALID, 0); // should already be invalid.
block.putInt(IDX_PHASE_NUMBER, newPhaseNumber);
logger.debug("Writing graph metaroot for phase: " + newPhaseNumber);
recordingPhase.writeToBlock(block, HEADER_SIZE_LONGS);
block.write();
metarootFile.force();
block.putInt(IDX_VALID, 1);
block.write();
metarootFile.force();
phaseIndex = newPhaseIndex;
phaseNumber = newPhaseNumber;
prepared = true;
} catch (IOException ex) {
logger.error("I/O error while performing prepare.", ex);
throw new SimpleXAResourceException(
"I/O error while performing prepare.", ex
);
} finally {
if (!prepared) {
// Something went wrong!
logger.error("Prepare failed.");
if (recordingPhaseToken != null) {
recordingPhaseToken.release();
recordingPhaseToken = null;
}
}
}
}
/**
* METHOD TO DO
*
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void commit() throws SimpleXAResourceException {
if (logger.isDebugEnabled()) {
logger.debug("Commit " + this.getClass() + ":" + System.identityHashCode(this));
}
if (!prepared) {
// commit without prepare.
throw new SimpleXAResourceException(
"commit() called without previous prepare()."
);
}
// Perform a commit.
try {
// Invalidate the metaroot of the old phase.
Block block = metarootBlocks[1 - phaseIndex];
block.putInt(IDX_VALID, 0);
block.write();
metarootFile.force();
// Release the token for the previously committed phase.
synchronized (committedPhaseLock) {
if (committedPhaseToken != null) {
committedPhaseToken.release();
}
committedPhaseToken = recordingPhaseToken;
}
recordingPhaseToken = null;
} catch (IOException ex) {
logger.fatal("I/O error while performing commit.", ex);
throw new SimpleXAResourceException(
"I/O error while performing commit.", ex
);
} finally {
prepared = false;
if (recordingPhaseToken != null) {
// Something went wrong!
recordingPhaseToken.release();
recordingPhaseToken = null;
logger.error("Commit failed. Calling close().");
try {
close();
} catch (Throwable t) {
logger.error("Exception on forced close()", t);
}
}
}
}
/**
* METHOD TO DO
*
* @return RETURNED VALUE TO DO
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized int[] recover() throws SimpleXAResourceException {
if (logger.isDebugEnabled()) {
logger.debug("Recover " + this.getClass() + ":" + System.identityHashCode(this));
}
if (currentPhase != null) {
return new int[0];
}
if (wrongFileVersion) {
throw new SimpleXAResourceException("Wrong metaroot file version.");
}
try {
openMetarootFile(false);
} catch (IOException ex) {
throw new SimpleXAResourceException("I/O error", ex);
}
// Count the number of valid phases.
int phaseCount = 0;
if (metarootBlocks[0].getInt(IDX_VALID) != 0) {
++phaseCount;
}
if (metarootBlocks[1].getInt(IDX_VALID) != 0) {
++phaseCount;
}
// Read the phase numbers.
int[] phaseNumbers = new int[phaseCount];
int index = 0;
if (metarootBlocks[0].getInt(IDX_VALID) != 0) {
phaseNumbers[index++] = metarootBlocks[0].getInt(IDX_PHASE_NUMBER);
}
if (metarootBlocks[1].getInt(IDX_VALID) != 0) {
phaseNumbers[index++] = metarootBlocks[1].getInt(IDX_PHASE_NUMBER);
}
return phaseNumbers;
}
/**
* @param phaseNumber PARAMETER TO DO
* @throws IOException EXCEPTION TO DO
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void selectPhase(int phaseNumber)
throws IOException, SimpleXAResourceException
{
if (logger.isDebugEnabled()) {
logger.debug("SelectPhase(" + phaseNumber + ") " +
this.getClass() + ":" + System.identityHashCode(this));
}
if (currentPhase != null) {
throw new SimpleXAResourceException(
"selectPhase() called on initialized Graph."
);
}
if (metarootFile == null) {
throw new SimpleXAResourceException("Graph metaroot file is not open.");
}
// Locate the metaroot corresponding to the given phase number.
if (
metarootBlocks[0].getInt(IDX_VALID) != 0 &&
metarootBlocks[0].getInt(IDX_PHASE_NUMBER) == phaseNumber
) {
phaseIndex = 0;
// A new phase will be saved in the other metaroot.
} else if (
metarootBlocks[1].getInt(IDX_VALID) != 0 &&
metarootBlocks[1].getInt(IDX_PHASE_NUMBER) == phaseNumber
) {
phaseIndex = 1;
// A new phase will be saved in the other metaroot.
} else {
throw new SimpleXAResourceException(
"Invalid phase number: " + phaseNumber
);
}
// Load a duplicate of the selected phase. The duplicate will have a
// phase number which is one higher than the original phase.
try {
synchronized (committedPhaseLock) {
committedPhaseToken = new Phase(
metarootBlocks[phaseIndex], HEADER_SIZE_LONGS
).use();
}
this.phaseNumber = phaseNumber;
} catch (IllegalStateException ex) {
throw new SimpleXAResourceException(
"Cannot construct initial phase.", ex
);
}
new Phase();
// Invalidate the on-disk metaroot that the new phase will be saved to.
Block block = metarootBlocks[1 - phaseIndex];
block.putInt(IDX_VALID, 0);
block.write();
metarootFile.force();
}
/**
* METHOD TO DO
*
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
public synchronized void rollback() throws SimpleXAResourceException {
if (logger.isDebugEnabled()) {
logger.debug("Rollback " + this.getClass() + ":" + System.identityHashCode(this));
}
checkInitialized();
try {
if (prepared) {
// Restore phaseIndex and phaseNumber to their previous values.
phaseIndex = 1 - phaseIndex;
--phaseNumber;
recordingPhaseToken = null;
prepared = false;
// Invalidate the metaroot of the other phase.
Block block = metarootBlocks[1 - phaseIndex];
block.putInt(IDX_VALID, 0);
block.write();
metarootFile.force();
}
} catch (IOException ex) {
throw new SimpleXAResourceException(
"I/O error while performing rollback (invalidating metaroot)", ex
);
} finally {
try {
new Phase(committedPhaseToken.getPhase());
} catch (IOException ex) {
throw new SimpleXAResourceException(
"I/O error while performing rollback (new committed phase)", ex
);
}
}
}
/**
* METHOD TO DO
*
* @return RETURNED VALUE TO DO
*/
public synchronized String toString() {
if (currentPhase == null) {
return "Uninitialized Graph.";
}
return currentPhase.toString();
}
/*
* Sets the NodePool attribute of the XAStatementStoreImpl object
*
* @param nodePool The new NodePool value
*
public void setNodePool(XANodePoolImpl nodePool) {
this.nodePool = nodePool;
}
*/
/**
* METHOD TO DO
*/
public synchronized void unmap() {
if (committedPhaseToken != null) {
recordingPhaseToken = null;
prepared = false;
try {
new Phase(committedPhaseToken.getPhase());
} catch (Throwable t) {
logger.warn("Exception while rolling back in unmap()", t);
}
currentPhase = null;
synchronized (committedPhaseLock) {
committedPhaseToken.release();
committedPhaseToken = null;
}
}
if (tripleAVLFiles != null) {
for (int i = 0; i < NR_INDEXES; ++i) {
if (tripleAVLFiles[i] != null) {
tripleAVLFiles[i].unmap();
}
}
}
if (metarootFile != null) {
if (metarootBlocks[0] != null) metarootBlocks[0] = null;
if (metarootBlocks[1] != null) metarootBlocks[1] = null;
metarootFile.unmap();
}
}
/**
* METHOD TO DO
*
* @return RETURNED VALUE TO DO
*/
synchronized long checkIntegrity() {
checkInitialized();
return currentPhase.checkIntegrity();
}
/**
* METHOD TO DO
*
* @param node PARAMETER TO DO
* @throws Exception EXCEPTION TO DO
*/
private void notifyReleaseNodeListeners(long node) throws Exception {
for (ReleaseNodeListener l: releaseNodeListeners) l.releaseNode(node);
}
/**
* METHOD TO DO
*
* @param clear PARAMETER TO DO
* @throws IOException EXCEPTION TO DO
* @throws SimpleXAResourceException EXCEPTION TO DO
*/
private void openMetarootFile(boolean clear)
throws IOException, SimpleXAResourceException {
if (metarootFile == null) {
metarootFile = AbstractBlockFile.openBlockFile(
fileName + ".g", METAROOT_SIZE * Constants.SIZEOF_LONG,
BlockFile.IOType.EXPLICIT
);
long nrBlocks = metarootFile.getNrBlocks();
if (nrBlocks != NR_METAROOTS) {
if (nrBlocks > 0) {
logger.info(
"Graph metaroot file for triple store \"" + fileName +
"\" has invalid number of blocks: " + nrBlocks
);
if (nrBlocks < NR_METAROOTS) {
clear = true;
metarootFile.clear();
}
} else {
// Perform initialization on empty file.
clear = true;
}
metarootFile.setNrBlocks(NR_METAROOTS);
}
metarootBlocks[0] = metarootFile.readBlock(0);
metarootBlocks[1] = metarootFile.readBlock(1);
}
if (clear) {
// Invalidate the metaroots on disk.
metarootBlocks[0].putInt(IDX_MAGIC, FILE_MAGIC);
metarootBlocks[0].putInt(IDX_VERSION, FILE_VERSION);
metarootBlocks[0].putInt(IDX_VALID, 0);
metarootBlocks[0].write();
metarootBlocks[1].putInt(IDX_MAGIC, 0);
metarootBlocks[1].putInt(IDX_VERSION, 0);
metarootBlocks[1].putInt(IDX_VALID, 0);
metarootBlocks[1].write();
metarootFile.force();
}
}
/**
* METHOD TO DO
*/
private void checkInitialized() {
if (currentPhase == null) {
throw new IllegalStateException(
"No current phase. " +
"Graph has not been initialized or has been closed."
);
}
}
final class ReadOnlyGraph implements XAStatementStore {
private Phase phase = null;
private Phase.Token token = null;
/**
* CONSTRUCTOR ReadOnlyGraph TO DO
*/
ReadOnlyGraph() {
synchronized (committedPhaseLock) {
if (committedPhaseToken == null) {
throw new IllegalStateException("Cannot create read only view of uninitialized Graph.");
}
}
}
public synchronized boolean isEmpty() {
return phase.isEmpty();
}
/**
* Returns a count of the number of triples in the graph
*
* @return a count of the number of triples in the graph
*/
public synchronized long getNrTriples() {
return phase.getNrTriples();
}
/**
* Adds a triple to the graph.
*
* @param node0 The 0 node of the triple.
* @param node1 The 1 node of the triple.
* @param node2 The 2 node of the triple.
* @param node3 The 3 node of the triple.
* @throws StatementStoreException EXCEPTION TO DO
*/
public void addTriple(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
throw new UnsupportedOperationException(
"Trying to modify a read-only graph."
);
}
/**
* Removes all triples matching the given specification.
*
* @param node0 the value for the first element of the triples
* @param node1 the value for the second element of the triples
* @param node2 the value for the third element of the triples
* @param node3 the value for the fourth element of the triples
* @throws StatementStoreException if something exceptional happens
*/
public void removeTriples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
throw new UnsupportedOperationException(
"Trying to modify a read-only graph."
);
}
/**
* Finds triples matching the given specification.
*
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A StoreTuples which contains the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized StoreTuples findTuples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
return phase.findTuples(node0, node1, node2, node3);
}
/**
* Finds triples matching the given specification.
*
* @param mask The mask of the index to use. This is only allowable for 3 variables
* and a given graph.
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A StoreTuples which contains the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
public synchronized StoreTuples findTuples(
int mask, long node0, long node1, long node2, long node3
) throws StatementStoreException {
if (!checkMask(mask, node0, node1, node2, node3)) throw new StatementStoreException("Bad explicit index selection for given node pattern.");
return phase.findTuples(mask, node0, node1, node2, node3);
}
/**
* Returns a StoreTuples which contains all triples in the store. The
* parameters provide a hint about how the StoreTuples will be used. This
* information is used to select the index from which the StoreTuples will
* be obtained.
*
* @param node0Bound specifies that node0 will be bound
* @param node1Bound specifies that node1 will be bound
* @param node2Bound specifies that node2 will be bound
* @param node3Bound specifies that node3 will be bound
* @return the {@link StoreTuples}
* @throws StatementStoreException if something exceptional happens
*/
public synchronized StoreTuples findTuples(
boolean node0Bound, boolean node1Bound, boolean node2Bound,
boolean node3Bound
) throws StatementStoreException {
return phase.findTuples(node0Bound, node1Bound, node2Bound, node3Bound);
}
public synchronized boolean existsTriples(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
return phase.existsTriples(node0, node1, node2, node3);
}
public XAStatementStore newReadOnlyStatementStore() {
throw new UnsupportedOperationException();
}
public XAStatementStore newWritableStatementStore() {
throw new UnsupportedOperationException();
}
public void close() {
throw new UnsupportedOperationException("Trying to close a read-only graph.");
}
public void delete() {
throw new UnsupportedOperationException(
"Trying to delete a read-only graph."
);
}
/**
* Release the phase.
*/
public synchronized void release() {
if (logger.isDebugEnabled()) {
logger.debug("Releasing " + this.getClass() + ":" + System.identityHashCode(this));
}
try {
if (token != null) token.release();
} finally {
phase = null;
token = null;
}
}
public synchronized void refresh() {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing " + this.getClass() + ":" + System.identityHashCode(this));
}
synchronized (committedPhaseLock) {
Phase committedPhase = committedPhaseToken.getPhase();
if (phase != committedPhase) {
if (token != null) token.release();
phase = committedPhase;
token = phase.use();
}
}
}
public void addReleaseNodeListener(ReleaseNodeListener l) {
throw new UnsupportedOperationException();
}
public void removeReleaseNodeListener(ReleaseNodeListener l) {
throw new UnsupportedOperationException();
}
public void prepare() {
if (logger.isDebugEnabled()) {
logger.debug("Preparing " + this.getClass() + ":" + System.identityHashCode(this));
}
}
public void commit() {
if (logger.isDebugEnabled()) {
logger.debug("Commit " + this.getClass() + ":" + System.identityHashCode(this));
}
}
public void rollback() {
if (logger.isDebugEnabled()) {
logger.debug("Rollback " + this.getClass() + ":" + System.identityHashCode(this));
}
}
public void clear() {
if (logger.isDebugEnabled()) {
logger.debug("Clearing " + this.getClass() + ":" + System.identityHashCode(this));
}
}
public void clear(int phaseNumber) {
if (logger.isDebugEnabled()) {
logger.debug("Clearing (" + phaseNumber + ") " +
this.getClass() + ":" + System.identityHashCode(this));
}
}
public int[] recover() {
if (logger.isDebugEnabled()) {
logger.debug("Recovering " + this.getClass() + ":" + System.identityHashCode(this));
}
throw new UnsupportedOperationException("Attempting to recover ReadOnlyGraph");
}
public void selectPhase(int phaseNumber) {
if (logger.isDebugEnabled()) {
logger.debug("Selecting Phase " + this.getClass() + ":" + System.identityHashCode(this));
}
throw new UnsupportedOperationException("Attempting to selectPhase of ReadOnlyGraph");
}
public int getPhaseNumber() {
return phaseNumber;
}
/**
* Ignored for this implementation.
*/
public void initializeSystemNodes(long systemGraphNode, long rdfTypeNode, long systemGraphTypeNode) {
// do nothing
}
}
final class Phase implements PersistableMetaRoot {
final static int RECORD_SIZE = TripleAVLFile.Phase.RECORD_SIZE * NR_INDEXES;
private TripleAVLFile.Phase[] tripleAVLFilePhases =
new TripleAVLFile.Phase[NR_INDEXES];
/**
* CONSTRUCTOR Phase TO DO
*
* @throws IOException EXCEPTION TO DO
*/
Phase() throws IOException {
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase();
}
currentPhase = this;
dirty = true;
}
/**
* CONSTRUCTOR Phase TO DO
*
* @throws IOException EXCEPTION TO DO
*/
Phase(Phase p) throws IOException {
assert p != null;
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase(
p.tripleAVLFilePhases[i]
);
}
currentPhase = this;
dirty = true;
}
/**
* CONSTRUCTOR Phase TO DO
*
* @param b PARAMETER TO DO
* @param offset PARAMETER TO DO
* @throws IOException EXCEPTION TO DO
*/
Phase(Block b, int offset) throws IOException {
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i] = tripleAVLFiles[i].new Phase(b, offset);
offset += TripleAVLFile.Phase.RECORD_SIZE;
}
currentPhase = this;
dirty = false;
}
/**
* Writes this PersistableMetaRoot to the specified Block. The ints are
* written at the specified offset.
*
* @param b the Block.
* @param offset PARAMETER TO DO
*/
public void writeToBlock(Block b, int offset) {
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i].writeToBlock(b, offset);
offset += TripleAVLFile.Phase.RECORD_SIZE;
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < NR_INDEXES; ++i) {
StoreTuples ts = tripleAVLFilePhases[i].allTuples();
try {
sb.append(ts).append('\n');
} finally {
try {
ts.close();
} catch (TuplesException ex) {
logger.warn("TuplesException while closing Tuples", ex);
return ex.toString();
}
}
}
return sb.toString();
}
boolean isInUse() {
for (int i = 0; i < NR_INDEXES; ++i) {
if (tripleAVLFilePhases[i].isInUse()) {
return true;
}
}
return false;
}
boolean isEmpty() {
return tripleAVLFilePhases[TI_0123].isEmpty();
}
long getNrTriples() {
return tripleAVLFilePhases[TI_0123].getNrTriples();
}
/**
* Adds a new triple to the graph if it doesn't already exist.
*
* @param node0 the first element of the new triple
* @param node1 the second element of the new triple
* @param node2 the third element of the new triple
* @param node3 the fourth element of the new triple
* @throws StatementStoreException EXCEPTION TO DO
*/
void addTriple(long node0, long node1, long node2, long node3)
throws StatementStoreException {
assert node0 >= NodePool.MIN_NODE;
assert node1 >= NodePool.MIN_NODE;
assert node2 >= NodePool.MIN_NODE;
assert node3 >= NodePool.MIN_NODE;
//if (
// DEBUG && nodePool != null &&
// !nodePool.isValid(node0) && !nodePool.isValid(node1) &&
// !nodePool.isValid(node2) && !nodePool.isValid(node3)
//) throw new AssertionError(
// "Attempt to add a triple with an invalid node"
//);
long[] triple = new long[]{node0, node1, node2, node3};
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i].asyncAddTriple(triple);
}
}
/**
* Removes the specified triple.
*
* @param node0 the value for the first element of the triple
* @param node1 the value for the second element of the triple
* @param node2 the value for the third element of the triple
* @param node3 the value for the fourth element of the triple
* @throws StatementStoreException if something exceptional happens
*/
void removeTriple(
long node0, long node1, long node2, long node3
) throws StatementStoreException {
if (
node0 < NodePool.MIN_NODE ||
node1 < NodePool.MIN_NODE ||
node2 < NodePool.MIN_NODE ||
node3 < NodePool.MIN_NODE
) {
throw new StatementStoreException(
"Attempt to remove a triple with node number out of range: " +
node0 + " " + node1 + " " + node2 + " " + node3
);
}
try {
for (int i = 0; i < NR_INDEXES; ++i) {
tripleAVLFilePhases[i].removeTriple(node0, node1, node2, node3);
}
if (RELEASE_NODE_LISTENERS_ENABLED) {
// Check if any of the four nodes are no longer in use.
// TODO batch up multiple nodes to check instead of checking them
// on every removeTriple.
if (
!tripleAVLFilePhases[TI_0123].existsTriples(node0) &&
!tripleAVLFilePhases[TI_1203].existsTriples(node0) &&
!tripleAVLFilePhases[TI_2013].existsTriples(node0) &&
!tripleAVLFilePhases[TI_3012].existsTriples(node0)
) {
try {
notifyReleaseNodeListeners(node0);
} catch (Error e) {
throw new StatementStoreException("ReleaseNodeListener threw error", e);
} catch (Exception e) {
throw new StatementStoreException("ReleaseNodeListener threw exception", e);
}
}
if (node1 != node0) {
if (
!tripleAVLFilePhases[TI_0123].existsTriples(node1) &&
!tripleAVLFilePhases[TI_1203].existsTriples(node1) &&
!tripleAVLFilePhases[TI_2013].existsTriples(node1) &&
!tripleAVLFilePhases[TI_3012].existsTriples(node1)
) {
try {
notifyReleaseNodeListeners(node1);
} catch (Error e) {
throw new StatementStoreException("ReleaseNodeListener threw error", e);
} catch (Exception e) {
throw new StatementStoreException("ReleaseNodeListener threw exception", e);
}
}
}
if (node2 != node0 && node2 != node1) {
if (
!tripleAVLFilePhases[TI_0123].existsTriples(node2) &&
!tripleAVLFilePhases[TI_1203].existsTriples(node2) &&
!tripleAVLFilePhases[TI_2013].existsTriples(node2) &&
!tripleAVLFilePhases[TI_3012].existsTriples(node2)
) {
try {
notifyReleaseNodeListeners(node2);
} catch (Error e) {
throw new StatementStoreException("ReleaseNodeListener threw error", e);
} catch (Exception e) {
throw new StatementStoreException("ReleaseNodeListener threw exception", e);
}
}
}
if (node3 != node0 && node3 != node1 && node3 != node2) {
if (
!tripleAVLFilePhases[TI_0123].existsTriples(node3) &&
!tripleAVLFilePhases[TI_1203].existsTriples(node3) &&
!tripleAVLFilePhases[TI_2013].existsTriples(node3) &&
!tripleAVLFilePhases[TI_3012].existsTriples(node3)
) {
try {
notifyReleaseNodeListeners(node3);
} catch (Error e) {
throw new StatementStoreException("ReleaseNodeListener threw error", e);
} catch (Exception e) {
throw new StatementStoreException("ReleaseNodeListener threw exception", e);
}
}
}
}
} catch (IOException e) {
throw new StatementStoreException("I/O error", e);
}
}
/**
* Finds triples matching the given specification.
*
* @param variableMask the mask used to indicate the desired index.
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A StoreTuples containing all the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
StoreTuples findTuples(int variableMask, long node0, long node1, long node2, long node3) throws StatementStoreException {
if (
node0 < NodePool.NONE ||
node1 < NodePool.NONE ||
node2 < NodePool.NONE ||
node3 < NodePool.NONE
) {
// There is at least one query node. Return an empty StoreTuples.
return TuplesOperations.empty();
}
if (0 == (variableMask & MASK3)) throw new StatementStoreException("This version of find is for re-ordering graphs, base on a given mask.");
try {
switch (variableMask) {
case MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3);
case MASK0 | MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3);
case MASK1 | MASK3:
return tripleAVLFilePhases[TI_3120].findTuples(node3);
case MASK0 | MASK1 | MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3);
case MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].findTuples(node3);
case MASK0 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].findTuples(node3);
case MASK1 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3120].findTuples(node3);
case MASK0 | MASK1 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3);
default:
throw new AssertionError();
}
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
}
/**
* Finds triples matching the given specification.
*
* @param node0 The 0 node of the triple to find.
* @param node1 The 1 node of the triple to find.
* @param node2 The 2 node of the triple to find.
* @param node3 The 3 node of the triple to find.
* @return A StoreTuples containing all the triples which match the search.
* @throws StatementStoreException EXCEPTION TO DO
*/
StoreTuples findTuples(long node0, long node1, long node2, long node3) throws StatementStoreException {
if (
node0 < NodePool.NONE ||
node1 < NodePool.NONE ||
node2 < NodePool.NONE ||
node3 < NodePool.NONE
) {
// There is at least one query node. Return an empty StoreTuples.
return TuplesOperations.empty();
}
int variableMask =
(node0 != NONE ? MASK0 : 0) |
(node1 != NONE ? MASK1 : 0) |
(node2 != NONE ? MASK2 : 0) |
(node3 != NONE ? MASK3 : 0);
try {
switch (variableMask) {
case 0:
return tripleAVLFilePhases[TI_0123].allTuples();
case MASK0:
return tripleAVLFilePhases[TI_0123].findTuples(node0);
case MASK1:
return tripleAVLFilePhases[TI_1203].findTuples(node1);
case MASK0 | MASK1:
return tripleAVLFilePhases[TI_0123].findTuples(node0, node1);
case MASK2:
return tripleAVLFilePhases[TI_2013].findTuples(node2);
case MASK0 | MASK2:
return tripleAVLFilePhases[TI_2013].findTuples(node2, node0);
case MASK1 | MASK2:
return tripleAVLFilePhases[TI_1203].findTuples(node1, node2);
case MASK0 | MASK1 | MASK2:
return tripleAVLFilePhases[TI_0123].findTuples(node0, node1, node2);
case MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3);
case MASK0 | MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3, node0);
case MASK1 | MASK3:
return tripleAVLFilePhases[TI_3120].findTuples(node3, node1);
case MASK0 | MASK1 | MASK3:
return tripleAVLFilePhases[TI_3012].findTuples(node3, node0, node1);
case MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].findTuples(node3, node2);
case MASK0 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].findTuples(node3, node2, node0);
case MASK1 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3120].findTuples(node3, node1, node2);
case MASK0 | MASK1 | MASK2 | MASK3:
if (tripleAVLFilePhases[TI_0123].existsTriple(node0, node1, node2, node3)) {
return TuplesOperations.unconstrained();
}
return TuplesOperations.empty();
default:
throw new AssertionError();
}
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
}
StoreTuples findTuples(
boolean node0Bound, boolean node1Bound,
boolean node2Bound, boolean node3Bound
) throws StatementStoreException {
int variableMask =
(node0Bound ? MASK0 : 0) |
(node1Bound ? MASK1 : 0) |
(node2Bound ? MASK2 : 0) |
(node3Bound ? MASK3 : 0);
return tripleAVLFilePhases[selectIndex[variableMask]].allTuples();
}
boolean existsTriples(long node0, long node1, long node2, long node3) throws StatementStoreException {
if (
node0 < NodePool.NONE ||
node1 < NodePool.NONE ||
node2 < NodePool.NONE ||
node3 < NodePool.NONE
) {
// There is at least one query node. Return an empty StoreTuples.
return false;
}
int variableMask =
(node0 != NONE ? MASK0 : 0) |
(node1 != NONE ? MASK1 : 0) |
(node2 != NONE ? MASK2 : 0) |
(node3 != NONE ? MASK3 : 0);
try {
switch (variableMask) {
case 0:
return !tripleAVLFilePhases[TI_0123].isEmpty();
case MASK0:
return tripleAVLFilePhases[TI_0123].existsTriples(node0);
case MASK1:
return tripleAVLFilePhases[TI_1203].existsTriples(node1);
case MASK0 | MASK1:
return tripleAVLFilePhases[TI_0123].existsTriples(node0, node1);
case MASK2:
return tripleAVLFilePhases[TI_2013].existsTriples(node2);
case MASK0 | MASK2:
return tripleAVLFilePhases[TI_2013].existsTriples(node2, node0);
case MASK1 | MASK2:
return tripleAVLFilePhases[TI_1203].existsTriples(node1, node2);
case MASK0 | MASK1 | MASK2:
return tripleAVLFilePhases[TI_0123].existsTriples(node0, node1, node2);
case MASK3:
return tripleAVLFilePhases[TI_3012].existsTriples(node3);
case MASK0 | MASK3:
return tripleAVLFilePhases[TI_3012].existsTriples(node3, node0);
case MASK1 | MASK3:
return tripleAVLFilePhases[TI_3120].existsTriples(node3, node1);
case MASK0 | MASK1 | MASK3:
return tripleAVLFilePhases[TI_3012].existsTriples(node3, node0, node1);
case MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].existsTriples(node3, node2);
case MASK0 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3201].existsTriples(node3, node2, node0);
case MASK1 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_3120].existsTriples(node3, node1, node2);
case MASK0 | MASK1 | MASK2 | MASK3:
return tripleAVLFilePhases[TI_0123].existsTriple(node0, node1, node2, node3);
default:
throw new AssertionError();
}
} catch (IOException ex) {
throw new StatementStoreException("I/O error", ex);
}
}
long checkIntegrity() {
long nrTriples[] = new long[NR_INDEXES];
for (int i = 0; i < NR_INDEXES; ++i) {
nrTriples[i] = tripleAVLFilePhases[i].checkIntegrity();
}
for (int i = 1; i < NR_INDEXES; ++i) {
if (nrTriples[0] != nrTriples[i]) {
StringBuffer sb = new StringBuffer("tripleAVLFiles disagree on the number of triples:");
for (int j = 0; j < NR_INDEXES; ++j) sb.append(' ').append(nrTriples[j]);
throw new RuntimeException(sb.toString());
}
}
return nrTriples[0];
}
Token use() {
return new Token();
}
final class Token {
private TripleAVLFile.Phase.Token[] tripleAVLFileTokens = new TripleAVLFile.Phase.Token[NR_INDEXES];
private Phase phase = Phase.this;
/**
* CONSTRUCTOR Token TO DO
*/
Token() {
for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFileTokens[i] = tripleAVLFilePhases[i].use();
}
public Phase getPhase() {
assert tripleAVLFileTokens != null : "Invalid Token";
return phase;
}
public void release() {
assert tripleAVLFileTokens != null : "Invalid Token";
for (int i = 0; i < NR_INDEXES; ++i) tripleAVLFileTokens[i].release();
tripleAVLFileTokens = null;
phase = null;
}
}
}
/**
* Ignored for this implementation.
*/
public void initializeSystemNodes(long systemGraphNode, long rdfTypeNode, long systemGraphTypeNode) {
// do nothing
}
}