1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.io;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.annotations.Beta;
23 import com.google.common.base.Ascii;
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.hash.Funnels;
26 import com.google.common.hash.HashCode;
27 import com.google.common.hash.HashFunction;
28 import com.google.common.hash.Hasher;
29
30 import java.io.BufferedInputStream;
31 import java.io.ByteArrayInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.OutputStream;
36 import java.io.Reader;
37 import java.nio.charset.Charset;
38 import java.util.Arrays;
39 import java.util.Iterator;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public abstract class ByteSource implements InputSupplier<InputStream> {
60
61 private static final int BUF_SIZE = 0x1000;
62
63
64
65
66 protected ByteSource() {}
67
68
69
70
71
72 public CharSource asCharSource(Charset charset) {
73 return new AsCharSource(charset);
74 }
75
76
77
78
79
80
81
82
83
84 public abstract InputStream openStream() throws IOException;
85
86
87
88
89
90
91
92
93
94
95 @Override
96 @Deprecated
97 public final InputStream getInput() throws IOException {
98 return openStream();
99 }
100
101
102
103
104
105
106
107
108
109
110
111
112
113 public InputStream openBufferedStream() throws IOException {
114 InputStream in = openStream();
115 return (in instanceof BufferedInputStream)
116 ? (BufferedInputStream) in
117 : new BufferedInputStream(in);
118 }
119
120
121
122
123
124
125
126 public ByteSource slice(long offset, long length) {
127 return new SlicedByteSource(offset, length);
128 }
129
130
131
132
133
134
135
136
137 public boolean isEmpty() throws IOException {
138 Closer closer = Closer.create();
139 try {
140 InputStream in = closer.register(openStream());
141 return in.read() == -1;
142 } catch (Throwable e) {
143 throw closer.rethrow(e);
144 } finally {
145 closer.close();
146 }
147 }
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public long size() throws IOException {
165 Closer closer = Closer.create();
166 try {
167 InputStream in = closer.register(openStream());
168 return countBySkipping(in);
169 } catch (IOException e) {
170
171 } finally {
172 closer.close();
173 }
174
175 closer = Closer.create();
176 try {
177 InputStream in = closer.register(openStream());
178 return countByReading(in);
179 } catch (Throwable e) {
180 throw closer.rethrow(e);
181 } finally {
182 closer.close();
183 }
184 }
185
186
187
188
189
190 private long countBySkipping(InputStream in) throws IOException {
191 long count = 0;
192 while (true) {
193
194
195 long skipped = in.skip(Math.min(in.available(), Integer.MAX_VALUE));
196 if (skipped <= 0) {
197 if (in.read() == -1) {
198 return count;
199 } else if (count == 0 && in.available() == 0) {
200
201
202 throw new IOException();
203 }
204 count++;
205 } else {
206 count += skipped;
207 }
208 }
209 }
210
211 private static final byte[] countBuffer = new byte[BUF_SIZE];
212
213 private long countByReading(InputStream in) throws IOException {
214 long count = 0;
215 long read;
216 while ((read = in.read(countBuffer)) != -1) {
217 count += read;
218 }
219 return count;
220 }
221
222
223
224
225
226
227
228
229 public long copyTo(OutputStream output) throws IOException {
230 checkNotNull(output);
231
232 Closer closer = Closer.create();
233 try {
234 InputStream in = closer.register(openStream());
235 return ByteStreams.copy(in, output);
236 } catch (Throwable e) {
237 throw closer.rethrow(e);
238 } finally {
239 closer.close();
240 }
241 }
242
243
244
245
246
247
248
249 public long copyTo(ByteSink sink) throws IOException {
250 checkNotNull(sink);
251
252 Closer closer = Closer.create();
253 try {
254 InputStream in = closer.register(openStream());
255 OutputStream out = closer.register(sink.openStream());
256 return ByteStreams.copy(in, out);
257 } catch (Throwable e) {
258 throw closer.rethrow(e);
259 } finally {
260 closer.close();
261 }
262 }
263
264
265
266
267
268
269 public byte[] read() throws IOException {
270 Closer closer = Closer.create();
271 try {
272 InputStream in = closer.register(openStream());
273 return ByteStreams.toByteArray(in);
274 } catch (Throwable e) {
275 throw closer.rethrow(e);
276 } finally {
277 closer.close();
278 }
279 }
280
281
282
283
284
285
286
287
288
289
290 @Beta
291 public <T> T read(ByteProcessor<T> processor) throws IOException {
292 checkNotNull(processor);
293
294 Closer closer = Closer.create();
295 try {
296 InputStream in = closer.register(openStream());
297 return ByteStreams.readBytes(in, processor);
298 } catch (Throwable e) {
299 throw closer.rethrow(e);
300 } finally {
301 closer.close();
302 }
303 }
304
305
306
307
308
309
310 public HashCode hash(HashFunction hashFunction) throws IOException {
311 Hasher hasher = hashFunction.newHasher();
312 copyTo(Funnels.asOutputStream(hasher));
313 return hasher.hash();
314 }
315
316
317
318
319
320
321
322
323 public boolean contentEquals(ByteSource other) throws IOException {
324 checkNotNull(other);
325
326 byte[] buf1 = new byte[BUF_SIZE];
327 byte[] buf2 = new byte[BUF_SIZE];
328
329 Closer closer = Closer.create();
330 try {
331 InputStream in1 = closer.register(openStream());
332 InputStream in2 = closer.register(other.openStream());
333 while (true) {
334 int read1 = ByteStreams.read(in1, buf1, 0, BUF_SIZE);
335 int read2 = ByteStreams.read(in2, buf2, 0, BUF_SIZE);
336 if (read1 != read2 || !Arrays.equals(buf1, buf2)) {
337 return false;
338 } else if (read1 != BUF_SIZE) {
339 return true;
340 }
341 }
342 } catch (Throwable e) {
343 throw closer.rethrow(e);
344 } finally {
345 closer.close();
346 }
347 }
348
349
350
351
352
353
354
355
356
357
358
359
360 public static ByteSource concat(Iterable<? extends ByteSource> sources) {
361 return new ConcatenatedByteSource(sources);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382 public static ByteSource concat(Iterator<? extends ByteSource> sources) {
383 return concat(ImmutableList.copyOf(sources));
384 }
385
386
387
388
389
390
391
392
393
394
395
396
397
398 public static ByteSource concat(ByteSource... sources) {
399 return concat(ImmutableList.copyOf(sources));
400 }
401
402
403
404
405
406
407
408 public static ByteSource wrap(byte[] b) {
409 return new ByteArrayByteSource(b);
410 }
411
412
413
414
415
416
417 public static ByteSource empty() {
418 return EmptyByteSource.INSTANCE;
419 }
420
421
422
423
424
425 private final class AsCharSource extends CharSource {
426
427 private final Charset charset;
428
429 private AsCharSource(Charset charset) {
430 this.charset = checkNotNull(charset);
431 }
432
433 @Override
434 public Reader openStream() throws IOException {
435 return new InputStreamReader(ByteSource.this.openStream(), charset);
436 }
437
438 @Override
439 public String toString() {
440 return ByteSource.this.toString() + ".asCharSource(" + charset + ")";
441 }
442 }
443
444
445
446
447 private final class SlicedByteSource extends ByteSource {
448
449 private final long offset;
450 private final long length;
451
452 private SlicedByteSource(long offset, long length) {
453 checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
454 checkArgument(length >= 0, "length (%s) may not be negative", length);
455 this.offset = offset;
456 this.length = length;
457 }
458
459 @Override
460 public InputStream openStream() throws IOException {
461 return sliceStream(ByteSource.this.openStream());
462 }
463
464 @Override
465 public InputStream openBufferedStream() throws IOException {
466 return sliceStream(ByteSource.this.openBufferedStream());
467 }
468
469 private InputStream sliceStream(InputStream in) throws IOException {
470 if (offset > 0) {
471 try {
472 ByteStreams.skipFully(in, offset);
473 } catch (Throwable e) {
474 Closer closer = Closer.create();
475 closer.register(in);
476 try {
477 throw closer.rethrow(e);
478 } finally {
479 closer.close();
480 }
481 }
482 }
483 return ByteStreams.limit(in, length);
484 }
485
486 @Override
487 public ByteSource slice(long offset, long length) {
488 checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
489 checkArgument(length >= 0, "length (%s) may not be negative", length);
490 long maxLength = this.length - offset;
491 return ByteSource.this.slice(this.offset + offset, Math.min(length, maxLength));
492 }
493
494 @Override
495 public boolean isEmpty() throws IOException {
496 return length == 0 || super.isEmpty();
497 }
498
499 @Override
500 public String toString() {
501 return ByteSource.this.toString() + ".slice(" + offset + ", " + length + ")";
502 }
503 }
504
505 private static class ByteArrayByteSource extends ByteSource {
506
507 protected final byte[] bytes;
508
509 protected ByteArrayByteSource(byte[] bytes) {
510 this.bytes = checkNotNull(bytes);
511 }
512
513 @Override
514 public InputStream openStream() {
515 return new ByteArrayInputStream(bytes);
516 }
517
518 @Override
519 public InputStream openBufferedStream() throws IOException {
520 return openStream();
521 }
522
523 @Override
524 public boolean isEmpty() {
525 return bytes.length == 0;
526 }
527
528 @Override
529 public long size() {
530 return bytes.length;
531 }
532
533 @Override
534 public byte[] read() {
535 return bytes.clone();
536 }
537
538 @Override
539 public long copyTo(OutputStream output) throws IOException {
540 output.write(bytes);
541 return bytes.length;
542 }
543
544 @Override
545 public <T> T read(ByteProcessor<T> processor) throws IOException {
546 processor.processBytes(bytes, 0, bytes.length);
547 return processor.getResult();
548 }
549
550 @Override
551 public HashCode hash(HashFunction hashFunction) throws IOException {
552 return hashFunction.hashBytes(bytes);
553 }
554
555
556
557 @Override
558 public String toString() {
559 return "ByteSource.wrap("
560 + Ascii.truncate(BaseEncoding.base16().encode(bytes), 30, "...") + ")";
561 }
562 }
563
564 private static final class EmptyByteSource extends ByteArrayByteSource {
565
566 private static final EmptyByteSource INSTANCE = new EmptyByteSource();
567
568 private EmptyByteSource() {
569 super(new byte[0]);
570 }
571
572 @Override
573 public CharSource asCharSource(Charset charset) {
574 checkNotNull(charset);
575 return CharSource.empty();
576 }
577
578 @Override
579 public byte[] read() {
580 return bytes;
581 }
582
583 @Override
584 public String toString() {
585 return "ByteSource.empty()";
586 }
587 }
588
589 private static final class ConcatenatedByteSource extends ByteSource {
590
591 private final Iterable<? extends ByteSource> sources;
592
593 ConcatenatedByteSource(Iterable<? extends ByteSource> sources) {
594 this.sources = checkNotNull(sources);
595 }
596
597 @Override
598 public InputStream openStream() throws IOException {
599 return new MultiInputStream(sources.iterator());
600 }
601
602 @Override
603 public boolean isEmpty() throws IOException {
604 for (ByteSource source : sources) {
605 if (!source.isEmpty()) {
606 return false;
607 }
608 }
609 return true;
610 }
611
612 @Override
613 public long size() throws IOException {
614 long result = 0L;
615 for (ByteSource source : sources) {
616 result += source.size();
617 }
618 return result;
619 }
620
621 @Override
622 public String toString() {
623 return "ByteSource.concat(" + sources + ")";
624 }
625 }
626 }