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