blob: 17298c4eb08266e9d808b9d756c77f4518baf000 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.http.impl.nio;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import org.apache.hc.core5.annotation.Internal;
/**
* A buffer that expand its capacity on demand. Internally, this class is backed
* by an instance of {@link ByteBuffer}.
* <p>
* This class is not thread safe.
* </p>
* @since 4.0
*/
@Internal
public class ExpandableBuffer {
public enum Mode {
INPUT, OUTPUT
}
private Mode mode;
private ByteBuffer buffer;
/**
* Allocates buffer of the given size using the given allocator.
* <p>
* Sets the mode to input.
* </p>
*
* @param bufferSize the buffer size.
*/
protected ExpandableBuffer(final int bufferSize) {
super();
this.buffer = ByteBuffer.allocate(bufferSize);
this.mode = Mode.INPUT;
}
/**
* Returns the current mode:
* <p>
* {@link Mode#INPUT}: the buffer is in the input mode.
* <p>
* {@link Mode#OUTPUT}: the buffer is in the output mode.
*
* @return current input/output mode.
*/
protected Mode mode() {
return this.mode;
}
protected ByteBuffer buffer() {
return this.buffer;
}
/**
* Sets the mode to output. The buffer can now be read from.
*/
protected void setOutputMode() {
if (this.mode != Mode.OUTPUT) {
this.buffer.flip();
this.mode = Mode.OUTPUT;
}
}
/**
* Sets the mode to input. The buffer can now be written into.
*/
protected void setInputMode() {
if (this.mode != Mode.INPUT) {
if (this.buffer.hasRemaining()) {
this.buffer.compact();
} else {
this.buffer.clear();
}
this.mode = Mode.INPUT;
}
}
private void expandCapacity(final int capacity) {
final ByteBuffer oldBuffer = this.buffer;
this.buffer = ByteBuffer.allocate(capacity);
oldBuffer.flip();
this.buffer.put(oldBuffer);
}
/**
* Expands buffer's capacity.
*
* @throws BufferOverflowException in case we get over the maximum allowed value
*/
protected void expand() throws BufferOverflowException {
int newcapacity = (this.buffer.capacity() + 1) << 1;
if (newcapacity < 0) {
final int vmBytes = Long.SIZE >> 3;
final int javaBytes = 8; // this is to be checked when the JVM version changes
@SuppressWarnings("unused") // we really need the 8 if we're going to make this foolproof
final int headRoom = (vmBytes >= javaBytes) ? vmBytes : javaBytes;
// Reason: In GC the size of objects is passed as int (2 bytes).
// Then, the header size of the objects is added to the size.
// Long has the longest header available. Object header seems to be linked to it.
// Details: I added a minimum of 8 just to be safe and because 8 is used in
// java.lang.Object.ArrayList: private static final int MAX_ARRAY_SIZE = 2147483639.
//
// WARNING: This code assumes you are providing enough heap room with -Xmx.
// source of inspiration: https://bugs.openjdk.java.net/browse/JDK-8059914
newcapacity = Integer.MAX_VALUE - headRoom;
if (newcapacity <= this.buffer.capacity()) {
throw new BufferOverflowException();
}
}
expandCapacity(newcapacity);
}
/**
* Ensures the buffer can accommodate the exact required capacity.
*
* @param requiredCapacity the required capacity.
*/
protected void ensureCapacity(final int requiredCapacity) {
if (requiredCapacity > this.buffer.capacity()) {
expandCapacity(requiredCapacity);
}
}
/**
* Ensures the buffer can accommodate at least the required capacity adjusted to multiples of 1024.
*
* @param requiredCapacity the required capacity.
*/
protected void ensureAdjustedCapacity(final int requiredCapacity) {
if (requiredCapacity > this.buffer.capacity()) {
final int adjustedCapacity = ((requiredCapacity >> 10) + 1) << 10;
expandCapacity(adjustedCapacity);
}
}
/**
* Determines if the buffer contains data.
* <p>
* Sets the mode to output.
* </p>
*
* @return {@code true} if there is data in the buffer,
* {@code false} otherwise.
*/
protected boolean hasData() {
setOutputMode();
return this.buffer.hasRemaining();
}
/**
* Returns the length of this buffer.
* <p>
* Sets the mode to output.
* </p>
*
* @return buffer length.
*/
protected int length() {
setOutputMode();
return this.buffer.remaining();
}
/**
* Returns available capacity of this buffer.
*
* @return buffer length.
*/
protected int capacity() {
setInputMode();
return this.buffer.remaining();
}
/**
* Clears buffer.
* <p>
* Sets the mode to input.
* </p>
*/
protected void clear() {
this.buffer.clear();
this.mode = Mode.INPUT;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("[mode=");
sb.append(this.mode);
sb.append(" pos=");
sb.append(this.buffer.position());
sb.append(" lim=");
sb.append(this.buffer.limit());
sb.append(" cap=");
sb.append(this.buffer.capacity());
sb.append("]");
return sb.toString();
}
}