1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package org.apache.hc.core5.http.impl.nio; 29 30 import java.nio.BufferOverflowException; 31 import java.nio.ByteBuffer; 32 33 import org.apache.hc.core5.annotation.Internal; 34 35 /** 36 * A buffer that expand its capacity on demand. Internally, this class is backed 37 * by an instance of {@link ByteBuffer}. 38 * <p> 39 * This class is not thread safe. 40 * </p> 41 * @since 4.0 42 */ 43 @Internal 44 public class ExpandableBuffer { 45 46 public enum Mode { 47 INPUT, OUTPUT 48 } 49 50 private Mode mode; 51 private ByteBuffer buffer; 52 53 /** 54 * Allocates buffer of the given size using the given allocator. 55 * <p> 56 * Sets the mode to input. 57 * </p> 58 * 59 * @param bufferSize the buffer size. 60 */ 61 protected ExpandableBuffer(final int bufferSize) { 62 super(); 63 this.buffer = ByteBuffer.allocate(bufferSize); 64 this.mode = Mode.INPUT; 65 } 66 67 /** 68 * Returns the current mode: 69 * <p> 70 * {@link Mode#INPUT}: the buffer is in the input mode. 71 * <p> 72 * {@link Mode#OUTPUT}: the buffer is in the output mode. 73 * 74 * @return current input/output mode. 75 */ 76 protected Mode mode() { 77 return this.mode; 78 } 79 80 protected ByteBuffer buffer() { 81 return this.buffer; 82 } 83 84 /** 85 * Sets the mode to output. The buffer can now be read from. 86 */ 87 protected void setOutputMode() { 88 if (this.mode != Mode.OUTPUT) { 89 this.buffer.flip(); 90 this.mode = Mode.OUTPUT; 91 } 92 } 93 94 /** 95 * Sets the mode to input. The buffer can now be written into. 96 */ 97 protected void setInputMode() { 98 if (this.mode != Mode.INPUT) { 99 if (this.buffer.hasRemaining()) { 100 this.buffer.compact(); 101 } else { 102 this.buffer.clear(); 103 } 104 this.mode = Mode.INPUT; 105 } 106 } 107 108 private void expandCapacity(final int capacity) { 109 final ByteBuffer oldBuffer = this.buffer; 110 this.buffer = ByteBuffer.allocate(capacity); 111 oldBuffer.flip(); 112 this.buffer.put(oldBuffer); 113 } 114 115 /** 116 * Expands buffer's capacity. 117 * 118 * @throws BufferOverflowException in case we get over the maximum allowed value 119 */ 120 protected void expand() throws BufferOverflowException { 121 int newcapacity = (this.buffer.capacity() + 1) << 1; 122 if (newcapacity < 0) { 123 final int vmBytes = Long.SIZE >> 3; 124 final int javaBytes = 8; // this is to be checked when the JVM version changes 125 @SuppressWarnings("unused") // we really need the 8 if we're going to make this foolproof 126 final int headRoom = (vmBytes >= javaBytes) ? vmBytes : javaBytes; 127 // Reason: In GC the size of objects is passed as int (2 bytes). 128 // Then, the header size of the objects is added to the size. 129 // Long has the longest header available. Object header seems to be linked to it. 130 // Details: I added a minimum of 8 just to be safe and because 8 is used in 131 // java.lang.Object.ArrayList: private static final int MAX_ARRAY_SIZE = 2147483639. 132 // 133 // WARNING: This code assumes you are providing enough heap room with -Xmx. 134 // source of inspiration: https://bugs.openjdk.java.net/browse/JDK-8059914 135 newcapacity = Integer.MAX_VALUE - headRoom; 136 137 if (newcapacity <= this.buffer.capacity()) { 138 throw new BufferOverflowException(); 139 } 140 } 141 expandCapacity(newcapacity); 142 } 143 144 /** 145 * Ensures the buffer can accommodate the exact required capacity. 146 * 147 * @param requiredCapacity the required capacity. 148 */ 149 protected void ensureCapacity(final int requiredCapacity) { 150 if (requiredCapacity > this.buffer.capacity()) { 151 expandCapacity(requiredCapacity); 152 } 153 } 154 155 /** 156 * Ensures the buffer can accommodate at least the required capacity adjusted to multiples of 1024. 157 * 158 * @param requiredCapacity the required capacity. 159 */ 160 protected void ensureAdjustedCapacity(final int requiredCapacity) { 161 if (requiredCapacity > this.buffer.capacity()) { 162 final int adjustedCapacity = ((requiredCapacity >> 10) + 1) << 10; 163 expandCapacity(adjustedCapacity); 164 } 165 } 166 167 /** 168 * Determines if the buffer contains data. 169 * <p> 170 * Sets the mode to output. 171 * </p> 172 * 173 * @return {@code true} if there is data in the buffer, 174 * {@code false} otherwise. 175 */ 176 protected boolean hasData() { 177 setOutputMode(); 178 return this.buffer.hasRemaining(); 179 } 180 181 /** 182 * Returns the length of this buffer. 183 * <p> 184 * Sets the mode to output. 185 * </p> 186 * 187 * @return buffer length. 188 */ 189 protected int length() { 190 setOutputMode(); 191 return this.buffer.remaining(); 192 } 193 194 /** 195 * Returns available capacity of this buffer. 196 * 197 * @return buffer length. 198 */ 199 protected int capacity() { 200 setInputMode(); 201 return this.buffer.remaining(); 202 } 203 204 /** 205 * Clears buffer. 206 * <p> 207 * Sets the mode to input. 208 * </p> 209 */ 210 protected void clear() { 211 this.buffer.clear(); 212 this.mode = Mode.INPUT; 213 } 214 215 @Override 216 public String toString() { 217 final StringBuilder sb = new StringBuilder(); 218 sb.append("[mode="); 219 sb.append(this.mode); 220 sb.append(" pos="); 221 sb.append(this.buffer.position()); 222 sb.append(" lim="); 223 sb.append(this.buffer.limit()); 224 sb.append(" cap="); 225 sb.append(this.buffer.capacity()); 226 sb.append("]"); 227 return sb.toString(); 228 } 229 230 }