/*
* TransportCursor.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.simpleframework.transport;
import java.io.IOException;
/**
* The <code>TransportCursor</code> object represents a cursor that
* can read and buffer data from an underlying transport. If the
* number of bytes read from the cursor is more than required for
* the HTTP request then those bytes can be pushed back in to the
* cursor using the <code>reset</code> method. This will only allow
* the last read to be reset within the cursor safely.
*
* @author Niall Gallagher
*
* @see org.simpleframework.transport.Transport
*/
public class TransportCursor implements ByteCursor {
/**
* This is the stream for the bytes read by this cursor object.
*/
private ByteReader reader;
/**
* This is the buffer used to collect the bytes pushed back.
*/
private byte[] buffer;
/**
* This is the number of bytes that have been pushed back.
*/
private int count;
/**
* This is the mark from the last read from this cursor object.
*/
private int mark;
/**
* This is the position to read data from the internal buffer.
*/
private int pos;
/**
* This is the maximum number of bytes that can be pushed back.
*/
private int limit;
/**
* Constructor for the <code>TransportCursor</code> object. This
* requires a transport to read the bytes from. By default this
* will create a buffer of of the specified size to read the
* input in to which enabled bytes to be buffered internally.
*
* @param transport this is the underlying transport to use
*/
public TransportCursor(Transport transport) {
this(transport, 2048);
}
/**
* Constructor for the <code>TransportCursor</code> object. This
* requires a transport to read the bytes from. By default this
* will create a buffer of of the specified size to read the
* input in to which enabled bytes to be buffered internally.
*
* @param transport this is the underlying transport to use
* @param size this is the size of the internal buffer to use
*/
public TransportCursor(Transport transport, int size) {
this.reader = new TransportReader(transport, size);
this.buffer = new byte[0];
this.limit = size;
}
/**
* Determines whether the cursor is still open. The cursor is
* considered open if there are still bytes to read. If there is
* still bytes buffered and the underlying transport is closed
* then the cursor is still considered open.
*
* @return true if there is nothing more to be read from this
*/
public boolean isOpen() throws IOException {
return reader.isOpen();
}
/**
* Determines whether the cursor is ready for reading. When the
* cursor is ready then it guarantees that some amount of bytes
* can be read from the underlying stream without blocking.
*
* @return true if some data can be read without blocking
*/
public boolean isReady() throws IOException {
return ready() > 0;
}
/**
* Provides the number of bytes that can be read from the stream
* without blocking. This is typically the number of buffered or
* available bytes within the stream. When this reaches zero then
* the cursor may perform a blocking read.
*
* @return the number of bytes that can be read without blocking
*/
public int ready() throws IOException {
if(count > 0) {
return count;
}
return reader.ready();
}
/**
* Reads a block of bytes from the underlying stream. This will
* read up to the requested number of bytes from the underlying
* stream. If there are no ready bytes on the stream this can
* return zero, representing the fact that nothing was read.
*
* @param data this is the array to read the bytes in to
*
* @return this returns the number of bytes read from the stream
*/
public int read(byte[] data) throws IOException {
return read(data, 0, data.length);
}
/**
* Reads a block of bytes from the underlying stream. This will
* read up to the requested number of bytes from the underlying
* stream. If there are no ready bytes on the stream this can
* return zero, representing the fact that nothing was read.
*
* @param data this is the array to read the bytes in to
* @param off this is the offset to begin writing the bytes to
* @param len this is the number of bytes that are requested
*
* @return this returns the number of bytes read from the stream
*/
public int read(byte[] data, int off, int len) throws IOException {
if(count <= 0) {
mark = pos;
return reader.read(data, off, len);
}
int size = Math.min(count, len);
if(size > 0) {
System.arraycopy(buffer, pos, data, off, size);
mark = pos;
pos += size;
count -= size;
}
return size;
}
/**
* Pushes the provided data on to the cursor. Data pushed on to
* the cursor will be the next data read from the cursor. This
* complements the <code>reset</code> method which will reset
* the cursors position on a stream. Allowing data to be pushed
* on to the cursor allows more flexibility.
*
* @param data this is the data to be pushed on to the cursor
*/
public void push(byte[] data) throws IOException {
push(data, 0, data.length);
}
/**
* Pushes the provided data on to the cursor. Data pushed on to
* the cursor will be the next data read from the cursor. This
* complements the <code>reset</code> method which will reset
* the cursors position on a stream. Allowing data to be pushed
* on to the cursor allows more flexibility.
*
* @param data this is the data to be pushed on to the cursor
* @param off this is the offset to begin reading the bytes
* @param len this is the number of bytes that are to be used
*/
public void push(byte[] data, int off, int len) throws IOException {
int size = buffer.length;
if(size < len + count) {
expand(len + count);
}
int start = pos - len;
if(len > 0) {
System.arraycopy(data, off, buffer, start, len);
mark = start;
pos = start;
count += len;
}
}
/**
* This is used to ensure that there is enough space in the buffer
* to allow for more bytes to be added. If the buffer is already
* larger than the required capacity the this will do nothing.
*
* @param capacity the minimum size needed for the buffer
*/
private void expand(int capacity) throws IOException {
if(capacity > limit) {
throw new TransportException("Capacity limit exceeded");
}
byte[] temp = new byte[capacity];
int start = capacity - count;
int shift = pos - mark
;
if(count > 0) {
System.arraycopy(buffer, pos, temp, start, count);
}
pos = capacity - count;
mark = pos - shift;
buffer = temp;
}
/**
* Moves the cursor backward within the stream. This ensures
* that any bytes read from the last read can be pushed back
* in to the stream so that they can be read again. This will
* throw an exception if the reset can not be performed.
*
* @param size this is the number of bytes to reset back
*
* @return this is the number of bytes that have been reset
*/
public int reset(int size) throws IOException {
if(mark == pos) {
return reader.reset(size);
}
if(pos - size < mark) {
size = pos - mark;
}
if(size > 0) {
count += size;
pos -= size;
}
return size;
}
}