/* * Copyright 2021 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: * * https://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.net5.buffer.api; import java.nio.ByteBuffer; /** * A life cycled buffer of memory, with separate reader and writer offsets. *
* A buffer is a logically sequential stretch of memory with a certain capacity, an offset for writing, * and an offset for reading. * Buffers may be {@linkplain CompositeBuffer composed} of multiple {@linkplain #countComponents() components}, * where each component is a guaranteed contiguous chunk of memory. * *
* This carves the buffer into three regions, as demonstrated by this diagram: *
* +-------------------+------------------+------------------+ * | discardable bytes | readable bytes | writable bytes | * | | (CONTENT) | | * +-------------------+------------------+------------------+ * | | | | * 0 <= readerOffset <= writerOffset <= capacity ** *
* Splitting a buffer is useful for when you want to hand over a region of a buffer to some other, * perhaps unknown, piece of code, and relinquish your ownership of that buffer region in the process. * Examples include aggregating messages into an accumulator buffer, and sending messages down the pipeline for * further processing, as split buffer regions, once their data has been received in its entirety. * * If you instead wish to temporarily share a region of a buffer, you will have to pass offset and length along with the * buffer, or you will have to make a copy of the region. * *
* This method does not read or modify the {@linkplain #writerOffset() write offset} or the * {@linkplain #readerOffset() read offset}. * * @param srcPos The byte offset into this buffer from where the copying should start; the byte at this offset in * this buffer will be copied to the {@code destPos} index in the {@code dest} array. * @param dest The destination byte array. * @param destPos The index into the {@code dest} array from where the copying should start. * @param length The number of bytes to copy. * @throws NullPointerException if the destination array is null. * @throws IndexOutOfBoundsException if the source or destination positions, or the length, are negative, * or if the resulting end positions reaches beyond the end of either this buffer, or the destination array. * @throws BufferClosedException if this buffer is closed. */ void copyInto(int srcPos, byte[] dest, int destPos, int length); /** * Copies the given length of data from this buffer into the given destination byte buffer, beginning at the given * source position in this buffer, and the given destination position in the destination byte buffer. *
* This method does not read or modify the {@linkplain #writerOffset() write offset} or the * {@linkplain #readerOffset() read offset}, nor is the position of the destination buffer changed. *
* The position and limit of the destination byte buffer are also ignored, and do not influence {@code destPos} * or {@code length}. * * @param srcPos The byte offset into this buffer from where the copying should start; the byte at this offset in * this buffer will be copied to the {@code destPos} index in the {@code dest} array. * @param dest The destination byte buffer. * @param destPos The index into the {@code dest} array from where the copying should start. * @param length The number of bytes to copy. * @throws NullPointerException if the destination buffer is null. * @throws IndexOutOfBoundsException if the source or destination positions, or the length, are negative, * or if the resulting end positions reaches beyond the end of either this buffer, or the destination array. * @throws java.nio.ReadOnlyBufferException if the destination buffer is read-only. * @throws BufferClosedException if this buffer is closed. */ void copyInto(int srcPos, ByteBuffer dest, int destPos, int length); /** * Copies the given length of data from this buffer into the given destination buffer, beginning at the given * source position in this buffer, and the given destination position in the destination buffer. *
* This method does not read or modify the {@linkplain #writerOffset() write offset} or the * {@linkplain #readerOffset() read offset} on this buffer, nor on the destination buffer. *
* The read and write offsets of the destination buffer are also ignored, and do not influence {@code destPos} * or {@code length}. * * @param srcPos The byte offset into this buffer from where the copying should start; the byte at this offset in * this buffer will be copied to the {@code destPos} index in the {@code dest} array. * @param dest The destination buffer. * @param destPos The index into the {@code dest} array from where the copying should start. * @param length The number of bytes to copy. * @throws NullPointerException if the destination buffer is null. * @throws IndexOutOfBoundsException if the source or destination positions, or the length, are negative, * or if the resulting end positions reaches beyond the end of either this buffer, or the destination array. * @throws BufferReadOnlyException if the destination buffer is read-only. * @throws BufferClosedException if this or the destination buffer is closed. */ void copyInto(int srcPos, Buffer dest, int destPos, int length); /** * Writes into this buffer, all the readable bytes from the given buffer. * This updates the {@linkplain #writerOffset() write offset} of this buffer, and the * {@linkplain #readerOffset() reader offset} of the given buffer. * * @param source The buffer to read from. * @return This buffer. * @throws NullPointerException If the source buffer is {@code null}. */ default Buffer writeBytes(Buffer source) { int size = source.readableBytes(); int woff = writerOffset(); source.copyInto(source.readerOffset(), this, woff, size); source.readerOffset(source.readerOffset() + size); writerOffset(woff + size); return this; } /** * Writes into this buffer, all the bytes from the given byte array. * This updates the {@linkplain #writerOffset() write offset} of this buffer by the length of the array. * * @param source The byte array to read from. * @return This buffer. */ default Buffer writeBytes(byte[] source) { return writeBytes(source, 0, source.length); } /** * Writes into this buffer, all the bytes from the given byte array. * This updates the {@linkplain #writerOffset() write offset} of this buffer by the length of the array. * * @param source The byte array to read from. * @param srcPos Position in the {@code source} from where bytes should be written to this buffer. * @param length The number of bytes to copy. * @return This buffer. */ default Buffer writeBytes(byte[] source, int srcPos, int length) { int woff = writerOffset(); writerOffset(woff + length); for (int i = 0; i < length; i++) { setByte(woff + i, source[srcPos + i]); } return this; } /** * Resets the {@linkplain #readerOffset() read offset} and the {@linkplain #writerOffset() write offset} on this * buffer to zero, and return this buffer. * * @return This buffer instance. */ default Buffer resetOffsets() { readerOffset(0); writerOffset(0); return this; } /** * Opens a cursor to iterate the readable bytes of this buffer. The {@linkplain #readerOffset() reader offset} and * {@linkplain #writerOffset() writer offset} are not modified by the cursor. *
* Care should be taken to ensure that the buffer's lifetime extends beyond the cursor and the iteration, and that * the {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified * while the iteration takes place. Otherwise, unpredictable behaviour might result. * * @return A {@link ByteCursor} for iterating the readable bytes of this buffer. */ ByteCursor openCursor(); /** * Opens a cursor to iterate the given number bytes of this buffer, starting at the given offset. * The {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified by * the cursor. *
* Care should be taken to ensure that the buffer's lifetime extends beyond the cursor and the iteration, and that * the {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified * while the iteration takes place. Otherwise, unpredictable behaviour might result. * * @param fromOffset The offset into the buffer where iteration should start. * The first byte read from the iterator will be the byte at this offset. * @param length The number of bytes to iterate. * @return A {@link ByteCursor} for the given stretch of bytes of this buffer. * @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and * the {@code length} reaches outside the bounds of this buffer. */ ByteCursor openCursor(int fromOffset, int length); /** * Opens a cursor to iterate the readable bytes of this buffer, in reverse. * The {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified by * the cursor. *
* Care should be taken to ensure that the buffer's lifetime extends beyond the cursor and the iteration, and that * the {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified * while the iteration takes place. Otherwise, unpredictable behaviour might result. * * @return A {@link ByteCursor} for the readable bytes of this buffer. */ default ByteCursor openReverseCursor() { int woff = writerOffset(); return openReverseCursor(woff == 0? 0 : woff - 1, readableBytes()); } /** * Opens a cursor to iterate the given number bytes of this buffer, in reverse, starting at the given offset. * The {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified by * the cursor. *
* Care should be taken to ensure that the buffer's lifetime extends beyond the cursor and the iteration, and that * the {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified * while the iteration takes place. Otherwise, unpredictable behaviour might result. * * @param fromOffset The offset into the buffer where iteration should start. * The first byte read from the iterator will be the byte at this offset. * @param length The number of bytes to iterate. * @return A {@link ByteCursor} for the given stretch of bytes of this buffer. * @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and * the {@code length} reaches outside the bounds of this buffer. */ ByteCursor openReverseCursor(int fromOffset, int length); /** * Ensures that this buffer has at least the given number of bytes of * {@linkplain #writableBytes() available space for writing}. * If this buffer already has the necessary space, then this method returns immediately. * If this buffer does not already have the necessary space, then it will be expanded using the * {@link BufferAllocator} the buffer was created with. * This method is the same as calling {@link #ensureWritable(int, int, boolean)} where {@code allowCompaction} is * {@code false}. * * @param size The requested number of bytes of space that should be available for writing. * @return This buffer instance. * @throws IllegalStateException if this buffer is in a bad state. * @throws BufferClosedException if this buffer is closed. * @throws BufferReadOnlyException if this buffer is {@linkplain #readOnly() read-only}. */ default Buffer ensureWritable(int size) { ensureWritable(size, 1, true); return this; } /** * Ensures that this buffer has at least the given number of bytes of * {@linkplain #writableBytes() available space for writing}. * If this buffer already has the necessary space, then this method returns immediately. * If this buffer does not already have the necessary space, then space will be made available in one or all of * the following available ways: * *
* The copy is created with a {@linkplain #writerOffset() write offset} equal to the length of the copied data, * so that the entire contents of the copy is ready to be read. *
* The returned buffer will not be read-only, regardless of the {@linkplain #readOnly() read-only state} of this * buffer. * * @return A new buffer instance, with independent {@link #readerOffset()} and {@link #writerOffset()}, * that contains a copy of the readable region of this buffer. * @throws BufferClosedException if this buffer is closed. */ default Buffer copy() { int offset = readerOffset(); int length = readableBytes(); return copy(offset, length); } /** * Returns a copy of the given region of this buffer. * Modifying the content of the returned buffer will not affect this buffers contents. * The two buffers will maintain separate offsets. * This method does not modify {@link #readerOffset()} or {@link #writerOffset()} of this buffer. *
* The copy is created with a {@linkplain #writerOffset() write offset} equal to the length of the copy, * so that the entire contents of the copy is ready to be read. *
* The returned buffer will not be read-only, regardless of the {@linkplain #readOnly() read-only state} of this * buffer. * * @param offset The offset where copying should start from. This is the offset of the first byte copied. * @param length The number of bytes to copy, and the capacity of the returned buffer. * @return A new buffer instance, with independent {@link #readerOffset()} and {@link #writerOffset()}, * that contains a copy of the given region of this buffer. * @throws IllegalArgumentException if the {@code offset} or {@code length} reaches outside the bounds of the * buffer. * @throws BufferClosedException if this buffer is closed. */ Buffer copy(int offset, int length); /** * Splits the buffer into two, at the {@linkplain #writerOffset() write offset} position. *
* The region of this buffer that contain the previously read and readable bytes, will be captured and returned in * a new buffer, that will hold its own ownership of that region. This allows the returned buffer to be * independently {@linkplain #send() sent} to other threads. *
* The returned buffer will adopt the {@link #readerOffset()} of this buffer, and have its {@link #writerOffset()} * and {@link #capacity()} both set to the equal to the write-offset of this buffer. *
* The memory region in the returned buffer will become inaccessible through this buffer. This buffer will have its * capacity reduced by the capacity of the returned buffer, and the read and write offsets of this buffer will both * become zero, even though their position in memory remain unchanged. *
* Effectively, the following transformation takes place: *
{@code * This buffer: * +------------------------------------------+ * 0| |r/o |w/o |cap * +---+---------------------+----------------+ * / / / \ \ * / / / \ \ * / / / \ \ * / / / \ \ * / / / \ \ * +---+---------------------+ +---------------+ * | |r/o |w/o & cap |r/o & w/o |cap * +---+---------------------+ +---------------+ * Returned buffer. This buffer. * }* When the buffers are in this state, both of the split parts retain an atomic reference count on the * underlying memory. This means that shared underlying memory will not be deallocated or returned to a pool, until * all the split parts have been closed. *
* Composite buffers have it a little easier, in that at most only one of the constituent buffers will actually be * split. If the split point lands perfectly between two constituent buffers, then a composite buffer can * simply split its internal array in two. *
* Split buffers support all operations that normal buffers do, including {@link #ensureWritable(int)}. *
* See the Splitting buffers section for details. * * @return A new buffer with independent and exclusive ownership over the previously read and readable bytes from * this buffer. */ default Buffer split() { return split(writerOffset()); } /** * Splits the buffer into two, at the given {@code splitOffset}. *
* The region of this buffer that precede the {@code splitOffset}, will be captured and returned in a new * buffer, that will hold its own ownership of that region. This allows the returned buffer to be independently * {@linkplain #send() sent} to other threads. *
* The returned buffer will adopt the {@link #readerOffset()} and {@link #writerOffset()} of this buffer, * but truncated to fit within the capacity dictated by the {@code splitOffset}. *
* The memory region in the returned buffer will become inaccessible through this buffer. If the * {@link #readerOffset()} or {@link #writerOffset()} of this buffer lie prior to the {@code splitOffset}, * then those offsets will be moved forward, so they land on offset 0 after the split. *
* Effectively, the following transformation takes place: *
{@code * This buffer: * +--------------------------------+ * 0| |splitOffset |cap * +---------------+----------------+ * / / \ \ * / / \ \ * / / \ \ * / / \ \ * / / \ \ * +---------------+ +---------------+ * | |cap | |cap * +---------------+ +---------------+ * Returned buffer. This buffer. * }* When the buffers are in this state, both of the split parts retain an atomic reference count on the * underlying memory. This means that shared underlying memory will not be deallocated or returned to a pool, until * all the split parts have been closed. *
* Composite buffers have it a little easier, in that at most only one of the constituent buffers will actually be * split. If the split point lands perfectly between two constituent buffers, then a composite buffer can * simply split its internal array in two. *
* Split buffers support all operations that normal buffers do, including {@link #ensureWritable(int)}. *
* See the Splitting buffers section for details. * * @param splitOffset The offset into this buffer where it should be split. After the split, the data at this offset * will be at offset zero in this buffer. * @return A new buffer with independent and exclusive ownership over the bytes from the beginning to the given * offset of this buffer. */ Buffer split(int splitOffset); /** * Discards the read bytes, and moves the buffer contents to the beginning of the buffer. * * @return This buffer instance. * @throws BufferReadOnlyException if this buffer is {@linkplain #readOnly() read-only}. * @throws IllegalStateException if this buffer is in a bad state. */ Buffer compact(); /** * Get the number of "components" in this buffer. For composite buffers, this is the number of transitive * constituent buffers, while non-composite buffers only have one component. * * @return The number of components in this buffer. */ int countComponents(); /** * Get the number of "components" in this buffer, that are readable. These are the components that would be * processed by {@link #forEachReadable(int, ReadableComponentProcessor)}. For composite buffers, this is the * number of transitive constituent buffers that are readable, while non-composite buffers only have at most one * readable component. *
* The number of readable components may be less than the {@link #countComponents() component count}, if not all of * them have readable data. * * @return The number of readable components in this buffer. */ int countReadableComponents(); /** * Get the number of "components" in this buffer, that are writable. These are the components that would be * processed by {@link #forEachWritable(int, WritableComponentProcessor)}. For composite buffers, this is the * number of transitive constituent buffers that are writable, while non-composite buffers only have at most one * writable component. *
* The number of writable components may be less than the {@link #countComponents() component count}, if not all of * them have space for writing. * * @return The number of writable components in this buffer. */ int countWritableComponents(); /** * Processes all readable components of this buffer, and return the number of components processed. *
* The given {@linkplain ReadableComponentProcessor processor} is called for each readable component in this buffer, * and passed a component index, for the given component in the iteration, and a {@link ReadableComponent} object * for accessing the data within the given component. *
* The component index is specific to the particular invocation of this method. The first call to the consumer will * be passed the given initial index, and the next call will be passed the initial index plus one, and so on. *
* The {@linkplain ReadableComponentProcessor component processor} may stop the iteration at any time by returning * {@code false}. * This will cause the number of components processed to be returned as a negative number (to signal early return), * and the number of components processed may then be less than the * {@linkplain #countReadableComponents() readable component count}. *
* Note that the {@link ReadableComponent} instance passed to the consumer could be reused for * multiple calls, so the data must be extracted from the component in the context of the iteration. *
* The {@link ByteBuffer} instances obtained from the component, share lifetime with that internal component. * This means they can be accessed as long as the internal memory store remain unchanged. Methods that may cause * such changes are {@link #split(int)}, {@link #split()}, {@link #compact()}, {@link #ensureWritable(int)}, * {@link #ensureWritable(int, int, boolean)}, and {@link #send()}. *
* The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration. *
* Note that the arrays, memory addresses, and byte buffers exposed as components by this method, * should not be used for changing the buffer contents. Doing so may cause undefined behaviour. *
* Changes to position and limit of the byte buffers exposed via the processed components, are not reflected back to
* this buffer instance.
*
* @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to
* the {@linkplain ReadableComponentProcessor#process(int, ReadableComponent) processor}.
* @param processor The processor that will be used to process the buffer components.
* @return The number of readable components processed, as a positive number if all readable components were
* processed, or as a negative number if the iteration was stopped because
* {@link ReadableComponentProcessor#process(int, ReadableComponent)} returned {@code false}.
* In any case, the number of components processed may be less than {@link #countComponents()}.
*/
* The given {@linkplain WritableComponentProcessor processor} is called for each writable component in this buffer,
* and passed a component index, for the given component in the iteration, and a {@link WritableComponent} object
* for accessing the data within the given component.
*
* The component index is specific to the particular invocation of this method. The first call to the consumer will
* be passed the given initial index, and the next call will be passed the initial index plus one, and so on.
*
* The {@link WritableComponentProcessor component processor} may stop the iteration at any time by returning
* {@code false}.
* This will cause the number of components processed to be returned as a negative number (to signal early return),
* and the number of components processed may then be less than the
* {@linkplain #countReadableComponents() readable component count}.
*
* Note that the {@link WritableComponent} instance passed to the consumer could be reused for
* multiple calls, so the data must be extracted from the component in the context of the iteration.
*
* The {@link ByteBuffer} instances obtained from the component, share lifetime with that internal component.
* This means they can be accessed as long as the internal memory store remain unchanged. Methods that may cause
* such changes are {@link #split(int)}, {@link #split()}, {@link #compact()}, {@link #ensureWritable(int)},
* {@link #ensureWritable(int, int, boolean)}, and {@link #send()}.
*
* The best way to ensure this doesn't cause any trouble, is to use the buffers directly as part of the iteration.
*
* Changes to position and limit of the byte buffers exposed via the processed components, are not reflected back to
* this buffer instance.
*
* @param initialIndex The initial index of the iteration, and the index that will be passed to the first call to
* the {@linkplain WritableComponentProcessor#process(int, WritableComponent) processor}.
* @param processor The processor that will be used to process the buffer components.
* @return The number of writable components processed, as a positive number if all writable components were
* processed, or as a negative number if the iteration was stopped because
* {@link WritableComponentProcessor#process(int, WritableComponent)} returned {@code false}.
* In any case, the number of components processed may be less than {@link #countComponents()}.
*/