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  package org.apache.hc.client5.http.utils;
28  
29  import java.nio.ByteBuffer;
30  import java.nio.CharBuffer;
31  import java.nio.charset.CharacterCodingException;
32  import java.nio.charset.Charset;
33  import java.nio.charset.CharsetEncoder;
34  import java.nio.charset.CoderResult;
35  import java.nio.charset.CodingErrorAction;
36  import java.nio.charset.StandardCharsets;
37  
38  /**
39   * Builder class for sequences of bytes.
40   *
41   * @since 5.0
42   */
43  public final class ByteArrayBuilder {
44  
45      private CharsetEncoder charsetEncoder;
46      private ByteBuffer buffer;
47  
48      public ByteArrayBuilder() {
49      }
50  
51      public ByteArrayBuilder(final int initialCapacity) {
52          this.buffer = ByteBuffer.allocate(initialCapacity);
53      }
54  
55      public int capacity() {
56          return this.buffer != null ? this.buffer.capacity() : 0;
57      }
58  
59      static ByteBuffer ensureFreeCapacity(final ByteBuffer buffer, final int capacity) {
60          if (buffer == null) {
61              return ByteBuffer.allocate(capacity);
62          }
63          if (buffer.remaining() < capacity) {
64              final ByteBuffer newBuffer = ByteBuffer.allocate(buffer.position() + capacity);
65              buffer.flip();
66              newBuffer.put(buffer);
67              return newBuffer;
68          }
69          return buffer;
70      }
71  
72      static ByteBuffer encode(
73              final ByteBuffer buffer, final CharBuffer in, final CharsetEncoder encoder) throws CharacterCodingException {
74  
75          final int capacity = (int) (in.remaining() * encoder.averageBytesPerChar());
76          ByteBuffer out = ensureFreeCapacity(buffer, capacity);
77          while (in.hasRemaining()) {
78              CoderResult result = encoder.encode(in, out, true);
79              if (result.isError()) {
80                  result.throwException();
81              }
82              if (result.isUnderflow()) {
83                  result = encoder.flush(out);
84              }
85              if (result.isUnderflow()) {
86                  break;
87              }
88              if (result.isOverflow()) {
89                  out = ensureFreeCapacity(out, capacity);
90              }
91          }
92          return out;
93      }
94  
95      public void ensureFreeCapacity(final int freeCapacity) {
96          this.buffer = ensureFreeCapacity(this.buffer, freeCapacity);
97      }
98  
99      private void doAppend(final CharBuffer charBuffer) {
100         if (this.charsetEncoder == null) {
101             this.charsetEncoder = StandardCharsets.US_ASCII.newEncoder()
102                     .onMalformedInput(CodingErrorAction.IGNORE)
103                     .onUnmappableCharacter(CodingErrorAction.REPLACE);
104         }
105         this.charsetEncoder.reset();
106         try {
107             this.buffer = encode(this.buffer, charBuffer, this.charsetEncoder);
108         } catch (final CharacterCodingException ex) {
109             // Should never happen
110             throw new IllegalStateException("Unexpected character coding error", ex);
111         }
112     }
113 
114     public ByteArrayBuilder charset(final Charset charset) {
115         if (charset == null) {
116             this.charsetEncoder = null;
117         } else {
118             this.charsetEncoder = charset.newEncoder()
119                     .onMalformedInput(CodingErrorAction.IGNORE)
120                     .onUnmappableCharacter(CodingErrorAction.REPLACE);
121         }
122         return this;
123     }
124 
125     public ByteArrayBuilder append(final byte[] b, final int off, final int len) {
126         if (b == null) {
127             return this;
128         }
129         if ((off < 0) || (off > b.length) || (len < 0) ||
130                 ((off + len) < 0) || ((off + len) > b.length)) {
131             throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length);
132         }
133         ensureFreeCapacity(len);
134         this.buffer.put(b, off, len);
135         return this;
136     }
137 
138     public ByteArrayBuilder append(final byte[] b) {
139         if (b == null) {
140             return this;
141         }
142         return append(b, 0, b.length);
143     }
144 
145     public ByteArrayBuilder append(final CharBuffer charBuffer) {
146         if (charBuffer == null) {
147             return this;
148         }
149         doAppend(charBuffer);
150         return this;
151     }
152 
153     public ByteArrayBuilder append(final char[] b, final int off, final int len) {
154         if (b == null) {
155             return this;
156         }
157         if ((off < 0) || (off > b.length) || (len < 0) ||
158                 ((off + len) < 0) || ((off + len) > b.length)) {
159             throw new IndexOutOfBoundsException("off: " + off + " len: " + len + " b.length: " + b.length);
160         }
161         return append(CharBuffer.wrap(b, off, len));
162     }
163 
164     public ByteArrayBuilder append(final char[] b) {
165         if (b == null) {
166             return this;
167         }
168         return append(b, 0, b.length);
169     }
170 
171     public ByteArrayBuilder append(final String s) {
172         if (s == null) {
173             return this;
174         }
175         return append(CharBuffer.wrap(s));
176     }
177 
178     public ByteBuffer toByteBuffer() {
179         return this.buffer != null ? this.buffer.duplicate() : ByteBuffer.allocate(0);
180     }
181 
182     public byte[] toByteArray() {
183         if (this.buffer == null) {
184             return new byte[] {};
185         }
186         this.buffer.flip();
187         final byte[] b = new byte[this.buffer.remaining()];
188         this.buffer.get(b);
189         this.buffer.clear();
190         return b;
191     }
192 
193     public void reset() {
194         if (this.charsetEncoder != null) {
195             this.charsetEncoder.reset();
196         }
197         if (this.buffer != null) {
198             this.buffer.clear();
199         }
200     }
201 
202     @Override
203     public String toString() {
204         return this.buffer != null ? this.buffer.toString() : "null";
205     }
206 
207 }