![JAR search and dependency download from the Maven repository](/logo.png)
ca.odell.glazedlists.impl.io.Bufferlo Maven / Gradle / Ivy
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.text.ParseException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A high-level class for moving data and parsing protocols.
*
* @author Jesse Wilson
*/
public class Bufferlo implements CharSequence {
/** the buffers managed by this Bufferlo */
private LinkedList buffers = new LinkedList();
/** write to this bufferlo */
private BufferloOutputStream out = new BufferloOutputStream();
/** read from this bufferlo */
private BufferloInputStream in = new BufferloInputStream();
/**
* Clears the contents of this bufferlo.
*/
public void clear() {
buffers.clear();
}
/**
* Populate this Bufferlo with the data from the specified channel.
*/
public int readFromChannel(ReadableByteChannel source) throws IOException {
int totalRead = 0;
while(true) {
// we need a new place to write into
ByteBuffer writeInto = getWriteIntoBuffer();
// read in
int bytesRead = source.read(writeInto);
doneWriting();
// figure out what to do next
if(bytesRead < 0 && totalRead == 0) return bytesRead;
else if(bytesRead <= 0) return totalRead;
else totalRead += bytesRead;
}
}
/**
* Populate this Bufferlo with the data from the specified channel, up to
*
* @param bytesRequested the maximum number of bytes to read.
*/
public int readFromChannel(ReadableByteChannel source, int bytesRequested) throws IOException {
int totalRead = 0;
while(totalRead < bytesRequested) {
// we need a new place to write into
ByteBuffer writeInto = getWriteIntoBuffer();
int bytesRemaining = bytesRequested - totalRead;
int maxBytesToRead = Math.min(bytesRemaining, writeInto.remaining());
writeInto.limit(writeInto.position() + maxBytesToRead);
// read in
int bytesRead = source.read(writeInto);
doneWriting();
// figure out what to do next
if(bytesRead < 0 && totalRead == 0) return bytesRead;
else if(bytesRead <= 0) return totalRead;
else totalRead += bytesRead;
}
// we've read all we want
return totalRead;
}
/**
* Write the content of this Bufferlo to the specified channel.
*/
public long writeToChannel(GatheringByteChannel target) throws IOException {
// nothing to write
if(length() == 0) return 0;
// make all buffers readable
ByteBuffer[] toWrite = new ByteBuffer[buffers.size()];
for(int b = 0; b < buffers.size(); b++) {
ByteBuffer buffer = (ByteBuffer)buffers.get(b);
buffer.flip();
toWrite[b] = buffer;
}
// write them all out
long totalWritten = target.write(toWrite);
// restore the state on all buffers
for(ListIterator b = buffers.listIterator(); b.hasNext(); ) {
ByteBuffer buffer = (ByteBuffer)b.next();
if(!buffer.hasRemaining()) {
b.remove();
} else if(buffer.position() > 0) {
int bytesLeftToRead = buffer.remaining();
buffer.limit(buffer.capacity());
ByteBuffer noneRead = buffer.slice();
noneRead.position(bytesLeftToRead);
noneRead.limit(bytesLeftToRead);
b.set(noneRead);
} else {
buffer.position(buffer.limit());
}
}
// return the count of bytes written
return totalWritten;
}
/**
* Write the content of this Bufferlo to the specified channel, updating the
* specified {@link SelectionKey} as necessary.
*/
public long writeToChannel(GatheringByteChannel target, SelectionKey selectionKey) throws IOException {
// verify we can still write
if(!selectionKey.isValid()) throw new IOException("Key cancelled");
// do the write
long totalWritten = writeToChannel(target);
// adjust the key based on whether we have leftovers
if(length() > 0) {
selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
} else {
selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
}
// return the count of bytes written
return totalWritten;
}
/**
* Gets the bytes of this Bufferlo.
*/
public byte[] consumeBytes(int bytes) {
try {
byte[] result = new byte[bytes];
int totalRead = 0;
while(totalRead < bytes) {
int read = getInputStream().read(result, totalRead, (bytes - totalRead));
totalRead += read;
}
return result;
} catch(IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
* Gets an InputStream that reads this buffer.
*/
public BufferloInputStream getInputStream() {
return in;
}
/**
* Gets an OutputStream that writes this buffer.
*/
public BufferloOutputStream getOutputStream() {
return out;
}
/**
* Duplicates the exact state of this buffer. The returned buffer is read-only.
*/
public Bufferlo duplicate() {
Bufferlo result = new Bufferlo();
for(int i = 0; i < buffers.size(); i++) {
ByteBuffer buffer = (ByteBuffer)buffers.get(i);
result.buffers.add(removeTrailingSpace(buffer));
}
return result;
}
/**
* Read the specified bytes into a new Bufferlo. The returned buffer is read-only.
*/
public Bufferlo consume(int bytes) {
assert(bytes >= 0 && bytes <= length());
Bufferlo result = duplicate();
result.limit(bytes);
skip(bytes);
return result;
}
/**
* Writes the specified String to this Bufferlo.
*/
public void write(String data) {
append(stringToBytes(data));
}
/**
* Converts the specified String to bytes, one byte per character. The input
* String must contain US-ASCII one-byte characters or the result of this
* method is not specified.
*/
public static final ByteBuffer stringToBytes(String in) {
try {
return ByteBuffer.wrap(in.getBytes("US-ASCII"));
} catch(UnsupportedEncodingException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
* Writes the specified data to this bufferlo. This shortens the last ByteBuffer
* in the added set so that it will not be written to. This allows a read-only
* buffer to be added without it ever being modified.
*
* This will consume the specified Bufferlo.
*/
public Bufferlo append(Bufferlo data) {
buffers.addAll(data.buffers);
data.buffers.clear();
return this;
}
/**
* Appends a the specified data. The data will be consumed so it should be
* a duplicate if it is to be reused.
*/
public Bufferlo append(ByteBuffer data) {
ByteBuffer myCopy = data.slice();
myCopy.position(myCopy.limit());
buffers.add(myCopy);
data.position(data.limit());
return this;
}
/**
* Limits this Bufferlo to the specified size.
*/
public void limit(int bytes) {
int bytesLeft = bytes;
for(ListIterator b = buffers.listIterator(); b.hasNext(); ) {
ByteBuffer current = (ByteBuffer)b.next();
if(bytesLeft <= 0) {
b.remove();
} else if(current.capacity() >= bytesLeft) {
current.position(bytesLeft);
current.limit(bytesLeft);
}
bytesLeft -= current.capacity();
}
}
/**
* Skips the specified number of bytes.
*/
public void skip(int bytes) {
assert(bytes >= 0 && bytes <= length());
int bytesLeft = bytes;
for(ListIterator b = buffers.listIterator(); b.hasNext(); ) {
ByteBuffer current = (ByteBuffer)b.next();
if(bytesLeft >= current.limit()) {
bytesLeft -= current.limit();
b.remove();
} else {
current.position(bytesLeft);
ByteBuffer smaller = current.slice();
smaller.position(smaller.limit());
b.set(smaller);
break;
}
}
}
/**
* Creates a new ByteBuffer identical to the parameter but without space trailing
* after the limit. This allows a buffer to be added to the Bufferlo without
* that buffer ever being written to.
*
* This assumes that position == limit.
*/
private ByteBuffer removeTrailingSpace(ByteBuffer buffer) {
ByteBuffer clone = buffer.duplicate();
clone.position(0);
ByteBuffer noTrailingSpace = clone.slice();
noTrailingSpace.position(noTrailingSpace.limit());
return noTrailingSpace;
}
/**
* Write to the Bufferlo as a Stream.
*/
class BufferloOutputStream extends OutputStream {
@Override
public void write(int b) {
ByteBuffer writeBuffer = getWriteIntoBuffer();
writeBuffer.put((byte)b);
doneWriting();
}
}
/**
* Read from the Bufferlo as a Stream.
*/
class BufferloInputStream extends InputStream {
@Override
public int read() {
ByteBuffer readBuffer = getReadFromBuffer();
if(readBuffer == null) return -1;
int result = 0xFF & readBuffer.get();
doneReading();
return result;
}
}
/**
* Gets a buffer that we can read from. This buffer must be flipped back into
* write mode when this is complete by calling doneReading().
*/
private ByteBuffer getReadFromBuffer() {
if(buffers.isEmpty()) return null;
ByteBuffer readFrom = (ByteBuffer)buffers.getFirst();
readFrom.flip();
return readFrom;
}
/**
* Finishes reading the current read from buffer.
*/
private void doneReading() {
ByteBuffer readFrom = (ByteBuffer)buffers.getFirst();
// if we've exhaused this buffer
if(!readFrom.hasRemaining()) {
buffers.removeFirst();
// we still have more to read from this buffer
} else {
int bytesLeftToRead = readFrom.remaining();
readFrom.limit(readFrom.capacity());
ByteBuffer noneRead = readFrom.slice();
noneRead.position(bytesLeftToRead);
noneRead.limit(bytesLeftToRead);
buffers.set(0, noneRead);
}
}
/**
* Gets a buffer that we can write data into.
*/
private ByteBuffer getWriteIntoBuffer() {
// we have a buffer with space remaining
if(!buffers.isEmpty()) {
ByteBuffer last = (ByteBuffer)buffers.getLast();
if(last.position() < last.capacity()) {
last.limit(last.capacity());
return last;
}
}
// we need to create a new buffer
ByteBuffer writeInto = getNewBuffer();
buffers.addLast(writeInto);
return writeInto;
}
/**
* Finishes writing the current buffer.
*/
private void doneWriting() {
ByteBuffer writeInto = (ByteBuffer)buffers.getLast();
writeInto.limit(writeInto.position());
}
/**
* Get the number of bytes available.
*/
@Override
public int length() {
int bytesAvailable = 0;
for(Iterator b = buffers.iterator(); b.hasNext(); ) {
ByteBuffer buffer = (ByteBuffer)b.next();
bytesAvailable += buffer.position();
}
return bytesAvailable;
}
/**
* Gets the character at the specified index.
*/
@Override
public char charAt(int index) {
int bytesLeft = index;
for(Iterator b = buffers.iterator(); b.hasNext(); ) {
ByteBuffer buffer = (ByteBuffer)b.next();
if(bytesLeft < buffer.position()) {
return (char)buffer.get(bytesLeft);
} else {
bytesLeft -= buffer.position();
}
}
throw new IndexOutOfBoundsException();
}
/**
* Returns a new character sequence that is a subsequence of this.
*/
@Override
public CharSequence subSequence(int start, int end) {
Bufferlo clone = duplicate();
clone.skip(start);
clone.limit(end - start);
return clone;
}
/**
* Gets this Bufferlo as a String.
*/
@Override
public String toString() {
StringBuffer result = new StringBuffer();
for(int c = 0; c < length(); c++) {
result.append(charAt(c));
}
return result.toString();
}
public String toDebugString() {
return "BUFFERLO {" + buffers + "}";
}
/**
* Finds the first index of the specified regular expression.
*
* @return the index of the specified regular expression, or -1 if that
* regular expression does not currently exist in the ByteBuffer. This
* index is volatile because further operations on this ByteBufferParser
* may influence the location of the specified regular expression.
*/
public int indexOf(String regex) {
Matcher matcher = Pattern.compile(regex).matcher(this);
if(!matcher.find()) return -1;
return matcher.start();
}
/**
* Consumes the specified regular expression. This simply advances the buffer's
* position to the end of the regular expression.
*
* @throws ParseException if the specified expression is not in the input buffer
* @return the number of bytes consumed.
*/
public int consume(String regex) throws ParseException {
Matcher matcher = Pattern.compile(regex).matcher(this);
if(!matcher.find()) throw new ParseException(regex + " is not in current buffer", 0);
if(matcher.start() != 0) throw new ParseException(regex + " is not a prefix of " + this, 0);
skip(matcher.end());
return matcher.end();
}
/**
* Reads the String up until the specified regular expression and returns it.
* This advances the buffer's position to the end of the regular expression.
*
* @throws ParseException if the specified expression is not in the input buffer
*/
public String readUntil(String regex) throws ParseException {
return readUntil(regex, true);
}
/**
* Reads the String up until the specified regular expression and returns it.
*
* @param consume true to advance the buffer's position to the end of the
* regular expression, or false to not modify the buffer
* @throws ParseException if the specified expression is not in the input buffer
*/
public String readUntil(String regex, boolean consume) throws ParseException {
Matcher matcher = Pattern.compile(regex).matcher(this);
if(!matcher.find()) throw new ParseException(regex + " is not in current buffer", 0);
String result = subSequence(0, matcher.start()).toString();
if(consume) skip(matcher.end());
return result;
}
/**
* Gets a new buffer by creating it or removing it from the pool.
*/
private ByteBuffer getNewBuffer() {
int BUFFER_SIZE = 8196;
return ByteBuffer.allocateDirect(BUFFER_SIZE);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy