package org.f1x.store; /** * Message store backed by byte[] array. Limited to N last bytes. */ public final class InMemoryMessageStore implements MessageStore { private static final int SIZE_OF_INT = 4; private final CircularBuffer buffer; private int lastSeqNum; //TODO: Initialize from message log //TODO: Don't bother storing admin messages? public InMemoryMessageStore(int bufferSize) { buffer = new CircularBuffer (bufferSize); } @Override public void clean() { synchronized (buffer) { buffer.clear(); lastSeqNum = 0; } } @Override public void put(int seqNum, byte[] message, int offset, int length) { synchronized (buffer) { if (seqNum <= lastSeqNum) throw new IllegalStateException("Attempt to store message #" + seqNum + " which is less than expected " + (lastSeqNum+1)); lastSeqNum = seqNum; // Store message using [LEN][Message][SEQ][LEN] format buffer.writeInt(length); buffer.writeByteArray(message, offset, length); buffer.writeInt(seqNum); buffer.writeInt(length); } } @Override public int get(int targetSeqNum, byte[] result) { synchronized (buffer) { if (targetSeqNum <= lastSeqNum) { long position = buffer.tail - 2*SIZE_OF_INT; final long limit = Math.max(0, buffer.tail - buffer.bufferSize); while (position > limit) { // Reading [LEN][Message][SEQ][LEN] backwards int seqNum = buffer.readInt(position); int msgLen = buffer.readInt(position + SIZE_OF_INT); position -= msgLen; if (position > limit) { if (seqNum == targetSeqNum) { // found it! buffer.readByteArray(position, result, 0, Math.min(msgLen, result.length)); result[msgLen] = 0; return seqNum; } if (seqNum < targetSeqNum) { break; } position -= 3*SIZE_OF_INT; } } } } return -1; // not found } private class MessageStoreIter implements MessageStoreIterator { private int nextSeqNum; private final int toSeqNum; private MessageStoreIter(int fromSeqNum, int toSeqNum) { if (nextSeqNum > toSeqNum) throw new IllegalArgumentException(); this.nextSeqNum = fromSeqNum; this.toSeqNum = toSeqNum; } @Override public int next(byte[] result) { if (nextSeqNum > toSeqNum) return -1; synchronized (buffer) { if (nextSeqNum > lastSeqNum) return -1; long position = buffer.tail - 2*SIZE_OF_INT; final long limit = Math.max(0, buffer.tail - buffer.bufferSize); int lastScannedSeqNum = -1; while (position > limit) { // reading [LEN][Message][SEQ][LEN] backwards int seqNum = buffer.readInt(position); int msgLen = buffer.readInt(position + SIZE_OF_INT); position -= msgLen; if (position > limit) { // do we have full message? if (seqNum == nextSeqNum) { // found it! buffer.readByteArray(position, result, 0, Math.min(msgLen, result.length)); result[msgLen] = 0; nextSeqNum ++; return seqNum; } if (seqNum < nextSeqNum) { //overshot break; } lastScannedSeqNum = seqNum; position -= 3*SIZE_OF_INT; } } if (lastScannedSeqNum > 0 && lastScannedSeqNum <= toSeqNum) { nextSeqNum = lastScannedSeqNum; return next(result); } } return -1; // not found } } @Override public MessageStoreIterator iterator(int fromSeqNum, int toSeqNum) { return new MessageStoreIter (fromSeqNum, toSeqNum); } /// Implementation private static final class CircularBuffer { private final int bufferSize; private final byte [] entries; private final int indexMask; private long tail = 0; CircularBuffer(int bufferSize) { if (bufferSize < 1) throw new IllegalArgumentException("bufferSize must not be less than 1"); if (Integer.bitCount(bufferSize) != 1) throw new IllegalArgumentException("bufferSize must be a power of 2"); this.bufferSize = bufferSize; this.entries = new byte[bufferSize]; this.indexMask = bufferSize - 1; } /** Translates sequence number to ring buffer offset */ private int index(long sequence) { return (int) sequence & indexMask; } /** Writes single byte specified by value parameter and advances offset */ private int writeByte (int offset, int value) { assert offset >=0 && offset < bufferSize; entries[offset++] = (byte) (0xFF & value); if (offset == bufferSize) offset = 0; return offset; } /** Store given INT32 number in Big-Endian notation (Used to store message length) */ void writeInt (int value) { int offset = index(tail); offset = writeByte (offset, value >>> 24); offset = writeByte (offset, value >>> 16); offset = writeByte (offset, value >>> 8); writeByte (offset, value); tail += 4; } void writeByteArray(byte [] src, int srcPos, int length) { final int index = index(tail); final int wrappedSize = index + length - bufferSize; if (wrappedSize <= 0) { System.arraycopy(src, srcPos, entries, index, length); } else { assert wrappedSize < length; final int numberOfBytesToWrite = length - wrappedSize; System.arraycopy(src, srcPos, entries, index, numberOfBytesToWrite); System.arraycopy(src, srcPos + numberOfBytesToWrite, entries, 0, wrappedSize); } tail += length; } /** Read INT32 number stored in Big-Endian notation (Used to read message length) */ int readInt (long sequence) { assert sequence >= 0; int offset = index(sequence); int result = (0xFF & entries[offset++]); if (offset == bufferSize) offset = 0; result = (result << 8) + (0xFF & entries[offset++]); if (offset == bufferSize) offset = 0; result = (result << 8) + (0xFF & entries[offset++]); if (offset == bufferSize) offset = 0; result = (result << 8) + (0xFF & entries[offset]); return result; } void readByteArray (long sequence, byte [] dst, int dstPos, int length) { assert sequence >= 0; int index = index(sequence); final int wrappedSize = index + length - bufferSize; if (wrappedSize <= 0) { System.arraycopy(entries, index, dst, dstPos, length); } else { assert wrappedSize < length; final int numberOfBytesToWrite = length - wrappedSize; System.arraycopy(entries, index, dst, dstPos, numberOfBytesToWrite); System.arraycopy(entries, 0, dst, dstPos + numberOfBytesToWrite, wrappedSize); } } void clear() { tail = 0; } } public String dump () { StringBuilder sb = new StringBuilder(1024); long position = buffer.tail - 2*SIZE_OF_INT; final long limit = Math.max(0, buffer.tail - buffer.bufferSize); while (position > limit) { int seqNum = buffer.readInt(position); int msgLen = buffer.readInt(position + SIZE_OF_INT); position -= msgLen; if (position > limit) { byte [] message = new byte [msgLen]; buffer.readByteArray(position, message, 0, msgLen); sb.append("[SEQ:"); sb.append(seqNum); sb.append(" MSG:"); sb.append(new String (message)); sb.append("] "); position -= 3*SIZE_OF_INT; } } return sb.toString(); } }