org.eclipse.jetty.io.ByteBufferAggregator Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.io;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Aggregates data into a single ByteBuffer of a specified maximum size.
* The buffer automatically grows as data is written to it, up until it reaches the specified maximum size.
* Once the buffer is full, the aggregator will not aggregate any more bytes until its buffer is taken out,
* after which a new aggregate/take buffer cycle can start.
* The buffers are taken from the supplied {@link ByteBufferPool} or freshly allocated if one is not supplied.
*/
public class ByteBufferAggregator
{
private static final Logger LOG = LoggerFactory.getLogger(ByteBufferAggregator.class);
private final ByteBufferPool _bufferPool;
private final boolean _direct;
private final int _maxSize;
private RetainableByteBuffer _retainableByteBuffer;
private int _aggregatedSize;
private int _currentSize;
/**
* Creates a ByteBuffer aggregator.
* @param bufferPool The {@link ByteBufferPool} from which to acquire the buffers
* @param direct whether to get direct buffers
* @param startSize the starting size of the buffer
* @param maxSize the maximum size of the buffer which must be greater than {@code startSize}
*/
public ByteBufferAggregator(ByteBufferPool bufferPool, boolean direct, int startSize, int maxSize)
{
if (maxSize <= 0)
throw new IllegalArgumentException("maxSize must be > 0, was: " + maxSize);
if (startSize <= 0)
throw new IllegalArgumentException("startSize must be > 0, was: " + startSize);
if (startSize > maxSize)
throw new IllegalArgumentException("maxSize (" + maxSize + ") must be >= startSize (" + startSize + ")");
_bufferPool = (bufferPool == null) ? ByteBufferPool.NON_POOLING : bufferPool;
_direct = direct;
_maxSize = maxSize;
_currentSize = startSize;
}
/**
* Get the currently aggregated length.
* @return The current total aggregated bytes.
*/
public int length()
{
return _aggregatedSize;
}
/**
* Aggregates the given ByteBuffer. This copies bytes up to the specified maximum size, at which
* time this method returns {@code true} and {@link #takeRetainableByteBuffer()} must be called
* for this method to accept aggregating again.
* @param buffer the buffer to copy into this aggregator; its position is updated according to
* the number of aggregated bytes
* @return true if the aggregator's buffer is full and should be taken, false otherwise
*/
public boolean aggregate(ByteBuffer buffer)
{
tryExpandBufferCapacity(buffer.remaining());
if (_retainableByteBuffer == null)
{
_retainableByteBuffer = _bufferPool.acquire(_currentSize, _direct);
BufferUtil.flipToFill(_retainableByteBuffer.getByteBuffer());
}
int copySize = Math.min(_currentSize - _aggregatedSize, buffer.remaining());
ByteBuffer byteBuffer = _retainableByteBuffer.getByteBuffer();
byteBuffer.put(byteBuffer.position(), buffer, buffer.position(), copySize);
byteBuffer.position(byteBuffer.position() + copySize);
buffer.position(buffer.position() + copySize);
_aggregatedSize += copySize;
return _aggregatedSize == _maxSize;
}
private void tryExpandBufferCapacity(int remaining)
{
if (LOG.isDebugEnabled())
LOG.debug("tryExpandBufferCapacity remaining: {} _currentSize: {} _accumulatedSize={}", remaining, _currentSize, _aggregatedSize);
if (_currentSize == _maxSize)
return;
int capacityLeft = _currentSize - _aggregatedSize;
if (remaining <= capacityLeft)
return;
int need = remaining - capacityLeft;
_currentSize = Math.min(_maxSize, TypeUtil.ceilToNextPowerOfTwo(_currentSize + need));
if (_retainableByteBuffer != null)
{
BufferUtil.flipToFlush(_retainableByteBuffer.getByteBuffer(), 0);
RetainableByteBuffer newBuffer = _bufferPool.acquire(_currentSize, _direct);
BufferUtil.flipToFill(newBuffer.getByteBuffer());
newBuffer.getByteBuffer().put(_retainableByteBuffer.getByteBuffer());
_retainableByteBuffer.release();
_retainableByteBuffer = newBuffer;
}
}
/**
* Takes the buffer out of the aggregator. Once the buffer has been taken out,
* the aggregator resets itself and a new buffer will be acquired from the pool
* during the next {@link #aggregate(ByteBuffer)} call.
* @return the aggregated buffer, or null if nothing has been buffered yet
*/
public RetainableByteBuffer takeRetainableByteBuffer()
{
if (_retainableByteBuffer == null)
return null;
BufferUtil.flipToFlush(_retainableByteBuffer.getByteBuffer(), 0);
RetainableByteBuffer result = _retainableByteBuffer;
_retainableByteBuffer = null;
_aggregatedSize = 0;
return result;
}
@Override
public String toString()
{
return "%s@%x{a=%d c=%d m=%d b=%s}".formatted(getClass().getSimpleName(), hashCode(), _aggregatedSize, _currentSize, _maxSize, _retainableByteBuffer);
}
}