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
92
93
94
95
96
97
98
99
100
101
102
103
104
105 @Override
106 public CharSequenceInputStream get() {
107 return Uncheck.get(() -> new CharSequenceInputStream(getCharSequence(), getBufferSize(), charsetEncoder));
108 }
109
110 CharsetEncoder getCharsetEncoder() {
111 return charsetEncoder;
112 }
113
114 @Override
115 public Builder setCharset(final Charset charset) {
116 super.setCharset(charset);
117 charsetEncoder = newEncoder(getCharset());
118 return this;
119 }
120
121
122
123
124
125
126
127
128 public Builder setCharsetEncoder(final CharsetEncoder newEncoder) {
129 charsetEncoder = CharsetEncoders.toCharsetEncoder(newEncoder, () -> newEncoder(getCharsetDefault()));
130 super.setCharset(charsetEncoder.charset());
131 return this;
132 }
133
134 }
135
136 private static final int NO_MARK = -1;
137
138
139
140
141
142
143
144 public static Builder builder() {
145 return new Builder();
146 }
147
148 private static CharsetEncoder newEncoder(final Charset charset) {
149
150 return Charsets.toCharset(charset).newEncoder()
151 .onMalformedInput(CodingErrorAction.REPLACE)
152 .onUnmappableCharacter(CodingErrorAction.REPLACE);
153
154 }
155
156 private final ByteBuffer bBuf;
157 private int bBufMark;
158 private final CharBuffer cBuf;
159 private int cBufMark;
160 private final CharsetEncoder charsetEncoder;
161
162
163
164
165
166
167
168
169
170 @Deprecated
171 public CharSequenceInputStream(final CharSequence cs, final Charset charset) {
172 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
173 }
174
175
176
177
178
179
180
181
182
183
184 @Deprecated
185 public CharSequenceInputStream(final CharSequence cs, final Charset charset, final int bufferSize) {
186
187 this(cs, bufferSize, newEncoder(charset));
188
189 }
190
191 private CharSequenceInputStream(final CharSequence cs, final int bufferSize, final CharsetEncoder charsetEncoder) {
192 this.charsetEncoder = charsetEncoder;
193
194 this.bBuf = ByteBuffer.allocate(ReaderInputStream.checkMinBufferSize(charsetEncoder, bufferSize));
195 this.bBuf.flip();
196 this.cBuf = CharBuffer.wrap(cs);
197 this.cBufMark = NO_MARK;
198 this.bBufMark = NO_MARK;
199 try {
200 fillBuffer();
201 } catch (final CharacterCodingException ex) {
202
203
204 this.bBuf.clear();
205 this.bBuf.flip();
206 this.cBuf.rewind();
207 }
208 }
209
210
211
212
213
214
215
216
217
218 @Deprecated
219 public CharSequenceInputStream(final CharSequence cs, final String charset) {
220 this(cs, charset, IOUtils.DEFAULT_BUFFER_SIZE);
221 }
222
223
224
225
226
227
228
229
230
231
232 @Deprecated
233 public CharSequenceInputStream(final CharSequence cs, final String charset, final int bufferSize) {
234 this(cs, Charsets.toCharset(charset), bufferSize);
235 }
236
237
238
239
240
241
242
243 @Override
244 public int available() throws IOException {
245 return this.bBuf.remaining();
246 }
247
248 @Override
249 public void close() throws IOException {
250
251 }
252
253
254
255
256
257
258
259 private void fillBuffer() throws CharacterCodingException {
260 this.bBuf.compact();
261 final CoderResult result = this.charsetEncoder.encode(this.cBuf, this.bBuf, true);
262 if (result.isError()) {
263 result.throwException();
264 }
265 this.bBuf.flip();
266 }
267
268
269
270
271
272
273 CharsetEncoder getCharsetEncoder() {
274 return charsetEncoder;
275 }
276
277
278
279
280
281 @Override
282 public synchronized void mark(final int readLimit) {
283 this.cBufMark = this.cBuf.position();
284 this.bBufMark = this.bBuf.position();
285 this.cBuf.mark();
286 this.bBuf.mark();
287
288
289 }
290
291 @Override
292 public boolean markSupported() {
293 return true;
294 }
295
296 @Override
297 public int read() throws IOException {
298 for (;;) {
299 if (this.bBuf.hasRemaining()) {
300 return this.bBuf.get() & 0xFF;
301 }
302 fillBuffer();
303 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
304 return EOF;
305 }
306 }
307 }
308
309 @Override
310 public int read(final byte[] b) throws IOException {
311 return read(b, 0, b.length);
312 }
313
314 @Override
315 public int read(final byte[] array, int off, int len) throws IOException {
316 Objects.requireNonNull(array, "array");
317 if (len < 0 || off + len > array.length) {
318 throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len);
319 }
320 if (len == 0) {
321 return 0;
322 }
323 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
324 return EOF;
325 }
326 int bytesRead = 0;
327 while (len > 0) {
328 if (this.bBuf.hasRemaining()) {
329 final int chunk = Math.min(this.bBuf.remaining(), len);
330 this.bBuf.get(array, off, chunk);
331 off += chunk;
332 len -= chunk;
333 bytesRead += chunk;
334 } else {
335 fillBuffer();
336 if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) {
337 break;
338 }
339 }
340 }
341 return bytesRead == 0 && !this.cBuf.hasRemaining() ? EOF : bytesRead;
342 }
343
344 @Override
345 public synchronized void reset() throws IOException {
346
347
348
349
350
351
352
353
354
355
356
357 if (this.cBufMark != NO_MARK) {
358
359 if (this.cBuf.position() != 0) {
360 this.charsetEncoder.reset();
361 this.cBuf.rewind();
362 this.bBuf.rewind();
363 this.bBuf.limit(0);
364 while (this.cBuf.position() < this.cBufMark) {
365 this.bBuf.rewind();
366 this.bBuf.limit(0);
367 fillBuffer();
368 }
369 }
370 if (this.cBuf.position() != this.cBufMark) {
371 throw new IllegalStateException("Unexpected CharBuffer position: actual=" + cBuf.position() + " " +
372 "expected=" + this.cBufMark);
373 }
374 this.bBuf.position(this.bBufMark);
375 this.cBufMark = NO_MARK;
376 this.bBufMark = NO_MARK;
377 }
378 mark(0);
379 }
380
381 @Override
382 public long skip(long n) throws IOException {
383
384
385
386 long skipped = 0;
387 while (n > 0 && available() > 0) {
388 this.read();
389 n--;
390 skipped++;
391 }
392 return skipped;
393 }
394
395 }