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(this));
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 private CharSequenceInputStream(final Builder builder) {
171 this.charsetEncoder = builder.charsetEncoder;
172
173 this.bBuf = ByteBuffer.allocate(ReaderInputStream.checkMinBufferSize(builder.charsetEncoder, builder.getBufferSize()));
174 this.bBuf.flip();
175 this.cBuf = CharBuffer.wrap(Uncheck.get(() -> builder.getCharSequence()));
176 this.cBufMark = NO_MARK;
177 this.bBufMark = NO_MARK;
178 try {
179 fillBuffer();
180 } catch (final CharacterCodingException ex) {
181
182
183 this.bBuf.clear();
184 this.bBuf.flip();
185 this.cBuf.rewind();
186 }
187 }
188
189
190
191
192
193
194
195
196
197 @Deprecated
198 public CharSequenceInputStream(final CharSequence cs, final Charset charset) {
199 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
200 }
201
202
203
204
205
206
207
208
209
210
211 @Deprecated
212 public CharSequenceInputStream(final CharSequence cs, final Charset charset, final int bufferSize) {
213 this(builder().setCharSequence(cs).setCharset(charset).setBufferSize(bufferSize));
214 }
215
216
217
218
219
220
221
222
223
224 @Deprecated
225 public CharSequenceInputStream(final CharSequence cs, final String charset) {
226 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
227 }
228
229
230
231
232
233
234
235
236
237
238 @Deprecated
239 public CharSequenceInputStream(final CharSequence cs, final String charset, final int bufferSize) {
240 this(cs, Charsets.toCharset(charset), bufferSize);
241 }
242
243
244
245
246
247
248
249 @Override
250 public int available() throws IOException {
251 return this.bBuf.remaining();
252 }
253
254 @Override
255 public void close() throws IOException {
256 bBuf.position(bBuf.limit());
257 }
258
259
260
261
262
263
264
265 private void fillBuffer() throws CharacterCodingException {
266 this.bBuf.compact();
267 final CoderResult result = this.charsetEncoder.encode(this.cBuf, this.bBuf, true);
268 if (result.isError()) {
269 result.throwException();
270 }
271 this.bBuf.flip();
272 }
273
274
275
276
277
278
279 CharsetEncoder getCharsetEncoder() {
280 return charsetEncoder;
281 }
282
283
284
285
286
287 @Override
288 public synchronized void mark(final int readLimit) {
289 this.cBufMark = this.cBuf.position();
290 this.bBufMark = this.bBuf.position();
291 this.cBuf.mark();
292 this.bBuf.mark();
293
294
295 }
296
297 @Override
298 public boolean markSupported() {
299 return true;
300 }
301
302 @Override
303 public int read() throws IOException {
304 for (;;) {
305 if (this.bBuf.hasRemaining()) {
306 return this.bBuf.get() & 0xFF;
307 }
308 fillBuffer();
309 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
310 return EOF;
311 }
312 }
313 }
314
315 @Override
316 public int read(final byte[] b) throws IOException {
317 return read(b, 0, b.length);
318 }
319
320 @Override
321 public int read(final byte[] array, int off, int len) throws IOException {
322 Objects.requireNonNull(array, "array");
323 if (len < 0 || off + len > array.length) {
324 throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len);
325 }
326 if (len == 0) {
327 return 0;
328 }
329 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
330 return EOF;
331 }
332 int bytesRead = 0;
333 while (len > 0) {
334 if (this.bBuf.hasRemaining()) {
335 final int chunk = Math.min(this.bBuf.remaining(), len);
336 this.bBuf.get(array, off, chunk);
337 off += chunk;
338 len -= chunk;
339 bytesRead += chunk;
340 } else {
341 fillBuffer();
342 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
343 break;
344 }
345 }
346 }
347 return bytesRead == 0 && !this.cBuf.hasRemaining() ? EOF : bytesRead;
348 }
349
350 @Override
351 public synchronized void reset() throws IOException {
352
353
354
355
356
357
358
359
360
361
362
363 if (this.cBufMark != NO_MARK) {
364
365 if (this.cBuf.position() != 0) {
366 this.charsetEncoder.reset();
367 this.cBuf.rewind();
368 this.bBuf.rewind();
369 this.bBuf.limit(0);
370 while (this.cBuf.position() < this.cBufMark) {
371 this.bBuf.rewind();
372 this.bBuf.limit(0);
373 fillBuffer();
374 }
375 }
376 if (this.cBuf.position() != this.cBufMark) {
377 throw new IllegalStateException("Unexpected CharBuffer position: actual=" + cBuf.position() + " " +
378 "expected=" + this.cBufMark);
379 }
380 this.bBuf.position(this.bBufMark);
381 this.cBufMark = NO_MARK;
382 this.bBufMark = NO_MARK;
383 }
384 mark(0);
385 }
386
387 @Override
388 public long skip(long n) throws IOException {
389
390
391
392 long skipped = 0;
393 while (n > 0 && available() > 0) {
394 this.read();
395 n--;
396 skipped++;
397 }
398 return skipped;
399 }
400
401 }