/*
* 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.
* Portions copyright 2012 ForgeRock AS.
*/
package org.opends.server.types;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.util.StaticUtils;
/**
* An immutable sequence of bytes backed by a byte array.
*/
public final class ByteString implements ByteSequence
{
// Singleton empty byte string.
private static final ByteString EMPTY = wrap(new byte[0]);
// Used for tracing exceptions.
private static final DebugTracer TRACER = getTracer();
/**
* Returns an empty byte string.
*
* @return An empty byte string.
*/
public static ByteString empty()
{
return EMPTY;
}
/**
* Returns a byte string containing the big-endian encoded bytes of
* the provided integer.
*
* @param i
* The integer to encode.
* @return The byte string containing the big-endian encoded bytes
* of the provided integer.
*/
public static ByteString valueOf(int i)
{
byte[] bytes = new byte[4];
for (int j = 3; j >= 0; j--)
{
bytes[j] = (byte) (i & 0xFF);
i >>>= 8;
}
return wrap(bytes);
}
/**
* Returns a byte string containing the big-endian encoded bytes of
* the provided long.
*
* @param l
* The long to encode.
* @return The byte string containing the big-endian encoded bytes
* of the provided long.
*/
public static ByteString valueOf(long l)
{
byte[] bytes = new byte[8];
for (int i = 7; i >= 0; i--)
{
bytes[i] = (byte) (l & 0xFF);
l >>>= 8;
}
return wrap(bytes);
}
/**
* Returns a byte string containing the UTF-8 encoded bytes of the
* provided string.
*
* @param s
* The string to use.
* @return The byte string with the encoded bytes of the provided
* string.
*/
public static ByteString valueOf(String s)
{
return wrap(StaticUtils.getBytes(s));
}
/**
* Returns a byte string that wraps the provided byte array.
* <p>
* <b>NOTE:</b> this method takes ownership of the provided byte
* array and, therefore, the byte array MUST NOT be altered directly
* after this method returns.
*
* @param b
* The byte array to wrap.
* @return The byte string that wraps the given byte array.
*/
public static ByteString wrap(byte[] b)
{
return new ByteString(b, 0, b.length);
}
/**
* Returns a byte string that wraps a subsequence of the provided
* byte array.
* <p>
* <b>NOTE:</b> this method takes ownership of the provided byte
* array and, therefore, the byte array MUST NOT be altered directly
* after this method returns.
*
* @param b
* The byte array to wrap.
* @param offset
* The offset of the byte array to be used; must be
* non-negative and no larger than {@code b.length} .
* @param length
* The length of the byte array to be used; must be
* non-negative and no larger than {@code b.length -
* offset}.
* @return The byte string that wraps the given byte array.
* @throws IndexOutOfBoundsException
* If {@code offset} is negative or if {@code length} is
* negative or if {@code offset + length} is greater than
* {@code b.length}.
*/
public static ByteString wrap(byte[] b, int offset, int length)
throws IndexOutOfBoundsException
{
checkArrayBounds(b, offset, length);
return new ByteString(b, offset, length);
}
/**
* Checks the array bounds of the provided byte array sub-sequence,
* throwing an {@code IndexOutOfBoundsException} if they are
* illegal.
*
* @param b
* The byte array.
* @param offset
* The offset of the byte array to be checked; must be
* non-negative and no larger than {@code b.length}.
* @param length
* The length of the byte array to be checked; must be
* non-negative and no larger than {@code b.length -
* offset}.
* @throws IndexOutOfBoundsException
* If {@code offset} is negative or if {@code length} is
* negative or if {@code offset + length} is greater than
* {@code b.length}.
*/
static void checkArrayBounds(byte[] b, int offset, int length)
throws IndexOutOfBoundsException
{
if ((offset < 0) || (offset > b.length) || (length < 0)
|| ((offset + length) > b.length) || ((offset + length) < 0))
{
throw new IndexOutOfBoundsException();
}
}
/**
* Compares two byte array sub-sequences and returns a value that
* indicates their relative order.
*
* @param b1
* The byte array containing the first sub-sequence.
* @param offset1
* The offset of the first byte array sub-sequence.
* @param length1
* The length of the first byte array sub-sequence.
* @param b2
* The byte array containing the second sub-sequence.
* @param offset2
* The offset of the second byte array sub-sequence.
* @param length2
* The length of the second byte array sub-sequence.
* @return A negative integer if first byte array sub-sequence
* should come before the second byte array sub-sequence in
* ascending order, a positive integer if the first byte
* array sub-sequence should come after the byte array
* sub-sequence in ascending order, or zero if there is no
* difference between the two byte array sub-sequences with
* regard to ordering.
*/
static int compareTo(byte[] b1, int offset1, int length1, byte[] b2,
int offset2, int length2)
{
int count = Math.min(length1, length2);
int i = offset1;
int j = offset2;
while (count-- != 0)
{
int firstByte = 0xFF & b1[i++];
int secondByte = 0xFF & b2[j++];
if (firstByte != secondByte)
{
return firstByte - secondByte;
}
}
return length1 - length2;
}
/**
* Indicates whether two byte array sub-sequences are equal. In
* order for them to be considered equal, they must contain the same
* bytes in the same order.
*
* @param b1
* The byte array containing the first sub-sequence.
* @param offset1
* The offset of the first byte array sub-sequence.
* @param length1
* The length of the first byte array sub-sequence.
* @param b2
* The byte array containing the second sub-sequence.
* @param offset2
* The offset of the second byte array sub-sequence.
* @param length2
* The length of the second byte array sub-sequence.
* @return {@code true} if the two byte array sub-sequences have the
* same content, or {@code false} if not.
*/
static boolean equals(byte[] b1, int offset1, int length1,
byte[] b2, int offset2, int length2)
{
if (length1 != length2)
{
return false;
}
int i = offset1;
int j = offset2;
int count = length1;
while (count-- != 0)
{
if (b1[i++] != b2[j++])
{
return false;
}
}
return true;
}
/**
* Returns a hash code for the provided byte array sub-sequence.
*
* @param b
* The byte array.
* @param offset
* The offset of the byte array sub-sequence.
* @param length
* The length of the byte array sub-sequence.
* @return A hash code for the provided byte array sub-sequence.
*/
static int hashCode(byte[] b, int offset, int length)
{
int hashCode = 1;
int i = offset;
int count = length;
while (count-- != 0)
{
hashCode = 31 * hashCode + b[i++];
}
return hashCode;
}
/**
* Returns the UTF-8 decoded string representation of the provided
* byte array sub-sequence. If UTF-8 decoding fails, the platform's
* default encoding will be used.
*
* @param b
* The byte array.
* @param offset
* The offset of the byte array sub-sequence.
* @param length
* The length of the byte array sub-sequence.
* @return The string representation of the byte array sub-sequence.
*/
static String toString(byte[] b, int offset, int length)
{
String stringValue;
try
{
stringValue = new String(b, offset, length, "UTF-8");
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
stringValue = new String(b, offset, length);
}
return stringValue;
}
// The buffer where data is stored.
private final byte[] buffer;
// The number of bytes to expose from the buffer.
private final int length;
// The start index of the range of bytes to expose through this byte
// string.
private final int offset;
/**
* Creates a new byte string that wraps a subsequence of the
* provided byte array.
* <p>
* <b>NOTE:</b> this method takes ownership of the provided byte
* array and, therefore, the byte array MUST NOT be altered directly
* after this method returns.
*
* @param b
* The byte array to wrap.
* @param offset
* The offset of the byte array to be used; must be
* non-negative and no larger than {@code b.length} .
* @param length
* The length of the byte array to be used; must be
* non-negative and no larger than {@code b.length -
* offset}.
*/
private ByteString(byte[] b, int offset, int length)
{
this.buffer = b;
this.offset = offset;
this.length = length;
}
/**
* Returns a {@link ByteSequenceReader} which can be used to
* incrementally read and decode data from this byte string.
*
* @return The {@link ByteSequenceReader} which can be used to
* incrementally read and decode data from this byte string.
*/
public ByteSequenceReader asReader()
{
return new ByteSequenceReader(this);
}
/**
* {@inheritDoc}
*/
public byte byteAt(int index) throws IndexOutOfBoundsException
{
if (index >= length || index < 0)
{
throw new IndexOutOfBoundsException();
}
return buffer[offset + index];
}
/**
* {@inheritDoc}
*/
public int compareTo(byte[] b, int offset, int length)
throws IndexOutOfBoundsException
{
checkArrayBounds(b, offset, length);
return compareTo(this.buffer, this.offset, this.length,
b, offset, length);
}
/**
* {@inheritDoc}
*/
public int compareTo(ByteSequence o)
{
if (this == o) return 0;
return -(o.compareTo(buffer, offset, length));
}
/**
* {@inheritDoc}
*/
public byte[] copyTo(byte[] b)
{
copyTo(b, 0);
return b;
}
/**
* {@inheritDoc}
*/
public byte[] copyTo(byte[] b, int offset)
throws IndexOutOfBoundsException
{
if (offset < 0)
{
throw new IndexOutOfBoundsException();
}
System.arraycopy(buffer, this.offset, b, offset,
Math.min(length, b.length - offset));
return b;
}
/**
* {@inheritDoc}
*/
public ByteStringBuilder copyTo(ByteStringBuilder builder)
{
builder.append(buffer, offset, length);
return builder;
}
/**
* {@inheritDoc}
*/
public OutputStream copyTo(OutputStream stream) throws IOException
{
stream.write(buffer, offset, length);
return stream;
}
/**
* {@inheritDoc}
*/
public int copyTo(WritableByteChannel channel) throws IOException
{
return channel.write(ByteBuffer.wrap(buffer, offset, length));
}
/**
* {@inheritDoc}
*/
public boolean equals(byte[] b, int offset, int length)
throws IndexOutOfBoundsException
{
checkArrayBounds(b, offset, length);
return equals(this.buffer, this.offset, this.length,
b, offset, length);
}
/**
* Indicates whether the provided object is equal to this byte
* string. In order for it to be considered equal, the provided
* object must be a byte sequence containing the same bytes in the
* same order.
*
* @param o
* The object for which to make the determination.
* @return {@code true} if the provided object is a byte sequence
* whose content is equal to that of this byte string, or
* {@code false} if not.
*/
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
else if (o instanceof ByteSequence)
{
ByteSequence other = (ByteSequence) o;
return other.equals(buffer, offset, length);
}
else
{
return false;
}
}
/**
* Returns a hash code for this byte string. It will be the sum of
* all of the bytes contained in the byte string.
*
* @return A hash code for this byte string.
*/
@Override
public int hashCode()
{
return hashCode(buffer, offset, length);
}
/**
* {@inheritDoc}
*/
public int length()
{
return length;
}
/**
* {@inheritDoc}
*/
public ByteString subSequence(int start, int end)
throws IndexOutOfBoundsException
{
if ((start < 0) || (start > end) || (end > length))
{
throw new IndexOutOfBoundsException();
}
return new ByteString(buffer, offset + start, end - start);
}
/**
* {@inheritDoc}
*/
public byte[] toByteArray()
{
return copyTo(new byte[length]);
}
/**
* {@inheritDoc}
*/
public ByteString toByteString()
{
return this;
}
/**
* Returns a string representation of the contents of this byte
* sequence using hexadecimal characters and a space between each
* byte.
*
* @return A string representation of the contents of this byte
* sequence using hexadecimal characters.
*/
public String toHex()
{
StringBuilder builder = new StringBuilder((length - 1) * 3 + 2);
builder.append(StaticUtils.byteToHex(buffer[offset]));
for (int i = 1; i < length; i++)
{
builder.append(" ");
builder.append(StaticUtils.byteToHex(buffer[offset + i]));
}
return builder.toString();
}
/**
* Appends a string representation of the data in this byte sequence
* to the given buffer using the specified indent.
* <p>
* The data will be formatted with sixteen hex bytes in a row
* followed by the ASCII representation, then wrapping to a new line
* as necessary. The state of the byte buffer is not changed.
*
* @param builder
* The buffer to which the information is to be appended.
* @param indent
* The number of spaces to indent the output.
*/
public void toHexPlusAscii(StringBuilder builder, int indent)
{
StringBuilder indentBuf = new StringBuilder(indent);
for (int i = 0; i < indent; i++)
{
indentBuf.append(' ');
}
int pos = 0;
while ((length - pos) >= 16)
{
StringBuilder asciiBuf = new StringBuilder(17);
byte currentByte = buffer[offset + pos];
builder.append(indentBuf);
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(StaticUtils.byteToASCII(currentByte));
pos++;
for (int i = 1; i < 16; i++, pos++)
{
currentByte = buffer[offset + pos];
builder.append(' ');
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(StaticUtils.byteToASCII(currentByte));
if (i == 7)
{
builder.append(" ");
asciiBuf.append(' ');
}
}
builder.append(" ");
builder.append(asciiBuf);
builder.append(EOL);
}
int remaining = (length - pos);
if (remaining > 0)
{
StringBuilder asciiBuf = new StringBuilder(remaining + 1);
byte currentByte = buffer[offset + pos];
builder.append(indentBuf);
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(StaticUtils.byteToASCII(currentByte));
pos++;
for (int i = 1; i < 16; i++, pos++)
{
builder.append(' ');
if (i < remaining)
{
currentByte = buffer[offset + pos];
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(StaticUtils.byteToASCII(currentByte));
}
else
{
builder.append(" ");
}
if (i == 7)
{
builder.append(" ");
if (i < remaining)
{
asciiBuf.append(' ');
}
}
}
builder.append(" ");
builder.append(asciiBuf);
builder.append(EOL);
}
}
/**
* Returns the integer value represented by the first four bytes of
* this byte string in big-endian order.
*
* @return The integer value represented by the first four bytes of
* this byte string in big-endian order.
* @throws IndexOutOfBoundsException
* If this byte string has less than four bytes.
*/
public int toInt() throws IndexOutOfBoundsException
{
if (length < 4)
{
throw new IndexOutOfBoundsException();
}
int v = 0;
for (int i = 0; i < 4; i++)
{
v <<= 8;
v |= (buffer[offset + i] & 0xFF);
}
return v;
}
/**
* Returns the long value represented by the first eight bytes of
* this byte string in big-endian order.
*
* @return The long value represented by the first eight bytes of
* this byte string in big-endian order.
* @throws IndexOutOfBoundsException
* If this byte string has less than eight bytes.
*/
public long toLong() throws IndexOutOfBoundsException
{
if (length < 8)
{
throw new IndexOutOfBoundsException();
}
long v = 0;
for (int i = 0; i < 8; i++)
{
v <<= 8;
v |= (buffer[offset + i] & 0xFF);
}
return v;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return toString(buffer, offset, length);
}
/**
* {@inheritDoc}
*/
@Override
public ByteBuffer asByteBuffer()
{
return ByteBuffer.wrap(buffer, offset, length).asReadOnlyBuffer();
}
}