org.xnio.Buffers Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xnio-api Show documentation
Show all versions of xnio-api Show documentation
The API JAR of the XNIO project
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2008 Red Hat, Inc. and/or its affiliates.
*
* 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.xnio;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.ShortBuffer;
import java.nio.BufferOverflowException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.util.Arrays;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import static org.xnio._private.Messages.msg;
import org.wildfly.common.ref.CleanerReference;
import org.wildfly.common.ref.Reaper;
import org.wildfly.common.ref.Reference;
/**
* Buffer utility methods.
*
* @apiviz.exclude
*/
public final class Buffers {
private Buffers() {}
/**
* Flip a buffer.
*
* @see Buffer#flip()
* @param the buffer type
* @param buffer the buffer to flip
* @return the buffer instance
*/
public static T flip(T buffer) {
buffer.flip();
return buffer;
}
/**
* Clear a buffer.
*
* @see Buffer#clear()
* @param the buffer type
* @param buffer the buffer to clear
* @return the buffer instance
*/
public static T clear(T buffer) {
buffer.clear();
return buffer;
}
/**
* Set the buffer limit.
*
* @see Buffer#limit(int)
* @param the buffer type
* @param buffer the buffer to set
* @param limit the new limit
* @return the buffer instance
*/
public static T limit(T buffer, int limit) {
buffer.limit(limit);
return buffer;
}
/**
* Set the buffer mark.
*
* @see Buffer#mark()
* @param the buffer type
* @param buffer the buffer to mark
* @return the buffer instance
*/
public static T mark(T buffer) {
buffer.mark();
return buffer;
}
/**
* Set the buffer position.
*
* @see Buffer#position(int)
* @param the buffer type
* @param buffer the buffer to set
* @param position the new position
* @return the buffer instance
*/
public static T position(T buffer, int position) {
buffer.position(position);
return buffer;
}
/**
* Reset the buffer.
*
* @see Buffer#reset()
* @param the buffer type
* @param buffer the buffer to reset
* @return the buffer instance
*/
public static T reset(T buffer) {
buffer.reset();
return buffer;
}
/**
* Rewind the buffer.
*
* @see Buffer#rewind()
* @param the buffer type
* @param buffer the buffer to rewind
* @return the buffer instance
*/
public static T rewind(T buffer) {
buffer.rewind();
return buffer;
}
/**
* Slice the buffer. The original buffer's position will be moved up past the slice that was taken.
*
* @see ByteBuffer#slice()
* @param buffer the buffer to slice
* @param sliceSize the size of the slice
* @return the buffer slice
*/
public static ByteBuffer slice(ByteBuffer buffer, int sliceSize) {
final int oldRem = buffer.remaining();
if (sliceSize > oldRem || sliceSize < -oldRem) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (sliceSize < 0) {
// count from end (sliceSize is NEGATIVE)
buffer.limit(oldLim + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + sliceSize);
}
} else {
// count from start
buffer.limit(oldPos + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + sliceSize);
}
}
}
/**
* Copy a portion of the buffer into a newly allocated buffer. The original buffer's position will be moved up past the copy that was taken.
*
* @param buffer the buffer to slice
* @param count the size of the copy
* @param allocator the buffer allocator to use
* @return the buffer slice
*/
public static ByteBuffer copy(ByteBuffer buffer, int count, BufferAllocator allocator) {
final int oldRem = buffer.remaining();
if (count > oldRem || count < -oldRem) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (count < 0) {
// count from end (sliceSize is NEGATIVE)
final ByteBuffer target = allocator.allocate(-count);
buffer.position(oldLim + count);
try {
target.put(buffer);
return target;
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + count);
}
} else {
// count from start
final ByteBuffer target = allocator.allocate(count);
buffer.limit(oldPos + count);
try {
target.put(buffer);
return target;
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + count);
}
}
}
/**
* Copy as many bytes as possible from {@code source} into {@code destination}.
*
* @param destination the destination buffer
* @param source the source buffer
* @return the number of bytes put into the destination buffer
*/
public static int copy(final ByteBuffer destination, final ByteBuffer source) {
final int sr = source.remaining();
final int dr = destination.remaining();
if (dr >= sr) {
destination.put(source);
return sr;
} else {
destination.put(slice(source, dr));
return dr;
}
}
/**
* Copy as many bytes as possible from {@code sources} into {@code destinations} in a "scatter" fashion.
*
* @param destinations the destination buffers
* @param offset the offset into the destination buffers array
* @param length the number of buffers to update
* @param source the source buffer
* @return the number of bytes put into the destination buffers
*/
public static int copy(final ByteBuffer[] destinations, final int offset, final int length, final ByteBuffer source) {
int t = 0;
for (int i = 0; i < length; i ++) {
final ByteBuffer buffer = destinations[i + offset];
final int rem = buffer.remaining();
if (rem == 0) {
continue;
} else if (rem < source.remaining()) {
buffer.put(slice(source, rem));
t += rem;
} else {
t += source.remaining();
buffer.put(source);
return t;
}
}
return t;
}
/**
* Copy as many bytes as possible from {@code sources} into {@code destination} in a "gather" fashion.
*
* @param destination the destination buffer
* @param sources the source buffers
* @param offset the offset into the source buffers array
* @param length the number of buffers to read from
* @return the number of bytes put into the destination buffers
*/
public static int copy(final ByteBuffer destination, final ByteBuffer[] sources, final int offset, final int length) {
int t = 0;
for (int i = 0; i < length; i ++) {
final ByteBuffer buffer = sources[i + offset];
final int rem = buffer.remaining();
if (rem == 0) {
continue;
} else if (rem > destination.remaining()) {
t += destination.remaining();
destination.put(slice(buffer, destination.remaining()));
return t;
} else {
destination.put(buffer);
t += rem;
}
}
return t;
}
/**
* Copy as many bytes as possible from {@code sources} into {@code destinations} by a combined "scatter"/"gather" operation.
*
* @param destinations the destination buffers
* @param destOffset the offset into the destination buffers array
* @param destLength the number of buffers to write to
* @param sources the source buffers
* @param srcOffset the offset into the source buffers array
* @param srcLength the number of buffers to read from
* @return the number of bytes put into the destination buffers
*/
public static long copy(final ByteBuffer[] destinations, final int destOffset, final int destLength, final ByteBuffer[] sources, final int srcOffset, final int srcLength) {
long t = 0L;
int s = 0, d = 0;
if (destLength == 0 || srcLength == 0) {
return 0L;
}
ByteBuffer source = sources[srcOffset];
ByteBuffer dest = destinations[destOffset];
while (s < srcLength && d < destLength) {
source = sources[srcOffset + s];
dest = destinations[destOffset + d];
final int sr = source.remaining();
final int dr = dest.remaining();
if (sr < dr) {
dest.put(source);
s++;
t += sr;
} else if (sr > dr) {
dest.put(slice(source, dr));
d++;
t += dr;
} else {
dest.put(source);
s++;
d++;
t += sr;
}
}
return t;
}
/**
* Copy at most {@code count} bytes from {@code source} into {@code destination}.
*
* @param count the maximum number of bytes to copy
* @param destination the destination buffer
* @param source the source buffer
* @return the number of bytes put into the destination buffer
*/
public static int copy(int count, final ByteBuffer destination, final ByteBuffer source) {
int cnt = count >= 0? Math.min(Math.min(count, source.remaining()), destination.remaining()):
Math.max(Math.max(count, - source.remaining()), - destination.remaining());
final ByteBuffer copy = slice(source, cnt);
destination.put(copy);
return copy.position(); // cnt could be negative, so it is safer to return copy.position() instead of cnt
}
/**
* Copy at most {@code count} bytes from {@code sources} into {@code destinations} in a "scatter" fashion.
*
* @param count the maximum number of bytes to copy
* @param destinations the destination buffers
* @param offset the offset into the destination buffers array
* @param length the number of buffers to update
* @param source the source buffer
* @return the number of bytes put into the destination buffers
*/
public static int copy(int count, final ByteBuffer[] destinations, final int offset, final int length, final ByteBuffer source) {
if (source.remaining() > count) {
final int oldLimit = source.limit();
if (count < 0) {
// count from end (count is NEGATIVE)
throw msg.copyNegative();
} else {
try {
source.limit(source.position() + count);
return copy(destinations, offset, length, source);
} finally {
source.limit(oldLimit);
}
}
} else {
return copy(destinations, offset, length, source);
}
}
/**
* Copy at most {@code count} bytes from {@code sources} into {@code destination} in a "gather" fashion.
*
* @param count the maximum number of bytes to copy
* @param destination the destination buffer
* @param sources the source buffers
* @param offset the offset into the source buffers array
* @param length the number of buffers to read from
* @return the number of bytes put into the destination buffers
*/
public static int copy(int count, final ByteBuffer destination, final ByteBuffer[] sources, final int offset, final int length) {
if (destination.remaining() > count) {
if (count < 0) {
// count from end (count is NEGATIVE)
throw msg.copyNegative();
} else {
final int oldLimit = destination.limit();
try {
destination.limit(destination.position() + Math.min(count, destination.remaining()));
return copy(destination, sources, offset, length);
} finally {
destination.limit(oldLimit);
}
}
} else {
return copy(destination, sources, offset, length);
}
}
/**
* Copy at most {@code count} bytes from {@code sources} into {@code destinations} by a combined "scatter"/"gather" operation.
*
* @param count the maximum number of bytes to copy
* @param destinations the destination buffers
* @param destOffset the offset into the destination buffers array
* @param destLength the number of buffers to write to
* @param sources the source buffers
* @param srcOffset the offset into the source buffers array
* @param srcLength the number of buffers to read from
* @return the number of bytes put into the destination buffers
*/
public static long copy(long count, final ByteBuffer[] destinations, final int destOffset, final int destLength, final ByteBuffer[] sources, final int srcOffset, final int srcLength) {
long t = 0L;
int s = 0, d = 0;
if (count < 0) {
// count from end (count is NEGATIVE)
throw msg.copyNegative();
}
if (destLength == 0 || srcLength == 0 || count == 0L) {
return 0L;
}
while (s < srcLength && d < destLength) {
final ByteBuffer source = sources[srcOffset + s];
final ByteBuffer dest = destinations[destOffset + d];
final int sr = source.remaining();
final int dr = (int) Math.min(count, (long) dest.remaining());
if (sr < dr) {
dest.put(source);
s++;
t += sr;
count -= (long)sr;
} else if (sr > dr) {
dest.put(slice(source, dr));
d++;
t += dr;
count -= (long)dr;
} else {
dest.put(source);
s++;
d++;
t += sr;
count -= (long)sr;
}
}
return t;
}
/**
* Fill a buffer with a repeated value.
*
* @param buffer the buffer to fill
* @param value the value to fill
* @param count the number of bytes to fill
* @return the buffer instance
*/
public static ByteBuffer fill(ByteBuffer buffer, int value, int count) {
if (count > buffer.remaining()) {
throw msg.bufferUnderflow();
}
if (buffer.hasArray()) {
final int offs = buffer.arrayOffset();
Arrays.fill(buffer.array(), offs + buffer.position(), offs + buffer.limit(), (byte) value);
skip(buffer, count);
} else {
for (int i = count; i > 0; i--) {
buffer.put((byte)value);
}
}
return buffer;
}
/**
* Slice the buffer. The original buffer's position will be moved up past the slice that was taken.
*
* @see CharBuffer#slice()
* @param buffer the buffer to slice
* @param sliceSize the size of the slice
* @return the buffer slice
*/
public static CharBuffer slice(CharBuffer buffer, int sliceSize) {
if (sliceSize > buffer.remaining() || sliceSize < -buffer.remaining()) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (sliceSize < 0) {
// count from end (sliceSize is NEGATIVE)
buffer.limit(oldLim + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + sliceSize);
}
} else {
// count from start
buffer.limit(oldPos + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + sliceSize);
}
}
}
/**
* Fill a buffer with a repeated value.
*
* @param buffer the buffer to fill
* @param value the value to fill
* @param count the number of chars to fill
* @return the buffer instance
*/
public static CharBuffer fill(CharBuffer buffer, int value, int count) {
if (count > buffer.remaining()) {
throw msg.bufferUnderflow();
}
if (buffer.hasArray()) {
final int offs = buffer.arrayOffset();
Arrays.fill(buffer.array(), offs + buffer.position(), offs + buffer.limit(), (char) value);
skip(buffer, count);
} else {
for (int i = count; i > 0; i--) {
buffer.put((char)value);
}
}
return buffer;
}
/**
* Slice the buffer. The original buffer's position will be moved up past the slice that was taken.
*
* @see ShortBuffer#slice()
* @param buffer the buffer to slice
* @param sliceSize the size of the slice
* @return the buffer slice
*/
public static ShortBuffer slice(ShortBuffer buffer, int sliceSize) {
if (sliceSize > buffer.remaining() || sliceSize < -buffer.remaining()) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (sliceSize < 0) {
// count from end (sliceSize is NEGATIVE)
buffer.limit(oldLim + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + sliceSize);
}
} else {
// count from start
buffer.limit(oldPos + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + sliceSize);
}
}
}
/**
* Fill a buffer with a repeated value.
*
* @param buffer the buffer to fill
* @param value the value to fill
* @param count the number of shorts to fill
* @return the buffer instance
*/
public static ShortBuffer fill(ShortBuffer buffer, int value, int count) {
if (count > buffer.remaining()) {
throw msg.bufferUnderflow();
}
if (buffer.hasArray()) {
final int offs = buffer.arrayOffset();
Arrays.fill(buffer.array(), offs + buffer.position(), offs + buffer.limit(), (short) value);
skip(buffer, count);
} else {
for (int i = count; i > 0; i--) {
buffer.put((short)value);
}
}
return buffer;
}
/**
* Slice the buffer. The original buffer's position will be moved up past the slice that was taken.
*
* @see IntBuffer#slice()
* @param buffer the buffer to slice
* @param sliceSize the size of the slice
* @return the buffer slice
*/
public static IntBuffer slice(IntBuffer buffer, int sliceSize) {
if (sliceSize > buffer.remaining() || sliceSize < -buffer.remaining()) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (sliceSize < 0) {
// count from end (sliceSize is NEGATIVE)
buffer.limit(oldLim + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + sliceSize);
}
} else {
// count from start
buffer.limit(oldPos + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + sliceSize);
}
}
}
/**
* Fill a buffer with a repeated value.
*
* @param buffer the buffer to fill
* @param value the value to fill
* @param count the number of ints to fill
* @return the buffer instance
*/
public static IntBuffer fill(IntBuffer buffer, int value, int count) {
if (count > buffer.remaining()) {
throw msg.bufferUnderflow();
}
if (buffer.hasArray()) {
final int offs = buffer.arrayOffset();
Arrays.fill(buffer.array(), offs + buffer.position(), offs + buffer.limit(), value);
skip(buffer, count);
} else {
for (int i = count; i > 0; i--) {
buffer.put(value);
}
}
return buffer;
}
/**
* Slice the buffer. The original buffer's position will be moved up past the slice that was taken.
*
* @see LongBuffer#slice()
* @param buffer the buffer to slice
* @param sliceSize the size of the slice
* @return the buffer slice
*/
public static LongBuffer slice(LongBuffer buffer, int sliceSize) {
if (sliceSize > buffer.remaining() || sliceSize < -buffer.remaining()) {
throw msg.bufferUnderflow();
}
final int oldPos = buffer.position();
final int oldLim = buffer.limit();
if (sliceSize < 0) {
// count from end (sliceSize is NEGATIVE)
buffer.limit(oldLim + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldLim + sliceSize);
}
} else {
// count from start
buffer.limit(oldPos + sliceSize);
try {
return buffer.slice();
} finally {
buffer.limit(oldLim);
buffer.position(oldPos + sliceSize);
}
}
}
/**
* Fill a buffer with a repeated value.
*
* @param buffer the buffer to fill
* @param value the value to fill
* @param count the number of longs to fill
* @return the buffer instance
*/
public static LongBuffer fill(LongBuffer buffer, long value, int count) {
if (count > buffer.remaining()) {
throw msg.bufferUnderflow();
}
if (buffer.hasArray()) {
final int offs = buffer.arrayOffset();
Arrays.fill(buffer.array(), offs + buffer.position(), offs + buffer.limit(), value);
skip(buffer, count);
} else {
for (int i = count; i > 0; i--) {
buffer.put(value);
}
}
return buffer;
}
/**
* Advance a buffer's position relative to its current position.
*
* @see Buffer#position(int)
* @param the buffer type
* @param buffer the buffer to set
* @param cnt the distance to skip
* @return the buffer instance
* @throws BufferUnderflowException if there are fewer than {@code cnt} bytes remaining
*/
public static T skip(T buffer, int cnt) throws BufferUnderflowException {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (cnt > buffer.remaining()) {
throw msg.bufferUnderflow();
}
buffer.position(buffer.position() + cnt);
return buffer;
}
/**
* Attempt to advance a buffer's position relative to its current position.
*
* @see Buffer#position(int)
* @param buffer the buffer to set
* @param cnt the distance to skip
* @return the actual number of bytes skipped
*/
public static int trySkip(Buffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
final int rem = buffer.remaining();
if (cnt > rem) {
cnt = rem;
}
buffer.position(buffer.position() + cnt);
return cnt;
}
/**
* Attempt to advance a series of buffers' overall position relative to its current position.
*
* @see Buffer#position(int)
* @param buffers the buffers to set
* @param offs the offset into the buffers array
* @param len the number of buffers to consider
* @param cnt the distance to skip
* @return the actual number of bytes skipped
*/
public static long trySkip(Buffer[] buffers, int offs, int len, long cnt) {
if (cnt < 0L) {
throw msg.parameterOutOfRange("cnt");
}
if (len < 0) {
throw msg.parameterOutOfRange("len");
}
if (offs < 0) {
throw msg.parameterOutOfRange("offs");
}
if (offs > buffers.length) {
throw msg.parameterOutOfRange("offs");
}
if (offs + len > buffers.length) {
throw msg.parameterOutOfRange("offs");
}
long c = 0L;
for (int i = 0; i < len; i ++) {
final Buffer buffer = buffers[offs + i];
final int rem = buffer.remaining();
if (rem < cnt) {
buffer.position(buffer.position() + rem);
cnt -= (long) rem;
c += (long) rem;
} else {
buffer.position(buffer.position() + (int) cnt);
return c + cnt;
}
}
return c;
}
/**
* Rewind a buffer's position relative to its current position.
*
* @see Buffer#position(int)
* @param the buffer type
* @param buffer the buffer to set
* @param cnt the distance to skip backwards
* @return the buffer instance
*/
public static T unget(T buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (cnt > buffer.position()) {
throw msg.bufferUnderflow();
}
buffer.position(buffer.position() - cnt);
return buffer;
}
/**
* Take a certain number of bytes from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @param cnt the number of bytes to take
* @return the bytes
*/
public static byte[] take(ByteBuffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
if (lim - pos < cnt) {
throw new BufferUnderflowException();
}
final byte[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(pos + cnt);
final int start = offset + pos;
return Arrays.copyOfRange(array, start, start + cnt);
}
final byte[] bytes = new byte[cnt];
buffer.get(bytes);
return bytes;
}
/**
* Take a certain number of chars from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @param cnt the number of chars to take
* @return the chars
*/
public static char[] take(CharBuffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
if (lim - pos < cnt) {
throw new BufferUnderflowException();
}
final char[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(pos + cnt);
final int start = offset + pos;
return Arrays.copyOfRange(array, start, start + cnt);
}
final char[] chars = new char[cnt];
buffer.get(chars);
return chars;
}
/**
* Take a certain number of shorts from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @param cnt the number of shorts to take
* @return the shorts
*/
public static short[] take(ShortBuffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
if (lim - pos < cnt) {
throw new BufferUnderflowException();
}
final short[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(pos + cnt);
final int start = offset + pos;
return Arrays.copyOfRange(array, start, start + cnt);
}
final short[] shorts = new short[cnt];
buffer.get(shorts);
return shorts;
}
/**
* Take a certain number of ints from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @param cnt the number of ints to take
* @return the ints
*/
public static int[] take(IntBuffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
if (lim - pos < cnt) {
throw new BufferUnderflowException();
}
final int[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(pos + cnt);
final int start = offset + pos;
return Arrays.copyOfRange(array, start, start + cnt);
}
final int[] ints = new int[cnt];
buffer.get(ints);
return ints;
}
/**
* Take a certain number of longs from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @param cnt the number of longs to take
* @return the longs
*/
public static long[] take(LongBuffer buffer, int cnt) {
if (cnt < 0) {
throw msg.parameterOutOfRange("cnt");
}
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
if (lim - pos < cnt) {
throw new BufferUnderflowException();
}
final long[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(pos + cnt);
final int start = offset + pos;
return Arrays.copyOfRange(array, start, start + cnt);
}
final long[] longs = new long[cnt];
buffer.get(longs);
return longs;
}
private static final byte[] NO_BYTES = new byte[0];
/**
* Take all of the remaining bytes from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @return the bytes
*/
public static byte[] take(ByteBuffer buffer) {
final int remaining = buffer.remaining();
if (remaining == 0) return NO_BYTES;
if (buffer.hasArray()) {
final int pos = buffer.position();
final int lim = buffer.limit();
final byte[] array = buffer.array();
final int offset = buffer.arrayOffset();
buffer.position(lim);
return Arrays.copyOfRange(array, offset + pos, offset + lim);
}
final byte[] bytes = new byte[remaining];
buffer.get(bytes);
return bytes;
}
/**
* Take all of the remaining bytes from the buffers and return them in an array.
*
* @param buffers the buffer to read
* @param offs the offset into the array
* @param len the number of buffers
* @return the bytes
*/
public static byte[] take(final ByteBuffer[] buffers, final int offs, final int len) {
if (len == 1) return take(buffers[offs]);
final long remaining = Buffers.remaining(buffers, offs, len);
if (remaining == 0L) return NO_BYTES;
if (remaining > Integer.MAX_VALUE) throw new OutOfMemoryError("Array too large");
final byte[] bytes = new byte[(int) remaining];
int o = 0;
int rem;
ByteBuffer buffer;
for (int i = 0; i < len; i ++) {
buffer = buffers[i + offs];
rem = buffer.remaining();
buffer.get(bytes, o, rem);
o += rem;
}
return bytes;
}
/**
* Take all of the remaining chars from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @return the chars
*/
public static char[] take(CharBuffer buffer) {
final char[] chars = new char[buffer.remaining()];
buffer.get(chars);
return chars;
}
/**
* Take all of the remaining shorts from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @return the shorts
*/
public static short[] take(ShortBuffer buffer) {
final short[] shorts = new short[buffer.remaining()];
buffer.get(shorts);
return shorts;
}
/**
* Take all of the remaining ints from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @return the ints
*/
public static int[] take(IntBuffer buffer) {
final int[] ints = new int[buffer.remaining()];
buffer.get(ints);
return ints;
}
/**
* Take all of the remaining longs from the buffer and return them in an array.
*
* @param buffer the buffer to read
* @return the longs
*/
public static long[] take(LongBuffer buffer) {
final long[] longs = new long[buffer.remaining()];
buffer.get(longs);
return longs;
}
/**
* Create an object that returns the dumped form of the given byte buffer when its {@code toString()} method is called.
* Useful for logging byte buffers; if the {@code toString()} method is never called, the process of dumping the
* buffer is never performed.
*
* @param buffer the buffer
* @param indent the indentation to use
* @param columns the number of 8-byte columns
* @return a stringable object
*/
public static Object createDumper(final ByteBuffer buffer, final int indent, final int columns) {
if (columns <= 0) {
throw msg.parameterOutOfRange("columns");
}
if (indent < 0) {
throw msg.parameterOutOfRange("indent");
}
return new Object() {
public String toString() {
StringBuilder b = new StringBuilder();
try {
dump(buffer, b, indent, columns);
} catch (IOException e) {
// ignore, not possible!
}
return b.toString();
}
};
}
/**
* Dump a byte buffer to the given target.
*
* @param buffer the buffer
* @param dest the target
* @param indent the indentation to use
* @param columns the number of 8-byte columns
* @throws IOException if an error occurs during append
*/
public static void dump(final ByteBuffer buffer, final Appendable dest, final int indent, final int columns) throws IOException {
if (columns <= 0) {
throw msg.parameterOutOfRange("columns");
}
if (indent < 0) {
throw msg.parameterOutOfRange("indent");
}
final int pos = buffer.position();
final int remaining = buffer.remaining();
final int rowLength = (8 << (columns - 1));
final int n = Math.max(Integer.toString(buffer.remaining(), 16).length(), 4);
for (int idx = 0; idx < remaining; idx += rowLength) {
// state: start of line
for (int i = 0; i < indent; i ++) {
dest.append(' ');
}
final String s = Integer.toString(idx, 16);
for (int i = n - s.length(); i > 0; i --) {
dest.append('0');
}
dest.append(s);
dest.append(" - ");
appendHexRow(buffer, dest, pos + idx, columns);
appendTextRow(buffer, dest, pos + idx, columns);
dest.append('\n');
}
}
private static void appendHexRow(final ByteBuffer buffer, final Appendable dest, final int startPos, final int columns) throws IOException {
final int limit = buffer.limit();
int pos = startPos;
for (int c = 0; c < columns; c ++) {
for (int i = 0; i < 8; i ++) {
if (pos >= limit) {
dest.append(" ");
} else {
final int v = buffer.get(pos++) & 0xff;
final String hexVal = Integer.toString(v, 16);
if (v < 16) {
dest.append('0');
}
dest.append(hexVal);
}
dest.append(' ');
}
dest.append(' ');
dest.append(' ');
}
}
private static void appendTextRow(final ByteBuffer buffer, final Appendable dest, final int startPos, final int columns) throws IOException {
final int limit = buffer.limit();
int pos = startPos;
dest.append('[');
dest.append(' ');
for (int c = 0; c < columns; c ++) {
for (int i = 0; i < 8; i ++) {
if (pos >= limit) {
dest.append(' ');
} else {
final char v = (char) (buffer.get(pos++) & 0xff);
if (Character.isISOControl(v)) {
dest.append('.');
} else {
dest.append(v);
}
}
}
dest.append(' ');
}
dest.append(']');
}
/**
* Create an object that returns the dumped form of the given character buffer when its {@code toString()} method is called.
* Useful for logging character buffers; if the {@code toString()} method is never called, the process of dumping the
* buffer is never performed.
*
* @param buffer the buffer
* @param indent the indentation to use
* @param columns the number of 8-byte columns
* @return a stringable object
*/
public static Object createDumper(final CharBuffer buffer, final int indent, final int columns) {
if (columns <= 0) {
throw msg.parameterOutOfRange("columns");
}
if (indent < 0) {
throw msg.parameterOutOfRange("indent");
}
return new Object() {
public String toString() {
StringBuilder b = new StringBuilder();
try {
dump(buffer, b, indent, columns);
} catch (IOException e) {
// ignore, not possible!
}
return b.toString();
}
};
}
/**
* Dump a character buffer to the given target.
*
* @param buffer the buffer
* @param dest the target
* @param indent the indentation to use
* @param columns the number of 8-byte columns
* @throws IOException if an error occurs during append
*/
public static void dump(final CharBuffer buffer, final Appendable dest, final int indent, final int columns) throws IOException {
if (columns <= 0) {
throw msg.parameterOutOfRange("columns");
}
if (indent < 0) {
throw msg.parameterOutOfRange("indent");
}
final int pos = buffer.position();
final int remaining = buffer.remaining();
final int rowLength = (8 << (columns - 1));
final int n = Math.max(Integer.toString(buffer.remaining(), 16).length(), 4);
for (int idx = 0; idx < remaining; idx += rowLength) {
// state: start of line
for (int i = 0; i < indent; i ++) {
dest.append(' ');
}
final String s = Integer.toString(idx, 16);
for (int i = n - s.length(); i > 0; i --) {
dest.append('0');
}
dest.append(s);
dest.append(" - ");
appendHexRow(buffer, dest, pos + idx, columns);
appendTextRow(buffer, dest, pos + idx, columns);
dest.append('\n');
}
}
private static void appendHexRow(final CharBuffer buffer, final Appendable dest, final int startPos, final int columns) throws IOException {
final int limit = buffer.limit();
int pos = startPos;
for (int c = 0; c < columns; c ++) {
for (int i = 0; i < 8; i ++) {
if (pos >= limit) {
dest.append(" ");
} else {
final char v = buffer.get(pos++);
final String hexVal = Integer.toString(v, 16);
dest.append("0000".substring(hexVal.length()));
dest.append(hexVal);
}
dest.append(' ');
}
dest.append(' ');
dest.append(' ');
}
}
private static void appendTextRow(final CharBuffer buffer, final Appendable dest, final int startPos, final int columns) throws IOException {
final int limit = buffer.limit();
int pos = startPos;
dest.append('[');
dest.append(' ');
for (int c = 0; c < columns; c ++) {
for (int i = 0; i < 8; i ++) {
if (pos >= limit) {
dest.append(' ');
} else {
final char v = buffer.get(pos++);
if (Character.isISOControl(v) || Character.isHighSurrogate(v) || Character.isLowSurrogate(v)) {
dest.append('.');
} else {
dest.append(v);
}
}
}
dest.append(' ');
}
dest.append(']');
}
/**
* The empty byte buffer.
*/
public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
/**
* The empty pooled byte buffer. Freeing or discarding this buffer has no effect.
*/
public static final Pooled EMPTY_POOLED_BYTE_BUFFER = emptyPooledByteBuffer();
/**
* Determine whether any of the buffers has remaining data.
*
* @param buffers the buffers
* @param offs the offset into the buffers array
* @param len the number of buffers to check
* @return {@code true} if any of the selected buffers has remaining data
*/
public static boolean hasRemaining(final Buffer[] buffers, final int offs, final int len) {
for (int i = 0; i < len; i ++) {
if (buffers[i + offs].hasRemaining()) {
return true;
}
}
return false;
}
/**
* Determine whether any of the buffers has remaining data.
*
* @param buffers the buffers
* @return {@code true} if any of the selected buffers has remaining data
*/
public static boolean hasRemaining(final Buffer[] buffers) {
return hasRemaining(buffers, 0, buffers.length);
}
/**
* Get the total remaining size of all the given buffers.
*
* @param buffers the buffers
* @param offs the offset into the buffers array
* @param len the number of buffers to check
* @return the number of remaining elements
*/
public static long remaining(final Buffer[] buffers, final int offs, final int len) {
long t = 0L;
for (int i = 0; i < len; i ++) {
t += buffers[i + offs].remaining();
}
return t;
}
/**
* Get the total remaining size of all the given buffers.
*
* @param buffers the buffers
* @return the number of remaining elements
*/
public static long remaining(final Buffer[] buffers) {
return remaining(buffers, 0, buffers.length);
}
/**
* Put the string into the byte buffer, encoding it using "modified UTF-8" encoding.
*
* @param dest the byte buffer
* @param orig the source bytes
* @return the byte buffer
* @throws BufferOverflowException if there is not enough space in the buffer for the complete string
* @see DataOutput#writeUTF(String)
*/
public static ByteBuffer putModifiedUtf8(ByteBuffer dest, String orig) throws BufferOverflowException {
final char[] chars = orig.toCharArray();
for (char c : chars) {
if (c > 0 && c <= 0x7f) {
dest.put((byte) c);
} else if (c <= 0x07ff) {
dest.put((byte)(0xc0 | 0x1f & c >> 6));
dest.put((byte)(0x80 | 0x3f & c));
} else {
dest.put((byte)(0xe0 | 0x0f & c >> 12));
dest.put((byte)(0x80 | 0x3f & c >> 6));
dest.put((byte)(0x80 | 0x3f & c));
}
}
return dest;
}
/**
* Get a 0-terminated string from the byte buffer, decoding it using "modified UTF-8" encoding.
*
* @param src the source buffer
* @return the string
* @throws BufferUnderflowException if the end of the buffer was reached before encountering a {@code 0}
*/
public static String getModifiedUtf8Z(ByteBuffer src) throws BufferUnderflowException {
final StringBuilder builder = new StringBuilder();
for (;;) {
final int ch = readUTFChar(src);
if (ch == -1) {
return builder.toString();
}
builder.append((char) ch);
}
}
/**
* Get a modified UTF-8 string from the remainder of the buffer.
*
* @param src the buffer
* @return the modified UTF-8 string
* @throws BufferUnderflowException if the buffer ends abruptly in the midst of a single character
*/
public static String getModifiedUtf8(ByteBuffer src) throws BufferUnderflowException {
final StringBuilder builder = new StringBuilder();
while (src.hasRemaining()) {
final int ch = readUTFChar(src);
if (ch == -1) {
builder.append('\0');
} else {
builder.append((char) ch);
}
}
return builder.toString();
}
private static int readUTFChar(final ByteBuffer src) throws BufferUnderflowException {
final int a = src.get() & 0xff;
if (a == 0) {
return -1;
} else if (a < 0x80) {
return (char)a;
} else if (a < 0xc0) {
return '?';
} else if (a < 0xe0) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
return '?';
}
return (a & 0x1f) << 6 | b & 0x3f;
} else if (a < 0xf0) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
return '?';
}
final int c = src.get() & 0xff;
if ((c & 0xc0) != 0x80) {
return '?';
}
return (a & 0x0f) << 12 | (b & 0x3f) << 6 | c & 0x3f;
}
return '?';
}
/**
* Read an ASCIIZ ({@code NUL}-terminated) string from a byte buffer, appending the results to the given string
* builder. If no {@code NUL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character {@code '?'} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readAsciiZ(final ByteBuffer src, final StringBuilder builder) {
return readAsciiZ(src, builder, '?');
}
/**
* Read an ASCIIZ ({@code NUL}-terminated) string from a byte buffer, appending the results to the given string
* builder. If no {@code NUL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readAsciiZ(final ByteBuffer src, final StringBuilder builder, final char replacement) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final byte b = src.get();
if (b == 0) {
return true;
}
builder.append(b < 0 ? replacement : (char) b);
}
}
/**
* Read a single line of ASCII text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character {@code '?'} is written
* to the string builder in its place. The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readAsciiLine(final ByteBuffer src, final StringBuilder builder) {
return readAsciiLine(src, builder, '?', '\n');
}
/**
* Read a single line of ASCII text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place. The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readAsciiLine(final ByteBuffer src, final StringBuilder builder, final char replacement) {
return readAsciiLine(src, builder, replacement, '\n');
}
/**
* Read a single line of ASCII text from a byte buffer, appending the results to the given string
* builder, using the given delimiter character instead of {@code EOL}. If no delimiter character is encountered,
* {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place. The delimiter character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
* @param delimiter the character which marks the end of the line
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readAsciiLine(final ByteBuffer src, final StringBuilder builder, final char replacement, final char delimiter) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final byte b = src.get();
builder.append(b < 0 ? replacement : (char) b);
if (b == delimiter) {
return true;
}
}
}
/**
* Read the remainder of a buffer as ASCII text, appending the results to the given string
* builder. If an invalid byte is read, the character {@code '?'} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
*/
public static void readAscii(final ByteBuffer src, final StringBuilder builder) {
readAscii(src, builder, '?');
}
/**
* Read the remainder of a buffer as ASCII text, appending the results to the given string
* builder. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
*/
public static void readAscii(final ByteBuffer src, final StringBuilder builder, final char replacement) {
for (;;) {
if (! src.hasRemaining()) {
return;
}
final byte b = src.get();
builder.append(b < 0 ? replacement : (char) b);
}
}
/**
* Read the remainder of a buffer as ASCII text, up to a certain limit, appending the results to the given string
* builder. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @param limit the maximum number of characters to write
* @param replacement the replacement character for invalid bytes
*/
public static void readAscii(final ByteBuffer src, final StringBuilder builder, int limit, final char replacement) {
while (limit > 0) {
if (! src.hasRemaining()) {
return;
}
final byte b = src.get();
builder.append(b < 0 ? replacement : (char) b);
limit--;
}
}
/**
* Read a {@code NUL}-terminated Latin-1 string from a byte buffer, appending the results to the given string
* builder. If no {@code NUL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readLatin1Z(final ByteBuffer src, final StringBuilder builder) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final byte b = src.get();
if (b == 0) {
return true;
}
builder.append((char) (b & 0xff));
}
}
/**
* Read a single line of Latin-1 text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readLatin1Line(final ByteBuffer src, final StringBuilder builder) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final byte b = src.get();
builder.append((char) (b & 0xff));
if (b == '\n') {
return true;
}
}
}
/**
* Read a single line of Latin-1 text from a byte buffer, appending the results to the given string
* builder. If no delimiter character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. The delimiter character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param delimiter the character which marks the end of the line
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readLatin1Line(final ByteBuffer src, final StringBuilder builder, final char delimiter) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final byte b = src.get();
builder.append((char) (b & 0xff));
if (b == delimiter) {
return true;
}
}
}
/**
* Read the remainder of a buffer as Latin-1 text, appending the results to the given string
* builder.
*
* @param src the source buffer
* @param builder the destination builder
*/
public static void readLatin1(final ByteBuffer src, final StringBuilder builder) {
for (;;) {
if (! src.hasRemaining()) {
return;
}
final byte b = src.get();
builder.append((char) (b & 0xff));
}
}
/**
* Read a {@code NUL}-terminated {@link DataInput modified UTF-8} string from a byte buffer, appending the results to the given string
* builder. If no {@code NUL} byte is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte sequence is read, the character {@code '?'} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readModifiedUtf8Z(final ByteBuffer src, final StringBuilder builder) {
return readModifiedUtf8Z(src, builder, '?');
}
/**
* Read a {@code NUL}-terminated {@link DataInput modified UTF-8} string from a byte buffer, appending the results to the given string
* builder. If no {@code NUL} byte is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte sequence is read, the character designated by {@code replacement} is written
* to the string builder in its place.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character to use
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readModifiedUtf8Z(final ByteBuffer src, final StringBuilder builder, final char replacement) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final int a = src.get() & 0xff;
if (a == 0) {
return true;
} else if (a < 0x80) {
builder.append((char)a);
} else if (a < 0xc0) {
builder.append(replacement);
} else if (a < 0xe0) {
if (src.hasRemaining()) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
builder.append(replacement);
} else {
builder.append((char) ((a & 0x1f) << 6 | b & 0x3f));
}
} else {
unget(src, 1);
return false;
}
} else if (a < 0xf0) {
if (src.hasRemaining()) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
builder.append(replacement);
} else {
if (src.hasRemaining()) {
final int c = src.get() & 0xff;
if ((c & 0xc0) != 0x80) {
builder.append(replacement);
} else {
builder.append((char) ((a & 0x0f) << 12 | (b & 0x3f) << 6 | c & 0x3f));
}
} else {
unget(src, 2);
return false;
}
}
} else {
unget(src, 1);
return false;
}
} else {
builder.append(replacement);
}
}
}
/**
* Read a single line of {@link DataInput modified UTF-8} text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character {@code '?'} is written
* to the string builder in its place. The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readModifiedUtf8Line(final ByteBuffer src, final StringBuilder builder) {
return readModifiedUtf8Line(src, builder, '?');
}
/**
* Read a single line of {@link DataInput modified UTF-8} text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place. The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readModifiedUtf8Line(final ByteBuffer src, final StringBuilder builder, final char replacement) {
return readModifiedUtf8Line(src, builder, replacement, '\n');
}
/**
* Read a single line of {@link DataInput modified UTF-8} text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. If an invalid byte is read, the character designated by {@code replacement} is written
* to the string builder in its place. The delimiter character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param replacement the replacement character for invalid bytes
* @param delimiter the character which marks the end of the line
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readModifiedUtf8Line(final ByteBuffer src, final StringBuilder builder, final char replacement, final char delimiter) {
for (;;) {
if (! src.hasRemaining()) {
return false;
}
final int a = src.get() & 0xff;
if (a < 0x80) {
builder.append((char)a);
if (a == delimiter) {
return true;
}
} else if (a < 0xc0) {
builder.append(replacement);
} else if (a < 0xe0) {
if (src.hasRemaining()) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
builder.append(replacement);
} else {
final char ch = (char) ((a & 0x1f) << 6 | b & 0x3f);
builder.append(ch);
if (ch == delimiter) {
return true;
}
}
} else {
unget(src, 1);
return false;
}
} else if (a < 0xf0) {
if (src.hasRemaining()) {
final int b = src.get() & 0xff;
if ((b & 0xc0) != 0x80) {
builder.append(replacement);
} else {
if (src.hasRemaining()) {
final int c = src.get() & 0xff;
if ((c & 0xc0) != 0x80) {
builder.append(replacement);
} else {
final char ch = (char) ((a & 0x0f) << 12 | (b & 0x3f) << 6 | c & 0x3f);
builder.append(ch);
if (ch == delimiter) {
return true;
}
}
} else {
unget(src, 2);
return false;
}
}
} else {
unget(src, 1);
return false;
}
} else {
builder.append(replacement);
}
}
}
/**
* Read a single line of text from a byte buffer, appending the results to the given string
* builder. If no {@code EOL} character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. Invalid bytes are handled according to the policy specified by the {@code decoder} instance.
* Since this method decodes only one character at a time, it should not be expected to have the same performance
* as the other optimized, character set-specific methods specified in this class.
* The {@code EOL} character will be included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param decoder the decoder to use
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readLine(final ByteBuffer src, final StringBuilder builder, final CharsetDecoder decoder) {
return readLine(src, builder, decoder, '\n');
}
/**
* Read a single line of text from a byte buffer, appending the results to the given string
* builder. If no delimiter character is encountered, {@code false} is returned, indicating that more data needs
* to be acquired before the operation can be complete. On return, there may be data remaining
* in the source buffer. Invalid bytes are handled according to the policy specified by the {@code decoder} instance.
* Since this method decodes only one character at a time, it should not be expected to have the same performance
* as the other optimized, character set-specific methods specified in this class. The delimiter character will be
* included in the resultant string.
*
* @param src the source buffer
* @param builder the destination builder
* @param decoder the decoder to use
* @param delimiter the character which marks the end of the line
* @return {@code true} if the entire string was read, {@code false} if more data is needed
*/
public static boolean readLine(final ByteBuffer src, final StringBuilder builder, final CharsetDecoder decoder, final char delimiter) {
final CharBuffer oneChar = CharBuffer.allocate(1);
for (;;) {
final CoderResult coderResult = decoder.decode(src, oneChar, false);
if (coderResult.isUnderflow()) {
if (oneChar.hasRemaining()) {
return false;
}
} else if (oneChar.hasRemaining()) {
throw new IllegalStateException();
}
final char ch = oneChar.get(0);
builder.append(ch);
if (ch == delimiter) {
return true;
}
oneChar.clear();
}
}
/**
* Create a pooled wrapper around a buffer. The buffer is unreferenced for garbage collection when
* freed or discarded.
*
* @param buffer the buffer to wrap
* @param the buffer type
* @return the pooled wrapper
*/
public static Pooled pooledWrapper(final B buffer) {
return new Pooled() {
private volatile B buf = buffer;
public void discard() {
buf = null;
}
public void free() {
buf = null;
}
public B getResource() throws IllegalStateException {
final B buffer = buf;
if (buffer == null) {
throw new IllegalStateException();
}
return buffer;
}
public void close() {
free();
}
public String toString() {
return "Pooled wrapper around " + buffer;
}
};
}
/**
* Create a pooled wrapper around a buffer that was allocated via {@link ByteBufferPool}. The buffer is freed to the
* global pool when freed.
*
* @param buffer the buffer to wrap
* @return the pooled wrapper
*/
public static Pooled globalPooledWrapper(final ByteBuffer buffer) {
return new Pooled() {
private volatile ByteBuffer buf = buffer;
public void discard() {
ByteBuffer oldBuf = this.buf;
if (oldBuf == null) return;
final ByteBuffer buf = oldBuf.duplicate();
new CleanerReference(this.buf, null, new Reaper() {
public void reap(final Reference reference) {
// free the duplicate
ByteBufferPool.free(buf);
}
});
this.buf = null;
}
public void free() {
ByteBuffer oldBuf = this.buf;
if (oldBuf == null) return;
ByteBufferPool.free(oldBuf);
buf = null;
}
public ByteBuffer getResource() throws IllegalStateException {
final ByteBuffer buffer = buf;
if (buffer == null) {
throw new IllegalStateException();
}
return buffer;
}
public void close() {
free();
}
public String toString() {
return "Globally pooled wrapper around " + buffer;
}
};
}
/**
* Create a "pooled" empty buffer. Discarding or freeing the buffer has no effect; the returned buffer is
* always empty.
*
* @return a new pooled empty buffer
*/
public static Pooled emptyPooledByteBuffer() {
return new Pooled() {
public void discard() {
}
public void free() {
}
public ByteBuffer getResource() throws IllegalStateException {
return EMPTY_BYTE_BUFFER;
}
public void close() {
}
};
}
/**
* A buffer allocator which allocates slices off of the given buffer. Once the buffer is exhausted, further
* attempts to allocate buffers will result in {@link BufferUnderflowException}.
*
* @param buffer the source buffer
* @return the slice allocator
*/
public static BufferAllocator sliceAllocator(final ByteBuffer buffer) {
return new BufferAllocator() {
public ByteBuffer allocate(final int size) throws IllegalArgumentException {
return Buffers.slice(buffer, size);
}
};
}
/**
* A buffer pool which allocates a new buffer on every allocate request, and discards buffers on free.
*
* @param allocator the buffer allocator
* @param size the buffer size
* @param the buffer type
* @return the buffer pool
*/
public static Pool allocatedBufferPool(final BufferAllocator allocator, final int size) {
return new Pool() {
public Pooled allocate() {
return pooledWrapper(allocator.allocate(size));
}
};
}
/**
* A byte buffer pool which zeroes the content of the buffer before re-pooling it.
*
* @param delegate the delegate pool
* @return the wrapper pool
*/
public static Pool secureBufferPool(final Pool delegate) {
return new SecureByteBufferPool(delegate);
}
/**
* Determine whether the given pool is a secure pool. Note that this test will fail if used on a pool
* which wraps a secure pool.
*
* @param pool the pool to test
* @return {@code true} if it is a secure pool instance
*/
public static boolean isSecureBufferPool(Pool> pool) {
return pool instanceof SecureByteBufferPool;
}
/**
* Zero a buffer. Ensures that any potentially sensitive information in the buffer is
* overwritten.
*
* @param buffer the buffer
*/
public static void zero(ByteBuffer buffer) {
buffer.clear();
while (buffer.remaining() >= 8) {
buffer.putLong(0L);
}
while (buffer.hasRemaining()) {
buffer.put((byte) 0);
}
buffer.clear();
}
/**
* Zero a buffer. Ensures that any potentially sensitive information in the buffer is
* overwritten.
*
* @param buffer the buffer
*/
public static void zero(CharBuffer buffer) {
buffer.clear();
while (buffer.remaining() >= 32) {
buffer.put("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
while (buffer.hasRemaining()) {
buffer.put('\0');
}
buffer.clear();
}
/**
* Determine whether the given buffers list is comprised solely of direct buffers or solely of heap buffers.
*
* @param buffers the buffers
* @return {@code true} if all the buffers are direct, {@code false} if they are all heap buffers
* @throws IllegalArgumentException if both direct and heap buffers were found, or if a buffer is {@code null}
*/
public static boolean isDirect(Buffer... buffers) throws IllegalArgumentException {
return isDirect(buffers, 0, buffers.length);
}
/**
* Determine whether the given buffers list is comprised solely of direct buffers or solely of heap buffers.
*
* @param buffers the buffers
* @return {@code true} if all the buffers are direct, {@code false} if they are all heap buffers
* @throws IllegalArgumentException if both direct and heap buffers were found, or if a buffer is {@code null}
*/
public static boolean isDirect(final Buffer[] buffers, final int offset, final int length) {
boolean foundDirect = false;
boolean foundHeap = false;
for (int i = 0; i < length; i ++) {
final Buffer buffer = buffers[i + offset];
if (buffer == null) {
throw msg.nullParameter("buffer");
}
if (buffer.isDirect()) {
if (foundHeap) {
throw msg.mixedDirectAndHeap();
}
foundDirect = true;
} else {
if (foundDirect) {
throw msg.mixedDirectAndHeap();
}
foundHeap = true;
}
}
return foundDirect;
}
/**
* Assert the writability of the given buffers.
*
* @param buffers the buffers array
* @param offs the offset in the array to start searching
* @param len the number of buffers to check
* @throws ReadOnlyBufferException if any of the buffers are read-only
*/
public static void assertWritable(Buffer[] buffers, int offs, int len) throws ReadOnlyBufferException {
for (int i = 0; i < len; i ++) {
if (buffers[i + offs].isReadOnly()) {
throw msg.readOnlyBuffer();
}
}
}
/**
* Assert the writability of the given buffers.
*
* @param buffers the buffers array
* @throws ReadOnlyBufferException if any of the buffers are read-only
*/
public static void assertWritable(Buffer... buffers) throws ReadOnlyBufferException {
assertWritable(buffers, 0, buffers.length);
}
/**
* Add {@code count} bytes of random data to the target buffer.
*
* @param target the target buffer
* @param random the RNG
* @param count the number of bytes to add
*/
public static void addRandom(ByteBuffer target, Random random, int count) {
final byte[] bytes = new byte[count];
random.nextBytes(bytes);
target.put(bytes);
}
/**
* Add {@code count} bytes of random data to the target buffer using the thread-local RNG.
*
* @param target the target buffer
* @param count the number of bytes to add
*/
public static void addRandom(ByteBuffer target, int count) {
addRandom(target, IoUtils.getThreadLocalRandom(), count);
}
/**
* Add a random amount of random data to the target buffer.
*
* @param target the target buffer
* @param random the RNG
*/
public static void addRandom(ByteBuffer target, Random random) {
if (target.remaining() == 0) {
return;
}
addRandom(target, random, random.nextInt(target.remaining()));
}
/**
* Add a random amount of random data to the target buffer using the thread-local RNG.
*
* @param target the target buffer
*/
public static void addRandom(ByteBuffer target) {
addRandom(target, IoUtils.getThreadLocalRandom());
}
/**
* Fill a buffer from an input stream. Specially optimized for heap buffers. If a partial transfer occurs
* due to interruption, the buffer's position is updated accordingly.
*
* @param target the target buffer
* @param source the source stream
* @return the number of bytes transferred, or {@code -1} if no bytes were moved due to end-of-stream
* @throws IOException if the stream read fails
*/
public static int fillFromStream(ByteBuffer target, InputStream source) throws IOException {
final int remaining = target.remaining();
if (remaining == 0) {
return 0;
} else {
final int p = target.position();
if (target.hasArray()) {
// fast path
final int res;
try {
res = source.read(target.array(), p + target.arrayOffset(), remaining);
} catch (InterruptedIOException e) {
target.position(p + e.bytesTransferred);
throw e;
}
if (res > 0) {
target.position(p + res);
}
return res;
} else {
byte[] tmp = new byte[remaining];
final int res;
try {
res = source.read(tmp);
} catch (InterruptedIOException e) {
final int n = e.bytesTransferred;
target.put(tmp, 0, n);
target.position(p + n);
throw e;
}
if (res > 0) {
target.put(tmp, 0, res);
}
return res;
}
}
}
/**
* Get a debug-friendly description of the buffer.
*
* @param buffer the buffer to describe
* @return the string
*/
public static String debugString(ByteBuffer buffer) {
StringBuilder b = new StringBuilder();
b.append("1 buffer of ").append(buffer.remaining()).append(" bytes");
return b.toString();
}
/**
* Get a debug-friendly description of the buffer.
*
* @param buffers the buffers to describe
* @param offs the offset into the array
* @param len the number of buffers
* @return the string
*/
public static String debugString(ByteBuffer[] buffers, int offs, int len) {
StringBuilder b = new StringBuilder();
b.append(len).append(" buffer(s)");
if (len > 0) {
b.append(" of ").append(Buffers.remaining(buffers, offs, len)).append(" bytes");
}
return b.toString();
}
/**
* Empty a buffer to an output stream. Specially optimized for heap buffers. If a partial transfer occurs
* due to interruption, the buffer's position is updated accordingly.
*
* @param target the target stream
* @param source the source buffer
* @throws IOException if the stream write fails
*/
public static void emptyToStream(OutputStream target, ByteBuffer source) throws IOException {
final int remaining = source.remaining();
if (remaining == 0) {
return;
} else {
final int p = source.position();
if (source.hasArray()) {
// fast path
try {
target.write(source.array(), p + source.arrayOffset(), remaining);
} catch (InterruptedIOException e) {
source.position(p + e.bytesTransferred);
throw e;
}
source.position(source.limit());
return;
} else {
byte[] tmp = take(source);
try {
target.write(tmp);
} catch (InterruptedIOException e) {
source.position(p + e.bytesTransferred);
throw e;
} catch (IOException e) {
source.position(p);
throw e;
}
}
}
}
private static class SecureByteBufferPool implements Pool {
private final Pool delegate;
SecureByteBufferPool(final Pool delegate) {
this.delegate = delegate;
}
public Pooled allocate() {
return new SecurePooledByteBuffer(delegate.allocate());
}
}
private static class SecurePooledByteBuffer implements Pooled {
private static final AtomicIntegerFieldUpdater freedUpdater = AtomicIntegerFieldUpdater.newUpdater(SecurePooledByteBuffer.class, "freed");
private final Pooled allocated;
@SuppressWarnings("unused")
private volatile int freed;
SecurePooledByteBuffer(final Pooled allocated) {
this.allocated = allocated;
}
public void discard() {
if (freedUpdater.compareAndSet(this, 0, 1)) {
zero(allocated.getResource());
allocated.discard();
}
}
public void free() {
if (freedUpdater.compareAndSet(this, 0, 1)) {
zero(allocated.getResource());
allocated.free();
}
}
public ByteBuffer getResource() throws IllegalStateException {
// trust the delegate to handle illegal state since we can't do it securely by ourselves
return allocated.getResource();
}
public void close() {
free();
}
public String toString() {
return "Secure wrapper around " + allocated;
}
}
}