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.nio.channels.Channel; 035import java.nio.channels.Channels; 036import java.nio.channels.FileChannel; 037import java.nio.channels.ReadableByteChannel; 038import java.nio.channels.SeekableByteChannel; 039import java.nio.channels.WritableByteChannel; 040import java.nio.charset.Charset; 041import java.nio.file.Files; 042import java.nio.file.OpenOption; 043import java.nio.file.Path; 044import java.nio.file.Paths; 045import java.nio.file.StandardOpenOption; 046import java.util.Arrays; 047import java.util.Objects; 048 049import org.apache.commons.io.Charsets; 050import org.apache.commons.io.IORandomAccessFile; 051import org.apache.commons.io.IOUtils; 052import org.apache.commons.io.RandomAccessFileMode; 053import org.apache.commons.io.RandomAccessFiles; 054import org.apache.commons.io.channels.ByteArraySeekableByteChannel; 055import org.apache.commons.io.input.BufferedFileChannelInputStream; 056import org.apache.commons.io.input.CharSequenceInputStream; 057import org.apache.commons.io.input.CharSequenceReader; 058import org.apache.commons.io.input.ReaderInputStream; 059import org.apache.commons.io.output.RandomAccessFileOutputStream; 060import org.apache.commons.io.output.WriterOutputStream; 061 062/** 063 * Abstracts and wraps an <em>origin</em> for builders, where an origin is a {@code byte[]}, {@link Channel}, {@link CharSequence}, {@link File}, 064 * {@link InputStream}, {@link IORandomAccessFile}, {@link OutputStream}, {@link Path}, {@link RandomAccessFile}, {@link Reader}, {@link URI}, 065 * or {@link Writer}. 066 * <p> 067 * An origin represents where bytes/characters come from or go to. Concrete subclasses 068 * expose only the operations that make sense for the underlying source or sink; invoking an unsupported operation 069 * results in {@link UnsupportedOperationException} (see, for example, {@link #getFile()} and {@link #getPath()}). 070 * </p> 071 * <p> 072 * 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: 073 * </p> 074 * <ul> 075 * <li> 076 * A client allocates a {@linkplain Closeable} (or {@linkplain AutoCloseable}) resource, creates a Builder, and gives the Builder that resource by calling a 077 * setter method. No matter what happens next, the client is responsible for releasing the resource ({@code Closeable.close()}). In this case, the origin 078 * wraps but doesn't own the closeable resource. There is no transfer of ownership. 079 * </li> 080 * <li> 081 * 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 082 * (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 083 * client is responsible for releasing that resource. In this case, the origin doesn't wrap a closeable resource. 084 * </li> 085 * </ul> 086 * <p> 087 * In both cases, the client causes the allocation and is responsible for releasing the resource. 088 * </p> 089 * <p> 090 * The table below summarizes which views and conversions are supported for each origin type. 091 * Column headers show the target view; cells indicate whether that view is available from the origin in that row. 092 * </p> 093 * 094 * <table> 095 * <caption>Supported Conversions</caption> 096 * <thead> 097 * <tr> 098 * <th>Origin Type</th> 099 * <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 */ 347public 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}