001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.io.build; 019 020import java.io.ByteArrayInputStream; 021import java.io.Closeable; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileNotFoundException; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.OutputStream; 029import java.io.OutputStreamWriter; 030import java.io.RandomAccessFile; 031import java.io.Reader; 032import java.io.Writer; 033import java.net.URI; 034import java.net.URLConnection; 035import java.nio.channels.Channel; 036import java.nio.channels.Channels; 037import java.nio.channels.FileChannel; 038import java.nio.channels.ReadableByteChannel; 039import java.nio.channels.SeekableByteChannel; 040import java.nio.channels.WritableByteChannel; 041import java.nio.charset.Charset; 042import java.nio.file.Files; 043import java.nio.file.OpenOption; 044import java.nio.file.Path; 045import java.nio.file.Paths; 046import java.nio.file.StandardOpenOption; 047import java.time.Duration; 048import java.util.Arrays; 049import java.util.Objects; 050import java.util.stream.Stream; 051 052import org.apache.commons.io.Charsets; 053import org.apache.commons.io.IORandomAccessFile; 054import org.apache.commons.io.IOUtils; 055import org.apache.commons.io.RandomAccessFileMode; 056import org.apache.commons.io.RandomAccessFiles; 057import org.apache.commons.io.channels.ByteArraySeekableByteChannel; 058import org.apache.commons.io.input.BufferedFileChannelInputStream; 059import org.apache.commons.io.input.CharSequenceInputStream; 060import org.apache.commons.io.input.CharSequenceReader; 061import org.apache.commons.io.input.ReaderInputStream; 062import org.apache.commons.io.output.RandomAccessFileOutputStream; 063import org.apache.commons.io.output.WriterOutputStream; 064 065/** 066 * Abstracts and wraps an <em>origin</em> for builders, where an origin is a {@code byte[]}, {@link Channel}, {@link CharSequence}, {@link File}, 067 * {@link InputStream}, {@link IORandomAccessFile}, {@link OutputStream}, {@link Path}, {@link RandomAccessFile}, {@link Reader}, {@link URI}, 068 * or {@link Writer}. 069 * <p> 070 * An origin represents where bytes/characters come from or go to. Concrete subclasses 071 * expose only the operations that make sense for the underlying source or sink; invoking an unsupported operation 072 * results in {@link UnsupportedOperationException} (see, for example, {@link #getFile()} and {@link #getPath()}). 073 * </p> 074 * <p> 075 * 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: 076 * </p> 077 * <ul> 078 * <li> 079 * A client allocates a {@linkplain Closeable} (or {@linkplain AutoCloseable}) resource, creates a Builder, and gives the Builder that resource by calling a 080 * setter method. No matter what happens next, the client is responsible for releasing the resource ({@code Closeable.close()}). In this case, the origin 081 * wraps but doesn't own the closeable resource. There is no transfer of ownership. 082 * </li> 083 * <li> 084 * 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 085 * (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 086 * client is responsible for releasing that resource. In this case, the origin doesn't wrap a closeable resource. 087 * </li> 088 * </ul> 089 * <p> 090 * In both cases, the client causes the allocation and is responsible for releasing the resource. 091 * </p> 092 * <p> 093 * The table below summarizes which views and conversions are supported for each origin type. 094 * Column headers show the target view; cells indicate whether that view is available from the origin in that row. 095 * </p> 096 * 097 * <table> 098 * <caption>Supported Conversions</caption> 099 * <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 */ 350public 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}