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.util;
29  
30  import java.io.Serializable;
31  import java.nio.CharBuffer;
32  
33  import org.apache.http.protocol.HTTP;
34  
35  /**
36   * A resizable char array.
37   *
38   * @since 4.0
39   */
40  public final class CharArrayBuffer implements CharSequence, Serializable {
41  
42      private static final long serialVersionUID = -6208952725094867135L;
43  
44      private char[] buffer;
45      private int len;
46  
47      /**
48       * Creates an instance of {@link CharArrayBuffer} with the given initial
49       * capacity.
50       *
51       * @param capacity the capacity
52       */
53      public CharArrayBuffer(final int capacity) {
54          super();
55          Args.notNegative(capacity, "Buffer capacity");
56          this.buffer = new char[capacity];
57      }
58  
59      private void expand(final int newlen) {
60          final char newbuffer[] = new char[Math.max(this.buffer.length << 1, newlen)];
61          System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
62          this.buffer = newbuffer;
63      }
64  
65      /**
66       * Appends {@code len} chars to this buffer from the given source
67       * array starting at index {@code off}. The capacity of the buffer
68       * is increased, if necessary, to accommodate all {@code len} chars.
69       *
70       * @param   b        the chars to be appended.
71       * @param   off      the index of the first char to append.
72       * @param   len      the number of chars to append.
73       * @throws IndexOutOfBoundsException if {@code off} is out of
74       * range, {@code len} is negative, or
75       * {@code off} + {@code len} is out of range.
76       */
77      public void append(final char[] b, final int off, final int len) {
78          if (b == null) {
79              return;
80          }
81          if ((off < 0) || (off > b.length) || (len < 0) ||
82                  ((off + len) < 0) || ((off + len) > b.length)) {
83              throw new IndexOutOfBoundsException("off: "+off+" len: "+len+" b.length: "+b.length);
84          }
85          if (len == 0) {
86              return;
87          }
88          final int newlen = this.len + len;
89          if (newlen > this.buffer.length) {
90              expand(newlen);
91          }
92          System.arraycopy(b, off, this.buffer, this.len, len);
93          this.len = newlen;
94      }
95  
96      /**
97       * Appends chars of the given string to this buffer. The capacity of the
98       * buffer is increased, if necessary, to accommodate all chars.
99       *
100      * @param str    the string.
101      */
102     public void append(final String str) {
103         final String s = str != null ? str : "null";
104         final int strlen = s.length();
105         final int newlen = this.len + strlen;
106         if (newlen > this.buffer.length) {
107             expand(newlen);
108         }
109         s.getChars(0, strlen, this.buffer, this.len);
110         this.len = newlen;
111     }
112 
113     /**
114      * Appends {@code len} chars to this buffer from the given source
115      * buffer starting at index {@code off}. The capacity of the
116      * destination buffer is increased, if necessary, to accommodate all
117      * {@code len} chars.
118      *
119      * @param   b        the source buffer to be appended.
120      * @param   off      the index of the first char to append.
121      * @param   len      the number of chars to append.
122      * @throws IndexOutOfBoundsException if {@code off} is out of
123      * range, {@code len} is negative, or
124      * {@code off} + {@code len} is out of range.
125      */
126     public void append(final CharArrayBuffer b, final int off, final int len) {
127         if (b == null) {
128             return;
129         }
130         append(b.buffer, off, len);
131     }
132 
133     /**
134      * Appends all chars to this buffer from the given source buffer starting
135      * at index {@code 0}. The capacity of the destination buffer is
136      * increased, if necessary, to accommodate all {@link #length()} chars.
137      *
138      * @param   b        the source buffer to be appended.
139      */
140     public void append(final CharArrayBuffer b) {
141         if (b == null) {
142             return;
143         }
144         append(b.buffer,0, b.len);
145     }
146 
147     /**
148      * Appends {@code ch} char to this buffer. The capacity of the buffer
149      * is increased, if necessary, to accommodate the additional char.
150      *
151      * @param   ch        the char to be appended.
152      */
153     public void append(final char ch) {
154         final int newlen = this.len + 1;
155         if (newlen > this.buffer.length) {
156             expand(newlen);
157         }
158         this.buffer[this.len] = ch;
159         this.len = newlen;
160     }
161 
162     /**
163      * Appends {@code len} bytes to this buffer from the given source
164      * array starting at index {@code off}. The capacity of the buffer
165      * is increased, if necessary, to accommodate all {@code len} bytes.
166      * <p>
167      * The bytes are converted to chars using simple cast.
168      *
169      * @param   b        the bytes to be appended.
170      * @param   off      the index of the first byte to append.
171      * @param   len      the number of bytes to append.
172      * @throws IndexOutOfBoundsException if {@code off} is out of
173      * range, {@code len} is negative, or
174      * {@code off} + {@code len} is out of range.
175      */
176     public void append(final byte[] b, final int off, final int len) {
177         if (b == null) {
178             return;
179         }
180         if ((off < 0) || (off > b.length) || (len < 0) ||
181                 ((off + len) < 0) || ((off + len) > b.length)) {
182             throw new IndexOutOfBoundsException("off: "+off+" len: "+len+" b.length: "+b.length);
183         }
184         if (len == 0) {
185             return;
186         }
187         final int oldlen = this.len;
188         final int newlen = oldlen + len;
189         if (newlen > this.buffer.length) {
190             expand(newlen);
191         }
192         for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
193             this.buffer[i2] = (char) (b[i1] & 0xff);
194         }
195         this.len = newlen;
196     }
197 
198     /**
199      * Appends {@code len} bytes to this buffer from the given source
200      * array starting at index {@code off}. The capacity of the buffer
201      * is increased, if necessary, to accommodate all {@code len} bytes.
202      * <p>
203      * The bytes are converted to chars using simple cast.
204      *
205      * @param   b        the bytes to be appended.
206      * @param   off      the index of the first byte to append.
207      * @param   len      the number of bytes to append.
208      * @throws IndexOutOfBoundsException if {@code off} is out of
209      * range, {@code len} is negative, or
210      * {@code off} + {@code len} is out of range.
211      */
212     public void append(final ByteArrayBuffer b, final int off, final int len) {
213         if (b == null) {
214             return;
215         }
216         append(b.buffer(), off, len);
217     }
218 
219     /**
220      * Appends chars of the textual representation of the given object to this
221      * buffer. The capacity of the buffer is increased, if necessary, to
222      * accommodate all chars.
223      *
224      * @param obj    the object.
225      */
226     public void append(final Object obj) {
227         append(String.valueOf(obj));
228     }
229 
230     /**
231      * Clears content of the buffer. The underlying char array is not resized.
232      */
233     public void clear() {
234         this.len = 0;
235     }
236 
237     /**
238      * Converts the content of this buffer to an array of chars.
239      *
240      * @return char array
241      */
242     public char[] toCharArray() {
243         final char[] b = new char[this.len];
244         if (this.len > 0) {
245             System.arraycopy(this.buffer, 0, b, 0, this.len);
246         }
247         return b;
248     }
249 
250     /**
251      * Returns the {@code char} value in this buffer at the specified
252      * index. The index argument must be greater than or equal to
253      * {@code 0}, and less than the length of this buffer.
254      *
255      * @param      i   the index of the desired char value.
256      * @return     the char value at the specified index.
257      * @throws     IndexOutOfBoundsException  if {@code index} is
258      *             negative or greater than or equal to {@link #length()}.
259      */
260     public char charAt(final int i) {
261         return this.buffer[i];
262     }
263 
264     /**
265      * Returns reference to the underlying char array.
266      *
267      * @return the char array.
268      */
269     public char[] buffer() {
270         return this.buffer;
271     }
272 
273     /**
274      * Returns the current capacity. The capacity is the amount of storage
275      * available for newly appended chars, beyond which an allocation will
276      * occur.
277      *
278      * @return  the current capacity
279      */
280     public int capacity() {
281         return this.buffer.length;
282     }
283 
284     /**
285      * Returns the length of the buffer (char count).
286      *
287      * @return  the length of the buffer
288      */
289     public int length() {
290         return this.len;
291     }
292 
293     /**
294      * Ensures that the capacity is at least equal to the specified minimum.
295      * If the current capacity is less than the argument, then a new internal
296      * array is allocated with greater capacity. If the {@code required}
297      * argument is non-positive, this method takes no action.
298      *
299      * @param   required   the minimum required capacity.
300      */
301     public void ensureCapacity(final int required) {
302         if (required <= 0) {
303             return;
304         }
305         final int available = this.buffer.length - this.len;
306         if (required > available) {
307             expand(this.len + required);
308         }
309     }
310 
311     /**
312      * Sets the length of the buffer. The new length value is expected to be
313      * less than the current capacity and greater than or equal to
314      * {@code 0}.
315      *
316      * @param      len   the new length
317      * @throws     IndexOutOfBoundsException  if the
318      *               {@code len} argument is greater than the current
319      *               capacity of the buffer or less than {@code 0}.
320      */
321     public void setLength(final int len) {
322         if (len < 0 || len > this.buffer.length) {
323             throw new IndexOutOfBoundsException("len: "+len+" < 0 or > buffer len: "+this.buffer.length);
324         }
325         this.len = len;
326     }
327 
328     /**
329      * Returns {@code true} if this buffer is empty, that is, its
330      * {@link #length()} is equal to {@code 0}.
331      * @return {@code true} if this buffer is empty, {@code false}
332      *   otherwise.
333      */
334     public boolean isEmpty() {
335         return this.len == 0;
336     }
337 
338     /**
339      * Returns {@code true} if this buffer is full, that is, its
340      * {@link #length()} is equal to its {@link #capacity()}.
341      * @return {@code true} if this buffer is full, {@code false}
342      *   otherwise.
343      */
344     public boolean isFull() {
345         return this.len == this.buffer.length;
346     }
347 
348     /**
349      * Returns the index within this buffer of the first occurrence of the
350      * specified character, starting the search at the specified
351      * {@code beginIndex} and finishing at {@code endIndex}.
352      * If no such character occurs in this buffer within the specified bounds,
353      * {@code -1} is returned.
354      * <p>
355      * There is no restriction on the value of {@code beginIndex} and
356      * {@code endIndex}. If {@code beginIndex} is negative,
357      * it has the same effect as if it were zero. If {@code endIndex} is
358      * greater than {@link #length()}, it has the same effect as if it were
359      * {@link #length()}. If the {@code beginIndex} is greater than
360      * the {@code endIndex}, {@code -1} is returned.
361      *
362      * @param   ch     the char to search for.
363      * @param   from   the index to start the search from.
364      * @param   to     the index to finish the search at.
365      * @return  the index of the first occurrence of the character in the buffer
366      *   within the given bounds, or {@code -1} if the character does
367      *   not occur.
368      */
369     public int indexOf(final int ch, final int from, final int to) {
370         int beginIndex = from;
371         if (beginIndex < 0) {
372             beginIndex = 0;
373         }
374         int endIndex = to;
375         if (endIndex > this.len) {
376             endIndex = this.len;
377         }
378         if (beginIndex > endIndex) {
379             return -1;
380         }
381         for (int i = beginIndex; i < endIndex; i++) {
382             if (this.buffer[i] == ch) {
383                 return i;
384             }
385         }
386         return -1;
387     }
388 
389     /**
390      * Returns the index within this buffer of the first occurrence of the
391      * specified character, starting the search at {@code 0} and finishing
392      * at {@link #length()}. If no such character occurs in this buffer within
393      * those bounds, {@code -1} is returned.
394      *
395      * @param   ch          the char to search for.
396      * @return  the index of the first occurrence of the character in the
397      *   buffer, or {@code -1} if the character does not occur.
398      */
399     public int indexOf(final int ch) {
400         return indexOf(ch, 0, this.len);
401     }
402 
403     /**
404      * Returns a substring of this buffer. The substring begins at the specified
405      * {@code beginIndex} and extends to the character at index
406      * {@code endIndex - 1}.
407      *
408      * @param      beginIndex   the beginning index, inclusive.
409      * @param      endIndex     the ending index, exclusive.
410      * @return     the specified substring.
411      * @exception  StringIndexOutOfBoundsException  if the
412      *             {@code beginIndex} is negative, or
413      *             {@code endIndex} is larger than the length of this
414      *             buffer, or {@code beginIndex} is larger than
415      *             {@code endIndex}.
416      */
417     public String substring(final int beginIndex, final int endIndex) {
418         if (beginIndex < 0) {
419             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
420         }
421         if (endIndex > this.len) {
422             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
423         }
424         if (beginIndex > endIndex) {
425             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
426         }
427         return new String(this.buffer, beginIndex, endIndex - beginIndex);
428     }
429 
430     /**
431      * Returns a substring of this buffer with leading and trailing whitespace
432      * omitted. The substring begins with the first non-whitespace character
433      * from {@code beginIndex} and extends to the last
434      * non-whitespace character with the index lesser than
435      * {@code endIndex}.
436      *
437      * @param      beginIndex   the beginning index, inclusive.
438      * @param      endIndex     the ending index, exclusive.
439      * @return     the specified substring.
440      * @exception  IndexOutOfBoundsException  if the
441      *             {@code beginIndex} is negative, or
442      *             {@code endIndex} is larger than the length of this
443      *             buffer, or {@code beginIndex} is larger than
444      *             {@code endIndex}.
445      */
446     public String substringTrimmed(final int beginIndex, final int endIndex) {
447         if (beginIndex < 0) {
448             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
449         }
450         if (endIndex > this.len) {
451             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
452         }
453         if (beginIndex > endIndex) {
454             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
455         }
456         int beginIndex0 = beginIndex;
457         int endIndex0 = endIndex;
458         while (beginIndex0 < endIndex && HTTP.isWhitespace(this.buffer[beginIndex0])) {
459             beginIndex0++;
460         }
461         while (endIndex0 > beginIndex0 && HTTP.isWhitespace(this.buffer[endIndex0 - 1])) {
462             endIndex0--;
463         }
464         return new String(this.buffer, beginIndex0, endIndex0 - beginIndex0);
465     }
466 
467     /**
468      * {@inheritDoc}
469      * @since 4.4
470      */
471     @Override
472     public CharSequence subSequence(final int beginIndex, final int endIndex) {
473         if (beginIndex < 0) {
474             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
475         }
476         if (endIndex > this.len) {
477             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
478         }
479         if (beginIndex > endIndex) {
480             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
481         }
482         return CharBuffer.wrap(this.buffer, beginIndex, endIndex);
483     }
484 
485     @Override
486     public String toString() {
487         return new String(this.buffer, 0, this.len);
488     }
489 
490 }