1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.io.build;
19
20 import java.io.ByteArrayInputStream;
21 import java.io.Closeable;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.RandomAccessFile;
31 import java.io.Reader;
32 import java.io.Writer;
33 import java.net.URI;
34 import java.net.URLConnection;
35 import java.nio.channels.Channel;
36 import java.nio.channels.Channels;
37 import java.nio.channels.FileChannel;
38 import java.nio.channels.ReadableByteChannel;
39 import java.nio.channels.SeekableByteChannel;
40 import java.nio.channels.WritableByteChannel;
41 import java.nio.charset.Charset;
42 import java.nio.file.Files;
43 import java.nio.file.OpenOption;
44 import java.nio.file.Path;
45 import java.nio.file.Paths;
46 import java.nio.file.StandardOpenOption;
47 import java.time.Duration;
48 import java.util.Arrays;
49 import java.util.Objects;
50 import java.util.stream.Stream;
51
52 import org.apache.commons.io.Charsets;
53 import org.apache.commons.io.IORandomAccessFile;
54 import org.apache.commons.io.IOUtils;
55 import org.apache.commons.io.RandomAccessFileMode;
56 import org.apache.commons.io.RandomAccessFiles;
57 import org.apache.commons.io.channels.ByteArraySeekableByteChannel;
58 import org.apache.commons.io.input.BufferedFileChannelInputStream;
59 import org.apache.commons.io.input.CharSequenceInputStream;
60 import org.apache.commons.io.input.CharSequenceReader;
61 import org.apache.commons.io.input.ReaderInputStream;
62 import org.apache.commons.io.output.RandomAccessFileOutputStream;
63 import org.apache.commons.io.output.WriterOutputStream;
64
65 /**
66 * Abstracts and wraps an <em>origin</em> for builders, where an origin is a {@code byte[]}, {@link Channel}, {@link CharSequence}, {@link File},
67 * {@link InputStream}, {@link IORandomAccessFile}, {@link OutputStream}, {@link Path}, {@link RandomAccessFile}, {@link Reader}, {@link URI},
68 * or {@link Writer}.
69 * <p>
70 * An origin represents where bytes/characters come from or go to. Concrete subclasses
71 * expose only the operations that make sense for the underlying source or sink; invoking an unsupported operation
72 * results in {@link UnsupportedOperationException} (see, for example, {@link #getFile()} and {@link #getPath()}).
73 * </p>
74 * <p>
75 * An instance doesn't own its origin, it holds on to it to allow conversions. There are two use cases related to resource management for a Builder:
76 * </p>
77 * <ul>
78 * <li>
79 * A client allocates a {@linkplain Closeable} (or {@linkplain AutoCloseable}) resource, creates a Builder, and gives the Builder that resource by calling a
80 * setter method. No matter what happens next, the client is responsible for releasing the resource ({@code Closeable.close()}). In this case, the origin
81 * wraps but doesn't own the closeable resource. There is no transfer of ownership.
82 * </li>
83 * <li>
84 * A client creates a Builder and gives it a non-Closeable object, like a File or a Path. The client then calls the Builder's factory method
85 * (like {@linkplain #get()}), and that call returns a Closeable or a resource that requires releasing in some other way. No matter what happens next, the
86 * client is responsible for releasing that resource. In this case, the origin doesn't wrap a closeable resource.
87 * </li>
88 * </ul>
89 * <p>
90 * In both cases, the client causes the allocation and is responsible for releasing the resource.
91 * </p>
92 * <p>
93 * The table below summarizes which views and conversions are supported for each origin type.
94 * Column headers show the target view; cells indicate whether that view is available from the origin in that row.
95 * </p>
96 *
97 * <table>
98 * <caption>Supported Conversions</caption>
99 * <thead>
100 * <tr>
101 * <th>Origin Type</th>
102 * <th>byte[]</th>
103 * <th>CS</th>
104 * <th>File</th>
105 * <th>Path</th>
106 * <th>RAF</th>
107 * <th>IS</th>
108 * <th>Reader</th>
109 * <th>RBC</th>
110 * <th>OS</th>
111 * <th>Writer</th>
112 * <th>WBC</th>
113 * <th>Channel type<sup>2</sup></th>
114 * </tr>
115 * </thead>
116 * <tbody>
117 * <tr>
118 * <td>byte[]</td>
119 * <td>✔</td>
120 * <td>✔</td>
121 * <td>✖</td>
122 * <td>✖</td>
123 * <td>✖</td>
124 * <td>✔</td>
125 * <td>✔</td>
126 * <td>✔</td>
127 * <td>✖</td>
128 * <td>✖</td>
129 * <td>✖</td>
130 * <td>SBC</td>
131 * </tr>
132 * <tr>
133 * <td>{@link CharSequence} (CS)</td>
134 * <td>✔</td>
135 * <td>✔</td>
136 * <td>✖</td>
137 * <td>✖</td>
138 * <td>✖</td>
139 * <td>✔<sup>1</sup></td>
140 * <td>✔</td>
141 * <td>✔<sup>1</sup></td>
142 * <td>✖</td>
143 * <td>✖</td>
144 * <td>✖</td>
145 * <td>SBC</td>
146 * </tr>
147 * <tr>
148 * <td>{@link File}</td>
149 * <td>✔</td>
150 * <td>✔</td>
151 * <td>✔</td>
152 * <td>✔</td>
153 * <td>✔</td>
154 * <td>✔</td>
155 * <td>✔</td>
156 * <td>✔</td>
157 * <td>✔</td>
158 * <td>✔</td>
159 * <td>✔</td>
160 * <td>FC</td>
161 * </tr>
162 * <tr>
163 * <td>{@link Path}</td>
164 * <td>✔</td>
165 * <td>✔</td>
166 * <td>✔</td>
167 * <td>✔</td>
168 * <td>✔</td>
169 * <td>✔</td>
170 * <td>✔</td>
171 * <td>✔</td>
172 * <td>✔</td>
173 * <td>✔</td>
174 * <td>✔</td>
175 * <td>FC</td>
176 * </tr>
177 * <tr>
178 * <td>{@link IORandomAccessFile}</td>
179 * <td>✔</td>
180 * <td>✔</td>
181 * <td>✔</td>
182 * <td>✔</td>
183 * <td>✔</td>
184 * <td>✔</td>
185 * <td>✔</td>
186 * <td>✔</td>
187 * <td>✔</td>
188 * <td>✔</td>
189 * <td>✔</td>
190 * <td>FC</td>
191 * </tr>
192 * <tr>
193 * <td>{@link RandomAccessFile} (RAF)</td>
194 * <td>✔</td>
195 * <td>✔</td>
196 * <td>✖</td>
197 * <td>✖</td>
198 * <td>✔</td>
199 * <td>✔</td>
200 * <td>✔</td>
201 * <td>✔</td>
202 * <td>✔</td>
203 * <td>✔</td>
204 * <td>✔</td>
205 * <td>FC</td>
206 * </tr>
207 * <tr>
208 * <td>{@link InputStream} (IS)</td>
209 * <td>✔</td>
210 * <td>✔</td>
211 * <td>✖</td>
212 * <td>✖</td>
213 * <td>✖</td>
214 * <td>✔</td>
215 * <td>✔</td>
216 * <td>✔</td>
217 * <td>✖</td>
218 * <td>✖</td>
219 * <td>✖</td>
220 * <td>RBC</td>
221 * </tr>
222 * <tr>
223 * <td>{@link Reader}</td>
224 * <td>✔</td>
225 * <td>✔</td>
226 * <td>✖</td>
227 * <td>✖</td>
228 * <td>✖</td>
229 * <td>✔<sup>1</sup></td>
230 * <td>✔</td>
231 * <td>✔<sup>1</sup></td>
232 * <td>✖</td>
233 * <td>✖</td>
234 * <td>✖</td>
235 * <td>RBC</td>
236 * </tr>
237 * <tr>
238 * <td>{@link ReadableByteChannel} (RBC)</td>
239 * <td>✔</td>
240 * <td>✔</td>
241 * <td>✖</td>
242 * <td>✖</td>
243 * <td>✖</td>
244 * <td>✔</td>
245 * <td>✔</td>
246 * <td>✔</td>
247 * <td>✖</td>
248 * <td>✖</td>
249 * <td>✖</td>
250 * <td>RBC</td>
251 * </tr>
252 * <tr>
253 * <td>{@link OutputStream} (OS)</td>
254 * <td>✖</td>
255 * <td>✖</td>
256 * <td>✖</td>
257 * <td>✖</td>
258 * <td>✖</td>
259 * <td>✖</td>
260 * <td>✖</td>
261 * <td>✖</td>
262 * <td>✔</td>
263 * <td>✔</td>
264 * <td>✔</td>
265 * <td>WBC</td>
266 * </tr>
267 * <tr>
268 * <td>{@link Writer}</td>
269 * <td>✖</td>
270 * <td>✖</td>
271 * <td>✖</td>
272 * <td>✖</td>
273 * <td>✖</td>
274 * <td>✖</td>
275 * <td>✖</td>
276 * <td>✖</td>
277 * <td>✔<sup>1</sup></td>
278 * <td>✔</td>
279 * <td>✔<sup>1</sup></td>
280 * <td>WBC</td>
281 * </tr>
282 * <tr>
283 * <td>{@link WritableByteChannel} (WBC)</td>
284 * <td>✖</td>
285 * <td>✖</td>
286 * <td>✖</td>
287 * <td>✖</td>
288 * <td>✖</td>
289 * <td>✖</td>
290 * <td>✖</td>
291 * <td>✖</td>
292 * <td>✔</td>
293 * <td>✔</td>
294 * <td>✔</td>
295 * <td>WBC</td>
296 * </tr>
297 * <tr>
298 * <td>{@link URI} (FileSystem)</td>
299 * <td>✔</td>
300 * <td>✔</td>
301 * <td>✔</td>
302 * <td>✔</td>
303 * <td>✔</td>
304 * <td>✔</td>
305 * <td>✔</td>
306 * <td>✔</td>
307 * <td>✔</td>
308 * <td>✔</td>
309 * <td>✔</td>
310 * <td>FC</td>
311 * </tr>
312 * <tr>
313 * <td>{@link URI} (http/https)</td>
314 * <td>✔</td>
315 * <td>✔</td>
316 * <td>✖</td>
317 * <td>✖</td>
318 * <td>✖</td>
319 * <td>✔</td>
320 * <td>✔</td>
321 * <td>✔</td>
322 * <td>✖</td>
323 * <td>✖</td>
324 * <td>✖</td>
325 * <td>RBC</td>
326 * </tr>
327 * </tbody>
328 * </table>
329 *
330 * <p><strong>Legend</strong></p>
331 * <ul>
332 * <li>✔ = Supported</li>
333 * <li>✖ = Not supported (throws {@link UnsupportedOperationException})</li>
334 * <li><sup>1</sup> = Characters are converted to bytes using the default {@link Charset}.</li>
335 * <li><sup>2</sup> Minimum channel type provided by the origin:
336 * <ul>
337 * <li>RBC = {@linkplain ReadableByteChannel}</li>
338 * <li>WBC = {@linkplain WritableByteChannel}</li>
339 * <li>SBC = {@linkplain SeekableByteChannel}</li>
340 * <li>FC = {@linkplain FileChannel}</li>
341 * </ul>
342 * The exact channel type may be a subtype of the minimum shown.
343 * </li>
344 * </ul>
345 *
346 * @param <T> the type produced by the builder.
347 * @param <B> the concrete builder subclass type.
348 * @since 2.12.0
349 */
350 public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {
351
352 /**
353 * A {@link RandomAccessFile} origin.
354 * <p>
355 * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
356 * instead.
357 * </p>
358 *
359 * @param <T> the type of instances to build.
360 * @param <B> the type of builder subclass.
361 */
362 public abstract static class AbstractRandomAccessFileOrigin<T extends RandomAccessFile, B extends AbstractRandomAccessFileOrigin<T, B>>
363 extends AbstractOrigin<T, B> {
364
365 /**
366 * A {@link RandomAccessFile} origin.
367 * <p>
368 * Starting from this origin, you can everything except a Path and a File.
369 * </p>
370 *
371 * @param origin The origin, not null.
372 * @throws NullPointerException if {@code origin} is {@code null}.
373 */
374 public AbstractRandomAccessFileOrigin(final T origin) {
375 super(origin);
376 }
377
378 @Override
379 public byte[] getByteArray() throws IOException {
380 final long longLen = origin.length();
381 if (longLen > Integer.MAX_VALUE) {
382 throw new IllegalStateException("Origin too large.");
383 }
384 return RandomAccessFiles.read(origin, 0, (int) longLen);
385 }
386
387 @Override
388 public byte[] getByteArray(final long position, final int length) throws IOException {
389 return RandomAccessFiles.read(origin, position, length);
390 }
391
392 @Override
393 protected Channel getChannel(final OpenOption... options) throws IOException {
394 return getRandomAccessFile(options).getChannel();
395 }
396
397 @Override
398 public CharSequence getCharSequence(final Charset charset) throws IOException {
399 return new String(getByteArray(), charset);
400 }
401
402 @SuppressWarnings("resource")
403 @Override
404 public InputStream getInputStream(final OpenOption... options) throws IOException {
405 return BufferedFileChannelInputStream.builder().setFileChannel(origin.getChannel()).get();
406 }
407
408 @Override
409 public OutputStream getOutputStream(final OpenOption... options) throws IOException {
410 return RandomAccessFileOutputStream.builder().setRandomAccessFile(origin).get();
411 }
412
413 @Override
414 public T getRandomAccessFile(final OpenOption... openOption) {
415 // No conversion
416 return get();
417 }
418
419 @Override
420 public Reader getReader(final Charset charset) throws IOException {
421 return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
422 }
423
424 @Override
425 public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
426 return new OutputStreamWriter(getOutputStream(options), Charsets.toCharset(charset));
427 }
428
429 @Override
430 public long size() throws IOException {
431 return origin.length();
432 }
433 }
434
435 /**
436 * A {@code byte[]} origin.
437 */
438 public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {
439
440 /**
441 * Constructs a new instance for the given origin.
442 *
443 * @param origin The origin, not null.
444 * @throws NullPointerException if {@code origin} is {@code null}.
445 */
446 public ByteArrayOrigin(final byte[] origin) {
447 super(origin);
448 }
449
450 /**
451 * {@inheritDoc}
452 *
453 * <p>
454 * No conversion should occur when calling this method.
455 * </p>
456 */
457 @Override
458 public byte[] getByteArray() {
459 // No conversion
460 return get();
461 }
462
463 @Override
464 protected Channel getChannel(final OpenOption... options) throws IOException {
465 for (final OpenOption option : options) {
466 if (option == StandardOpenOption.WRITE) {
467 throw new UnsupportedOperationException("Only READ is supported for byte[] origins: " + Arrays.toString(options));
468 }
469 }
470 return ByteArraySeekableByteChannel.wrap(getByteArray());
471 }
472
473 /**
474 * {@inheritDoc}
475 * <p>
476 * The {@code options} parameter is ignored since a {@code byte[]} does not need an {@link OpenOption} to be read.
477 * </p>
478 */
479 @Override
480 public InputStream getInputStream(final OpenOption... options) throws IOException {
481 return new ByteArrayInputStream(origin);
482 }
483
484 @Override
485 public Reader getReader(final Charset charset) throws IOException {
486 return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
487 }
488
489 @Override
490 public long size() throws IOException {
491 return origin.length;
492 }
493
494 }
495
496 /**
497 * A {@link Channel} origin.
498 *
499 * @since 2.21.0
500 */
501 public static class ChannelOrigin extends AbstractOrigin<Channel, ChannelOrigin> {
502
503 /**
504 * Constructs a new instance for the given origin.
505 *
506 * @param origin The origin, not null.
507 * @throws NullPointerException if {@code origin} is {@code null}.
508 */
509 public ChannelOrigin(final Channel origin) {
510 super(origin);
511 }
512
513 @Override
514 public byte[] getByteArray() throws IOException {
515 return IOUtils.toByteArray(getInputStream());
516 }
517
518 /**
519 * {@inheritDoc}
520 *
521 * <p>
522 * No conversion should occur when calling this method.
523 * </p>
524 */
525 @Override
526 protected Channel getChannel(final OpenOption... options) throws IOException {
527 // No conversion
528 return get();
529 }
530
531 @Override
532 public InputStream getInputStream(final OpenOption... options) throws IOException {
533 return Channels.newInputStream(getChannel(ReadableByteChannel.class, options));
534 }
535
536 @Override
537 public OutputStream getOutputStream(final OpenOption... options) throws IOException {
538 return Channels.newOutputStream(getChannel(WritableByteChannel.class, options));
539 }
540
541 @Override
542 public Reader getReader(final Charset charset) throws IOException {
543 return Channels.newReader(
544 getChannel(ReadableByteChannel.class),
545 Charsets.toCharset(charset).newDecoder(),
546 -1);
547 }
548
549 @Override
550 public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
551 return Channels.newWriter(getChannel(WritableByteChannel.class, options), Charsets.toCharset(charset).newEncoder(), -1);
552 }
553
554 @Override
555 public long size() throws IOException {
556 if (origin instanceof SeekableByteChannel) {
557 return ((SeekableByteChannel) origin).size();
558 }
559 throw unsupportedOperation("size");
560 }
561 }
562
563 /**
564 * A {@link CharSequence} origin.
565 */
566 public static class CharSequenceOrigin extends AbstractOrigin<CharSequence, CharSequenceOrigin> {
567
568 /**
569 * Constructs a new instance for the given origin.
570 *
571 * @param origin The origin, not null.
572 * @throws NullPointerException if {@code origin} is {@code null}.
573 */
574 public CharSequenceOrigin(final CharSequence origin) {
575 super(origin);
576 }
577
578 @Override
579 public byte[] getByteArray() {
580 // TODO Pass in a Charset? Consider if call sites actually need this.
581 return origin.toString().getBytes(Charset.defaultCharset());
582 }
583
584 @Override
585 protected Channel getChannel(final OpenOption... options) throws IOException {
586 for (final OpenOption option : options) {
587 if (option == StandardOpenOption.WRITE) {
588 throw new UnsupportedOperationException("Only READ is supported for CharSequence origins: " + Arrays.toString(options));
589 }
590 }
591 return ByteArraySeekableByteChannel.wrap(getByteArray());
592 }
593
594 /**
595 * {@inheritDoc}
596 * <p>
597 * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
598 * </p>
599 * <p>
600 * No conversion should occur when calling this method.
601 * </p>
602 */
603 @Override
604 public CharSequence getCharSequence(final Charset charset) {
605 // No conversion
606 return get();
607 }
608
609 /**
610 * {@inheritDoc}
611 * <p>
612 * The {@code options} parameter is ignored since a {@link CharSequence} does not need an {@link OpenOption} to be read.
613 * </p>
614 */
615 @Override
616 public InputStream getInputStream(final OpenOption... options) throws IOException {
617 // TODO Pass in a Charset? Consider if call sites actually need this.
618 return CharSequenceInputStream.builder().setCharSequence(getCharSequence(Charset.defaultCharset())).get();
619 }
620
621 /**
622 * {@inheritDoc}
623 * <p>
624 * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
625 * </p>
626 */
627 @Override
628 public Reader getReader(final Charset charset) throws IOException {
629 return new CharSequenceReader(get());
630 }
631
632 @Override
633 public long size() throws IOException {
634 return origin.length();
635 }
636
637 }
638
639 /**
640 * A {@link File} origin.
641 * <p>
642 * Starting from this origin, you can get a byte array, a file, an input stream, an output stream, a path, a reader, and a writer.
643 * </p>
644 */
645 public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
646
647 /**
648 * Constructs a new instance for the given origin.
649 *
650 * @param origin The origin, not null.
651 * @throws NullPointerException if {@code origin} is {@code null}.
652 */
653 public FileOrigin(final File origin) {
654 super(origin);
655 }
656
657 @Override
658 public byte[] getByteArray(final long position, final int length) throws IOException {
659 try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
660 return RandomAccessFiles.read(raf, position, length);
661 }
662 }
663
664 @Override
665 protected Channel getChannel(final OpenOption... options) throws IOException {
666 return Files.newByteChannel(getPath(), options);
667 }
668
669 /**
670 * {@inheritDoc}
671 *
672 * <p>
673 * No conversion should occur when calling this method.
674 * </p>
675 */
676 @Override
677 public File getFile() {
678 // No conversion
679 return get();
680 }
681
682 @Override
683 public Path getPath() {
684 return get().toPath();
685 }
686 }
687
688 /**
689 * An {@link InputStream} origin.
690 * <p>
691 * This origin cannot provide some of the other aspects.
692 * </p>
693 */
694 public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
695
696 /**
697 * Constructs a new instance for the given origin.
698 *
699 * @param origin The origin, not null.
700 * @throws NullPointerException if {@code origin} is {@code null}.
701 */
702 public InputStreamOrigin(final InputStream origin) {
703 super(origin);
704 }
705
706 @Override
707 public byte[] getByteArray() throws IOException {
708 return IOUtils.toByteArray(origin);
709 }
710
711 @Override
712 protected Channel getChannel(final OpenOption... options) throws IOException {
713 return Channels.newChannel(getInputStream(options));
714 }
715
716 /**
717 * {@inheritDoc}
718 * <p>
719 * The {@code options} parameter is ignored since a {@link InputStream} does not need an {@link OpenOption} to be read.
720 * </p>
721 * <p>
722 * No conversion should occur when calling this method.
723 * </p>
724 */
725 @Override
726 public InputStream getInputStream(final OpenOption... options) {
727 // No conversion
728 return get();
729 }
730
731 @Override
732 public Reader getReader(final Charset charset) throws IOException {
733 return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
734 }
735
736 @Override
737 public long size() throws IOException {
738 if (origin instanceof FileInputStream) {
739 return ((FileInputStream) origin).getChannel().size();
740 }
741 throw unsupportedOperation("size");
742 }
743 }
744
745 /**
746 * An {@link IORandomAccessFile} origin.
747 *
748 * @since 2.18.0
749 */
750 public static class IORandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<IORandomAccessFile, IORandomAccessFileOrigin> {
751
752 /**
753 * A {@link RandomAccessFile} origin.
754 *
755 * @param origin The origin, not null.
756 */
757 public IORandomAccessFileOrigin(final IORandomAccessFile origin) {
758 super(origin);
759 }
760
761 @SuppressWarnings("resource")
762 @Override
763 public File getFile() {
764 return get().getFile();
765 }
766
767 @Override
768 public Path getPath() {
769 return getFile().toPath();
770 }
771
772 }
773
774 /**
775 * An {@link OutputStream} origin.
776 * <p>
777 * This origin cannot provide some of the other aspects.
778 * </p>
779 */
780 public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
781
782 /**
783 * Constructs a new instance for the given origin.
784 *
785 * @param origin The origin, not null.
786 * @throws NullPointerException if {@code origin} is {@code null}.
787 */
788 public OutputStreamOrigin(final OutputStream origin) {
789 super(origin);
790 }
791
792 @Override
793 protected Channel getChannel(final OpenOption... options) throws IOException {
794 return Channels.newChannel(getOutputStream(options));
795 }
796
797 /**
798 * {@inheritDoc}
799 * <p>
800 * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
801 * </p>
802 * <p>
803 * No conversion should occur when calling this method.
804 * </p>
805 */
806 @Override
807 public OutputStream getOutputStream(final OpenOption... options) {
808 // No conversion
809 return get();
810 }
811
812 /**
813 * {@inheritDoc}
814 * <p>
815 * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
816 * </p>
817 */
818 @Override
819 public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
820 return new OutputStreamWriter(origin, Charsets.toCharset(charset));
821 }
822 }
823
824 /**
825 * A {@link Path} origin.
826 * <p>
827 * Starting from this origin, you can get a byte array, a file, an input stream, an output stream, a path, a reader, and a writer.
828 * </p>
829 */
830 public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
831
832 /**
833 * Constructs a new instance for the given origin.
834 *
835 * @param origin The origin, not null.
836 * @throws NullPointerException if {@code origin} is {@code null}.
837 */
838 public PathOrigin(final Path origin) {
839 super(origin);
840 }
841
842 @Override
843 public byte[] getByteArray(final long position, final int length) throws IOException {
844 return RandomAccessFileMode.READ_ONLY.apply(origin, raf -> RandomAccessFiles.read(raf, position, length));
845 }
846
847 @Override
848 protected Channel getChannel(final OpenOption... options) throws IOException {
849 return Files.newByteChannel(getPath(), options);
850 }
851
852 @Override
853 public File getFile() {
854 return get().toFile();
855 }
856
857 /**
858 * {@inheritDoc}
859 *
860 * <p>
861 * No conversion should occur when calling this method.
862 * </p>
863 */
864 @Override
865 public Path getPath() {
866 // No conversion
867 return get();
868 }
869 }
870
871 /**
872 * A {@link RandomAccessFile} origin.
873 * <p>
874 * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
875 * instead.
876 * </p>
877 */
878 public static class RandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<RandomAccessFile, RandomAccessFileOrigin> {
879
880 /**
881 * A {@link RandomAccessFile} origin.
882 * <p>
883 * Starting from this origin, you can everything except a Path and a File.
884 * </p>
885 *
886 * @param origin The origin, not null.
887 */
888 public RandomAccessFileOrigin(final RandomAccessFile origin) {
889 super(origin);
890 }
891
892 }
893
894 /**
895 * A {@link Reader} origin.
896 * <p>
897 * This origin cannot provide conversions to other aspects.
898 * </p>
899 */
900 public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
901
902 /**
903 * Constructs a new instance for the given origin.
904 *
905 * @param origin The origin, not null.
906 * @throws NullPointerException if {@code origin} is {@code null}.
907 */
908 public ReaderOrigin(final Reader origin) {
909 super(origin);
910 }
911
912 @Override
913 public byte[] getByteArray() throws IOException {
914 // TODO Pass in a Charset? Consider if call sites actually need this.
915 return IOUtils.toByteArray(origin, Charset.defaultCharset());
916 }
917
918 @Override
919 protected Channel getChannel(final OpenOption... options) throws IOException {
920 return Channels.newChannel(getInputStream());
921 }
922
923 /**
924 * {@inheritDoc}
925 * <p>
926 * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
927 * </p>
928 */
929 @Override
930 public CharSequence getCharSequence(final Charset charset) throws IOException {
931 return IOUtils.toString(origin);
932 }
933
934 /**
935 * {@inheritDoc}
936 * <p>
937 * The {@code options} parameter is ignored since a {@link Reader} does not need an {@link OpenOption} to be read.
938 * </p>
939 */
940 @Override
941 public InputStream getInputStream(final OpenOption... options) throws IOException {
942 // TODO Pass in a Charset? Consider if call sites actually need this.
943 return ReaderInputStream.builder().setReader(origin).setCharset(Charset.defaultCharset()).get();
944 }
945
946 /**
947 * {@inheritDoc}
948 * <p>
949 * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
950 * </p>
951 * <p>
952 * No conversion should occur when calling this method.
953 * </p>
954 */
955 @Override
956 public Reader getReader(final Charset charset) throws IOException {
957 // No conversion
958 return get();
959 }
960 }
961
962 /**
963 * A {@link URI} origin.
964 */
965 public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
966
967 /**
968 * Options for connect and read from a URI.
969 *
970 * @since 2.22.0
971 */
972 public static final class URIOpenOption implements OpenOption {
973
974 /**
975 * Builds URIOpenOption.
976 */
977 public static class Builder extends AbstractSupplier<URIOpenOption, Builder> {
978
979 private Duration connectTimeout;
980 private Duration readTimeout;
981
982 /**
983 * Constructs a new instance.
984 */
985 public Builder() {
986 // empty
987 }
988
989 @Override
990 public URIOpenOption get() {
991 return new URIOpenOption(this);
992 }
993
994 /**
995 * Sets the connect timeout duration.
996 *
997 * @param connectTimeout the connect timeout duration.
998 * @return {@code this instance}.
999 */
1000 public Builder setConnectTimeout(final Duration connectTimeout) {
1001 this.connectTimeout = connectTimeout;
1002 return asThis();
1003 }
1004
1005 /**
1006 * Sets the read timeout duration.
1007 *
1008 * @param readTimeout the read timeout duration.
1009 * @return {@code this instance}.
1010 */
1011 public Builder setReadTimeout(final Duration readTimeout) {
1012 this.readTimeout = readTimeout;
1013 return asThis();
1014 }
1015 }
1016
1017 /**
1018 * Creates a new builder.
1019 *
1020 * @return a new builder.
1021 */
1022 public static Builder builder() {
1023 return new Builder();
1024 }
1025
1026 private final Duration connectTimeout;
1027
1028 private final Duration readTimeout;
1029
1030 private URIOpenOption(final Builder builder) {
1031 connectTimeout = builder.connectTimeout;
1032 readTimeout = builder.readTimeout;
1033 }
1034
1035 @Override
1036 public String toString() {
1037 return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]";
1038 }
1039 }
1040
1041 private static final String SCHEME_HTTPS = "https";
1042 private static final String SCHEME_HTTP = "http";
1043
1044 /**
1045 * Constructs a new instance for the given origin.
1046 *
1047 * @param origin The origin, not null.
1048 * @throws NullPointerException if {@code origin} is {@code null}.
1049 */
1050 public URIOrigin(final URI origin) {
1051 super(origin);
1052 }
1053
1054 /**
1055 * {@inheritDoc}
1056 *
1057 * @see URIOpenOption
1058 */
1059 @Override
1060 protected Channel getChannel(final OpenOption... options) throws IOException {
1061 final URI uri = get();
1062 final String scheme = uri.getScheme();
1063 if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
1064 return Channels.newChannel(getInputStream(uri, options));
1065 }
1066 return Files.newByteChannel(getPath(), options);
1067 }
1068
1069 @Override
1070 public File getFile() {
1071 return getPath().toFile();
1072 }
1073
1074 /**
1075 * {@inheritDoc}
1076 * <p>
1077 * Set timeouts with a {@link URIOpenOption}.
1078 * </p>
1079 *
1080 * @see URIOpenOption
1081 * @see URLConnection#setConnectTimeout(int)
1082 * @see URLConnection#setReadTimeout(int)
1083 */
1084 @Override
1085 public InputStream getInputStream(final OpenOption... options) throws IOException {
1086 final URI uri = get();
1087 final String scheme = uri.getScheme();
1088 if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
1089 return getInputStream(uri, options);
1090 }
1091 return Files.newInputStream(getPath(), options);
1092 }
1093
1094 private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException {
1095 final URLConnection connection = uri.toURL().openConnection();
1096 if (options != null) {
1097 Stream.of(options).forEach(option -> {
1098 if (option instanceof URIOpenOption) {
1099 final URIOpenOption connOption = (URIOpenOption) option;
1100 if (connOption.connectTimeout != null) {
1101 connection.setConnectTimeout(toMillis(connOption.connectTimeout));
1102 }
1103 if (connOption.readTimeout != null) {
1104 connection.setReadTimeout(toMillis(connOption.readTimeout));
1105 }
1106 }
1107 });
1108 }
1109 return connection.getInputStream();
1110 }
1111
1112 @Override
1113 public Path getPath() {
1114 return Paths.get(get());
1115 }
1116
1117 private int toMillis(final Duration duration) {
1118 return Math.toIntExact(duration.toMillis());
1119 }
1120 }
1121
1122 /**
1123 * A {@link Writer} origin.
1124 * <p>
1125 * This origin cannot provide conversions to other aspects.
1126 * </p>
1127 */
1128 public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
1129
1130 /**
1131 * Constructs a new instance for the given origin.
1132 *
1133 * @param origin The origin, not null.
1134 * @throws NullPointerException if {@code origin} is {@code null}.
1135 */
1136 public WriterOrigin(final Writer origin) {
1137 super(origin);
1138 }
1139
1140 @Override
1141 protected Channel getChannel(final OpenOption... options) throws IOException {
1142 return Channels.newChannel(getOutputStream());
1143 }
1144
1145 /**
1146 * {@inheritDoc}
1147 * <p>
1148 * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
1149 * </p>
1150 */
1151 @Override
1152 public OutputStream getOutputStream(final OpenOption... options) throws IOException {
1153 // TODO Pass in a Charset? Consider if call sites actually need this.
1154 return WriterOutputStream.builder().setWriter(origin).setCharset(Charset.defaultCharset()).get();
1155 }
1156
1157 /**
1158 * {@inheritDoc}
1159 * <p>
1160 * The {@code charset} parameter is ignored since a {@link Writer} does not need a {@link Charset} to be written.
1161 * </p>
1162 * <p>
1163 * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
1164 * </p>
1165 * <p>
1166 * No conversion should occur when calling this method.
1167 * </p>
1168 */
1169 @Override
1170 public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
1171 // No conversion
1172 return get();
1173 }
1174 }
1175
1176 /**
1177 * The non-null origin.
1178 */
1179 final T origin;
1180
1181 /**
1182 * Constructs a new instance for subclasses.
1183 *
1184 * @param origin The origin, not null.
1185 * @throws NullPointerException if {@code origin} is {@code null}.
1186 */
1187 protected AbstractOrigin(final T origin) {
1188 this.origin = Objects.requireNonNull(origin, "origin");
1189 }
1190
1191 /**
1192 * Gets the origin, never null.
1193 *
1194 * @return the origin, never null.
1195 */
1196 @Override
1197 public T get() {
1198 return origin;
1199 }
1200
1201 /**
1202 * Gets this origin as a byte array, if possible.
1203 *
1204 * @return this origin as a byte array, if possible.
1205 * @throws IOException if an I/O error occurs.
1206 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1207 */
1208 public byte[] getByteArray() throws IOException {
1209 return Files.readAllBytes(getPath());
1210 }
1211
1212 /**
1213 * Gets a portion of this origin as a byte array, if possible.
1214 *
1215 * @param position the initial index of the range to be copied, inclusive.
1216 * @param length How many bytes to copy.
1217 * @return this origin as a byte array, if possible.
1218 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1219 * @throws ArithmeticException if the {@code position} overflows an int.
1220 * @throws IOException if an I/O error occurs.
1221 * @since 2.13.0
1222 */
1223 public byte[] getByteArray(final long position, final int length) throws IOException {
1224 final byte[] bytes = getByteArray();
1225 // Checks for int overflow.
1226 final int start = Math.toIntExact(position);
1227 if (start < 0 || length < 0 || start + length < 0 || start + length > bytes.length) {
1228 throw new IllegalArgumentException("Couldn't read array (start: " + start + ", length: " + length + ", data length: " + bytes.length + ").");
1229 }
1230 return Arrays.copyOfRange(bytes, start, start + length);
1231 }
1232
1233 /**
1234 * Gets this origin as a Channel of the given type, if possible.
1235 *
1236 * @param channelType The type of channel to return.
1237 * @param options Options specifying how a file-based origin is opened, ignored otherwise.
1238 * @return A new Channel on the origin of the given type.
1239 * @param <C> The type of channel to return.
1240 * @throws IOException If an I/O error occurs.
1241 * @throws UnsupportedOperationException If this origin cannot be converted to a channel of the given type.
1242 * @see #getChannel(OpenOption...)
1243 * @since 2.21.0
1244 */
1245 public final <C extends Channel> C getChannel(final Class<C> channelType, final OpenOption... options) throws IOException {
1246 Objects.requireNonNull(channelType, "channelType");
1247 final Channel channel = getChannel(options);
1248 if (channelType.isInstance(channel)) {
1249 return channelType.cast(channel);
1250 }
1251 throw unsupportedChannelType(channelType);
1252 }
1253
1254 /**
1255 * Gets this origin as a Channel, if possible.
1256 *
1257 * @param options Options specifying how a file-based origin is opened, ignored otherwise.
1258 * @return A new Channel on the origin.
1259 * @throws IOException If an I/O error occurs.
1260 * @throws UnsupportedOperationException If this origin cannot be converted to a channel.
1261 * @see #getChannel(Class, OpenOption...)
1262 * @since 2.21.0
1263 */
1264 protected Channel getChannel(final OpenOption... options) throws IOException {
1265 throw unsupportedOperation("getChannel");
1266 }
1267
1268 /**
1269 * Gets this origin as a byte array, if possible.
1270 *
1271 * @param charset The charset to use if conversion from bytes is needed.
1272 * @return this origin as a byte array, if possible.
1273 * @throws IOException if an I/O error occurs.
1274 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1275 */
1276 public CharSequence getCharSequence(final Charset charset) throws IOException {
1277 return new String(getByteArray(), charset);
1278 }
1279
1280 /**
1281 * Gets this origin as a File, if possible.
1282 *
1283 * @return this origin as a File, if possible.
1284 * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1285 */
1286 public File getFile() {
1287 throw unsupportedOperation("getFile");
1288 }
1289
1290 /**
1291 * Gets this origin as an InputStream, if possible.
1292 *
1293 * @param options options specifying how the file is opened.
1294 * @return this origin as an InputStream, if possible.
1295 * @throws IOException if an I/O error occurs.
1296 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1297 */
1298 public InputStream getInputStream(final OpenOption... options) throws IOException {
1299 return Files.newInputStream(getPath(), options);
1300 }
1301
1302 /**
1303 * Gets this origin as an OutputStream, if possible.
1304 *
1305 * @param options options specifying how the file is opened.
1306 * @return this origin as an OutputStream, if possible.
1307 * @throws IOException if an I/O error occurs.
1308 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1309 */
1310 public OutputStream getOutputStream(final OpenOption... options) throws IOException {
1311 return Files.newOutputStream(getPath(), options);
1312 }
1313
1314 /**
1315 * Gets this origin as a Path, if possible.
1316 *
1317 * @return this origin as a Path, if possible.
1318 * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1319 */
1320 public Path getPath() {
1321 throw unsupportedOperation("getPath");
1322 }
1323
1324 /**
1325 * Gets this origin as a RandomAccessFile, if possible.
1326 *
1327 * @param openOption options like {@link StandardOpenOption}.
1328 * @return this origin as a RandomAccessFile, if possible.
1329 * @throws FileNotFoundException See {@link RandomAccessFile#RandomAccessFile(File, String)}.
1330 * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1331 * @since 2.18.0
1332 */
1333 public RandomAccessFile getRandomAccessFile(final OpenOption... openOption) throws FileNotFoundException {
1334 return RandomAccessFileMode.valueOf(openOption).create(getFile());
1335 }
1336
1337 /**
1338 * Gets a new Reader on the origin, buffered by default.
1339 *
1340 * @param charset the charset to use for decoding, null maps to the default Charset.
1341 * @return a new Reader on the origin.
1342 * @throws IOException if an I/O error occurs opening the file.
1343 */
1344 public Reader getReader(final Charset charset) throws IOException {
1345 return Files.newBufferedReader(getPath(), Charsets.toCharset(charset));
1346 }
1347
1348 /**
1349 * Gets simple name of the underlying class.
1350 *
1351 * @return The simple name of the underlying class.
1352 */
1353 private String getSimpleClassName() {
1354 return getClass().getSimpleName();
1355 }
1356
1357 /**
1358 * Gets a new Writer on the origin, buffered by default.
1359 *
1360 * @param charset the charset to use for encoding.
1361 * @param options options specifying how the file is opened.
1362 * @return a new Writer on the origin.
1363 * @throws IOException if an I/O error occurs opening or creating the file.
1364 * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1365 */
1366 public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
1367 return Files.newBufferedWriter(getPath(), Charsets.toCharset(charset), options);
1368 }
1369
1370 /**
1371 * Gets the size of the origin, if possible.
1372 *
1373 * @return the size of the origin in bytes or characters.
1374 * @throws IOException if an I/O error occurs.
1375 * @since 2.13.0
1376 */
1377 public long size() throws IOException {
1378 return Files.size(getPath());
1379 }
1380
1381 @Override
1382 public String toString() {
1383 return getSimpleClassName() + "[" + origin.toString() + "]";
1384 }
1385
1386 UnsupportedOperationException unsupportedChannelType(final Class<? extends Channel> channelType) {
1387 return new UnsupportedOperationException(String.format(
1388 "%s#getChannel(%s) for %s origin %s",
1389 getSimpleClassName(),
1390 channelType.getSimpleName(),
1391 origin.getClass().getSimpleName(),
1392 origin));
1393 }
1394
1395 UnsupportedOperationException unsupportedOperation(final String method) {
1396 return new UnsupportedOperationException(String.format(
1397 "%s#%s() for %s origin %s",
1398 getSimpleClassName(), method, origin.getClass().getSimpleName(), origin));
1399 }
1400 }