/*
* 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 Cursor {
/**
* This is the source for the bytes read by this cursor object.
*/
private Source source;
/**
* 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.source = new TransportSource(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
*/
@Override
public boolean isOpen() throws IOException {
return this.source.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
*/
@Override
public boolean isReady() throws IOException {
return this.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
*/
@Override
public int ready() throws IOException {
if (this.count > 0) return this.count;
return this.source.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
*/
@Override
public int read(byte[] data) throws IOException {
return this.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
*/
@Override
public int read(byte[] data, int off, int len) throws IOException {
if (this.count <= 0) {
this.mark = this.pos;
return this.source.read(data, off, len);
}
int size = Math.min(this.count, len);
if (size > 0) {
System.arraycopy(this.buffer, this.pos, data, off, size);
this.mark = this.pos;
this.pos += size;
this.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
*/
@Override
public void push(byte[] data) throws IOException {
this.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
*/
@Override
public void push(byte[] data, int off, int len) throws IOException {
int size = this.buffer.length;
if (size < (len + this.count)) {
this.expand(len + this.count);
}
int start = this.pos - len;
if (len > 0) {
System.arraycopy(data, off, this.buffer, start, len);
this.mark = start;
this.pos = start;
this.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 > this.limit)
throw new TransportException("Capacity limit exceeded");
byte[] temp = new byte[capacity];
int start = capacity - this.count;
int shift = this.pos - this.mark;
if (this.count > 0) {
System.arraycopy(this.buffer, this.pos, temp, start, this.count);
}
this.pos = capacity - this.count;
this.mark = this.pos - shift;
this.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
*/
@Override
public int reset(int size) throws IOException {
if (this.mark == this.pos) return this.source.reset(size);
if ((this.pos - size) < this.mark) {
size = this.pos - this.mark;
}
if (size > 0) {
this.count += size;
this.pos -= size;
}
return size;
}
}