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