/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.util; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import jpcsp.Memory; public class FIFOByteBuffer { private byte[] buffer; private int bufferReadOffset; private int bufferWriteOffset; private int bufferLength; public FIFOByteBuffer() { buffer = new byte[0]; clear(); } public FIFOByteBuffer(byte[] buffer) { this.buffer = buffer; bufferReadOffset = 0; bufferWriteOffset = 0; bufferLength = buffer.length; } private int incrementOffset(int offset, int n) { offset += n; if (offset >= buffer.length) { offset -= buffer.length; } else if (offset < 0) { offset += buffer.length; } return offset; } public void clear() { bufferReadOffset = 0; bufferWriteOffset = 0; bufferLength = 0; } private void checkBufferForWrite(int length) { if (bufferLength + length > buffer.length) { // The buffer has to be extended byte[] extendedBuffer = new byte[bufferLength + length]; if (bufferReadOffset + bufferLength <= buffer.length) { System.arraycopy(buffer, bufferReadOffset, extendedBuffer, 0, bufferLength); } else { int lengthEndBuffer = buffer.length - bufferReadOffset; System.arraycopy(buffer, bufferReadOffset, extendedBuffer, 0, lengthEndBuffer); System.arraycopy(buffer, 0, extendedBuffer, lengthEndBuffer, bufferLength - lengthEndBuffer); } buffer = extendedBuffer; bufferReadOffset = 0; bufferWriteOffset = bufferLength; } } private void copyToBuffer(int offset, int length, Buffer src) { ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, offset, length); Utilities.putBuffer(byteBuffer, src, ByteOrder.LITTLE_ENDIAN, length); } public void write(Buffer src, int length) { if (buffer == null) { return; // FIFOByteBuffer has been deleted } checkBufferForWrite(length); // Copy the src content to the buffer at offset bufferWriteOffset if (bufferWriteOffset + length <= buffer.length) { // No buffer wrap, only 1 copy operation necessary copyToBuffer(bufferWriteOffset, length, src); } else { // The buffer wraps at the end, 2 copy operations necessary int lengthEndBuffer = buffer.length - bufferWriteOffset; if ((lengthEndBuffer & 3) == 0 || !(src instanceof IntBuffer)) { copyToBuffer(bufferWriteOffset, lengthEndBuffer, src); copyToBuffer(0, length - lengthEndBuffer, src); } else { // Making a copy from an IntBuffer on non-int boundaries int lengthEndBuffer4 = lengthEndBuffer & ~3; copyToBuffer(bufferWriteOffset, lengthEndBuffer4, src); // Copy one int-value across non-int boundaries... int overlapValue = ((IntBuffer) src).get(); byte[] bytes4 = new byte[4]; ByteBuffer src1 = ByteBuffer.wrap(bytes4).order(ByteOrder.LITTLE_ENDIAN); src1.asIntBuffer().put(overlapValue); int bytesCopyLength1 = lengthEndBuffer & 3; copyToBuffer(bufferWriteOffset + lengthEndBuffer4, bytesCopyLength1, src1); int bytesCopyLength2 = bytes4.length - bytesCopyLength1; copyToBuffer(0, bytesCopyLength2, src1); copyToBuffer(bytesCopyLength2, length - lengthEndBuffer - bytesCopyLength2, src); } } bufferWriteOffset = incrementOffset(bufferWriteOffset, length); bufferLength += length; } public void write(int address, int length) { if (length > 0 && Memory.isAddressGood(address)) { Buffer memoryBuffer = Memory.getInstance().getBuffer(address, length); write(memoryBuffer, length); } } public void write(ByteBuffer src) { write(src, src.remaining()); } public void write(byte[] src) { write(ByteBuffer.wrap(src), src.length); } public void write(byte[] src, int offset, int length) { write(ByteBuffer.wrap(src, offset, length), length); } public int readByteBuffer(ByteBuffer dst) { if (buffer == null) { return 0; } int length = dst.remaining(); if (length > bufferLength) { length = bufferLength; } if (bufferReadOffset + length > buffer.length) { int lengthEndBuffer = buffer.length - bufferReadOffset; dst.put(buffer, bufferReadOffset, lengthEndBuffer); dst.put(buffer, 0, length - lengthEndBuffer); } else { dst.put(buffer, bufferReadOffset, length); } bufferReadOffset = incrementOffset(bufferReadOffset, length); bufferLength -= length; return length; } public boolean forward(int length) { if (buffer == null || length < 0) { return false; } if (length == 0) { return true; } if (length > bufferLength) { return false; } bufferLength -= length; bufferReadOffset = incrementOffset(bufferReadOffset, length); return true; } public boolean rewind(int length) { if (buffer == null || length < 0) { return false; } if (length == 0) { return true; } int maxRewindLength = buffer.length - bufferLength; if (length > maxRewindLength) { return false; } bufferLength += length; bufferReadOffset = incrementOffset(bufferReadOffset, -length); return true; } public int length() { return bufferLength; } public void delete() { buffer = null; } /** * Prepare the internal buffer to receive all least length bytes. * This is just a hint to avoid resizing the internal buffer too often. * * @param length recommended size in bytes for the internal buffer. */ public void setBufferLength(int length) { if (buffer != null && length > buffer.length) { checkBufferForWrite(length - buffer.length); } } @Override public String toString() { return String.format("FIFOByteBuffer(size=%d, bufferLength=%d, readOffset=%d, writeOffset=%d)", buffer.length, bufferLength, bufferReadOffset, bufferWriteOffset); } }