/** * 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; }
/** * Constructor * * @param config Configuration of the History Tree * @param seqNumber The (unique) sequence number assigned to this particular node * @param parentSeqNumber The sequence number of this node's parent node * @param start The earliest timestamp stored in this node */ protected HTNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { fConfig = config; fNodeStart = start; fSequenceNumber = seqNumber; fParentSequenceNumber = parentSeqNumber; fStringSectionOffset = config.getBlockSize(); fSizeOfIntervalSection = 0; fIsOnDisk = false; fIntervals = new ArrayList<>(); }
/** * Constructor * * @param config Configuration of the History Tree * @param seqNumber The (unique) sequence number assigned to this particular node * @param parentSeqNumber The sequence number of this node's parent node * @param start The earliest timestamp stored in this node */ protected HTNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { this.config = config; this.nodeStart = start; this.sequenceNumber = seqNumber; this.parentSequenceNumber = parentSeqNumber; this.stringSectionOffset = config.getBlockSize(); this.sizeOfIntervalSection = 0; this.isOnDisk = false; this.intervals = new ArrayList<>(); }
/** * Returns the current space utilization of this node, as a percentage. (used space / total usable * space, which excludes the header) * * @return The percentage (value between 0 and 100) of space utilization in in this node. */ public long getNodeUsagePercent() { fRwl.readLock().lock(); try { final int blockSize = fConfig.getBlockSize(); float freePercent = (float) getNodeFreeSpace() / (float) (blockSize - getTotalHeaderSize()) * 100F; return (long) (100L - freePercent); } finally { fRwl.readLock().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; }