/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project licenses this file to you 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 io.netty.buffer;

import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.RecyclableArrayList;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ReadOnlyBufferException;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.util.Collections;

ByteBuf implementation which allows to wrap an array of ByteBuf in a read-only mode. This is useful to write an array of ByteBufs.
/** * {@link ByteBuf} implementation which allows to wrap an array of {@link ByteBuf} in a read-only mode. * This is useful to write an array of {@link ByteBuf}s. */
final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf { private static final ByteBuf[] EMPTY = { Unpooled.EMPTY_BUFFER }; private final int nioBufferCount; private final int capacity; private final ByteBufAllocator allocator; private final ByteOrder order; private final Object[] buffers; private final boolean direct; FixedCompositeByteBuf(ByteBufAllocator allocator, ByteBuf... buffers) { super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY); if (buffers.length == 0) { this.buffers = EMPTY; order = ByteOrder.BIG_ENDIAN; nioBufferCount = 1; capacity = 0; direct = false; } else { ByteBuf b = buffers[0]; this.buffers = new Object[buffers.length]; this.buffers[0] = b; boolean direct = true; int nioBufferCount = b.nioBufferCount(); int capacity = b.readableBytes(); order = b.order(); for (int i = 1; i < buffers.length; i++) { b = buffers[i]; if (buffers[i].order() != order) { throw new IllegalArgumentException("All ByteBufs need to have same ByteOrder"); } nioBufferCount += b.nioBufferCount(); capacity += b.readableBytes(); if (!b.isDirect()) { direct = false; } this.buffers[i] = b; } this.nioBufferCount = nioBufferCount; this.capacity = capacity; this.direct = direct; } setIndex(0, capacity()); this.allocator = allocator; } @Override public boolean isWritable() { return false; } @Override public boolean isWritable(int size) { return false; } @Override public ByteBuf discardReadBytes() { throw new ReadOnlyBufferException(); } @Override public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setBytes(int index, ByteBuffer src) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setByte(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setByte(int index, int value) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setShort(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setShort(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setShortLE(int index, int value) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setMedium(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setMedium(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setMediumLE(int index, int value) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setInt(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setInt(int index, int value) { throw new ReadOnlyBufferException(); } @Override protected void _setIntLE(int index, int value) { throw new ReadOnlyBufferException(); } @Override public ByteBuf setLong(int index, long value) { throw new ReadOnlyBufferException(); } @Override protected void _setLong(int index, long value) { throw new ReadOnlyBufferException(); } @Override protected void _setLongLE(int index, long value) { throw new ReadOnlyBufferException(); } @Override public int setBytes(int index, InputStream in, int length) { throw new ReadOnlyBufferException(); } @Override public int setBytes(int index, ScatteringByteChannel in, int length) { throw new ReadOnlyBufferException(); } @Override public int setBytes(int index, FileChannel in, long position, int length) { throw new ReadOnlyBufferException(); } @Override public int capacity() { return capacity; } @Override public int maxCapacity() { return capacity; } @Override public ByteBuf capacity(int newCapacity) { throw new ReadOnlyBufferException(); } @Override public ByteBufAllocator alloc() { return allocator; } @Override public ByteOrder order() { return order; } @Override public ByteBuf unwrap() { return null; } @Override public boolean isDirect() { return direct; } private Component findComponent(int index) { int readable = 0; for (int i = 0 ; i < buffers.length; i++) { Component comp = null; ByteBuf b; Object obj = buffers[i]; boolean isBuffer; if (obj instanceof ByteBuf) { b = (ByteBuf) obj; isBuffer = true; } else { comp = (Component) obj; b = comp.buf; isBuffer = false; } readable += b.readableBytes(); if (index < readable) { if (isBuffer) { // Create a new component and store it in the array so it not create a new object // on the next access. comp = new Component(i, readable - b.readableBytes(), b); buffers[i] = comp; } return comp; } } throw new IllegalStateException(); }
Return the ByteBuf stored at the given index of the array.
/** * Return the {@link ByteBuf} stored at the given index of the array. */
private ByteBuf buffer(int i) { Object obj = buffers[i]; if (obj instanceof ByteBuf) { return (ByteBuf) obj; } return ((Component) obj).buf; } @Override public byte getByte(int index) { return _getByte(index); } @Override protected byte _getByte(int index) { Component c = findComponent(index); return c.buf.getByte(index - c.offset); } @Override protected short _getShort(int index) { Component c = findComponent(index); if (index + 2 <= c.endOffset) { return c.buf.getShort(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); } else { return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); } } @Override protected short _getShortLE(int index) { Component c = findComponent(index); if (index + 2 <= c.endOffset) { return c.buf.getShortLE(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); } else { return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); } } @Override protected int _getUnsignedMedium(int index) { Component c = findComponent(index); if (index + 3 <= c.endOffset) { return c.buf.getUnsignedMedium(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getShort(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; } else { return _getShort(index) & 0xFFFF | (_getByte(index + 2) & 0xFF) << 16; } } @Override protected int _getUnsignedMediumLE(int index) { Component c = findComponent(index); if (index + 3 <= c.endOffset) { return c.buf.getUnsignedMediumLE(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getShortLE(index) & 0xffff | (_getByte(index + 2) & 0xff) << 16; } else { return (_getShortLE(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; } } @Override protected int _getInt(int index) { Component c = findComponent(index); if (index + 4 <= c.endOffset) { return c.buf.getInt(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getShort(index) & 0xffff) << 16 | _getShort(index + 2) & 0xffff; } else { return _getShort(index) & 0xFFFF | (_getShort(index + 2) & 0xFFFF) << 16; } } @Override protected int _getIntLE(int index) { Component c = findComponent(index); if (index + 4 <= c.endOffset) { return c.buf.getIntLE(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getShortLE(index) & 0xFFFF | (_getShortLE(index + 2) & 0xFFFF) << 16; } else { return (_getShortLE(index) & 0xffff) << 16 | _getShortLE(index + 2) & 0xffff; } } @Override protected long _getLong(int index) { Component c = findComponent(index); if (index + 8 <= c.endOffset) { return c.buf.getLong(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL; } else { return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32; } } @Override protected long _getLongLE(int index) { Component c = findComponent(index); if (index + 8 <= c.endOffset) { return c.buf.getLongLE(index - c.offset); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getIntLE(index) & 0xffffffffL | (_getIntLE(index + 4) & 0xffffffffL) << 32; } else { return (_getIntLE(index) & 0xffffffffL) << 32 | _getIntLE(index + 4) & 0xffffffffL; } } @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { checkDstIndex(index, length, dstIndex, dst.length); if (length == 0) { return this; } Component c = findComponent(index); int i = c.index; int adjustment = c.offset; ByteBuf s = c.buf; for (;;) { int localLength = Math.min(length, s.readableBytes() - (index - adjustment)); s.getBytes(index - adjustment, dst, dstIndex, localLength); index += localLength; dstIndex += localLength; length -= localLength; adjustment += s.readableBytes(); if (length <= 0) { break; } s = buffer(++i); } return this; } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { int limit = dst.limit(); int length = dst.remaining(); checkIndex(index, length); if (length == 0) { return this; } try { Component c = findComponent(index); int i = c.index; int adjustment = c.offset; ByteBuf s = c.buf; for (;;) { int localLength = Math.min(length, s.readableBytes() - (index - adjustment)); dst.limit(dst.position() + localLength); s.getBytes(index - adjustment, dst); index += localLength; length -= localLength; adjustment += s.readableBytes(); if (length <= 0) { break; } s = buffer(++i); } } finally { dst.limit(limit); } return this; } @Override public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { checkDstIndex(index, length, dstIndex, dst.capacity()); if (length == 0) { return this; } Component c = findComponent(index); int i = c.index; int adjustment = c.offset; ByteBuf s = c.buf; for (;;) { int localLength = Math.min(length, s.readableBytes() - (index - adjustment)); s.getBytes(index - adjustment, dst, dstIndex, localLength); index += localLength; dstIndex += localLength; length -= localLength; adjustment += s.readableBytes(); if (length <= 0) { break; } s = buffer(++i); } return this; } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { int count = nioBufferCount(); if (count == 1) { return out.write(internalNioBuffer(index, length)); } else { long writtenBytes = out.write(nioBuffers(index, length)); if (writtenBytes > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else { return (int) writtenBytes; } } } @Override public int getBytes(int index, FileChannel out, long position, int length) throws IOException { int count = nioBufferCount(); if (count == 1) { return out.write(internalNioBuffer(index, length), position); } else { long writtenBytes = 0; for (ByteBuffer buf : nioBuffers(index, length)) { writtenBytes += out.write(buf, position + writtenBytes); } if (writtenBytes > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else { return (int) writtenBytes; } } } @Override public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { checkIndex(index, length); if (length == 0) { return this; } Component c = findComponent(index); int i = c.index; int adjustment = c.offset; ByteBuf s = c.buf; for (;;) { int localLength = Math.min(length, s.readableBytes() - (index - adjustment)); s.getBytes(index - adjustment, out, localLength); index += localLength; length -= localLength; adjustment += s.readableBytes(); if (length <= 0) { break; } s = buffer(++i); } return this; } @Override public ByteBuf copy(int index, int length) { checkIndex(index, length); boolean release = true; ByteBuf buf = alloc().buffer(length); try { buf.writeBytes(this, index, length); release = false; return buf; } finally { if (release) { buf.release(); } } } @Override public int nioBufferCount() { return nioBufferCount; } @Override public ByteBuffer nioBuffer(int index, int length) { checkIndex(index, length); if (buffers.length == 1) { ByteBuf buf = buffer(0); if (buf.nioBufferCount() == 1) { return buf.nioBuffer(index, length); } } ByteBuffer merged = ByteBuffer.allocate(length).order(order()); ByteBuffer[] buffers = nioBuffers(index, length); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < buffers.length; i++) { merged.put(buffers[i]); } merged.flip(); return merged; } @Override public ByteBuffer internalNioBuffer(int index, int length) { if (buffers.length == 1) { return buffer(0).internalNioBuffer(index, length); } throw new UnsupportedOperationException(); } @Override public ByteBuffer[] nioBuffers(int index, int length) { checkIndex(index, length); if (length == 0) { return EmptyArrays.EMPTY_BYTE_BUFFERS; } RecyclableArrayList array = RecyclableArrayList.newInstance(buffers.length); try { Component c = findComponent(index); int i = c.index; int adjustment = c.offset; ByteBuf s = c.buf; for (;;) { int localLength = Math.min(length, s.readableBytes() - (index - adjustment)); switch (s.nioBufferCount()) { case 0: throw new UnsupportedOperationException(); case 1: array.add(s.nioBuffer(index - adjustment, localLength)); break; default: Collections.addAll(array, s.nioBuffers(index - adjustment, localLength)); } index += localLength; length -= localLength; adjustment += s.readableBytes(); if (length <= 0) { break; } s = buffer(++i); } return array.toArray(new ByteBuffer[array.size()]); } finally { array.recycle(); } } @Override public boolean hasArray() { switch (buffers.length) { case 0: return true; case 1: return buffer(0).hasArray(); default: return false; } } @Override public byte[] array() { switch (buffers.length) { case 0: return EmptyArrays.EMPTY_BYTES; case 1: return buffer(0).array(); default: throw new UnsupportedOperationException(); } } @Override public int arrayOffset() { switch (buffers.length) { case 0: return 0; case 1: return buffer(0).arrayOffset(); default: throw new UnsupportedOperationException(); } } @Override public boolean hasMemoryAddress() { switch (buffers.length) { case 0: return Unpooled.EMPTY_BUFFER.hasMemoryAddress(); case 1: return buffer(0).hasMemoryAddress(); default: return false; } } @Override public long memoryAddress() { switch (buffers.length) { case 0: return Unpooled.EMPTY_BUFFER.memoryAddress(); case 1: return buffer(0).memoryAddress(); default: throw new UnsupportedOperationException(); } } @Override protected void deallocate() { for (int i = 0; i < buffers.length; i++) { buffer(i).release(); } } @Override public String toString() { String result = super.toString(); result = result.substring(0, result.length() - 1); return result + ", components=" + buffers.length + ')'; } private static final class Component { private final int index; private final int offset; private final ByteBuf buf; private final int endOffset; Component(int index, int offset, ByteBuf buf) { this.index = index; this.offset = offset; endOffset = offset + buf.readableBytes(); this.buf = buf; } } }