/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.util;
import java.nio.ByteBuffer;
import java.util.Arrays;
public final class WrappingByteSource implements ByteSource {
// WrappingByteSource interface
/**
* Converts an array-backed ByteBuffer to a WrappingByteSource.
* @param byteBuffer the ByteBuffer that is wrapping a byte[]
* @return a WrappingByteSource that represents the same byte[] wrapping as the incoming ByteBuffer
* @throws NullPointerException if byteBuffer is null
* @throws IllegalArgumentException if {@code byteBuffer.hasArray() == false}
*/
public static WrappingByteSource fromByteBuffer(ByteBuffer byteBuffer) {
if (!byteBuffer.hasArray()) {
throw new IllegalArgumentException("incoming ByteBuffer must have a backing array");
}
return new WrappingByteSource().wrap(
byteBuffer.array(),
byteBuffer.arrayOffset() + byteBuffer.position(),
byteBuffer.arrayOffset() + byteBuffer.limit() - byteBuffer.position()
);
}
public WrappingByteSource wrap(byte[] bytes) {
return wrap(bytes, 0, bytes.length);
}
public WrappingByteSource wrap(byte[] bytes, int offset, int length) {
ArgumentValidation.notNull("byte array", bytes);
if (bytes.length == 0) {
ArgumentValidation.isEQ("length on wrapped byte[0]", length, 0);
ArgumentValidation.isEQ("offset on wrapped byte[0]", offset, 0);
this.bytes = bytes;
this.offset = 0;
this.length = 0;
return this;
}
ArgumentValidation.isGTE("length", length, 0);
boolean offsetError = offset < 0;
if (length > 0)
offsetError |= offset >= bytes.length;
else
offsetError |= offset > bytes.length;
if (offsetError) {
throw new IllegalArgumentException("offset must be between 0 and bytes.length (" + bytes.length + ')');
}
int lastIndex = offset + length;
ArgumentValidation.isLTE("last index", lastIndex, bytes.length);
this.bytes = bytes;
this.offset = offset;
this.length = length;
return this;
}
public WrappingByteSource() {
// nothing
}
public WrappingByteSource(byte[] bytes) {
wrap(bytes);
}
public WrappingByteSource(byte[] bytes, int offset, int length) {
wrap(bytes, offset, length);
}
// ByteSource interface
@Override
public byte[] byteArray() {
return bytes;
}
@Override
public int byteArrayOffset() {
return offset;
}
@Override
public int byteArrayLength() {
return length;
}
// Comparable interface
@Override
public int compareTo(ByteSource o) {
int minlength = Math.min(byteArrayLength(), o.byteArrayLength());
byte[] obytes = o.byteArray();
for (int i=0; i < minlength; ++i) {
int myoffset = i + byteArrayOffset();
int oofset = i + o.byteArrayOffset();
int compare = bytes[myoffset] - obytes[oofset];
if (compare != 0)
return compare;
}
// all bytes were equal, return shorter array
return byteArrayLength() - o.byteArrayLength();
}
@Override
public byte[] toByteSubarray() {
return Arrays.copyOfRange(bytes, offset, offset+length);
}
// Object interface
@Override
public String toString() {
return String.format("WrappingByteSource(byte[%d] offset=%d length=%d)", bytes.length, offset, length);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WrappingByteSource that = (WrappingByteSource) o;
if (length != that.length)
return false;
if (offset == that.offset && bytes == that.bytes)
return true;
for(int i=0; i < length; ++i) {
if (bytes[offset+i] != that.bytes[that.offset+i])
return false;
}
return true;
}
@Override
public int hashCode() {
// this isn't the greatest hash, but it should be good enough.
// it relies on offset, length, and the first HASH_BYTES bytes (or as many bytes as are available).
int result = 1;
for (int i=0; i< length; ++i) {
result = 31 * result + bytes[offset+i];
}
result = 31 * result + length;
return result;
}
// object state
private byte[] bytes;
private int offset;
private int length;
// consts
/**
* The maximum number of bytes that will contribute to the object's hashCode
*/
private final int HASH_BYTES = 5;
}