diff --git a/buffer/src/main/java/io/netty/buffer/ReadOnlyDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/ReadOnlyDirectByteBuf.java new file mode 100644 index 0000000000..b1b4d8b212 --- /dev/null +++ b/buffer/src/main/java/io/netty/buffer/ReadOnlyDirectByteBuf.java @@ -0,0 +1,327 @@ +/* + * 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.ResourceLeak; + +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.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + + +/** + * Read-only ByteBuf which wraps a read-only direct ByteBuffer. + */ +class ReadOnlyDirectByteBuf extends AbstractReferenceCountedByteBuf { + private final ResourceLeak leak = leakDetector.open(this); + + protected final ByteBuffer buffer; + private final ByteBufAllocator allocator; + private ByteBuffer tmpNioBuf; + + public ReadOnlyDirectByteBuf(ByteBufAllocator allocator, ByteBuffer buffer) { + super(buffer.remaining()); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("buffer must be readOnly"); + } + this.allocator = allocator; + this.buffer = buffer.slice().order(ByteOrder.BIG_ENDIAN); + writerIndex(buffer.limit()); + } + + @Override + protected void deallocate() { + leak.close(); + } + + @Override + public byte getByte(int index) { + ensureAccessible(); + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return buffer.get(index); + } + + @Override + public short getShort(int index) { + ensureAccessible(); + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + ensureAccessible(); + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff; + } + + @Override + public int getInt(int index) { + ensureAccessible(); + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index); + } + + @Override + public long getLong(int index) { + ensureAccessible(); + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else if (dst.nioBufferCount() > 0) { + for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) { + int bbLen = bb.remaining(); + getBytes(index, bb); + index += bbLen; + } + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.get(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + return this; + } + + @Override + protected void _setByte(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setShort(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setMedium(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setInt(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setLong(int index, long value) { + throw new ReadOnlyBufferException(); + } + + @Override + public int capacity() { + return maxCapacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBufAllocator alloc() { + return allocator; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public ByteBuf unwrap() { + return null; + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + throw new ReadOnlyBufferException(); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + 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 int setBytes(int index, InputStream in, int length) throws IOException { + throw new ReadOnlyBufferException(); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + throw new ReadOnlyBufferException(); + } + + protected final ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = buffer.duplicate(); + } + return tmpNioBuf; + } + + @Override + public ByteBuf copy(int index, int length) { + ensureAccessible(); + ByteBuffer src; + try { + src = (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } catch (IllegalArgumentException e) { + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length)); + } + + ByteBuffer dst = ByteBuffer.allocateDirect(length); + dst.put(src); + dst.order(order()); + dst.clear(); + return new UnpooledDirectByteBuf(alloc(), dst, maxCapacity()); + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + ensureAccessible(); + if (index == 0 && length == capacity()) { + return buffer.duplicate(); + } else { + return ((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length)).slice(); + } + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public int arrayOffset() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuf suspendIntermediaryDeallocations() { + return this; + } + + @Override + public ByteBuf resumeIntermediaryDeallocations() { + return this; + } +} diff --git a/buffer/src/main/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBuf.java new file mode 100644 index 0000000000..1607a6006b --- /dev/null +++ b/buffer/src/main/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBuf.java @@ -0,0 +1,134 @@ +/* + * 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.PlatformDependent; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + + +/** + * Read-only ByteBuf which wraps a read-only direct ByteBuffer and use unsafe for best performance. + */ +final class ReadOnlyUnsafeDirectByteBuf extends ReadOnlyDirectByteBuf { + private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + private final long memoryAddress; + + public ReadOnlyUnsafeDirectByteBuf(ByteBufAllocator allocator, ByteBuffer buffer) { + super(allocator, buffer); + memoryAddress = PlatformDependent.directBufferAddress(buffer); + } + + @Override + protected byte _getByte(int index) { + return PlatformDependent.getByte(addr(index)); + } + + @Override + protected short _getShort(int index) { + short v = PlatformDependent.getShort(addr(index)); + return NATIVE_ORDER? v : Short.reverseBytes(v); + } + + @Override + protected int _getUnsignedMedium(int index) { + long addr = addr(index); + return (PlatformDependent.getByte(addr) & 0xff) << 16 | + (PlatformDependent.getByte(addr + 1) & 0xff) << 8 | + PlatformDependent.getByte(addr + 2) & 0xff; + } + + @Override + protected int _getInt(int index) { + int v = PlatformDependent.getInt(addr(index)); + return NATIVE_ORDER? v : Integer.reverseBytes(v); + } + + @Override + protected long _getLong(int index) { + long v = PlatformDependent.getLong(addr(index)); + return NATIVE_ORDER? v : Long.reverseBytes(v); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.capacity() - length) { + throw new IndexOutOfBoundsException("dstIndex: " + dstIndex); + } + + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + if (length != 0) { + PlatformDependent.copyMemory(addr(index), dst, dstIndex, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + return this; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + UnpooledUnsafeDirectByteBuf copy = (UnpooledUnsafeDirectByteBuf) alloc().directBuffer(length, maxCapacity()); + if (length != 0) { + PlatformDependent.copyMemory(addr(index), copy.addr(0), length); + copy.setIndex(0, length); + } + return copy; + } + + private long addr(int index) { + return memoryAddress + index; + } +} diff --git a/buffer/src/main/java/io/netty/buffer/Unpooled.java b/buffer/src/main/java/io/netty/buffer/Unpooled.java index 77303d4eb9..8a0f7d9565 100644 --- a/buffer/src/main/java/io/netty/buffer/Unpooled.java +++ b/buffer/src/main/java/io/netty/buffer/Unpooled.java @@ -223,9 +223,17 @@ public final class Unpooled { buffer.arrayOffset() + buffer.position(), buffer.remaining()).order(buffer.order()); } else if (PlatformDependent.hasUnsafe()) { - return new UnpooledUnsafeDirectByteBuf(ALLOC, buffer, buffer.remaining()); + if (buffer.isReadOnly()) { + return new ReadOnlyUnsafeDirectByteBuf(ALLOC, buffer); + } else { + return new UnpooledUnsafeDirectByteBuf(ALLOC, buffer, buffer.remaining()); + } } else { - return new UnpooledDirectByteBuf(ALLOC, buffer, buffer.remaining()); + if (buffer.isReadOnly()) { + return new ReadOnlyDirectByteBuf(ALLOC, buffer); + } else { + return new UnpooledDirectByteBuf(ALLOC, buffer, buffer.remaining()); + } } } diff --git a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java index a0510dbf20..815922dfc0 100644 --- a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeDirectByteBuf.java @@ -493,7 +493,7 @@ final class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf return null; } - private long addr(int index) { + long addr(int index) { return memoryAddress + index; } } diff --git a/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufTest.java b/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufTest.java new file mode 100644 index 0000000000..5c8fc9d7c0 --- /dev/null +++ b/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufTest.java @@ -0,0 +1,142 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; + +public class ReadOnlyDirectByteBufTest { + + protected ByteBuf buffer(ByteBuffer buffer) { + return new ReadOnlyDirectByteBuf(UnpooledByteBufAllocator.DEFAULT, buffer.asReadOnlyBuffer()); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructWithNotDirectBuffer() { + buffer(ByteBuffer.allocate(1)); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetByte() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setByte(0, 1); + } + + + @Test(expected = ReadOnlyBufferException.class) + public void testSetInt() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setInt(0, 1); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetShort() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setShort(0, 1); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetMedium() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setMedium(0, 1); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetLong() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setLong(0, 1); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetBytesViaArray() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setBytes(0, "test".getBytes()); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetBytesViaBuffer() { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setBytes(0, Unpooled.copyInt(1)); + } + + @Test(expected = ReadOnlyBufferException.class) + public void testSetBytesViaStream() throws IOException { + ByteBuf buf = buffer(ByteBuffer.allocateDirect(8).asReadOnlyBuffer()); + buf.setBytes(0, new ByteArrayInputStream("test".getBytes()), 2); + } + + @Test + public void testGetReadByte() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(2).put(new byte[]{(byte) 1, (byte) 2}).flip()).asReadOnlyBuffer()); + Assert.assertEquals(1, buf.getByte(0)); + Assert.assertEquals(2, buf.getByte(1)); + + Assert.assertEquals(1, buf.readByte()); + Assert.assertEquals(2, buf.readByte()); + Assert.assertFalse(buf.isReadable()); + } + + @Test + public void testGetReadInt() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(8).putInt(1).putInt(2).flip()).asReadOnlyBuffer()); + Assert.assertEquals(1, buf.getInt(0)); + Assert.assertEquals(2, buf.getInt(4)); + + Assert.assertEquals(1, buf.readInt()); + Assert.assertEquals(2, buf.readInt()); + Assert.assertFalse(buf.isReadable()); + } + + + @Test + public void testGetReadShort() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(8).putShort((short) 1).putShort((short) 2).flip()).asReadOnlyBuffer()); + Assert.assertEquals(1, buf.getShort(0)); + Assert.assertEquals(2, buf.getShort(2)); + + Assert.assertEquals(1, buf.readShort()); + Assert.assertEquals(2, buf.readShort()); + Assert.assertFalse(buf.isReadable()); + } + + @Test + public void testGetReadLong() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(16).putLong(1).putLong(2).flip()).asReadOnlyBuffer()); + Assert.assertEquals(1, buf.getLong(0)); + Assert.assertEquals(2, buf.getLong(8)); + + Assert.assertEquals(1, buf.readLong()); + Assert.assertEquals(2, buf.readLong()); + Assert.assertFalse(buf.isReadable()); + } + + @Test + public void testCopy() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(16).putLong(1).putLong(2).flip()).asReadOnlyBuffer()); + Assert.assertEquals(buf, buf.copy()); + } + + @Test + public void testCopyWithOffset() { + ByteBuf buf = buffer(((ByteBuffer) ByteBuffer.allocateDirect(16).putLong(1).putLong(2).flip()).asReadOnlyBuffer()); + Assert.assertEquals(buf.slice(1, 9), buf.copy(1, 9)); + } +} diff --git a/buffer/src/test/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBufTest.java b/buffer/src/test/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBufTest.java new file mode 100644 index 0000000000..4ce322660c --- /dev/null +++ b/buffer/src/test/java/io/netty/buffer/ReadOnlyUnsafeDirectByteBufTest.java @@ -0,0 +1,39 @@ +/* + * 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.PlatformDependent; +import org.junit.BeforeClass; + +import java.nio.ByteBuffer; + +import static org.junit.Assume.assumeTrue; + +public class ReadOnlyUnsafeDirectByteBufTest extends ReadOnlyDirectByteBufTest { + + /** + * Needs unsafe to run + */ + @BeforeClass + public static void assumeConditions(){ + assumeTrue(PlatformDependent.hasUnsafe()); + } + + @Override + protected ByteBuf buffer(ByteBuffer buffer) { + return new ReadOnlyUnsafeDirectByteBuf(UnpooledByteBufAllocator.DEFAULT, buffer); + } +} diff --git a/buffer/src/test/java/io/netty/buffer/UnpooledTest.java b/buffer/src/test/java/io/netty/buffer/UnpooledTest.java index e53f2b33ef..9f61c6533e 100644 --- a/buffer/src/test/java/io/netty/buffer/UnpooledTest.java +++ b/buffer/src/test/java/io/netty/buffer/UnpooledTest.java @@ -570,4 +570,17 @@ public class UnpooledTest { assertEquals(0, Unpooled.copyBoolean(new boolean[0]).capacity()); } + + @Test + public void wrappedReadOnlyDirectBuffer() { + ByteBuffer buffer = ByteBuffer.allocateDirect(12); + for (int i = 0; i < 12; i++) { + buffer.put((byte) i); + } + buffer.flip(); + ByteBuf wrapped = wrappedBuffer(buffer.asReadOnlyBuffer()); + for (int i = 0; i < 12; i++) { + assertEquals((byte) i, wrapped.readByte()); + } + } }