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