/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.io;
import totalcross.sys.*;
/** Creates a byte array stream, which is a growable array of bytes.
* This class cannot be used for output AND input, but only for output OR input.
* <p>
* If you plan to read or write huge amount of data, consider using the
* CompressedByteArrayStream class instead.
* @see CompressedByteArrayStream
*/
public class ByteArrayStream extends RandomAccessStream
{
private byte []buffer;
private int len; // guich@563_5
private byte[] writeBuf; // flsobral@tc110_71: used by readFully.
/** Creates a ByteArrayStream.
* @param buffer The initial buffer from where data will be read or written into.
*/
public ByteArrayStream(byte []buffer)
{
if (buffer == null)
throw new IllegalArgumentException("Argument 'buffer' cannot be null");
this.len = buffer.length;
this.buffer = buffer;
pos = 0;
}
/** Creates a ByteArrayStream.
* @param buffer The initial buffer from where data will be read or written into.
* @param len The length to read from the buffer.
*/
public ByteArrayStream(byte []buffer, int len)
{
if (buffer == null)
throw new IllegalArgumentException("Argument 'buffer' cannot be null");
if (len < 0)
throw new IllegalArgumentException("Argument 'len' must be greater or equal than 0");
if (len > buffer.length)
throw new IllegalArgumentException("Argument 'len' must not be greater than 'buffer.length'");
this.len = len;
this.buffer = buffer;
pos = 0;
}
/** Creates a ByteArrayStream.
* @param size The initial size that the byte array will have.
*/
public ByteArrayStream(int size)
{
if (size < 0) //flsobral@tc100b4: now we check for invalid argument value.
throw new IllegalArgumentException("Argument 'size' must be greater or equal than 0");
buffer = new byte[size];
len = size;
pos = 0;
}
/** Sets the current position as the maximum size of the buffer so that no
* more than the current written data will be read. This already resets the
* read position to zero.
* @see #reset()
*/
public void mark() // guich@563_5
{
len = pos;
pos = 0;
}
/** Returns the number of bytes available in the buffer from the actual read position.
* @since SuperWaba 4.02
*/
public int available()
{
return len - pos;
}
/** does nothing. */
public void close()
{
// dont set buffer to null here or the PDBFile class will stop working!
}
/** Gets the internal buffer used.
* The actual read or written data may differ from the buffer's length; use the count
* method to get the correct value.
* @see #count()
*/
public byte []getBuffer()
{
return buffer;
}
/** Sets the buffer to be used, resetting the current position.
* @param buffer the new internal buffer.
* @since TotalCross 1.0.
*/
public void setBuffer(byte[] buffer)
{
if (buffer == null)
throw new IllegalArgumentException("Argument 'buffer' cannot be null");
len = buffer.length;
this.buffer = buffer;
pos = 0;
}
/**
* Returns the current position in the buffer.
* @deprecated use {@link #getPos()} instead.
*/
public int count()
{
return pos;
}
public int getPos()
{
return pos;
}
public int readBytes(byte buf[], int start, int count)
{
int remains = len - pos;
if (count < 0)
throw new IllegalArgumentException();
if (count > remains)
{
if (remains <= 0)
return -1; // flsobral@tc111_11: return -1 on EOF.
else
count = remains;
}
Vm.arrayCopy(buffer,pos,buf,start,count);
pos += count;
return count;
}
/** Resets the position to 0 so the buffer can be reused, and sets the mark to the buffer real limits.
* @see #mark()
*/
public void reset()
{
pos = 0;
len = buffer.length;
}
/**
* Moves the cursor n bytes from the current position, moving backwards if n is negative, or forward if n is
* positive.<br>
* The cursor cannot be placed outside the stream limits, stopping at position 0 when moving backwards, or at the
* last position of the stream, when moving forward.
*
* @param n
* the number of bytes to move.
* @return the number of bytes actually moved.
*/
public int skipBytes(int n)
{
int off = pos + n; // This here is for performance reason
if (off < 0) // pos + n < 0 --> if the new position would be negative
off = -pos;
else if (off > len) // pos + n > len --> if the new position would be out of bounds
off = len - pos; // jeffque@tc200: skip to the end of the buffer, not the last readable byte
else
off = n;
pos += off;
return off;
}
/** Reuses the already read part of the buffer. This method shifts the buffer
* from the current position to 0, so you can append more data to the buffer.
* @return The number of bytes shifted
* @since SuperWaba 4.01
*/
public int reuse() // guich@401_34
{
int shifted = pos;
if (pos > 0)
{
Vm.arrayCopy(buffer, pos, buffer, 0, len-pos);
pos = 0;
}
return shifted;
}
public int writeBytes(byte buf[], int start, int count)
{
if (len < (count+pos)) // grow the buffer
{
int size = (count+pos)*12/10; // grows 20% above the new needed capacity
byte []newBuffer = new byte[size];
Vm.arrayCopy(buffer,0,newBuffer,0,pos);
buffer = newBuffer;
len = buffer.length;
}
if (buf != buffer || start != pos) // flsobral@tc100: avoid pointless call to arrayCopy if used internally by readFully
Vm.arrayCopy(buf,start,buffer,pos,count);
pos += count;
return count;
}
/** Sets the size of the current byte array. If the current size is smaller then the given one,
* a new byte array is created with the given size. If there's already enough room for the given
* size, nothing is made. Note that this method does not reset the current position.
* @param newSize the new array size
* @param copyOldData If true, the old data up to <code>pos</code> is copied into the new buffer.
* @since SuperWaba 5.1
*/
public void setSize(int newSize, boolean copyOldData) // guich@510_15: added method - guich@512_9: added copyOldData
{
if (len < newSize)
{
byte []buf = buffer;
buffer = new byte[newSize];
len = buffer.length;
if (copyOldData)
Vm.arrayCopy(buf,0,buffer,0,pos);
}
}
/** Returns a copy of the data inside this buffer. The returned buffer will have the exact size
* of the stored data
* @since SuperWaba 5.1
*/
public byte[] toByteArray() // guich@510_15
{
byte []b = new byte[pos];
Vm.arrayCopy(buffer,0,b,0,pos);
return b;
}
public void setPos(int offset, int origin) throws IOException
{
int newPos;
switch (origin)
{
case SEEK_SET: newPos = offset; break;
case SEEK_CUR: newPos = this.pos + offset; break;
case SEEK_END: newPos = this.len + offset - 1; break;
default: throw new IllegalArgumentException();
}
if (newPos < 0)
throw new IOException();
if (newPos >= this.len) {
this.pos = this.len; //flsobral@tc120: ensure the current data is copy by setSize.
setSize(newPos + 1, true);
}
this.pos = newPos;
}
public void setPos(int newPos) throws IOException // flsobral@tc120: now may throw an IOException
{
if (newPos < 0)
throw new IOException();
if (newPos >= this.len) {
this.pos = this.len; //flsobral@tc120: ensure the current data is copy by setSize.
setSize(newPos + 1, true);
}
this.pos = newPos;
}
/**
* Reads all data from the input stream into our buffer.
* When returned, data is marked as ready to be read.
*
* @param inputStream
* The input stream from where data will be read
* @param retryCount
* The number of times to retry if no data is read. In remote
* connections, use at least 5; for files, it can be 0.
* @param bufSize
* The size of buffer used to read data.
* @throws totalcross.io.IOException
* @since TotalCross 1.0
*/
public void readFully(Stream inputStream, int retryCount, int bufSize) throws totalcross.io.IOException // guich@570_31
{
byte[] buf = (writeBuf != null && writeBuf.length >= bufSize) ? writeBuf : (writeBuf = new byte[bufSize]); // flsobral@tc110_71: readFully now uses an internal buffer to read data before writing.
reset();
while (true)
{
int n = inputStream.readBytes(buf, 0, buf.length);
if (n <= 0 && --retryCount <= 0)
break;
if (n > 0) // write only if something was read
writeBytes(buf, 0, n);
}
mark(); // flsobral@tc100b4: replaced reset() by mark().
}
}