org.springframework.core.io.buffer.DataBuffer Maven / Gradle / Ivy
/*
* Copyright 2002-2019 the original author or authors.
*
* 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
*
* 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 org.springframework.core.io.buffer;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.function.IntPredicate;
import org.springframework.util.Assert;
/**
* Basic abstraction over byte buffers.
*
* {@code DataBuffer}s has a separate {@linkplain #readPosition() read} and
* {@linkplain #writePosition() write} position, as opposed to {@code ByteBuffer}'s
* single {@linkplain ByteBuffer#position() position}. As such, the {@code DataBuffer}
* does not require a {@linkplain ByteBuffer#flip() flip} to read after writing. In general,
* the following invariant holds for the read and write positions, and the capacity:
*
*
* 0 <=
* readPosition <=
* writePosition <=
* capacity
*
*
* The {@linkplain #capacity() capacity} of a {@code DataBuffer} is expanded on demand,
* similar to {@code StringBuilder}.
*
*
The main purpose of the {@code DataBuffer} abstraction is to provide a convenient wrapper
* around {@link ByteBuffer} which is similar to Netty's {@link io.netty.buffer.ByteBuf} but
* can also be used on non-Netty platforms (i.e. Servlet containers).
*
* @author Arjen Poutsma
* @author Brian Clozel
* @since 5.0
* @see DataBufferFactory
*/
public interface DataBuffer {
/**
* Return the {@link DataBufferFactory} that created this buffer.
* @return the creating buffer factory
*/
DataBufferFactory factory();
/**
* Return the index of the first byte in this buffer that matches
* the given predicate.
* @param predicate the predicate to match
* @param fromIndex the index to start the search from
* @return the index of the first byte that matches {@code predicate};
* or {@code -1} if none match
*/
int indexOf(IntPredicate predicate, int fromIndex);
/**
* Return the index of the last byte in this buffer that matches
* the given predicate.
* @param predicate the predicate to match
* @param fromIndex the index to start the search from
* @return the index of the last byte that matches {@code predicate};
* or {@code -1} if none match
*/
int lastIndexOf(IntPredicate predicate, int fromIndex);
/**
* Return the number of bytes that can be read from this data buffer.
* @return the readable byte count
*/
int readableByteCount();
/**
* Return the number of bytes that can be written to this data buffer.
* @return the writable byte count
* @since 5.0.1
*/
int writableByteCount();
/**
* Return the number of bytes that this buffer can contain.
* @return the capacity
* @since 5.0.1
*/
int capacity();
/**
* Set the number of bytes that this buffer can contain.
*
If the new capacity is lower than the current capacity, the contents
* of this buffer will be truncated. If the new capacity is higher than
* the current capacity, it will be expanded.
* @param capacity the new capacity
* @return this buffer
*/
DataBuffer capacity(int capacity);
/**
* Ensure that the current buffer has enough {@link #writableByteCount()}
* to write the amount of data given as an argument. If not, the missing
* capacity will be added to the buffer.
* @param capacity the writable capacity to check for
* @return this buffer
* @since 5.1.4
*/
default DataBuffer ensureCapacity(int capacity) {
return this;
}
/**
* Return the position from which this buffer will read.
* @return the read position
* @since 5.0.1
*/
int readPosition();
/**
* Set the position from which this buffer will read.
* @param readPosition the new read position
* @return this buffer
* @throws IndexOutOfBoundsException if {@code readPosition} is smaller than 0
* or greater than {@link #writePosition()}
* @since 5.0.1
*/
DataBuffer readPosition(int readPosition);
/**
* Return the position to which this buffer will write.
* @return the write position
* @since 5.0.1
*/
int writePosition();
/**
* Set the position to which this buffer will write.
* @param writePosition the new write position
* @return this buffer
* @throws IndexOutOfBoundsException if {@code writePosition} is smaller than
* {@link #readPosition()} or greater than {@link #capacity()}
* @since 5.0.1
*/
DataBuffer writePosition(int writePosition);
/**
* Read a single byte at the given index from this data buffer.
* @param index the index at which the byte will be read
* @return the byte at the given index
* @throws IndexOutOfBoundsException when {@code index} is out of bounds
* @since 5.0.4
*/
byte getByte(int index);
/**
* Read a single byte from the current reading position from this data buffer.
* @return the byte at this buffer's current reading position
*/
byte read();
/**
* Read this buffer's data into the specified destination, starting at the current
* reading position of this buffer.
* @param destination the array into which the bytes are to be written
* @return this buffer
*/
DataBuffer read(byte[] destination);
/**
* Read at most {@code length} bytes of this buffer into the specified destination,
* starting at the current reading position of this buffer.
* @param destination the array into which the bytes are to be written
* @param offset the index within {@code destination} of the first byte to be written
* @param length the maximum number of bytes to be written in {@code destination}
* @return this buffer
*/
DataBuffer read(byte[] destination, int offset, int length);
/**
* Write a single byte into this buffer at the current writing position.
* @param b the byte to be written
* @return this buffer
*/
DataBuffer write(byte b);
/**
* Write the given source into this buffer, starting at the current writing position
* of this buffer.
* @param source the bytes to be written into this buffer
* @return this buffer
*/
DataBuffer write(byte[] source);
/**
* Write at most {@code length} bytes of the given source into this buffer, starting
* at the current writing position of this buffer.
* @param source the bytes to be written into this buffer
* @param offset the index within {@code source} to start writing from
* @param length the maximum number of bytes to be written from {@code source}
* @return this buffer
*/
DataBuffer write(byte[] source, int offset, int length);
/**
* Write one or more {@code DataBuffer}s to this buffer, starting at the current
* writing position. It is the responsibility of the caller to
* {@linkplain DataBufferUtils#release(DataBuffer) release} the given data buffers.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
*/
DataBuffer write(DataBuffer... buffers);
/**
* Write one or more {@link ByteBuffer} to this buffer, starting at the current
* writing position.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
*/
DataBuffer write(ByteBuffer... buffers);
/**
* Write the given {@code CharSequence} using the given {@code Charset},
* starting at the current writing position.
* @param charSequence the char sequence to write into this buffer
* @param charset the charset to encode the char sequence with
* @return this buffer
* @since 5.1.4
*/
default DataBuffer write(CharSequence charSequence, Charset charset) {
Assert.notNull(charSequence, "CharSequence must not be null");
Assert.notNull(charset, "Charset must not be null");
if (charSequence.length() != 0) {
CharsetEncoder charsetEncoder = charset.newEncoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
CharBuffer inBuffer = CharBuffer.wrap(charSequence);
int estimatedSize = (int) (inBuffer.remaining() * charsetEncoder.averageBytesPerChar());
ByteBuffer outBuffer = ensureCapacity(estimatedSize)
.asByteBuffer(writePosition(), writableByteCount());
while (true) {
CoderResult cr = (inBuffer.hasRemaining() ?
charsetEncoder.encode(inBuffer, outBuffer, true) : CoderResult.UNDERFLOW);
if (cr.isUnderflow()) {
cr = charsetEncoder.flush(outBuffer);
}
if (cr.isUnderflow()) {
break;
}
if (cr.isOverflow()) {
writePosition(writePosition() + outBuffer.position());
int maximumSize = (int) (inBuffer.remaining() * charsetEncoder.maxBytesPerChar());
ensureCapacity(maximumSize);
outBuffer = asByteBuffer(writePosition(), writableByteCount());
}
}
writePosition(writePosition() + outBuffer.position());
}
return this;
}
/**
* Create a new {@code DataBuffer} whose contents is a shared subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
*
Note that this method will not call
* {@link DataBufferUtils#retain(DataBuffer)} on the resulting slice: the reference
* count will not be increased.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified slice of this data buffer
*/
DataBuffer slice(int index, int length);
/**
* Create a new {@code DataBuffer} whose contents is a shared, retained subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
*
Note that unlike {@link #slice(int, int)}, this method
* will call {@link DataBufferUtils#retain(DataBuffer)} (or equivalent) on the
* resulting slice.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified, retained slice of this data buffer
* @since 5.2
*/
default DataBuffer retainedSlice(int index, int length) {
return DataBufferUtils.retain(slice(index, length));
}
/**
* Expose this buffer's bytes as a {@link ByteBuffer}. Data between this
* {@code DataBuffer} and the returned {@code ByteBuffer} is shared; though
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* will not be reflected in the reading nor writing position of this data buffer.
* @return this data buffer as a byte buffer
*/
ByteBuffer asByteBuffer();
/**
* Expose a subsequence of this buffer's bytes as a {@link ByteBuffer}. Data between
* this {@code DataBuffer} and the returned {@code ByteBuffer} is shared; though
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* will not be reflected in the reading nor writing position of this data buffer.
* @param index the index at which to start the byte buffer
* @param length the length of the returned byte buffer
* @return this data buffer as a byte buffer
* @since 5.0.1
*/
ByteBuffer asByteBuffer(int index, int length);
/**
* Expose this buffer's data as an {@link InputStream}. Both data and read position are
* shared between the returned stream and this data buffer. The underlying buffer will
* not be {@linkplain DataBufferUtils#release(DataBuffer) released}
* when the input stream is {@linkplain InputStream#close() closed}.
* @return this data buffer as an input stream
* @see #asInputStream(boolean)
*/
InputStream asInputStream();
/**
* Expose this buffer's data as an {@link InputStream}. Both data and read position are
* shared between the returned stream and this data buffer.
* @param releaseOnClose whether the underlying buffer will be
* {@linkplain DataBufferUtils#release(DataBuffer) released} when the input stream is
* {@linkplain InputStream#close() closed}.
* @return this data buffer as an input stream
* @since 5.0.4
*/
InputStream asInputStream(boolean releaseOnClose);
/**
* Expose this buffer's data as an {@link OutputStream}. Both data and write position are
* shared between the returned stream and this data buffer.
* @return this data buffer as an output stream
*/
OutputStream asOutputStream();
/**
* Return this buffer's data a String using the specified charset. Default implementation
* delegates to {@code toString(readPosition(), readableByteCount(), charset)}.
* @param charset the character set to use
* @return a string representation of all this buffers data
* @since 5.2
*/
default String toString(Charset charset) {
Assert.notNull(charset, "Charset must not be null");
return toString(readPosition(), readableByteCount(), charset);
}
/**
* Return a part of this buffer's data as a String using the specified charset.
* @param index the index at which to start the string
* @param length the number of bytes to use for the string
* @param charset the charset to use
* @return a string representation of a part of this buffers data
* @since 5.2
*/
String toString(int index, int length, Charset charset);
}