/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009 Sun Microsystems, Inc.
*/
package org.opends.server.types;
/**
* An interface for iteratively reading date from a
* {@link ByteSequence}. {@code ByteSequenceReader} must be created
* using the associated {@code ByteSequence}'s
* {@code asReader()} method.
*/
public final class ByteSequenceReader
{
// The current position in the byte sequence.
private int pos = 0;
// The underlying byte sequence.
private final ByteSequence sequence;
/**
* Creates a new byte sequence reader whose source is the provided
* byte sequence.
* <p>
* <b>NOTE:</b> any concurrent changes to the underlying byte
* sequence (if mutable) may cause subsequent reads to overrun and
* fail.
* <p>
* This constructor is package private: construction must be
* performed using {@link ByteSequence#asReader()}.
*
* @param sequence
* The byte sequence to be read.
*/
ByteSequenceReader(ByteSequence sequence)
{
this.sequence = sequence;
}
/**
* Relative get method. Reads the byte at the current position.
*
* @return The byte at this reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < 1}.
*/
public byte get() throws IndexOutOfBoundsException
{
byte b = sequence.byteAt(pos);
pos++;
return b;
}
/**
* Relative bulk get method. This method transfers bytes from this
* reader into the given destination array. An invocation of this
* method of the form:
*
* <pre>
* src.get(b);
* </pre>
*
* Behaves in exactly the same way as the invocation:
*
* <pre>
* src.get(b, 0, b.length);
* </pre>
*
* @param b
* The byte array into which bytes are to be written.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < b.length}.
*/
public void get(byte[] b) throws IndexOutOfBoundsException
{
get(b, 0, b.length);
}
/**
* Relative bulk get method. Copies {@code length} bytes from this
* reader into the given array, starting at the current position of
* this reader and at the given {@code offset} in the array. The
* position of this reader is then incremented by {@code length}. In
* other words, an invocation of this method of the form:
*
* <pre>
* src.get(b, offset, length);
* </pre>
*
* Has exactly the same effect as the loop:
*
* <pre>
* for (int i = offset; i < offset + length; i++)
* b[i] = src.get();
* </pre>
*
* Except that it first checks that there are sufficient bytes in
* this buffer and it is potentially much more efficient.
*
* @param b
* The byte array into which bytes are to be written.
* @param offset
* The offset within the array of the first byte to be
* written; must be non-negative and no larger than {@code
* b.length}.
* @param length
* The number of bytes to be written to the given array;
* must be non-negative and no larger than {@code b.length}
* .
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < length}.
*/
public void get(byte[] b, int offset, int length)
throws IndexOutOfBoundsException
{
if ((offset < 0) || (length < 0) || (offset + length > b.length)
|| (length > remaining()))
{
throw new IndexOutOfBoundsException();
}
sequence.subSequence(pos, pos + length).copyTo(b, offset);
pos += length;
}
/**
* Relative get method for reading a multi-byte BER length. Reads
* the next one to five bytes at this reader's current position,
* composing them into a integer value and then increments the
* position by the number of bytes read.
*
* @return The integer value representing the length at this
* reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request.
*/
public int getBERLength() throws IndexOutOfBoundsException
{
// Make sure we have at least one byte to read.
int newPos = pos + 1;
if (newPos > sequence.length())
{
throw new IndexOutOfBoundsException();
}
int length = (sequence.byteAt(pos) & 0x7F);
if (length != sequence.byteAt(pos))
{
// Its a multi-byte length
int numLengthBytes = length;
newPos = pos + 1 + numLengthBytes;
// Make sure we have the bytes needed
if (numLengthBytes > 4 || newPos > sequence.length())
{
// Shouldn't have more than 4 bytes
throw new IndexOutOfBoundsException();
}
length = 0x00;
for (int i = pos + 1; i < newPos; i++)
{
length = (length << 8) | (sequence.byteAt(i) & 0xFF);
}
}
pos = newPos;
return length;
}
/**
* Relative bulk get method. Returns a {@link ByteSequence} whose
* content is the next {@code length} bytes from this reader,
* starting at the current position of this reader. The position of
* this reader is then incremented by {@code length}.
* <p>
* <b>NOTE:</b> The value returned from this method should NEVER be
* cached as it prevents the contents of the underlying byte stream
* from being garbage collected.
*
* @param length
* The length of the byte sequence to be returned.
* @return The byte sequence whose content is the next {@code
* length} bytes from this reader.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < length}.
*/
public ByteSequence getByteSequence(int length)
throws IndexOutOfBoundsException
{
int newPos = pos + length;
ByteSequence subSequence = sequence.subSequence(pos, newPos);
pos = newPos;
return subSequence;
}
/**
* Relative bulk get method. Returns a {@link ByteString} whose
* content is the next {@code length} bytes from this reader,
* starting at the current position of this reader. The position of
* this reader is then incremented by {@code length}.
* <p>
* An invocation of this method of the form:
*
* <pre>
* src.getByteString(length);
* </pre>
*
* Has exactly the same effect as:
*
* <pre>
* src.getByteSequence(length).toByteString();
* </pre>
*
* <b>NOTE:</b> The value returned from this method should NEVER be
* cached as it prevents the contents of the underlying byte stream
* from being garbage collected.
*
* @param length
* The length of the byte string to be returned.
* @return The byte string whose content is the next {@code length}
* bytes from this reader.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < length}.
*/
public ByteString getByteString(int length)
throws IndexOutOfBoundsException
{
return getByteSequence(length).toByteString();
}
/**
* Relative get method for reading an short value. Reads the next
* 2 bytes at this reader's current position, composing them into
* an short value according to big-endian byte order, and then
* increments the position by two.
*
* @return The integer value at this reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < 2}.
*/
public short getShort() throws IndexOutOfBoundsException
{
if (remaining() < 2)
{
throw new IndexOutOfBoundsException();
}
short v = 0;
for (int i = 0; i < 2; i++)
{
v <<= 8;
v |= (sequence.byteAt(pos++) & 0xFF);
}
return v;
}
/**
* Relative get method for reading an integer value. Reads the next
* four bytes at this reader's current position, composing them into
* an integer value according to big-endian byte order, and then
* increments the position by four.
*
* @return The integer value at this reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < 4}.
*/
public int getInt() throws IndexOutOfBoundsException
{
if (remaining() < 4)
{
throw new IndexOutOfBoundsException();
}
int v = 0;
for (int i = 0; i < 4; i++)
{
v <<= 8;
v |= (sequence.byteAt(pos++) & 0xFF);
}
return v;
}
/**
* Relative get method for reading a long value. Reads the next
* eight bytes at this reader's current position, composing them
* into a long value according to big-endian byte order, and then
* increments the position by eight.
*
* @return The long value at this reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < 8}.
*/
public long getLong() throws IndexOutOfBoundsException
{
if (remaining() < 8)
{
throw new IndexOutOfBoundsException();
}
long v = 0;
for (int i = 0; i < 8; i++)
{
v <<= 8;
v |= (sequence.byteAt(pos++) & 0xFF);
}
return v;
}
/**
* Relative get method for reading a UTF-8 encoded string. Reads the
* next number of specified bytes at this reader's current position,
* decoding them into a string using UTF-8 and then increments the
* position by the number of bytes read. If UTF-8 decoding fails,
* the platform's default encoding will be used.
*
* @param length
* The number of bytes to read and decode.
* @return The string value at the reader's current position.
* @throws IndexOutOfBoundsException
* If there are fewer bytes remaining in this reader than
* are required to satisfy the request, that is, if
* {@code remaining() < length}.
*/
public String getString(int length) throws IndexOutOfBoundsException
{
if (remaining() < length)
{
throw new IndexOutOfBoundsException();
}
int newPos = pos + length;
String str = sequence.subSequence(pos, pos + length).toString();
pos = newPos;
return str;
}
/**
* Returns this reader's position.
*
* @return The position of this reader.
*/
public int position()
{
return pos;
}
/**
* Sets this reader's position.
*
* @param pos
* The new position value; must be non-negative and no
* larger than the length of the underlying byte sequence.
* @throws IndexOutOfBoundsException
* If the position is negative or larger than the length
* of the underlying byte sequence.
*/
public void position(int pos) throws IndexOutOfBoundsException
{
if (pos > sequence.length() || pos < 0)
{
throw new IndexOutOfBoundsException();
}
this.pos = pos;
}
/**
* Returns the number of bytes between the current position and the
* end of the underlying byte sequence.
*
* @return The number of bytes between the current position and the
* end of the underlying byte sequence.
*/
public int remaining()
{
return sequence.length() - pos;
}
/**
* Rewinds this reader's position to zero.
* <p>
* An invocation of this method of the form:
*
* <pre>
* src.rewind();
* </pre>
*
* Has exactly the same effect as:
*
* <pre>
* src.position(0);
* </pre>
*/
public void rewind()
{
position(0);
}
/**
* Skips the given number of bytes. Negative values are allowed.
* <p>
* An invocation of this method of the form:
*
* <pre>
* src.skip(length);
* </pre>
*
* Has exactly the same effect as:
*
* <pre>
* src.position(position() + length);
* </pre>
*
* @param length
* The number of bytes to skip.
* @throws IndexOutOfBoundsException
* If the new position is less than 0 or greater than the
* length of the underlying byte sequence.
*/
public void skip(int length) throws IndexOutOfBoundsException
{
position(pos + length);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return sequence.toString();
}
}