/** * Reader factory method. Build a Node object (of the right type) by reading a block in the file. * * @param config Configuration of the History Tree * @param fc FileChannel to the history file, ALREADY SEEKED at the start of the node. * @return The node object * @throws IOException If there was an error reading from the file channel */ public static final HTNode readNode(HTConfig config, FileChannel fc) throws IOException { HTNode newNode = null; int res, i; ByteBuffer buffer = ByteBuffer.allocate(config.getBlockSize()); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.clear(); res = fc.read(buffer); assert (res == config.getBlockSize()); buffer.flip(); /* Read the common header part */ byte typeByte = buffer.get(); NodeType type = NodeType.fromByte(typeByte); long start = buffer.getLong(); long end = buffer.getLong(); int seqNb = buffer.getInt(); int parentSeqNb = buffer.getInt(); int intervalCount = buffer.getInt(); int stringSectionOffset = buffer.getInt(); buffer.get(); // TODO Used to be "isDone", to be removed from the header /* Now the rest of the header depends on the node type */ switch (type) { case CORE: /* Core nodes */ newNode = new CoreNode(config, seqNb, parentSeqNb, start); newNode.readSpecificHeader(buffer); break; case LEAF: /* Leaf nodes */ newNode = new LeafNode(config, seqNb, parentSeqNb, start); newNode.readSpecificHeader(buffer); break; default: /* Unrecognized node type */ throw new IOException(); } /* * At this point, we should be done reading the header and 'buffer' * should only have the intervals left */ for (i = 0; i < intervalCount; i++) { HTInterval interval = HTInterval.readFrom(buffer); newNode.fIntervals.add(interval); newNode.fSizeOfIntervalSection += interval.getIntervalSize(); } /* Assign the node's other information we have read previously */ newNode.fNodeEnd = end; newNode.fStringSectionOffset = stringSectionOffset; newNode.fIsOnDisk = true; return newNode; }
/** * Get a single Interval from the information in this node If the key/timestamp pair cannot be * found, we return null. * * @param key The attribute quark to look for * @param t The timestamp * @return The Interval containing the information we want, or null if it wasn't found * @throws TimeRangeException If 't' is invalid */ public HTInterval getRelevantInterval(int key, long t) throws TimeRangeException { fRwl.readLock().lock(); try { for (int i = getStartIndexFor(t); i < fIntervals.size(); i++) { HTInterval curInterval = fIntervals.get(i); if (curInterval.getAttribute() == key && curInterval.getStartTime() <= t && curInterval.getEndTime() >= t) { return curInterval; } } /* We didn't find the relevant information in this node */ return null; } finally { fRwl.readLock().unlock(); } }
/** * Add an interval to this node * * @param newInterval Interval to add to this node */ public void addInterval(HTInterval newInterval) { fRwl.writeLock().lock(); try { /* Just in case, should be checked before even calling this function */ assert (newInterval.getIntervalSize() <= getNodeFreeSpace()); /* Find the insert position to keep the list sorted */ int index = fIntervals.size(); while (index > 0 && newInterval.compareTo(fIntervals.get(index - 1)) < 0) { index--; } fIntervals.add(index, newInterval); fSizeOfIntervalSection += newInterval.getIntervalSize(); /* Update the in-node offset "pointer" */ fStringSectionOffset -= (newInterval.getStringsEntrySize()); } finally { fRwl.writeLock().unlock(); } }
/** * Write this node to the given file channel. * * @param fc The file channel to write to (should be sought to be correct position) * @throws IOException If there was an error writing */ public final void writeSelf(FileChannel fc) throws IOException { /* * Yes, we are taking the *read* lock here, because we are reading the * information in the node to write it to disk. */ fRwl.readLock().lock(); try { final int blockSize = fConfig.getBlockSize(); int curStringsEntryEndPos = blockSize; ByteBuffer buffer = ByteBuffer.allocate(blockSize); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.clear(); /* Write the common header part */ buffer.put(getNodeType().toByte()); buffer.putLong(fNodeStart); buffer.putLong(fNodeEnd); buffer.putInt(fSequenceNumber); buffer.putInt(fParentSequenceNumber); buffer.putInt(fIntervals.size()); buffer.putInt(fStringSectionOffset); buffer.put((byte) 1); // TODO Used to be "isDone", to be removed from header /* Now call the inner method to write the specific header part */ writeSpecificHeader(buffer); /* Back to us, we write the intervals */ for (HTInterval interval : fIntervals) { int size = interval.writeInterval(buffer, curStringsEntryEndPos); curStringsEntryEndPos -= size; } /* * Write padding between the end of the Data section and the start * of the Strings section (needed to fill the node in case there is * no Strings section) */ while (buffer.position() < fStringSectionOffset) { buffer.put((byte) 0); } /* * If the offsets were right, the size of the Strings section should * be == to the expected size */ assert (curStringsEntryEndPos == fStringSectionOffset); /* Finally, write everything in the Buffer to disk */ // if we don't do this, flip() will lose what's after. buffer.position(blockSize); buffer.flip(); int res = fc.write(buffer); assert (res == blockSize); } finally { fRwl.readLock().unlock(); } fIsOnDisk = true; }