View Javadoc
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 }