/* * Copyright 2014, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.grpc.internal; import static com.google.common.base.Charsets.UTF_8; import com.google.common.base.Preconditions; import io.grpc.KnownLength; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; /** * Utility methods for creating {@link ReadableBuffer} instances. */ public final class ReadableBuffers { private static final ReadableBuffer EMPTY_BUFFER = new ByteArrayWrapper(new byte[0]); /** * Returns an empty {@link ReadableBuffer} instance. */ public static ReadableBuffer empty() { return EMPTY_BUFFER; } /** * Shortcut for {@code wrap(bytes, 0, bytes.length}. */ public static ReadableBuffer wrap(byte[] bytes) { return new ByteArrayWrapper(bytes, 0, bytes.length); } /** * Creates a new {@link ReadableBuffer} that is backed by the given byte array. * * @param bytes the byte array being wrapped. * @param offset the starting offset for the buffer within the byte array. * @param length the length of the buffer from the {@code offset} index. */ public static ReadableBuffer wrap(byte[] bytes, int offset, int length) { return new ByteArrayWrapper(bytes, offset, length); } /** * Creates a new {@link ReadableBuffer} that is backed by the given {@link ByteBuffer}. Calls to * read from the buffer will increment the position of the {@link ByteBuffer}. */ public static ReadableBuffer wrap(ByteBuffer bytes) { return new ByteReadableBufferWrapper(bytes); } /** * Reads an entire {@link ReadableBuffer} to a new array. After calling this method, the buffer * will contain no readable bytes. */ public static byte[] readArray(ReadableBuffer buffer) { Preconditions.checkNotNull(buffer, "buffer"); int length = buffer.readableBytes(); byte[] bytes = new byte[length]; buffer.readBytes(bytes, 0, length); return bytes; } /** * Reads the entire {@link ReadableBuffer} to a new {@link String} with the given charset. */ public static String readAsString(ReadableBuffer buffer, Charset charset) { Preconditions.checkNotNull(charset, "charset"); byte[] bytes = readArray(buffer); return new String(bytes, charset); } /** * Reads the entire {@link ReadableBuffer} to a new {@link String} using UTF-8 decoding. */ public static String readAsStringUtf8(ReadableBuffer buffer) { return readAsString(buffer, UTF_8); } /** * Creates a new {@link InputStream} backed by the given buffer. Any read taken on the stream will * automatically increment the read position of this buffer. Closing the stream, however, does not * affect the original buffer. * * @param buffer the buffer backing the new {@link InputStream}. * @param owner if {@code true}, the returned stream will close the buffer when closed. */ public static InputStream openStream(ReadableBuffer buffer, boolean owner) { return new BufferInputStream(owner ? buffer : ignoreClose(buffer)); } /** * Decorates the given {@link ReadableBuffer} to ignore calls to {@link ReadableBuffer#close}. * * @param buffer the buffer to be decorated. * @return a wrapper around {@code buffer} that ignores calls to {@link ReadableBuffer#close}. */ public static ReadableBuffer ignoreClose(ReadableBuffer buffer) { return new ForwardingReadableBuffer(buffer) { @Override public void close() { // Ignore. } }; } /** * A {@link ReadableBuffer} that is backed by a byte array. */ private static class ByteArrayWrapper extends AbstractReadableBuffer { int offset; final int end; final byte[] bytes; ByteArrayWrapper(byte[] bytes) { this(bytes, 0, bytes.length); } ByteArrayWrapper(byte[] bytes, int offset, int length) { Preconditions.checkArgument(offset >= 0, "offset must be >= 0"); Preconditions.checkArgument(length >= 0, "length must be >= 0"); Preconditions.checkArgument(offset + length <= bytes.length, "offset + length exceeds array boundary"); this.bytes = Preconditions.checkNotNull(bytes, "bytes"); this.offset = offset; this.end = offset + length; } @Override public int readableBytes() { return end - offset; } @Override public void skipBytes(int length) { checkReadable(length); offset += length; } @Override public int readUnsignedByte() { checkReadable(1); return bytes[offset++] & 0xFF; } @Override public void readBytes(byte[] dest, int destIndex, int length) { System.arraycopy(bytes, offset, dest, destIndex, length); offset += length; } @Override public void readBytes(ByteBuffer dest) { Preconditions.checkNotNull(dest, "dest"); int length = dest.remaining(); checkReadable(length); dest.put(bytes, offset, length); offset += length; } @Override public void readBytes(OutputStream dest, int length) throws IOException { checkReadable(length); dest.write(bytes, offset, length); offset += length; } @Override public ByteArrayWrapper readBytes(int length) { checkReadable(length); int originalOffset = offset; offset += length; return new ByteArrayWrapper(bytes, originalOffset, length); } @Override public boolean hasArray() { return true; } @Override public byte[] array() { return bytes; } @Override public int arrayOffset() { return offset; } } /** * A {@link ReadableBuffer} that is backed by a {@link ByteBuffer}. */ private static class ByteReadableBufferWrapper extends AbstractReadableBuffer { final ByteBuffer bytes; ByteReadableBufferWrapper(ByteBuffer bytes) { this.bytes = Preconditions.checkNotNull(bytes, "bytes"); } @Override public int readableBytes() { return bytes.remaining(); } @Override public int readUnsignedByte() { checkReadable(1); return bytes.get() & 0xFF; } @Override public void skipBytes(int length) { checkReadable(length); bytes.position(bytes.position() + length); } @Override public void readBytes(byte[] dest, int destOffset, int length) { checkReadable(length); bytes.get(dest, destOffset, length); } @Override public void readBytes(ByteBuffer dest) { Preconditions.checkNotNull(dest, "dest"); int length = dest.remaining(); checkReadable(length); // Change the limit so that only length bytes are available. int prevLimit = bytes.limit(); bytes.limit(bytes.position() + length); // Write the bytes and restore the original limit. dest.put(bytes); bytes.limit(prevLimit); } @Override public void readBytes(OutputStream dest, int length) throws IOException { checkReadable(length); if (hasArray()) { dest.write(array(), arrayOffset(), length); bytes.position(bytes.position() + length); } else { // The buffer doesn't support array(). Copy the data to an intermediate buffer. byte[] array = new byte[length]; bytes.get(array); dest.write(array); } } @Override public ByteReadableBufferWrapper readBytes(int length) { checkReadable(length); ByteBuffer buffer = bytes.duplicate(); buffer.limit(bytes.position() + length); bytes.position(bytes.position() + length); return new ByteReadableBufferWrapper(buffer); } @Override public boolean hasArray() { return bytes.hasArray(); } @Override public byte[] array() { return bytes.array(); } @Override public int arrayOffset() { return bytes.arrayOffset() + bytes.position(); } } /** * An {@link InputStream} that is backed by a {@link ReadableBuffer}. */ private static final class BufferInputStream extends InputStream implements KnownLength { final ReadableBuffer buffer; public BufferInputStream(ReadableBuffer buffer) { this.buffer = Preconditions.checkNotNull(buffer, "buffer"); } @Override public int available() throws IOException { return buffer.readableBytes(); } @Override public int read() { if (buffer.readableBytes() == 0) { // EOF. return -1; } return buffer.readUnsignedByte(); } @Override public int read(byte[] dest, int destOffset, int length) throws IOException { if (buffer.readableBytes() == 0) { // EOF. return -1; } length = Math.min(buffer.readableBytes(), length); buffer.readBytes(dest, destOffset, length); return length; } } private ReadableBuffers() {} }