/*
* Copyright 2002-2016 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
*
* 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 org.springframework.core.io.buffer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.Function;
import java.util.function.IntPredicate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Default implementation of the {@link DataBuffer} interface that uses a {@link
* ByteBuffer} internally, with separate read and write positions. Constructed
* using the {@link DefaultDataBufferFactory}.
*
* @author Arjen Poutsma
* @since 5.0
* @see DefaultDataBufferFactory
*/
public class DefaultDataBuffer implements DataBuffer {
private final DefaultDataBufferFactory dataBufferFactory;
private ByteBuffer byteBuffer;
private int readPosition;
private int writePosition;
/**
* Create a new {@code DefaultDataBuffer} based on the given
* {@code ByteBuffer}. Both reading and writing position of this buffer are
* based on the current {@linkplain
* ByteBuffer#position() position} of the given buffer.
* @param byteBuffer the buffer to base this buffer on
*/
DefaultDataBuffer(ByteBuffer byteBuffer, DefaultDataBufferFactory dataBufferFactory) {
this(byteBuffer, byteBuffer.position(), byteBuffer.position(), dataBufferFactory);
}
DefaultDataBuffer(ByteBuffer byteBuffer, int readPosition, int writePosition,
DefaultDataBufferFactory dataBufferFactory) {
Assert.notNull(byteBuffer, "'byteBuffer' must not be null");
Assert.isTrue(readPosition >= 0, "'readPosition' must be 0 or higher");
Assert.isTrue(writePosition >= 0, "'writePosition' must be 0 or higher");
Assert.isTrue(readPosition <= writePosition,
"'readPosition' must be smaller than or equal to 'writePosition'");
Assert.notNull(dataBufferFactory, "'dataBufferFactory' must not be null");
this.byteBuffer = byteBuffer;
this.readPosition = readPosition;
this.writePosition = writePosition;
this.dataBufferFactory = dataBufferFactory;
}
/**
* Directly exposes the native {@code ByteBuffer} that this buffer is based on.
* @return the wrapped byte buffer
*/
public ByteBuffer getNativeBuffer() {
return this.byteBuffer;
}
@Override
public DefaultDataBufferFactory factory() {
return this.dataBufferFactory;
}
@Override
public int indexOf(IntPredicate predicate, int fromIndex) {
Assert.notNull(predicate, "'predicate' must not be null");
if (fromIndex < 0) {
fromIndex = 0;
}
else if (fromIndex >= this.writePosition) {
return -1;
}
for (int i = fromIndex; i < this.writePosition; i++) {
byte b = this.byteBuffer.get(i);
if (predicate.test(b)) {
return i;
}
}
return -1;
}
@Override
public int lastIndexOf(IntPredicate predicate, int fromIndex) {
Assert.notNull(predicate, "'predicate' must not be null");
int i = Math.min(fromIndex, this.writePosition - 1);
for (; i >= 0; i--) {
byte b = this.byteBuffer.get(i);
if (predicate.test(b)) {
return i;
}
}
return -1;
}
@Override
public int readableByteCount() {
return this.writePosition - this.readPosition;
}
@Override
public byte read() {
return readInternal(ByteBuffer::get);
}
@Override
public DefaultDataBuffer read(byte[] destination) {
Assert.notNull(destination, "'destination' must not be null");
readInternal(b -> b.get(destination));
return this;
}
@Override
public DefaultDataBuffer read(byte[] destination, int offset, int length) {
Assert.notNull(destination, "'destination' must not be null");
readInternal(b -> b.get(destination, offset, length));
return this;
}
/**
* Internal read method that keeps track of the {@link #readPosition} before and after
* applying the given function on {@link #byteBuffer}.
*/
private <T> T readInternal(Function<ByteBuffer, T> function) {
// Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer
((Buffer) this.byteBuffer).position(this.readPosition);
try {
return function.apply(this.byteBuffer);
}
finally {
this.readPosition = this.byteBuffer.position();
}
}
@Override
public DefaultDataBuffer write(byte b) {
ensureExtraCapacity(1);
writeInternal(buffer -> buffer.put(b));
return this;
}
@Override
public DefaultDataBuffer write(byte[] source) {
Assert.notNull(source, "'source' must not be null");
ensureExtraCapacity(source.length);
writeInternal(buffer -> buffer.put(source));
return this;
}
@Override
public DefaultDataBuffer write(byte[] source, int offset, int length) {
Assert.notNull(source, "'source' must not be null");
ensureExtraCapacity(length);
writeInternal(buffer -> buffer.put(source, offset, length));
return this;
}
@Override
public DataBuffer write(DataBuffer... buffers) {
if (!ObjectUtils.isEmpty(buffers)) {
ByteBuffer[] byteBuffers =
Arrays.stream(buffers).map(DataBuffer::asByteBuffer)
.toArray(ByteBuffer[]::new);
write(byteBuffers);
}
return this;
}
@Override
public DefaultDataBuffer write(ByteBuffer... byteBuffers) {
Assert.notEmpty(byteBuffers, "'byteBuffers' must not be empty");
int extraCapacity = Arrays.stream(byteBuffers).mapToInt(ByteBuffer::remaining).sum();
ensureExtraCapacity(extraCapacity);
Arrays.stream(byteBuffers)
.forEach(byteBuffer -> writeInternal(buffer -> buffer.put(byteBuffer)));
return this;
}
/**
* Internal write method that keeps track of the {@link #writePosition} before and
* after applying the given function on {@link #byteBuffer}.
*/
private <T> T writeInternal(Function<ByteBuffer, T> function) {
// Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer
((Buffer) this.byteBuffer).position(this.writePosition);
try {
return function.apply(this.byteBuffer);
}
finally {
this.writePosition = this.byteBuffer.position();
}
}
@Override
public DataBuffer slice(int index, int length) {
int oldPosition = this.byteBuffer.position();
// Explicit access via Buffer base type for compatibility
// with covariant return type on JDK 9's ByteBuffer...
Buffer buffer = this.byteBuffer;
try {
buffer.position(index);
ByteBuffer slice = this.byteBuffer.slice();
// Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer
((Buffer) slice).limit(length);
return new SlicedDefaultDataBuffer(slice, 0, length, this.dataBufferFactory);
}
finally {
buffer.position(oldPosition);
}
}
@Override
public ByteBuffer asByteBuffer() {
ByteBuffer duplicate = this.byteBuffer.duplicate();
// Explicit access via Buffer base type for compatibility
// with covariant return type on JDK 9's ByteBuffer...
Buffer buffer = duplicate;
buffer.position(this.readPosition);
buffer.limit(this.writePosition);
return duplicate;
}
@Override
public InputStream asInputStream() {
return new DefaultDataBufferInputStream();
}
@Override
public OutputStream asOutputStream() {
return new DefaultDataBufferOutputStream();
}
private void ensureExtraCapacity(int extraCapacity) {
int neededCapacity = this.writePosition + extraCapacity;
if (neededCapacity > this.byteBuffer.capacity()) {
grow(neededCapacity);
}
}
void grow(int minCapacity) {
ByteBuffer oldBuffer = this.byteBuffer;
ByteBuffer newBuffer =
(oldBuffer.isDirect() ? ByteBuffer.allocateDirect(minCapacity) :
ByteBuffer.allocate(minCapacity));
// Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer
final int remaining = readableByteCount();
((Buffer) oldBuffer).position(this.readPosition).limit(this.writePosition);
newBuffer.put(oldBuffer);
this.byteBuffer = newBuffer;
this.readPosition = 0;
this.writePosition = remaining;
oldBuffer.clear();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DefaultDataBuffer)) {
return false;
}
DefaultDataBuffer other = (DefaultDataBuffer) obj;
return (this.readPosition == other.readPosition &&
this.writePosition == other.writePosition &&
this.byteBuffer.equals(other.byteBuffer));
}
@Override
public int hashCode() {
return this.byteBuffer.hashCode();
}
@Override
public String toString() {
return this.byteBuffer.toString();
}
private class DefaultDataBufferInputStream extends InputStream {
@Override
public int available() throws IOException {
return readableByteCount();
}
@Override
public int read() {
return readInternal(
buffer -> readableByteCount() > 0 ? buffer.get() & 0xFF : -1);
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException {
return readInternal(buffer -> {
int count = readableByteCount();
if (count > 0) {
int minLen = Math.min(len, count);
buffer.get(bytes, off, minLen);
return minLen;
}
else {
return -1;
}
});
}
}
private class DefaultDataBufferOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
ensureExtraCapacity(1);
writeInternal(buffer -> buffer.put((byte) b));
}
@Override
public void write(byte[] bytes, int off, int len) throws IOException {
ensureExtraCapacity(len);
writeInternal(buffer -> buffer.put(bytes, off, len));
}
}
private static class SlicedDefaultDataBuffer extends DefaultDataBuffer {
SlicedDefaultDataBuffer(ByteBuffer byteBuffer, int readPosition,
int writePosition, DefaultDataBufferFactory dataBufferFactory) {
super(byteBuffer, readPosition, writePosition, dataBufferFactory);
}
@Override
void grow(int minCapacity) {
throw new UnsupportedOperationException(
"Growing the capacity of a sliced buffer is not supported");
}
}
}