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.util;
29  
30  import java.io.Serializable;
31  import java.nio.CharBuffer;
32  
33  import org.apache.hc.core5.http.Chars;
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[] array;
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.array = new char[capacity];
57      }
58  
59      private void expand(final int newlen) {
60          final char[] newArray = new char[Math.max(this.array.length << 1, newlen)];
61          System.arraycopy(this.array, 0, newArray, 0, this.len);
62          this.array = newArray;
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.array.length) {
90              expand(newlen);
91          }
92          System.arraycopy(b, off, this.array, 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.array.length) {
107             expand(newlen);
108         }
109         s.getChars(0, strlen, this.array, 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.array, 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.array,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.array.length) {
156             expand(newlen);
157         }
158         this.array[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.array.length) {
190             expand(newlen);
191         }
192         for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
193             this.array[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.array(), 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.array, 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     @Override
261     public char charAt(final int i) {
262         return this.array[i];
263     }
264 
265     /**
266      * Returns reference to the underlying char array.
267      *
268      * @return the char array.
269      */
270     public char[] array() {
271         return this.array;
272     }
273 
274     /**
275      * Returns the current capacity. The capacity is the amount of storage
276      * available for newly appended chars, beyond which an allocation will
277      * occur.
278      *
279      * @return  the current capacity
280      */
281     public int capacity() {
282         return this.array.length;
283     }
284 
285     /**
286      * Returns the length of the buffer (char count).
287      *
288      * @return  the length of the buffer
289      */
290     @Override
291     public int length() {
292         return this.len;
293     }
294 
295     /**
296      * Ensures that the capacity is at least equal to the specified minimum.
297      * If the current capacity is less than the argument, then a new internal
298      * array is allocated with greater capacity. If the {@code required}
299      * argument is non-positive, this method takes no action.
300      *
301      * @param   required   the minimum required capacity.
302      */
303     public void ensureCapacity(final int required) {
304         if (required <= 0) {
305             return;
306         }
307         final int available = this.array.length - this.len;
308         if (required > available) {
309             expand(this.len + required);
310         }
311     }
312 
313     /**
314      * Sets the length of the buffer. The new length value is expected to be
315      * less than the current capacity and greater than or equal to
316      * {@code 0}.
317      *
318      * @param      len   the new length
319      * @throws     IndexOutOfBoundsException  if the
320      *               {@code len} argument is greater than the current
321      *               capacity of the buffer or less than {@code 0}.
322      */
323     public void setLength(final int len) {
324         if (len < 0 || len > this.array.length) {
325             throw new IndexOutOfBoundsException("len: "+len+" < 0 or > buffer len: "+this.array.length);
326         }
327         this.len = len;
328     }
329 
330     /**
331      * Returns {@code true} if this buffer is empty, that is, its
332      * {@link #length()} is equal to {@code 0}.
333      * @return {@code true} if this buffer is empty, {@code false}
334      *   otherwise.
335      */
336     public boolean isEmpty() {
337         return this.len == 0;
338     }
339 
340     /**
341      * Returns {@code true} if this buffer is full, that is, its
342      * {@link #length()} is equal to its {@link #capacity()}.
343      * @return {@code true} if this buffer is full, {@code false}
344      *   otherwise.
345      */
346     public boolean isFull() {
347         return this.len == this.array.length;
348     }
349 
350     /**
351      * Returns the index within this buffer of the first occurrence of the
352      * specified character, starting the search at the specified
353      * {@code beginIndex} and finishing at {@code endIndex}.
354      * If no such character occurs in this buffer within the specified bounds,
355      * {@code -1} is returned.
356      * <p>
357      * There is no restriction on the value of {@code beginIndex} and
358      * {@code endIndex}. If {@code beginIndex} is negative,
359      * it has the same effect as if it were zero. If {@code endIndex} is
360      * greater than {@link #length()}, it has the same effect as if it were
361      * {@link #length()}. If the {@code beginIndex} is greater than
362      * the {@code endIndex}, {@code -1} is returned.
363      *
364      * @param   ch     the char to search for.
365      * @param   from   the index to start the search from.
366      * @param   to     the index to finish the search at.
367      * @return  the index of the first occurrence of the character in the buffer
368      *   within the given bounds, or {@code -1} if the character does
369      *   not occur.
370      */
371     public int indexOf(final int ch, final int from, final int to) {
372         int beginIndex = from;
373         if (beginIndex < 0) {
374             beginIndex = 0;
375         }
376         int endIndex = to;
377         if (endIndex > this.len) {
378             endIndex = this.len;
379         }
380         if (beginIndex > endIndex) {
381             return -1;
382         }
383         for (int i = beginIndex; i < endIndex; i++) {
384             if (this.array[i] == ch) {
385                 return i;
386             }
387         }
388         return -1;
389     }
390 
391     /**
392      * Returns the index within this buffer of the first occurrence of the
393      * specified character, starting the search at {@code 0} and finishing
394      * at {@link #length()}. If no such character occurs in this buffer within
395      * those bounds, {@code -1} is returned.
396      *
397      * @param   ch          the char to search for.
398      * @return  the index of the first occurrence of the character in the
399      *   buffer, or {@code -1} if the character does not occur.
400      */
401     public int indexOf(final int ch) {
402         return indexOf(ch, 0, this.len);
403     }
404 
405     /**
406      * Returns a substring of this buffer. The substring begins at the specified
407      * {@code beginIndex} and extends to the character at index
408      * {@code endIndex - 1}.
409      *
410      * @param      beginIndex   the beginning index, inclusive.
411      * @param      endIndex     the ending index, exclusive.
412      * @return     the specified substring.
413      * @throws  StringIndexOutOfBoundsException  if the
414      *             {@code beginIndex} is negative, or
415      *             {@code endIndex} is larger than the length of this
416      *             buffer, or {@code beginIndex} is larger than
417      *             {@code endIndex}.
418      */
419     public String substring(final int beginIndex, final int endIndex) {
420         if (beginIndex < 0) {
421             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
422         }
423         if (endIndex > this.len) {
424             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
425         }
426         if (beginIndex > endIndex) {
427             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
428         }
429         return new String(this.array, beginIndex, endIndex - beginIndex);
430     }
431 
432     private static boolean isWhitespace(final char ch) {
433         return ch == Chars.SP || ch == Chars.HT || ch == Chars.CR || ch == Chars.LF;
434     }
435 
436     /**
437      * Returns a substring of this buffer with leading and trailing whitespace
438      * omitted. The substring begins with the first non-whitespace character
439      * from {@code beginIndex} and extends to the last
440      * non-whitespace character with the index lesser than
441      * {@code endIndex}.
442      *
443      * @param      beginIndex   the beginning index, inclusive.
444      * @param      endIndex     the ending index, exclusive.
445      * @return     the specified substring.
446      * @throws  IndexOutOfBoundsException  if the
447      *             {@code beginIndex} is negative, or
448      *             {@code endIndex} is larger than the length of this
449      *             buffer, or {@code beginIndex} is larger than
450      *             {@code endIndex}.
451      */
452     public String substringTrimmed(final int beginIndex, final int endIndex) {
453         if (beginIndex < 0) {
454             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
455         }
456         if (endIndex > this.len) {
457             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
458         }
459         if (beginIndex > endIndex) {
460             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
461         }
462         int beginIndex0 = beginIndex;
463         int endIndex0 = endIndex;
464         while (beginIndex0 < endIndex && isWhitespace(this.array[beginIndex0])) {
465             beginIndex0++;
466         }
467         while (endIndex0 > beginIndex0 && isWhitespace(this.array[endIndex0 - 1])) {
468             endIndex0--;
469         }
470         return new String(this.array, beginIndex0, endIndex0 - beginIndex0);
471     }
472 
473     /**
474      * {@inheritDoc}
475      * @since 4.4
476      */
477     @Override
478     public CharSequence subSequence(final int beginIndex, final int endIndex) {
479         if (beginIndex < 0) {
480             throw new IndexOutOfBoundsException("Negative beginIndex: " + beginIndex);
481         }
482         if (endIndex > this.len) {
483             throw new IndexOutOfBoundsException("endIndex: " + endIndex + " > length: " + this.len);
484         }
485         if (beginIndex > endIndex) {
486             throw new IndexOutOfBoundsException("beginIndex: " + beginIndex + " > endIndex: " + endIndex);
487         }
488         return CharBuffer.wrap(this.array, beginIndex, endIndex);
489     }
490 
491     @Override
492     public String toString() {
493         return new String(this.array, 0, this.len);
494     }
495 
496 }