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 /** 47 * A buffer's mode. 48 */ 49 public enum Mode { 50 51 /** A buffer is in the input mode. */ 52 INPUT, 53 54 /** A buffer is in the output mode. */ 55 OUTPUT 56 } 57 58 private Mode mode; 59 private ByteBuffer buffer; 60 61 /** 62 * Allocates buffer of the given size using the given allocator. 63 * <p> 64 * Sets the mode to {@link Mode#INPUT INPUT}. 65 * </p> 66 * 67 * @param bufferSize the buffer size. 68 */ 69 protected ExpandableBuffer(final int bufferSize) { 70 super(); 71 this.buffer = ByteBuffer.allocate(bufferSize); 72 this.mode = Mode.INPUT; 73 } 74 75 /** 76 * Returns the current mode: 77 * <ul> 78 * <li>{@link Mode#INPUT INPUT}: the buffer is in the input mode.</li> 79 * <li>{@link Mode#OUTPUT OUTPUT}: the buffer is in the output mode.</li> 80 * </ul> 81 * 82 * @return current input/output mode, never null. 83 */ 84 protected Mode mode() { 85 return this.mode; 86 } 87 88 protected ByteBuffer buffer() { 89 return this.buffer; 90 } 91 92 /** 93 * Sets the mode to {@link Mode#OUTPUT OUTPUT}. The buffer can now be read from. 94 */ 95 protected void setOutputMode() { 96 if (this.mode != Mode.OUTPUT) { 97 this.buffer.flip(); 98 this.mode = Mode.OUTPUT; 99 } 100 } 101 102 /** 103 * Sets the mode to {@link Mode#INPUT INPUT}. The buffer can now be written into. 104 */ 105 protected void setInputMode() { 106 if (this.mode != Mode.INPUT) { 107 if (this.buffer.hasRemaining()) { 108 this.buffer.compact(); 109 } else { 110 this.buffer.clear(); 111 } 112 this.mode = Mode.INPUT; 113 } 114 } 115 116 private void expandCapacity(final int capacity) { 117 final ByteBuffer oldBuffer = this.buffer; 118 this.buffer = ByteBuffer.allocate(capacity); 119 oldBuffer.flip(); 120 this.buffer.put(oldBuffer); 121 } 122 123 /** 124 * Expands buffer's capacity. 125 * 126 * @throws BufferOverflowException in case we get over the maximum allowed value 127 */ 128 protected void expand() throws BufferOverflowException { 129 int newcapacity = (this.buffer.capacity() + 1) << 1; 130 if (newcapacity < 0) { 131 final int vmBytes = Long.SIZE >> 3; 132 final int javaBytes = 8; // this is to be checked when the JVM version changes 133 @SuppressWarnings("unused") // we really need the 8 if we're going to make this foolproof 134 final int headRoom = Math.max(vmBytes, javaBytes); 135 // Reason: In GC the size of objects is passed as int (2 bytes). 136 // Then, the header size of the objects is added to the size. 137 // Long has the longest header available. Object header seems to be linked to it. 138 // Details: I added a minimum of 8 just to be safe and because 8 is used in 139 // java.lang.Object.ArrayList: private static final int MAX_ARRAY_SIZE = 2147483639. 140 // 141 // WARNING: This code assumes you are providing enough heap room with -Xmx. 142 // source of inspiration: https://bugs.openjdk.java.net/browse/JDK-8059914 143 newcapacity = Integer.MAX_VALUE - headRoom; 144 145 if (newcapacity <= this.buffer.capacity()) { 146 throw new BufferOverflowException(); 147 } 148 } 149 expandCapacity(newcapacity); 150 } 151 152 /** 153 * Ensures the buffer can accommodate the exact required capacity. 154 * 155 * @param requiredCapacity the required capacity. 156 */ 157 protected void ensureCapacity(final int requiredCapacity) { 158 if (requiredCapacity > this.buffer.capacity()) { 159 expandCapacity(requiredCapacity); 160 } 161 } 162 163 /** 164 * Ensures the buffer can accommodate at least the required capacity adjusted to multiples of 1024. 165 * 166 * @param requiredCapacity the required capacity. 167 */ 168 protected void ensureAdjustedCapacity(final int requiredCapacity) { 169 if (requiredCapacity > this.buffer.capacity()) { 170 final int adjustedCapacity = ((requiredCapacity >> 10) + 1) << 10; 171 expandCapacity(adjustedCapacity); 172 } 173 } 174 175 /** 176 * Determines if the buffer contains data. 177 * <p> 178 * Sets the mode to output. 179 * </p> 180 * 181 * @return {@code true} if there is data in the buffer, 182 * {@code false} otherwise. 183 */ 184 protected boolean hasData() { 185 setOutputMode(); 186 return this.buffer.hasRemaining(); 187 } 188 189 /** 190 * Returns the length of this buffer. 191 * <p> 192 * Sets the mode to output. 193 * </p> 194 * 195 * @return buffer length. 196 */ 197 protected int length() { 198 setOutputMode(); 199 return this.buffer.remaining(); 200 } 201 202 /** 203 * Returns available capacity of this buffer. 204 * 205 * @return buffer length. 206 */ 207 protected int capacity() { 208 setInputMode(); 209 return this.buffer.remaining(); 210 } 211 212 /** 213 * Clears buffer. 214 * <p> 215 * Sets the mode to {@link Mode#INPUT INPUT}. 216 * </p> 217 */ 218 protected void clear() { 219 this.buffer.clear(); 220 this.mode = Mode.INPUT; 221 } 222 223 @Override 224 public String toString() { 225 final StringBuilder sb = new StringBuilder(); 226 sb.append("[mode="); 227 sb.append(this.mode); 228 sb.append(" pos="); 229 sb.append(this.buffer.position()); 230 sb.append(" lim="); 231 sb.append(this.buffer.limit()); 232 sb.append(" cap="); 233 sb.append(this.buffer.capacity()); 234 sb.append("]"); 235 return sb.toString(); 236 } 237 238 }