/* * 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; // Java 2 standard packages import java.io.*; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.*; // Third party packages import org.apache.log4j.Logger; // 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.*; import org.mulgara.store.tuples.*; import org.mulgara.store.xa.*; import org.mulgara.util.Constants; /** * @created 2001-10-13 * * @author David Makepeace * @author Paula Gearon * * @version $Revision: 1.9 $ * * @modified $Date: 2005/05/16 11:07:08 $ @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 final class TripleAVLFile { /** * Logger. */ private final static Logger logger = Logger.getLogger(TripleAVLFile.class); /** * Get line separator. */ private static final String eol = System.getProperty("line.separator"); private final static String BLOCKFILE_EXT = "_tb"; private final static int SIZEOF_TRIPLE = 4; private final static int BLOCK_SIZE = 8 * 1024; private final static int MAX_TRIPLES = BLOCK_SIZE / Constants.SIZEOF_LONG / SIZEOF_TRIPLE; private final static int IDX_NR_TRIPLES_I = 1; private final static int IDX_LOW_TRIPLE = 1; private final static int IDX_HIGH_TRIPLE = IDX_LOW_TRIPLE + SIZEOF_TRIPLE; private final static int IDX_BLOCK_ID = IDX_HIGH_TRIPLE + SIZEOF_TRIPLE; private final static int PAYLOAD_SIZE = IDX_BLOCK_ID + 1; @SuppressWarnings("unused") private File file; private AVLFile avlFile; private ManagedBlockFile blockFile; private Phase currentPhase; private TripleWriteThread tripleWriteThread; private int order0; private int order1; private int order2; private int order3; private int[] sortOrder; private AVLComparator avlComparator; private TripleComparator tripleComparator; /** * CONSTRUCTOR TripleAVLFile TO DO * * @param file PARAMETER TO DO * @param sortOrder PARAMETER TO DO * @throws IOException EXCEPTION TO DO */ public TripleAVLFile(File file, int[] sortOrder) throws IOException { this.file = file; this.sortOrder = sortOrder; order0 = sortOrder[0]; order1 = sortOrder[1]; order2 = sortOrder[2]; order3 = sortOrder[3]; avlFile = new AVLFile(file, PAYLOAD_SIZE); blockFile = new ManagedBlockFile(file + BLOCKFILE_EXT, BLOCK_SIZE, BlockFile.IOType.DEFAULT); avlComparator = new TripleAVLComparator(sortOrder); tripleComparator = new TripleComparator(sortOrder); tripleWriteThread = new TripleWriteThread(file); } /** * CONSTRUCTOR TripleAVLFile TO DO * * @param fileName PARAMETER TO DO * @param sortOrder PARAMETER TO DO * @throws IOException EXCEPTION TO DO */ public TripleAVLFile(String fileName, int[] sortOrder) throws IOException { this(new File(fileName), sortOrder); } /** * Binary search for a triple given a range to work within. * * @param triples The int buffer holding the triples to search on. * @param comp The comparator to use based on the ordering within <i>triples * </i>. * @param left The start of the range to search in (inclusive). * @param right The end of the range to search in (exclusive). * @param triple The triple to search for. * @return The index of the found triple in the int buffer, or if not found * then -index-1 of the triple above the point where the triple would be. */ private static int binarySearch( Block triples, TripleComparator comp, int left, int right, long[] triple ) { for (;;) { // if the range is zero then the node was not found. // return the next node up. if (left >= right) { return -right - 1; } // find the middle of this range int middle = (left + right) >>> 1; // determine if the required triple is above or below the middle int c = comp.compare(triple, triples, middle); // if it's in the middle then return it if (c == 0) { return middle; } if (c < 0) { // if it's below the middle then search there right = middle; } else { // if it's below the middle then search there left = middle + 1; } } } /** * METHOD TO DO * * @param triples PARAMETER TO DO * @param nrTriples PARAMETER TO DO * @param index PARAMETER TO DO * @param triple PARAMETER TO DO */ private static void insertTripleInBlock( Block triples, int nrTriples, int index, long[] triple ) { if (index < nrTriples) { triples.put( (index + 1) * SIZEOF_TRIPLE * Constants.SIZEOF_LONG, triples, index * SIZEOF_TRIPLE * Constants.SIZEOF_LONG, (nrTriples - index) * SIZEOF_TRIPLE * Constants.SIZEOF_LONG ); } //triples.put(index * SIZEOF_TRIPLE, triple); int pos = index * SIZEOF_TRIPLE; triples.putLong(pos++, triple[0]); triples.putLong(pos++, triple[1]); triples.putLong(pos++, triple[2]); triples.putLong(pos, triple[3]); } /** * METHOD TO DO * * @param triples PARAMETER TO DO * @param nrTriples PARAMETER TO DO * @param index PARAMETER TO DO */ private static void removeTripleFromBlock( Block triples, int nrTriples, int index ) { if (index + 1 < nrTriples) { triples.put( index * SIZEOF_TRIPLE * Constants.SIZEOF_LONG, triples, (index + 1) * SIZEOF_TRIPLE * Constants.SIZEOF_LONG, (nrTriples - index - 1) * SIZEOF_TRIPLE * Constants.SIZEOF_LONG ); } } /** * Truncates the file to zero length. * * @throws IOException if an I/O error occurs. */ public void clear() throws IOException { avlFile.clear(); blockFile.clear(); } /** * Ensures that all data for this BlockFile is stored in persistent storage * before returning. * * @throws IOException if an I/O error occurs. */ public void force() throws IOException { avlFile.force(); blockFile.force(); } /** * METHOD TO DO */ public synchronized void unmap() { if (tripleWriteThread != null) { tripleWriteThread.close(); tripleWriteThread = null; } if (avlFile != null) { avlFile.unmap(); } if (blockFile != null) { blockFile.unmap(); } } /** * Closes the block file. * * @throws IOException EXCEPTION TO DO */ public synchronized void close() throws IOException { boolean success = false; try { if (avlFile != null) { avlFile.close(); } success = true; } finally { if (blockFile != null) { try { blockFile.close(); } catch (IOException e) { if (success) throw e; // This is a new exception, need to re-throw it. else logger.info("Suppressing I/O exception cleaning up from failed write", e); // Log suppressed exception. } } } } /** * Closes and deletes the block file. * * @throws IOException EXCEPTION TO DO */ public synchronized void delete() throws IOException { try { try { if (avlFile != null) { avlFile.delete(); } } finally { if (blockFile != null) { blockFile.delete(); } } } finally { avlFile = null; blockFile = null; } } private final static class TripleLocation { public AVLNode node; public int offset; /** * CONSTRUCTOR TripleLocation TO DO * * @param node PARAMETER TO DO * @param offset PARAMETER TO DO */ TripleLocation(AVLNode node, int offset) { this.node = node; this.offset = offset; } public boolean equals(Object o) { if (o == null) { return false; } if (o == this) { return true; } TripleLocation tl; try { tl = (TripleLocation) o; } catch (ClassCastException ex) { return false; } return tl.node == null && node == null || ( tl.node != null && node != null && tl.node.getId() == node.getId() && tl.offset == offset ); } public int hashCode() { return node == null ? 0 : ((int) node.getId() + 1) * 17 + offset; } } private static final class TripleAVLComparator implements AVLComparator { private TripleComparator tripleComparator; /** * CONSTRUCTOR TripleAVLComparator TO DO * * @param sortOrder PARAMETER TO DO */ TripleAVLComparator(int[] sortOrder) { this.tripleComparator = new TripleComparator( AVLNode.IDX_PAYLOAD + IDX_LOW_TRIPLE, sortOrder ); } public int compare(long[] key, AVLNode node) { Block block = node.getBlock(); if (tripleComparator.compare(key, block, 0) < 0) { return -1; } if (tripleComparator.compare(key, block, 1) > 0) { return 1; } return 0; } } /** * Inner class to compare two triples in an int buffer, using a given ordering * for each of the node columns. */ private final static class TripleComparator implements Comparator<Object> { private int offset; /** * The required ordering of node columns. */ private int c0, c1, c2, c3; /** * Construct a comparator according to requested ordering. * * @param offset PARAMETER TO DO * @param sortOrder The order of the columns to sort on */ TripleComparator(int offset, int[] sortOrder) { this.offset = offset; c0 = sortOrder[0]; c1 = sortOrder[1]; c2 = sortOrder[2]; c3 = sortOrder[3]; } /** * Construct a comparator according to requested ordering. * * @param sortOrder The order of the columns to sort on */ TripleComparator(int[] sortOrder) { this(0, sortOrder); } /** * Compare triples in 2 int buffers. * * @param key PARAMETER TO DO * @param b PARAMETER TO DO * @param i PARAMETER TO DO * @return -1 If the first triple is smaller than the second, 1 if the first * triple is larger than the second, and 0 if they are equal. */ public int compare(long[] key, Block b, int i) { // calculate the offset into the buffers of each triple int index = offset + i * SIZEOF_TRIPLE; int c; if ( (c = XAUtils.compare(key[c0], b.getLong(index + c0))) == 0 && (c = XAUtils.compare(key[c1], b.getLong(index + c1))) == 0 && (c = XAUtils.compare(key[c2], b.getLong(index + c2))) == 0 ) { c = XAUtils.compare(key[c3], b.getLong(index + c3)); } return c; } /** * Compare triples in 2 int arrays. * * @param t1 The array holding the first triple. * @param t2 The array holding the second triple. * @return -1 If the first triple is smaller than the second, 1 if the first * triple is larger than the second, and 0 if they are equal. */ public int compare(long[] t1, long[] t2) { int c; if ( (c = XAUtils.compare(t1[c0], t2[c0])) == 0 && (c = XAUtils.compare(t1[c1], t2[c1])) == 0 && (c = XAUtils.compare(t1[c2], t2[c2])) == 0 ) { c = XAUtils.compare(t1[c3], t2[c3]); } return c; } /** * Compare triples in 2 long arrays. * * @param t1 The array holding the first triple. * @param t2 The array holding the second triple. * @return -1 If the first triple is smaller than the second, 1 if the first * triple is larger than the second, and 0 if they are equal. */ public int compare(Object t1, Object t2) { return compare((long[])t1, (long[])t2); } } static String toString(long[] la) { StringBuffer sb = new StringBuffer("["); for (int i = 0; i < la.length; ++i) { if (i != 0) sb.append(','); sb.append(la[i]); } sb.append(']'); return sb.toString(); } static String toString(int[] ia) { StringBuffer sb = new StringBuffer("["); for (int i = 0; i < ia.length; ++i) { if (i != 0) sb.append(','); sb.append(ia[i]); } sb.append(']'); return sb.toString(); } public final class Phase implements PersistableMetaRoot { // in longs private final static int HEADER_SIZE = 1; public final static int RECORD_SIZE = HEADER_SIZE + AVLFile.Phase.RECORD_SIZE + ManagedBlockFile.Phase.RECORD_SIZE; @SuppressWarnings("unused") private final static int IDX_NR_FILE_TRIPLES = 0; private long nrFileTriples; private AVLFile.Phase avlFilePhase; private ManagedBlockFile.Phase blockFilePhase; private AVLNode cachedNode = null; private Block cachedBlock = null; /** * CONSTRUCTOR Phase TO DO * * @throws IOException EXCEPTION TO DO */ public Phase() throws IOException { if (tripleWriteThread != null) tripleWriteThread.drain(); if (currentPhase == null) { nrFileTriples = 0; } else { nrFileTriples = currentPhase.nrFileTriples; } avlFilePhase = avlFile.new Phase(); blockFilePhase = blockFile.new Phase(); check(); if (tripleWriteThread != null) tripleWriteThread.setPhase(this); currentPhase = this; } /** * CONSTRUCTOR Phase TO DO * * @throws IOException EXCEPTION TO DO */ public Phase(Phase p) throws IOException { assert p != null; if (tripleWriteThread != null) { if (p == currentPhase) tripleWriteThread.drain(); else tripleWriteThread.abort(); } nrFileTriples = p.nrFileTriples; avlFilePhase = avlFile.new Phase(p.avlFilePhase); blockFilePhase = blockFile.new Phase(p.blockFilePhase); check(); if (tripleWriteThread != null) tripleWriteThread.setPhase(this); currentPhase = this; } /** * CONSTRUCTOR Phase TO DO * * @param b PARAMETER TO DO * @param offset PARAMETER TO DO * @throws IOException EXCEPTION TO DO */ public Phase(Block b, int offset) throws IOException { if (tripleWriteThread != null) tripleWriteThread.drain(); nrFileTriples = b.getLong(offset++); avlFilePhase = avlFile.new Phase(b, offset); offset += AVLFile.Phase.RECORD_SIZE; blockFilePhase = blockFile.new Phase(b, offset); check(); if (tripleWriteThread != null) tripleWriteThread.setPhase(this); currentPhase = this; } public long getNrTriples() { if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); return nrFileTriples; } public boolean isInUse() { return blockFilePhase.isInUse(); } public boolean isEmpty() { if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); return avlFilePhase.isEmpty(); } /** * 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) { if (tripleWriteThread != null) tripleWriteThread.drain(); check(); b.putLong(offset++, nrFileTriples); avlFilePhase.writeToBlock(b, offset); offset += AVLFile.Phase.RECORD_SIZE; blockFilePhase.writeToBlock(b, offset); } /** * 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 IOException EXCEPTION TO DO */ public void addTriple(long node0, long node1, long node2, long node3) throws IOException { addTriple(new long[] {node0, node1, node2, node3}); } /** * Adds a triple to the graph. * * @param triple The triple to add * @throws IOException EXCEPTION TO DO */ void addTriple(long[] triple) throws IOException { if (this != currentPhase) throw new IllegalStateException("Attempt to modify a read-only phase."); if (tripleWriteThread != null) tripleWriteThread.drain(); boolean success = false; try { syncAddTriple(triple); success = true; } finally { try { releaseCache(); } catch (IOException e) { if (success) throw e; // This is a new exception, need to re-throw it. else logger.info("Suppressing I/O exception cleaning up from failed write", e); // Log suppressed exception. } } } /** * Adds multiple triples to the graph. * * @param triples The triples to add * @throws IOException EXCEPTION TO DO */ void syncAddTriples(long[][] triples) throws IOException { if (this != currentPhase) throw new IllegalStateException("Attempt to modify a read-only phase."); Arrays.sort(triples, tripleComparator); boolean success = false; try { for (int i = 0; i < triples.length; ++i) { long[] triple = triples[i]; triples[i] = null; // Allow early garbage collection. // Add the triple to the TripleAVLFile and check that the triple // wasn't already there. syncAddTriple(triple); } success = true; } finally { try { releaseCache(); } catch (IOException e) { if (success) throw e; // This is a new exception, need to re-throw it. else logger.info("Suppressing I/O exception cleaning up from failed write", e); // Log suppressed exception. } } } private void releaseCache() throws IOException { try { if (cachedNode != null) cachedNode.release(); if (cachedBlock != null) { cachedBlock.write(); } } finally { cachedNode = null; cachedBlock = null; } } private void releaseBlockToCache(Block block) throws IOException { if (cachedBlock != null) { cachedBlock.write(); } cachedBlock = block; } private Block getCachedBlock(long blockId) { if (cachedBlock != null && blockId == cachedBlock.getBlockId()) { // Block is cached. Remove from cache and return it. Block block = cachedBlock; cachedBlock = null; return block; } return null; } private void releaseNodeToCache(AVLNode node) { if (cachedNode != null) cachedNode.release(); cachedNode = node; } /** * Adds a triple to the graph. * * @param triple The triple to add * @throws IOException EXCEPTION TO DO */ private void syncAddTriple(long[] triple) throws IOException { AVLNode startNode; if (cachedNode == null) { startNode = avlFilePhase.getRootNode(); } else { startNode = cachedNode; cachedNode = null; } AVLNode[] findResult = AVLNode.find(startNode, avlComparator, triple); if (startNode != null) startNode.release(); if (findResult == null) { // Tree is empty. Create a node and allocate a triple block. Block newTripleBlock = blockFilePhase.allocateBlock(); AVLNode newNode = avlFilePhase.newAVLNodeInstance(); newNode.putPayloadLong(IDX_LOW_TRIPLE, triple[0]); newNode.putPayloadLong(IDX_LOW_TRIPLE + 1, triple[1]); newNode.putPayloadLong(IDX_LOW_TRIPLE + 2, triple[2]); newNode.putPayloadLong(IDX_LOW_TRIPLE + 3, triple[3]); newNode.putPayloadLong(IDX_HIGH_TRIPLE, triple[0]); newNode.putPayloadLong(IDX_HIGH_TRIPLE + 1, triple[1]); newNode.putPayloadLong(IDX_HIGH_TRIPLE + 2, triple[2]); newNode.putPayloadLong(IDX_HIGH_TRIPLE + 3, triple[3]); newNode.putPayloadInt(IDX_NR_TRIPLES_I, 1); newNode.putPayloadLong( IDX_BLOCK_ID, newTripleBlock.getBlockId() ); newNode.write(); newTripleBlock.put(0, triple); //newTripleBlock.write(); releaseBlockToCache(newTripleBlock); avlFilePhase.insertFirst(newNode); releaseNodeToCache(newNode); incNrTriples(); return; } AVLNode node; if (findResult.length == 1 || findResult[1] == null) { node = findResult[0]; } else if (findResult[0] == null) { node = findResult[1]; } else { // Between two nodes. //// Choose the node with the smaller number of triples. //if ( // findResult[0].getPayloadInt(IDX_NR_TRIPLES_I) <= // findResult[1].getPayloadInt(IDX_NR_TRIPLES_I) //) { // node = findResult[0]; //} else { // node = findResult[1]; //} // Preferentially choose the lower node. if ( findResult[0].getPayloadInt(IDX_NR_TRIPLES_I) < MAX_TRIPLES || findResult[1].getPayloadInt(IDX_NR_TRIPLES_I) == MAX_TRIPLES ) { node = findResult[0]; } else { node = findResult[1]; } } node.incRefCount(); boolean success = false; Block tripleBlock = null; boolean tripleBlockDirty = false; try { int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); if (findResult.length == 1) { // Found the node. See if it matches the high or low triple. if ( // Low triple. node.getPayloadLong(IDX_LOW_TRIPLE) == triple[0] && node.getPayloadLong(IDX_LOW_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_LOW_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_LOW_TRIPLE + 3) == triple[3] ) return; if ( nrTriples > 1 && // High triple. node.getPayloadLong(IDX_HIGH_TRIPLE) == triple[0] && node.getPayloadLong(IDX_HIGH_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_HIGH_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_HIGH_TRIPLE + 3) == triple[3] ) return; } // Get the triple block. long blockId = node.getPayloadLong(IDX_BLOCK_ID); tripleBlock = getCachedBlock(blockId); if (tripleBlock == null) { tripleBlock = blockFilePhase.readBlock(blockId); } else { // Blocks in the cache are always dirty. tripleBlockDirty = true; } int index; if (findResult.length == 2) { index = node == findResult[0] ? nrTriples : 0; } else { // Find the triple or where the triple should be inserted. index = binarySearch( tripleBlock, tripleComparator, 0, nrTriples, triple ); if (index >= 0) return; index = -index - 1; // Make index positive. } // Tell the node that it will be modified. node.modify(); // Split the node if the triple block is full. if (nrTriples == MAX_TRIPLES) { // Split the block. Allocate a new node and block to take the upper // portion of the current block. //int splitPoint = MAX_TRIPLES / 2; int splitPoint = index == 0 ? 0 : ( index == MAX_TRIPLES ? MAX_TRIPLES : MAX_TRIPLES / 2 ); assert splitPoint > 0 || index == 0; assert splitPoint < MAX_TRIPLES || index == MAX_TRIPLES; Block newTripleBlock = blockFilePhase.allocateBlock(); AVLNode newNode = avlFilePhase.newAVLNodeInstance(); // Low triple. if (splitPoint < MAX_TRIPLES) { int pos = splitPoint * SIZEOF_TRIPLE; newNode.putPayloadLong( IDX_LOW_TRIPLE, tripleBlock.getLong(pos++) ); newNode.putPayloadLong( IDX_LOW_TRIPLE + 1, tripleBlock.getLong(pos++) ); newNode.putPayloadLong( IDX_LOW_TRIPLE + 2, tripleBlock.getLong(pos++) ); newNode.putPayloadLong( IDX_LOW_TRIPLE + 3, tripleBlock.getLong(pos) ); } // High triple. if (index < MAX_TRIPLES) { int pos = IDX_HIGH_TRIPLE; newNode.putPayloadLong( IDX_HIGH_TRIPLE, node.getPayloadLong(pos++) ); newNode.putPayloadLong( IDX_HIGH_TRIPLE + 1, node.getPayloadLong(pos++) ); newNode.putPayloadLong( IDX_HIGH_TRIPLE + 2, node.getPayloadLong(pos++) ); newNode.putPayloadLong( IDX_HIGH_TRIPLE + 3, node.getPayloadLong(pos) ); } if (index < MAX_TRIPLES && index <= splitPoint) { newNode.putPayloadInt(IDX_NR_TRIPLES_I, MAX_TRIPLES - splitPoint); newNode.putPayloadLong( IDX_BLOCK_ID, newTripleBlock.getBlockId() ); } // Update existing node's high triple unless it will be updated // later or will not change. if (index != splitPoint) { // If splitPoint is zero then index must also be zero. // If splitPoint is MAX_TRIPLES then index is also MAX_TRIPLES. // If splitPoint is MAX_TRIPLES then no change is required. int pos = (splitPoint - 1) * SIZEOF_TRIPLE; node.putPayloadLong( IDX_HIGH_TRIPLE, tripleBlock.getLong(pos++) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 1, tripleBlock.getLong(pos++) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 2, tripleBlock.getLong(pos++) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 3, tripleBlock.getLong(pos) ); } // Copy the top portion of the full block to the new block. if (splitPoint < MAX_TRIPLES) { newTripleBlock.put( 0, tripleBlock, splitPoint * SIZEOF_TRIPLE * Constants.SIZEOF_LONG, (MAX_TRIPLES - splitPoint) * SIZEOF_TRIPLE * Constants.SIZEOF_LONG ); } // Update the number of triples unless it will be updated later or // will not change. if (index > splitPoint) { node.putPayloadInt(IDX_NR_TRIPLES_I, splitPoint); } // Insert the new node into the tree. if (findResult.length == 1 || findResult[0] == null) { node.incRefCount(); AVLFile.release(findResult); findResult = null; findResult = new AVLNode[] { node, node.getNextNode() }; } int li = AVLFile.leafIndex(findResult); findResult[li].insert(newNode, 1 - li); if (index == MAX_TRIPLES || index > splitPoint) { nrTriples = MAX_TRIPLES - splitPoint; index -= splitPoint; if (tripleBlockDirty) tripleBlock.write(); tripleBlock = newTripleBlock; node.write(); node.release(); node = newNode; } else { nrTriples = splitPoint; newTripleBlock.write(); newNode.write(); newNode.release(); } // In case nodes are written by insert(). node.modify(); } // Duplicate the triple block. tripleBlock.modify(); node.putPayloadLong(IDX_BLOCK_ID, tripleBlock.getBlockId()); insertTripleInBlock(tripleBlock, nrTriples, index, triple); //tripleBlock.write(); tripleBlockDirty = true; node.putPayloadInt(IDX_NR_TRIPLES_I, nrTriples + 1); if (index == nrTriples) { node.putPayloadLong(IDX_HIGH_TRIPLE, triple[0]); node.putPayloadLong(IDX_HIGH_TRIPLE + 1, triple[1]); node.putPayloadLong(IDX_HIGH_TRIPLE + 2, triple[2]); node.putPayloadLong(IDX_HIGH_TRIPLE + 3, triple[3]); } if (index == 0) { node.putPayloadLong(IDX_LOW_TRIPLE, triple[0]); node.putPayloadLong(IDX_LOW_TRIPLE + 1, triple[1]); node.putPayloadLong(IDX_LOW_TRIPLE + 2, triple[2]); node.putPayloadLong(IDX_LOW_TRIPLE + 3, triple[3]); } node.write(); incNrTriples(); success = true; return; } finally { if (tripleBlock != null) { try { if (tripleBlockDirty) releaseBlockToCache(tripleBlock); } catch (IOException e) { if (success) throw e; // Otherwise succeeded, need to throw this new exception. else logger.info("Suppressing I/O exception for failed AVL file", e); } } AVLFile.release(findResult); releaseNodeToCache(node); } } /** * Asynchronously adds a triple to the graph. * * @param triple The triple to add */ public void asyncAddTriple(long[] triple) { if (this != currentPhase) { throw new IllegalStateException("Attempt to modify a read-only phase."); } tripleWriteThread.addTriple(triple); } /** * Remove a triple from the graph. * * @param node0 The 0 node of the triple to remove. * @param node1 The 1 node of the triple to remove. * @param node2 The 2 node of the triple to remove. * @param node3 PARAMETER TO DO * @throws IOException EXCEPTION TO DO */ public void removeTriple(long node0, long node1, long node2, long node3) throws IOException { if (this != currentPhase) { throw new IllegalStateException("Attempt to modify a read-only phase."); } long[] triple = new long[] {node0, node1, node2, node3}; if (tripleWriteThread != null) tripleWriteThread.drain(); AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); if (findResult == null || findResult.length == 2) { if (findResult != null) AVLFile.release(findResult); // Triple not found return; } // Found the node. AVLNode node = findResult[0]; node.incRefCount(); AVLFile.release(findResult); int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); if (nrTriples == 1) { // Free the triple block and the avl node. blockFilePhase.freeBlock(node.getPayloadLong(IDX_BLOCK_ID)); node.remove(); decNrTriples(); return; } if (nrTriples == 2) { // Special case. If the triple matches the high triple and there are // only two triples in this node then we can simply set the high triple // to the low triple and decrement the number of triples. if ( // High triple. node.getPayloadLong(IDX_HIGH_TRIPLE) == node0 && node.getPayloadLong(IDX_HIGH_TRIPLE + 1) == node1 && node.getPayloadLong(IDX_HIGH_TRIPLE + 2) == node2 && node.getPayloadLong(IDX_HIGH_TRIPLE + 3) == node3 ) { node.modify(); node.putPayloadLong( IDX_HIGH_TRIPLE, node.getPayloadLong(IDX_LOW_TRIPLE) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 1, node.getPayloadLong(IDX_LOW_TRIPLE + 1) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 2, node.getPayloadLong(IDX_LOW_TRIPLE + 2) ); node.putPayloadLong( IDX_HIGH_TRIPLE + 3, node.getPayloadLong(IDX_LOW_TRIPLE + 3) ); node.putPayloadInt(IDX_NR_TRIPLES_I, 1); node.write(); node.release(); decNrTriples(); return; } } // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); try { // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); // If triple not found if (index < 0) return; // Duplicate both the AVLNode and the triple block. node.modify(); tripleBlock.modify(); node.putPayloadLong(IDX_BLOCK_ID, tripleBlock.getBlockId()); removeTripleFromBlock(tripleBlock, nrTriples, index); tripleBlock.write(); node.putPayloadInt(IDX_NR_TRIPLES_I, nrTriples - 1); if (index == nrTriples - 1) { int pos = (index - 1) * SIZEOF_TRIPLE; node.putPayloadLong(IDX_HIGH_TRIPLE, tripleBlock.getLong(pos++)); node.putPayloadLong(IDX_HIGH_TRIPLE + 1, tripleBlock.getLong(pos++)); node.putPayloadLong(IDX_HIGH_TRIPLE + 2, tripleBlock.getLong(pos++)); node.putPayloadLong(IDX_HIGH_TRIPLE + 3, tripleBlock.getLong(pos)); } else if (index == 0) { node.putPayloadLong(IDX_LOW_TRIPLE, tripleBlock.getLong(0)); node.putPayloadLong(IDX_LOW_TRIPLE + 1, tripleBlock.getLong(1)); node.putPayloadLong(IDX_LOW_TRIPLE + 2, tripleBlock.getLong(2)); node.putPayloadLong(IDX_LOW_TRIPLE + 3, tripleBlock.getLong(3)); } node.write(); decNrTriples(); return; } finally { node.release(); } } public StoreTuples findTuples(long node0) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = node0; endTriple[order0] = node0 + 1; return new TuplesImpl(startTriple, endTriple, 1); } public StoreTuples findTuples(long node0, long node1) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = node0; startTriple[order1] = node1; endTriple[order0] = node0; endTriple[order1] = node1 + 1; return new TuplesImpl(startTriple, endTriple, 2); } public StoreTuples findTuples(long node0, long node1, long node2) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = node0; startTriple[order1] = node1; startTriple[order2] = node2; endTriple[order0] = node0; endTriple[order1] = node1; endTriple[order2] = node2 + 1; return new TuplesImpl(startTriple, endTriple, 3); } public StoreTuples findTuplesForMeta(long graph) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = graph; endTriple[order0] = graph + 1; return new MetaTuplesImpl(startTriple, endTriple, 1); } public StoreTuples findTuplesForMeta(long graph, long node1) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = graph; startTriple[order1] = node1; endTriple[order0] = graph; endTriple[order1] = node1 + 1; return new MetaTuplesImpl(startTriple, endTriple, 2); } public StoreTuples findTuplesForMeta(long graph, long node1, long node2) throws IOException { long[] startTriple = new long[SIZEOF_TRIPLE]; long[] endTriple = new long[SIZEOF_TRIPLE]; startTriple[order0] = graph; startTriple[order1] = node1; startTriple[order2] = node2; endTriple[order0] = graph; endTriple[order1] = node1; endTriple[order2] = node2 + 1; return new MetaTuplesImpl(startTriple, endTriple, 3); } public StoreTuples allTuples() { return new TuplesImpl(); } public boolean existsTriples(long node0) throws IOException { long[] triple = new long[SIZEOF_TRIPLE]; triple[order0] = node0; if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); // If Triplestore is empty. if (findResult == null) return false; try { // Found the node. AVLNode node = findResult[findResult.length == 1 ? 0 : 1]; // If Triple is less than the minimum or greater than the maximum. if (node == null) return false; int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); // If exact match on a node that only contains one triple. if (findResult.length == 1 && nrTriples == 1) return true; // See if it matches the high or low triple. if (node.getPayloadLong(IDX_LOW_TRIPLE + order0) == node0) return true; // If this triple's value falls between two nodes. if (findResult.length == 2) return false; if (node.getPayloadLong(IDX_HIGH_TRIPLE + order0) == node0) return true; // Check if there is no point looking inside the triple block since we have // already checked the only two triples for this node. if (nrTriples == 2) return false; // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); boolean exists; if (index >= 0) { exists = true; } else { index = -index - 1; exists = index < nrTriples && tripleBlock.getLong(index * SIZEOF_TRIPLE + order0) == node0; } return exists; } finally { AVLFile.release(findResult); } } public boolean existsTriples(long node0, long node1) throws IOException { long[] triple = new long[SIZEOF_TRIPLE]; triple[order0] = node0; triple[order1] = node1; if (this == currentPhase && tripleWriteThread != null)tripleWriteThread.drain(); AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); // If Triplestore is empty. if (findResult == null) return false; try { // Found the node. AVLNode node = findResult[findResult.length == 1 ? 0 : 1]; // Check if Triple is less than the minimum or greater than the maximum. if (node == null) return false; int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); // Check if exact match on a node that only contains one triple. if (nrTriples == 1) return true; // See if it matches the high or low triple. if ( node.getPayloadLong(IDX_LOW_TRIPLE + order0) == node0 && node.getPayloadLong(IDX_LOW_TRIPLE + order1) == node1 ) { return true; } // Check if this triple's value falls between two nodes. if (findResult.length == 2) return false; if ( node.getPayloadLong(IDX_HIGH_TRIPLE + order0) == node0 && node.getPayloadLong(IDX_HIGH_TRIPLE + order1) == node1 ) { return true; } // Check if there is no point looking inside the triple block since we have // already checked the only two triples for this node. if (nrTriples == 2) return false; // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); boolean exists; if (index >= 0) { exists = true; } else { index = -index - 1; exists = index < nrTriples && tripleBlock.getLong(index * SIZEOF_TRIPLE + order0) == node0 && tripleBlock.getLong(index * SIZEOF_TRIPLE + order1) == node1; } return exists; } finally { AVLFile.release(findResult); } } public boolean existsTriples(long node0, long node1, long node2) throws IOException { long[] triple = new long[SIZEOF_TRIPLE]; triple[order0] = node0; triple[order1] = node1; triple[order2] = node2; if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); // Check if Triplestore is empty. if (findResult == null) return false; try { // Found the node. AVLNode node = findResult[findResult.length == 1 ? 0 : 1]; // Check if Triple is less than the minimum or greater than the maximum. if (node == null) return false; int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); // Check if exact match on a node that only contains one triple. if (nrTriples == 1) return true; // See if it matches the high or low triple. if ( node.getPayloadLong(IDX_LOW_TRIPLE + order0) == node0 && node.getPayloadLong(IDX_LOW_TRIPLE + order1) == node1 && node.getPayloadLong(IDX_LOW_TRIPLE + order2) == node2 ) { return true; } // Check if this triple's value falls between two nodes. if (findResult.length == 2) return false; if ( node.getPayloadLong(IDX_HIGH_TRIPLE + order0) == node0 && node.getPayloadLong(IDX_HIGH_TRIPLE + order1) == node1 && node.getPayloadLong(IDX_HIGH_TRIPLE + order2) == node2 ) { return true; } // Check if there is no point looking inside the triple block since we have // already checked the only two triples for this node. if (nrTriples == 2) return false; // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); boolean exists; if (index >= 0) { exists = true; } else { index = -index - 1; exists = index < nrTriples && tripleBlock.getLong(index * SIZEOF_TRIPLE + order0) == node0 && tripleBlock.getLong(index * SIZEOF_TRIPLE + order1) == node1 && tripleBlock.getLong(index * SIZEOF_TRIPLE + order2) == node2; } return exists; } finally { AVLFile.release(findResult); } } public boolean existsTriple(long node0, long node1, long node2, long node3) throws IOException { long[] triple = new long[SIZEOF_TRIPLE]; triple[order0] = node0; triple[order1] = node1; triple[order2] = node2; triple[order3] = node3; return existsTriple(triple); } public Token use() { return new Token(); } public long checkIntegrity() { if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); AVLNode node = avlFilePhase.getRootNode(); if (node == null) return 0; node = node.getMinNode_R(); long[] prevTriple = new long[] {0, 0, 0, 0}; long[] triple = new long[SIZEOF_TRIPLE]; long nodeIndex = 0; long totalNrTriples = 0; do { int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); if (nrTriples < 1 || nrTriples > MAX_TRIPLES) { throw new Error( "NR_TRIPLES (" + nrTriples + ") is out of bounds in node: " + node.getId() + " (index " + nodeIndex + ")" ); } triple[0] = node.getPayloadLong(IDX_LOW_TRIPLE); triple[1] = node.getPayloadLong(IDX_LOW_TRIPLE + 1); triple[2] = node.getPayloadLong(IDX_LOW_TRIPLE + 2); triple[3] = node.getPayloadLong(IDX_LOW_TRIPLE + 3); if (tripleComparator.compare(prevTriple, triple) >= 0) { throw new Error( "LOW_TRIPLE (" + triple[0] + " " + triple[1] + " " + triple[2] + " " + triple[3] + ") is not greater than prevTriple (" + prevTriple[0] + " " + prevTriple[1] + " " + prevTriple[2] + " " + prevTriple[3] + ") in node: " + node.getId() + " (index " + nodeIndex + ")" ); } Block block; try { block = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); } catch (IOException ex) { throw new Error("I/O Error", ex); } if (tripleComparator.compare(triple, block, 0) != 0) { long[] firstTriple = new long[SIZEOF_TRIPLE]; block.get(0, firstTriple); throw new Error( "LOW_TRIPLE (" + triple[0] + " " + triple[1] + " " + triple[2] + " " + triple[3] + ") is not equal to the first triple (" + firstTriple[0] + " " + firstTriple[1] + " " + firstTriple[2] + " " + firstTriple[3] + ") in node: " + node.getId() + " (index " + nodeIndex + ")" ); } System.arraycopy(triple, 0, prevTriple, 0, SIZEOF_TRIPLE); for (int i = 1; i < nrTriples; ++i) { block.get(i * SIZEOF_TRIPLE, triple); if (tripleComparator.compare(prevTriple, triple) >= 0) { throw new Error( "Triple #" + i + " (" + " not greater than previous triple in block in node: " + node.getId() + " (index " + nodeIndex + ")" ); } System.arraycopy(triple, 0, prevTriple, 0, SIZEOF_TRIPLE); } triple[0] = node.getPayloadLong(IDX_HIGH_TRIPLE); triple[1] = node.getPayloadLong(IDX_HIGH_TRIPLE + 1); triple[2] = node.getPayloadLong(IDX_HIGH_TRIPLE + 2); triple[3] = node.getPayloadLong(IDX_HIGH_TRIPLE + 3); if (tripleComparator.compare(triple, prevTriple) != 0) { throw new Error( "HIGH_TRIPLE (" + triple[0] + " " + triple[1] + " " + triple[2] + " " + triple[3] + ") is not equal to the last triple (" + prevTriple[0] + " " + prevTriple[1] + " " + prevTriple[2] + " " + prevTriple[3] + ") in node: " + node.getId() + " (index " + nodeIndex + ")" ); } ++nodeIndex; totalNrTriples += nrTriples; } while ((node = node.getNextNode_R()) != null); if (totalNrTriples != nrFileTriples) { throw new Error( "nrFileTriples (" + nrFileTriples + ") does not match actual number of triples (" + totalNrTriples + ") in: " + order0 + order1 + order2 + order3 ); } return totalNrTriples; } private void check() { assert !isEmpty() || nrFileTriples == 0 : "AVLFile not empty but nrFileTriples == 0"; assert isEmpty() || nrFileTriples > 0 : "AVLFile is empty but nrFileTriples == " + nrFileTriples; } private void incNrTriples() { ++nrFileTriples; } private void decNrTriples() { assert nrFileTriples > 0; --nrFileTriples; } private boolean existsTriple(long[] triple) throws IOException { if (this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); // Check if Triplestore is empty. if (findResult == null) return false; try { // Check if triple not found. if (findResult.length == 2) return false; // Found the node. AVLNode node = findResult[0]; int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); if (nrTriples == 1) return true; // See if it matches the high or low triple. if ( ( // Low triple. node.getPayloadLong(IDX_LOW_TRIPLE) == triple[0] && node.getPayloadLong(IDX_LOW_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_LOW_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_LOW_TRIPLE + 3) == triple[3] ) || ( // High triple. node.getPayloadLong(IDX_HIGH_TRIPLE) == triple[0] && node.getPayloadLong(IDX_HIGH_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_HIGH_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_HIGH_TRIPLE + 3) == triple[3] ) ) { return true; } if (nrTriples == 2) { return false; } // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); return index >= 0; } finally { AVLFile.release(findResult); } } private TripleLocation findTriple(long[] triple) throws IOException { AVLNode[] findResult = avlFilePhase.find(avlComparator, triple); if (findResult == null) return null; AVLNode node; if (findResult.length == 2) { node = findResult[1]; if (node != null) node.incRefCount(); AVLFile.release(findResult); return new TripleLocation(node, 0); } // Found the node. node = findResult[0]; node.incRefCount(); AVLFile.release(findResult); int nrTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); if (nrTriples == 1) { return new TripleLocation(node, 0); } // See if it matches the low triple. if ( // Low triple. node.getPayloadLong(IDX_LOW_TRIPLE) == triple[0] && node.getPayloadLong(IDX_LOW_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_LOW_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_LOW_TRIPLE + 3) == triple[3] ) { return new TripleLocation(node, 0); } if (nrTriples == 2) { return new TripleLocation(node, 1); } // See if it matches the high triple. if ( // High triple. node.getPayloadLong(IDX_HIGH_TRIPLE) == triple[0] && node.getPayloadLong(IDX_HIGH_TRIPLE + 1) == triple[1] && node.getPayloadLong(IDX_HIGH_TRIPLE + 2) == triple[2] && node.getPayloadLong(IDX_HIGH_TRIPLE + 3) == triple[3] ) { return new TripleLocation(node, nrTriples - 1); } // Get the triple block. Block tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); // Find the triple. int index = binarySearch(tripleBlock, tripleComparator, 0, nrTriples, triple); if (index < 0) index = -index - 1; return new TripleLocation(node, index); } public final class Token { private AVLFile.Phase.Token avlFileToken; private ManagedBlockFile.Phase.Token blockFileToken; private Phase phase = Phase.this; /** * CONSTRUCTOR Token TO DO */ Token() { avlFileToken = avlFilePhase.use(); blockFileToken = blockFilePhase.use(); } public Phase getPhase() { assert avlFileToken != null : "Invalid Token"; assert blockFileToken != null : "Invalid Token"; return phase; } public void release() { assert avlFileToken != null : "Invalid Token"; assert blockFileToken != null : "Invalid Token"; avlFileToken.release(); avlFileToken = null; blockFileToken.release(); blockFileToken = null; phase = null; } } private abstract class AbstractStoreTuples implements StoreTuples { // keep a stack trace of the instantiation of this object protected List<Integer> objectIds = new ArrayList<Integer>(); private long[] startTriple; private TripleLocation start; private long[] endTriple; private TripleLocation end; protected Token token; private long nrTriples; private boolean nrTriplesValid = false; @SuppressWarnings("unused") private int prefixLength; protected int[] columnMap; protected Variable[] variables; private long[] tmpTriple = new long[SIZEOF_TRIPLE]; protected AVLNode node; protected boolean beforeStart; private int nrBlockTriples = 0; protected Block tripleBlock = null; protected int offset; private long endBlockId = Block.INVALID_BLOCK_ID; private int endOffset = 0; private long[] prefix = null; private int rowCardinality = -1; /** * CONSTRUCTOR TuplesImpl TO DO * * @param startTriple PARAMETER TO DO * @param endTriple PARAMETER TO DO * @param prefixLength PARAMETER TO DO * @throws IOException EXCEPTION TO DO */ AbstractStoreTuples( long[] startTriple, long[] endTriple, int prefixLength ) throws IOException { assert prefixLength >= 1 && prefixLength < SIZEOF_TRIPLE; if (Phase.this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); this.startTriple = startTriple; this.endTriple = endTriple; this.prefixLength = prefixLength; start = findTriple(startTriple); end = findTriple(endTriple); if (end != null && end.node != null) { endBlockId = end.node.getId(); endOffset = end.offset; } if (start != null && start.node != null && (start.node.getId() != endBlockId || start.offset < endOffset)) { token = use(); beforeStart = true; } else { close(); } } /** * CONSTRUCTOR TuplesImpl TO DO */ AbstractStoreTuples() { if (Phase.this == currentPhase && tripleWriteThread != null) tripleWriteThread.drain(); this.nrTriples = Phase.this.nrFileTriples; this.nrTriplesValid = true; columnMap = sortOrder; this.variables = new Variable[] { StatementStore.VARIABLES[order0], StatementStore.VARIABLES[order1], StatementStore.VARIABLES[order2], StatementStore.VARIABLES[order3] }; startTriple = new long[SIZEOF_TRIPLE]; endTriple = new long[] { Constants.MASK63, Constants.MASK63, Constants.MASK63, Constants.MASK63 }; prefixLength = 0; AVLNode node = avlFilePhase.getRootNode(); if (node != null) { start = new TripleLocation(node.getMinNode_R(), 0); token = use(); end = new TripleLocation(null, 0); beforeStart = true; } else { close(); } } public int[] getColumnOrder() { return (int[])columnMap.clone(); } public long getRawColumnValue(int column) throws TuplesException { return getColumnValue(column); } public long getColumnValue(int column) throws TuplesException { try { return tripleBlock.getLong(offset * SIZEOF_TRIPLE + columnMap[column]); } catch (ArrayIndexOutOfBoundsException ex) { if (column < 0 || column >= variables.length) { throw new TuplesException("Column index out of range: " + column); } throw ex; } catch (NullPointerException ex) { if (beforeStart || node == null) { throw new TuplesException("No current row. Before start: " + beforeStart + " node: " + node); } throw ex; } } public Variable[] getVariables() { return (Variable[])variables.clone(); } public int getNumberOfVariables() { return variables.length; } public long getRowCount() throws TuplesException { if (nrTriplesValid) return nrTriples; nrTriplesValid = true; if (start == null) return nrTriples = 0; long n = endOffset - start.offset; AVLNode curNode = start.node; curNode.incRefCount(); while (curNode != null && curNode.getId() != endBlockId) { n += curNode.getPayloadInt(IDX_NR_TRIPLES_I); curNode = curNode.getNextNode_R(); } if (curNode != null) { curNode.release(); } return nrTriples = n; } public long getRowUpperBound() throws TuplesException { return getRowCount(); } public long getRowExpectedCount() throws TuplesException { return getRowCount(); } public int getRowCardinality() throws TuplesException { if (rowCardinality != -1) return rowCardinality; Tuples temp = (Tuples)this.clone(); temp.beforeFirst(); if (!temp.next()) { rowCardinality = Cursor.ZERO; } else if (!temp.next()) { rowCardinality = Cursor.ONE; } else { rowCardinality = Cursor.MANY; } temp.close(); return rowCardinality; } public int getColumnIndex(Variable variable) throws TuplesException { for (int i = 0; i < variables.length; ++i) { if (variables[i].equals(variable)) return i; } throw new TuplesException("variable doesn't match any column"); } public boolean isColumnEverUnbound(int column) { return false; } public boolean isMaterialized() { return true; } public boolean isUnconstrained() { return false; } public RowComparator getComparator() { return DefaultRowComparator.getInstance(); } public List<Tuples> getOperands() { return Collections.emptyList(); } public boolean isEmpty() { return start == null; } public boolean next() throws TuplesException { if (prefix.length > variables.length) throw new TuplesException("prefix too long: " + prefix.length); // Move to the next row. if (!advance()) { return false; } if (prefix.length == 0) return true; // See if the current row matches the prefix. RowComparator comparator = getComparator(); int c = comparator.compare(prefix, this); if (c == 0) return true; closeIterator(); if (c < 0) return false; // Reorder the prefix to create a triple. System.arraycopy(startTriple, 0, tmpTriple, 0, SIZEOF_TRIPLE); for (int i = 0; i < prefix.length; ++i) tmpTriple[columnMap[i]] = prefix[i]; // Check if the prefix is past the end triple. if (tripleComparator.compare(tmpTriple, endTriple) >= 0) return false; // Locate the first triple greater than or equal to the prefix. TripleLocation tLoc; try { tLoc = findTriple(tmpTriple); } catch (IOException ex) { throw new TuplesException("I/O error", ex); } if (tLoc.node != null) { if (tLoc.node.getId() != endBlockId || tLoc.offset < endOffset) { node = tLoc.node; offset = tLoc.offset; readTripleBlock(); return comparator.compare(prefix, this) == 0; } else { tLoc.node.release(); } } return false; } public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException { if (prefix == null) throw new IllegalArgumentException("Null \"prefix\" parameter"); if (suffixTruncation != 0) throw new TuplesException("Suffix truncation not implemented"); beforeStart = true; this.prefix = prefix; } public void beforeFirst() { beforeStart = true; prefix = Tuples.NO_PREFIX; } public boolean hasNoDuplicates() { return true; } /** * 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 (logger.isDebugEnabled()) { logger.debug("Renaming variables. before: " + Arrays.asList(variables) + " constraint: " + constraint); } for (int i = 0; i < columnMap.length; ++i) variables[i] = (Variable) constraint.getElement(columnMap[i]); if (logger.isDebugEnabled()) { logger.debug("Renaming variables. after: " + Arrays.asList(variables)); } } public String toString() { Tuples cloned = (Tuples) clone(); NumberFormat formatter = new DecimalFormat("000000"); try { StringBuffer buffer = new StringBuffer(eol + "{"); Variable[] variables = cloned.getVariables(); for (int i = 0; i < variables.length; i++) { buffer.append(variables[i]); for (int j = 0; j < (6 - variables[i].toString().length()); j++) buffer.append(" "); buffer.append(" "); } if (cloned.isMaterialized()) { buffer.append("("); buffer.append(cloned.getRowCount()); buffer.append(" rows)" + eol); } else { buffer.append("(unevaluated, "); buffer.append(cloned.getRowCount()); buffer.append(" rows max)" + eol); } cloned.beforeFirst(); int rowNo = 0; while (cloned.next()) { if (++rowNo > 20) { buffer.append("..." + eol); break; } buffer.append("["); for (int i = 0; i < variables.length; i++) { buffer.append(formatter.format(cloned.getColumnValue(i))); buffer.append(" "); } buffer.append("]" + eol); } buffer.append("}"); return buffer.toString(); } catch (TuplesException e) { return e.toString(); } finally { try { cloned.close(); } catch (Exception e) { logger.warn("Failed to close tuples after serializing", e); } } } public Object clone() { try { AbstractStoreTuples copy = (AbstractStoreTuples) super.clone(); tmpTriple = new long[SIZEOF_TRIPLE]; if (start != null) { start.node.incRefCount(); if (end.node != null) end.node.incRefCount(); copy.token = use(); copy.tripleBlock = null; copy.node = null; copy.beforeFirst(); } copy.variables = getVariables(); copy.objectIds = new ArrayList<Integer>(objectIds); copy.objectIds.add(new Integer(System.identityHashCode(this))); return copy; } catch (CloneNotSupportedException ex) { throw new Error(); } } public void close() { closeIterator(); if (token != null) { token.release(); token = null; } startTriple = null; if (start != null) { if (start.node != null) start.node.release(); start = null; } endTriple = null; if (end != null) { if (end.node != null) end.node.release(); end = null; } } /* Don't enable this in production unless you want a significant increase in heap usage, * out-of-heap errors, and 60% slow-down of the queries. public void finalize() { if (logger.isDebugEnabled()) { if (stack != null) { logger.debug("TuplesImpl not closed (" + System.identityHashCode(this) + ")\n" + stack); logger.debug("----Provenance : " + objectIds); } } } */ public boolean equals(Object o) { if (o == null) { return false; } if (o == this) return true; // Make sure the object is a Tuples. Tuples t; try { t = (Tuples) o; } catch (ClassCastException ex) { return false; } Tuples t1 = null; Tuples t2 = null; try { if (getRowCount() != t.getRowCount()) return false; if (getRowCount() == 0) return true; // Return false if the column variable names don't match or the number // of columns differ. if (!Arrays.asList(getVariables()).equals(Arrays.asList(t.getVariables()))) return false; // Clone the two Tuples objects. t1 = (Tuples) this.clone(); t2 = (Tuples) t.clone(); // Get the default comparator. RowComparator comp = getComparator(); t1.beforeFirst(); t2.beforeFirst(); while (t1.next()) { if (!t2.next() || comp.compare(t1, t2) != 0) return false; } return !t2.next(); } catch (TuplesException ex) { throw new RuntimeException(ex.toString(), ex); } finally { try { try { if (t1 != null) t1.close(); } finally { if (t2 != null) t2.close(); } } catch (TuplesException ex) { throw new RuntimeException(ex.toString(), ex); } } } /** * Added to match {@link #equals(Object)}. * Based on the same approach as {@link AbstractTuples#hashCode()} */ public int hashCode() { return TuplesOperations.hashCode(this); } private boolean advance() throws TuplesException { if (beforeStart) { // Reset the iterator position to the start. beforeStart = false; if (start != null) { // Tuples is not empty. Reset to first triple. closeIterator(); node = start.node; node.incRefCount(); offset = start.offset; } } else if (node != null) { if (++offset == nrBlockTriples) { offset = 0; tripleBlock = null; node = node.getNextNode_R(); } if ( node != null && node.getId() == endBlockId && offset >= endOffset ) { closeIterator(); } } readTripleBlock(); return node != null; } private void closeIterator() { if (tripleBlock != null) tripleBlock = null; if (node != null) { node.release(); node = null; } } private void readTripleBlock() throws TuplesException { if (tripleBlock == null && node != null) { nrBlockTriples = node.getPayloadInt(IDX_NR_TRIPLES_I); try { tripleBlock = blockFilePhase.readBlock(node.getPayloadLong(IDX_BLOCK_ID)); } catch (IOException ex) { throw new TuplesException("I/O error", ex); } } } /** * Copied from AbstractTuples */ public Annotation getAnnotation(Class<? extends Annotation> annotationClass) throws TuplesException { return null; } } /** * The standard implementation of the StoreTuples for this phase. */ private final class TuplesImpl extends AbstractStoreTuples { TuplesImpl(long[] startTriple, long[] endTriple, int prefixLength) throws IOException { super(startTriple, endTriple, prefixLength); int nrColumns = SIZEOF_TRIPLE - prefixLength; variables = new Variable[nrColumns]; // Set up a column order which moves the prefix columns to the end. columnMap = new int[nrColumns]; for (int i = 0; i < nrColumns; ++i) { columnMap[i] = sortOrder[(i + prefixLength) % SIZEOF_TRIPLE]; variables[i] = StatementStore.VARIABLES[columnMap[i]]; } } TuplesImpl() { super(); } } /** * A version of StoreTuples which is designed to set the Meta variable to a requested value. * * @created Dec 22, 2008 * @author Paula Gearon * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a> * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a> */ private final class MetaTuplesImpl extends AbstractStoreTuples { /** The meta node this tuples comes from. */ private final long metaNode; /** * Constructs the Tuples to come from the store. * @param startTriple The first triple for this tuples. * @param endTriple The first triple pattern that is NOT part of this tuples. * This may not appear in the store, but if anything is <= this triple * then it is NOT in the tuples. * @param prefixLength The number of elements used to identify the requires triples. * @throws IOException If there was an I/O error accessing the triples data in the store. */ MetaTuplesImpl(long[] startTriple, long[] endTriple, int prefixLength) throws IOException { super(startTriple, endTriple, prefixLength); int nrColumns = SIZEOF_TRIPLE - prefixLength; variables = new Variable[nrColumns + 1]; // Set up a column order which moves the prefix columns to the end. columnMap = new int[nrColumns + 1]; for (int i = 0; i < nrColumns; ++i) { columnMap[i] = sortOrder[(i + prefixLength) % SIZEOF_TRIPLE]; variables[i] = StatementStore.VARIABLES[columnMap[i]]; } // make the last variable "Meta" columnMap[nrColumns] = nrColumns; variables[nrColumns] = StatementStore.VARIABLES[StatementStore.VARIABLES.length - 1]; metaNode = startTriple[order0]; } /** * Get the value from the given column on the current row. * @param column The column to get the value of the binding from. * @return The binding for the given column on the current row. * @throws TuplesException If the current state is wrong, or if the column requested is invalid. */ public long getColumnValue(int column) throws TuplesException { if (column == variables.length - 1) return metaNode; // inlining rather than calling to super try { return tripleBlock.getLong(offset * SIZEOF_TRIPLE + columnMap[column]); } catch (ArrayIndexOutOfBoundsException ex) { if (column < 0 || column >= variables.length) { throw new TuplesException("Column index out of range: " + column); } throw ex; } catch (NullPointerException ex) { if (beforeStart || node == null) { throw new TuplesException("No current row. Before start: " + beforeStart + " node: " + node); } throw ex; } } } } }