1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.io.input;
19
20 import static org.apache.commons.io.IOUtils.EOF;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.CharBuffer;
26 import java.nio.charset.CharacterCodingException;
27 import java.nio.charset.Charset;
28 import java.nio.charset.CharsetEncoder;
29 import java.nio.charset.CoderResult;
30 import java.nio.charset.CodingErrorAction;
31 import java.util.Objects;
32
33 import org.apache.commons.io.Charsets;
34 import org.apache.commons.io.IOUtils;
35 import org.apache.commons.io.build.AbstractStreamBuilder;
36 import org.apache.commons.io.charset.CharsetEncoders;
37 import org.apache.commons.io.function.Uncheck;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public class CharSequenceInputStream extends InputStream {
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public static class Builder extends AbstractStreamBuilder<CharSequenceInputStream, Builder> {
85
86 private CharsetEncoder charsetEncoder = newEncoder(getCharset());
87
88
89
90
91 public Builder() {
92
93 }
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 @Override
114 public CharSequenceInputStream get() {
115 return Uncheck.get(() -> new CharSequenceInputStream(getCharSequence(), getBufferSize(), charsetEncoder));
116 }
117
118 CharsetEncoder getCharsetEncoder() {
119 return charsetEncoder;
120 }
121
122 @Override
123 public Builder setCharset(final Charset charset) {
124 super.setCharset(charset);
125 charsetEncoder = newEncoder(getCharset());
126 return this;
127 }
128
129
130
131
132
133
134
135
136 public Builder setCharsetEncoder(final CharsetEncoder newEncoder) {
137 charsetEncoder = CharsetEncoders.toCharsetEncoder(newEncoder, () -> newEncoder(getCharsetDefault()));
138 super.setCharset(charsetEncoder.charset());
139 return this;
140 }
141
142 }
143
144 private static final int NO_MARK = -1;
145
146
147
148
149
150
151
152 public static Builder builder() {
153 return new Builder();
154 }
155
156 private static CharsetEncoder newEncoder(final Charset charset) {
157
158 return Charsets.toCharset(charset).newEncoder()
159 .onMalformedInput(CodingErrorAction.REPLACE)
160 .onUnmappableCharacter(CodingErrorAction.REPLACE);
161
162 }
163
164 private final ByteBuffer bBuf;
165 private int bBufMark;
166 private final CharBuffer cBuf;
167 private int cBufMark;
168 private final CharsetEncoder charsetEncoder;
169
170
171
172
173
174
175
176
177
178 @Deprecated
179 public CharSequenceInputStream(final CharSequence cs, final Charset charset) {
180 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
181 }
182
183
184
185
186
187
188
189
190
191
192 @Deprecated
193 public CharSequenceInputStream(final CharSequence cs, final Charset charset, final int bufferSize) {
194
195 this(cs, bufferSize, newEncoder(charset));
196
197 }
198
199 private CharSequenceInputStream(final CharSequence cs, final int bufferSize, final CharsetEncoder charsetEncoder) {
200 this.charsetEncoder = charsetEncoder;
201
202 this.bBuf = ByteBuffer.allocate(ReaderInputStream.checkMinBufferSize(charsetEncoder, bufferSize));
203 this.bBuf.flip();
204 this.cBuf = CharBuffer.wrap(cs);
205 this.cBufMark = NO_MARK;
206 this.bBufMark = NO_MARK;
207 try {
208 fillBuffer();
209 } catch (final CharacterCodingException ex) {
210
211
212 this.bBuf.clear();
213 this.bBuf.flip();
214 this.cBuf.rewind();
215 }
216 }
217
218
219
220
221
222
223
224
225
226 @Deprecated
227 public CharSequenceInputStream(final CharSequence cs, final String charset) {
228 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
229 }
230
231
232
233
234
235
236
237
238
239
240 @Deprecated
241 public CharSequenceInputStream(final CharSequence cs, final String charset, final int bufferSize) {
242 this(cs, Charsets.toCharset(charset), bufferSize);
243 }
244
245
246
247
248
249
250
251 @Override
252 public int available() throws IOException {
253 return this.bBuf.remaining();
254 }
255
256 @Override
257 public void close() throws IOException {
258 bBuf.position(bBuf.limit());
259 }
260
261
262
263
264
265
266
267 private void fillBuffer() throws CharacterCodingException {
268 this.bBuf.compact();
269 final CoderResult result = this.charsetEncoder.encode(this.cBuf, this.bBuf, true);
270 if (result.isError()) {
271 result.throwException();
272 }
273 this.bBuf.flip();
274 }
275
276
277
278
279
280
281 CharsetEncoder getCharsetEncoder() {
282 return charsetEncoder;
283 }
284
285
286
287
288
289 @Override
290 public synchronized void mark(final int readLimit) {
291 this.cBufMark = this.cBuf.position();
292 this.bBufMark = this.bBuf.position();
293 this.cBuf.mark();
294 this.bBuf.mark();
295
296
297 }
298
299 @Override
300 public boolean markSupported() {
301 return true;
302 }
303
304 @Override
305 public int read() throws IOException {
306 for (;;) {
307 if (this.bBuf.hasRemaining()) {
308 return this.bBuf.get() & 0xFF;
309 }
310 fillBuffer();
311 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
312 return EOF;
313 }
314 }
315 }
316
317 @Override
318 public int read(final byte[] b) throws IOException {
319 return read(b, 0, b.length);
320 }
321
322 @Override
323 public int read(final byte[] array, int off, int len) throws IOException {
324 Objects.requireNonNull(array, "array");
325 if (len < 0 || off + len > array.length) {
326 throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len);
327 }
328 if (len == 0) {
329 return 0;
330 }
331 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
332 return EOF;
333 }
334 int bytesRead = 0;
335 while (len > 0) {
336 if (this.bBuf.hasRemaining()) {
337 final int chunk = Math.min(this.bBuf.remaining(), len);
338 this.bBuf.get(array, off, chunk);
339 off += chunk;
340 len -= chunk;
341 bytesRead += chunk;
342 } else {
343 fillBuffer();
344 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
345 break;
346 }
347 }
348 }
349 return bytesRead == 0 && !this.cBuf.hasRemaining() ? EOF : bytesRead;
350 }
351
352 @Override
353 public synchronized void reset() throws IOException {
354
355
356
357
358
359
360
361
362
363
364
365 if (this.cBufMark != NO_MARK) {
366
367 if (this.cBuf.position() != 0) {
368 this.charsetEncoder.reset();
369 this.cBuf.rewind();
370 this.bBuf.rewind();
371 this.bBuf.limit(0);
372 while (this.cBuf.position() < this.cBufMark) {
373 this.bBuf.rewind();
374 this.bBuf.limit(0);
375 fillBuffer();
376 }
377 }
378 if (this.cBuf.position() != this.cBufMark) {
379 throw new IllegalStateException("Unexpected CharBuffer position: actual=" + cBuf.position() + " " +
380 "expected=" + this.cBufMark);
381 }
382 this.bBuf.position(this.bBufMark);
383 this.cBufMark = NO_MARK;
384 this.bBufMark = NO_MARK;
385 }
386 mark(0);
387 }
388
389 @Override
390 public long skip(long n) throws IOException {
391
392
393
394 long skipped = 0;
395 while (n > 0 && available() > 0) {
396 this.read();
397 n--;
398 skipped++;
399 }
400 return skipped;
401 }
402
403 }