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