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