diff --git a/.asf.yaml b/.asf.yaml index 5ea25ac8897..33a8dd8c4ff 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -23,7 +23,8 @@ notifications: pullrequests: issues@commons.apache.org jira_options: link label jobs: notifications@commons.apache.org - issues_bot_dependabot: notifications@commons.apache.org - pullrequests_bot_dependabot: notifications@commons.apache.org + # commits_bot_dependabot: dependabot@commons.apache.org + issues_bot_dependabot: dependabot@commons.apache.org + pullrequests_bot_dependabot: dependabot@commons.apache.org issues_bot_codecov-commenter: notifications@commons.apache.org pullrequests_bot_codecov-commenter: notifications@commons.apache.org diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 00079caf1bc..90ec55f742e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,10 +18,8 @@ updates: - package-ecosystem: "maven" directory: "/" schedule: - interval: "weekly" - day: "friday" + interval: "quarterly" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - day: "friday" + interval: "quarterly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7578b4da036..9ff35c83e79 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,8 +23,8 @@ Before you push a pull request, review this list: - [ ] Read the [contribution guidelines](CONTRIBUTING.md) for this project. - [ ] Read the [ASF Generative Tooling Guidance](https://www.apache.org/legal/generative-tooling.html) if you use Artificial Intelligence (AI). -- [ ] I used AI to create any part of, or all of, this pull request. +- [ ] I used AI to create any part of, or all of, this pull request. Which AI tool was used to create this pull request, and to what extent did it contribute? - [ ] Run a successful build using the default [Maven](https://maven.apache.org/) goal with `mvn`; that's `mvn` on the command line by itself. -- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best-practice. +- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice. - [ ] Write a pull request description that is detailed enough to understand what the pull request does, how, and why. - [ ] Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fcc16fe390a..32fe63cd78f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,6 +42,7 @@ jobs: security-events: write strategy: + max-parallel: 20 fail-fast: false matrix: language: [ 'java' ] @@ -50,10 +51,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae #v5.0.5 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -62,7 +63,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -73,7 +74,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -87,4 +88,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 1e043924237..f0d8ca94e32 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -26,6 +26,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review PR' - uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 7d0f0892434..ee9557cf46a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -37,9 +37,10 @@ jobs: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: + max-parallel: 20 matrix: - os: [ubuntu-latest, windows-latest, macos-13] - java: [ 8, 11, 17, 21 ] + os: [ubuntu-latest, windows-latest, macos-latest] + java: [ 8, 11, 17, 21, 25 ] experimental: [false] # Keep the same parameter order as the matrix above include: @@ -48,16 +49,6 @@ jobs: java: 21 experimental: false deploy: true - # Experimental builds: Java 25-ea - - os: ubuntu-latest - java: 25 - experimental: true - - os: windows-latest - java: 25 - experimental: true - - os: macos-latest - java: 25 - experimental: true # Experimental builds: Java 26-ea - os: ubuntu-latest java: 26-ea @@ -70,19 +61,19 @@ jobs: experimental: true fail-fast: false steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae #v5.0.5 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - distribution: 'temurin' + distribution: ${{ runner.os == 'macOS' && matrix.java == '8' && 'zulu' || 'temurin' }} java-version: ${{ matrix.java }} # these values cause the plugin to set up the Maven settings.xml file server-id: apache.snapshots.https # Value of the distributionManagement/repository/id field of the pom.xml diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index cf4d6ba87b0..715e03fcf97 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -42,12 +42,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # 2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # 2.4.3 with: results_file: results.sarif results_format: sarif @@ -59,13 +59,13 @@ jobs: publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5 + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index f43fea02686..91824e5be73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -### https://raw.github.com/github/gitignore/14b7566ce157ce95b07006466bacee160f242284/maven.gitignore - target/ pom.xml.tag pom.xml.releaseBackup @@ -7,16 +5,19 @@ pom.xml.versionsBackup pom.xml.next release.properties - site-content /.classpath /.project /.settings/ -### Ignore IntelliJ files +# Ignore IntelliJ files /.idea/ *.iml /bin/ -### Ignore Visual Studio code files +# Ignore Visual Studio code files /.vscode/ + +# NetBeans files +nb-configuration.xml +nbactions.xml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f708680735d..e924b4ac04e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ Making Changes + Respect the original code style: + Only use spaces for indentation; you can check for unnecessary whitespace with `git diff` before committing. + Create minimal diffs - disable _On Save_ actions like _Reformat Source Code_ or _Organize Imports_. If you feel the source code should be reformatted create a separate PR for this change first. -+ Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible but is a best-practice. ++ Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible but is a best practice. Unit tests are typically in the `src/test/java` directory. + Run a successful build using the default [Maven](https://maven.apache.org/) goal with `mvn`; that's `mvn` on the command line by itself. + Write a pull request description that is detailed enough to understand what the pull request does, how, and why. @@ -110,7 +110,6 @@ Additional Resources + [Contributor License Agreement][cla] + [General GitHub documentation](https://help.github.com/) + [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/) -+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) [cla]:https://www.apache.org/licenses/#clas [jira]:https://issues.apache.org/jira/browse/IO diff --git a/NOTICE.txt b/NOTICE.txt index 2a4682551b1..b9fb860712d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Commons IO -Copyright 2002-2025 The Apache Software Foundation +Copyright 2002-2026 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). diff --git a/README.md b/README.md index bb31ce854a6..8e4ae6db426 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Apache Commons IO [![Java CI](https://github.com/apache/commons-io/actions/workflows/maven.yml/badge.svg)](https://github.com/apache/commons-io/actions/workflows/maven.yml) [![Maven Central](https://img.shields.io/maven-central/v/commons-io/commons-io?label=Maven%20Central)](https://search.maven.org/artifact/commons-io/commons-io) -[![Javadocs](https://javadoc.io/badge/commons-io/commons-io/2.20.0.svg)](https://javadoc.io/doc/commons-io/commons-io/2.20.0) +[![Javadocs](https://javadoc.io/badge/commons-io/commons-io/2.22.0.svg)](https://javadoc.io/doc/commons-io/commons-io/2.22.0) [![CodeQL](https://github.com/apache/commons-io/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/apache/commons-io/actions/workflows/codeql-analysis.yml) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/apache/commons-io/badge)](https://api.securityscorecards.dev/projects/github.com/apache/commons-io) @@ -69,7 +69,7 @@ Alternatively, you can pull it from the central Maven repositories: commons-io commons-io - 2.20.0 + 2.22.0 ``` @@ -90,7 +90,7 @@ There are some guidelines which will make applying PRs easier for us: + Respect the existing code style for each file. + Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change. + Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running `mvn`. -+ Before you pushing a PR, run `mvn` (by itself), this runs the default goal, which contains all build checks. ++ Before you push a PR, run `mvn` (without arguments). This runs the default goal which contains all build checks. + To see the code coverage report, regardless of coverage failures, run `mvn clean site -Dcommons.jacoco.haltOnFailure=false -Pjacoco` If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas). @@ -112,7 +112,6 @@ Additional Resources + [Apache Commons Homepage](https://commons.apache.org/) + [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/IO) + [Apache Commons Slack Channel](https://the-asf.slack.com/archives/C60NVB8AD) -+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) Apache Commons Components ------------------------- diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1f30f8b0f5a..904a8f55302 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,281 @@ +Apache Commons IO 2.22.0 Release Notes +-------------------------------------- + +The Apache Commons IO team is pleased to announce the release of Apache Commons IO 2.22.0. + +Introduction +------------ + +The Apache Commons IO library contains utility classes, stream implementations, file filters, +file comparators, endian transformation classes, and much more. + +This is a feature and maintenance release. Java 8 or later is required. + +New features +------------ + +o Add and use IOUtils.closeQuietlySuppress(Closeable, Throwable) #818. Thanks to Gary Gregory, Piotr P. Karwasz. +o Add ProxyWriter.setReference(Writer). Thanks to Gary Gregory. +o Add ProxyWriter.unwrap(). Thanks to Gary Gregory. +o Add ProxyReader.setReference(Reader). Thanks to Gary Gregory. +o Add ProxyReader.unrwap(). Thanks to Gary Gregory. +o IO-883: ByteArraySeekableByteChannel should optionally configure a read-only channel. Thanks to Konrad Windszus, Gary Gregory, Makarand Hinge. +o IO-883: Add ByteArraySeekableByteChannel.Builder and builder(). Thanks to Gary Gregory. +o IO-883: Add AbstractStreamBuilder.getByteArray(). Thanks to Gary Gregory. +o CloseShieldInputStream now supports a custom close shield as a function #836. Thanks to Gary Gregory. +o Add FlushShieldOutputStream to workaround issues in generic code that ends up calling third parties like like org.tukaani.xz.LZMAOutputStream.flush(). Thanks to Gary Gregory. +o Add filter channels. Thanks to Gary Gregory. + +Fixed Bugs +---------- + +o Fix Apache RAT plugin console warnings. Thanks to Gary Gregory. +o ByteArraySeekableByteChannel.position(long) and truncate(long) shouldn't throw an IllegalArgumentException for a new positive position that's too large #817. Thanks to Gary Gregory, Piotr P. Karwasz. +o Fix malformed Javadoc comments. Thanks to Gary Gregory. +o ReadAheadInputStream.close() doesn't always close its filtered input stream. Thanks to Stanislav Fort, Gary Gregory. +o ReadAheadInputStream now restores the current thread's interrupt flag when catching InterruptedException. Thanks to Gary Gregory. +o FileAlterationMonitor.stop(long) now restores the current thread's interrupt flag when catching InterruptedException. Thanks to Gary Gregory. +o FileCleaningTracker now restores the current thread's interrupt flag when catching InterruptedException. Thanks to Gary Gregory. +o ThreadMonitor.run() now restores the current thread's interrupt flag when catching InterruptedException. Thanks to Gary Gregory. +o ThrottledInputStream.throttle() now restores the current thread's interrupt flag when catching InterruptedException. Thanks to Gary Gregory. +o ThrottledInputStream.throttle() doesn't preserve the original InterruptedException as the cause of its InterruptedIOException. Thanks to Gary Gregory. +o All thread names are now prefixed with "commons-io-". Thanks to Gary Gregory. +o IO-639: ReversedLinesFileReader does not read first line if its empty #829. Thanks to Kishor, Mashrur Mia. +o IO-886: Fixed incorrect regular expression in PathUtils.RelativeSortedPaths.extractKey(String, String). Thanks to Peter De Maeyer. +o Fix typos in Javadoc of FileUtils and related test classes #833. Thanks to Martin Wiesner. +o IO-887: WriterOutputStream from a builder fails on malformed or unmappable input bytes. Thanks to Daniel Vega, Gary Gregory. +o BoundedReader now extends ProxyReader. Thanks to Gary Gregory. +o AbstractStreamBuilder.setOpenOptions(OpenOption...) now makes a defensive copy of its input array. Thanks to Gary Gregory. +o IO-885: Path visits follow links #832. Thanks to Peter De Maeyer, Gary Gregory. +o BOMInputStream fail-fast and tracks its ByteOrderMark as a final #835. Thanks to Gary Gregory. +o Refactor UnixLineEndingInputStream and WindowsLineEndingInputStream for duplication. Thanks to Gary Gregory. +o IO-857: [Javadoc] PathUtils.cleanDirectory() methods vs FileUtils. Thanks to Peter De Maeyer, Gary Gregory. +o Fix JaCoCo report generation (code coverage). Thanks to Gary Gregory. +o AbstractStreamBuilder.setBufferSizeDefault(int) now resets to default for input less than or equal to zero. Thanks to Gary Gregory. + +Changes +------- + +o Bump org.apache.commons:commons-parent from 91 to 98 #816. Thanks to Gary Gregory, Dependabot. +o Bump commons-codec:commons-codec from 1.19.0 to 1.21.0 #812. Thanks to Gary Gregory, Dependabot. +o Bump commons.bytebuddy.version from 1.17.8 to 1.18.8 #814, #820, #838. Thanks to Gary Gregory, Dependabot. +o Bump commons-lang3 from 3.19.0 to 3.20.0. Thanks to Gary Gregory, Dependabot. + + +Commons IO 2.7 and up requires Java 8 or above. +Commons IO 2.6 requires Java 7 or above. +Commons IO 2.3 through 2.5 requires Java 6 or above. +Commons IO 2.2 requires Java 5 or above. +Commons IO 1.4 requires Java 1.3 or above. + +Historical list of changes: https://commons.apache.org/proper/commons-io/changes.html + +For complete information on Apache Commons IO, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Commons IO website: + +https://commons.apache.org/proper/commons-io/ + +Download page: https://commons.apache.org/proper/commons-io/download_io.cgi + +Have fun! +-Apache Commons Team + +------------------------------------------------------------------------------ + + +Apache Commons IO 2.22.0 Release Notes +-------------------------------------- + +The Apache Commons IO team is pleased to announce the release of Apache Commons IO 2.22.0. + +Introduction +------------ + +The Apache Commons IO library contains utility classes, stream implementations, file filters, +file comparators, endian transformation classes, and much more. + +This is a feature and maintenance release. Java 8 or later is required. + +New features +------------ + +o Add and use IOUtils.closeQuietlySuppress(Closeable, Throwable) #818. Thanks to Gary Gregory, Piotr P. Karwasz. + +Fixed Bugs +---------- + +o Fix Apache RAT plugin console warnings. Thanks to Gary Gregory. +o ByteArraySeekableByteChannel.position(long) and truncate(long) shouldn't throw an IllegalArgumentException for a new positive position that's too large #817. Thanks to Gary Gregory, Piotr P. Karwasz. +o Fix malformed Javadoc comments. Thanks to Gary Gregory. +o ReadAheadInputStream.close() doesn't always close its filtered input stream. Thanks to Stanislav Fort, Gary Gregory. + +Changes +------- + +o Bump org.apache.commons:commons-parent from 91 to 96 #816. Thanks to Gary Gregory, Dependabot. +o Bump commons-codec:commons-codec from 1.19.0 to 1.20.0 #812. Thanks to Gary Gregory, Dependabot. +o Bump commons.bytebuddy.version from 1.17.8 to 1.18.4 #814, #820. Thanks to Gary Gregory, Dependabot. +o Bump commons-lang3 from 3.19.0 to 3.20.0. Thanks to Gary Gregory, Dependabot. + + +Commons IO 2.7 and up requires Java 8 or above. +Commons IO 2.6 requires Java 7 or above. +Commons IO 2.3 through 2.5 requires Java 6 or above. +Commons IO 2.2 requires Java 5 or above. +Commons IO 1.4 requires Java 1.3 or above. + +Historical list of changes: https://commons.apache.org/proper/commons-io/changes.html + +For complete information on Apache Commons IO, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Commons IO website: + +https://commons.apache.org/proper/commons-io/ + +Download page: https://commons.apache.org/proper/commons-io/download_io.cgi + +Have fun! +-Apache Commons Team + +------------------------------------------------------------------------------ + +Apache Commons IO 2.22.0 Release Notes +-------------------------------------- + +The Apache Commons IO team is pleased to announce the release of Apache Commons IO 2.22.0. + +Introduction +------------ + +The Apache Commons IO library contains utility classes, stream implementations, file filters, +file comparators, endian transformation classes, and much more. + +This is a feature and maintenance release. Java 8 or later is required. + +New features +------------ + +o Add and use IOUtils.closeQuietly(Closeable, Throwable) #818. Thanks to Gary Gregory. + +Fixed Bugs +---------- + +o Fix Apache RAT plugin console warnings. Thanks to Gary Gregory. +o ByteArraySeekableByteChannel.position(long) and truncate(long) shouldn't throw an IllegalArgumentException for a new positive position that's too large #817. Thanks to Gary Gregory, Piotr P. Karwasz. +o Fix malformed Javadoc comments. Thanks to Gary Gregory. +o ReadAheadInputStream.close() doesn't always close its filtered input stream. Thanks to Stanislav Fort, Gary Gregory. + +Changes +------- + +o Bump org.apache.commons:commons-parent from 91 to 95 #816. Thanks to Gary Gregory, Dependabot. +o Bump commons-codec:commons-codec from 1.19.0 to 1.20.0 #812. Thanks to Gary Gregory, Dependabot. +o Bump commons.bytebuddy.version from 1.17.8 to 1.18.3 #814, #820. Thanks to Gary Gregory, Dependabot. +o Bump commons-lang3 from 3.19.0 to 3.20.0. Thanks to Gary Gregory, Dependabot. + + +Commons IO 2.7 and up requires Java 8 or above. +Commons IO 2.6 requires Java 7 or above. +Commons IO 2.3 through 2.5 requires Java 6 or above. +Commons IO 2.2 requires Java 5 or above. +Commons IO 1.4 requires Java 1.3 or above. + +Historical list of changes: https://commons.apache.org/proper/commons-io/changes.html + +For complete information on Apache Commons IO, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Commons IO website: + +https://commons.apache.org/proper/commons-io/ + +Download page: https://commons.apache.org/proper/commons-io/download_io.cgi + +Have fun! +-Apache Commons Team + +------------------------------------------------------------------------------ + + +Apache Commons IO 2.21.0 Release Notes +-------------------------------------- + +The Apache Commons IO team is pleased to announce the release of Apache Commons IO 2.21.0. + +Introduction +------------ + +The Apache Commons IO library contains utility classes, stream implementations, file filters, +file comparators, endian transformation classes, and much more. + +Version 2.21.0: Java 8 or later is required. + +New features +------------ + +o FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763. Thanks to strangelookingnerd, Gary Gregory. +o Add org.apache.commons.io.FileUtils.ONE_RB #763. Thanks to strangelookingnerd, Gary Gregory. +o Add org.apache.commons.io.FileUtils.ONE_QB #763. Thanks to strangelookingnerd, Gary Gregory. +o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], int, int, long). Thanks to Gary Gregory. +o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], long). Thanks to Gary Gregory. +o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(int, long). Thanks to Gary Gregory. +o Add length unit support in FileSystem limits. Thanks to Piotr P. Karwasz. +o Add IOUtils.toByteArray(InputStream, int, int) for safer chunked reading with size validation. Thanks to Piotr P. Karwasz. +o Add org.apache.commons.io.file.PathUtils.getPath(String, String). Thanks to Gary Gregory. +o Add org.apache.commons.io.channels.ByteArraySeekableByteChannel. Thanks to Gary Gregory. +o Add IOIterable.asIterable(). Thanks to Gary Gregory. +o Add NIO channel support to `AbstractStreamBuilder`. Thanks to Piotr P. Karwasz. +o Add CloseShieldChannel to close-shielded NIO Channels #786. Thanks to Piotr P. Karwasz. +o Added IOUtils.checkFromIndexSize as a Java 8 backport of Objects.checkFromIndexSize #790. Thanks to Piotr P. Karwasz. + +Fixed Bugs +---------- + +o When testing on Java 21 and up, enable -XX:+EnableDynamicAgentLoading. Thanks to Gary Gregory. +o When testing on Java 24 and up, don't fail FileUtilsListFilesTest for a different behavior in the JRE. Thanks to Gary Gregory. +o ValidatingObjectInputStream does not validate dynamic proxy interfaces. Thanks to Stanislav Fort, Gary Gregory. +o BoundedInputStream.getRemaining() now reports Long.MAX_VALUE instead of 0 when no limit is set. Thanks to Piotr P. Karwasz. +o BoundedInputStream.available() correctly accounts for the maximum read limit. Thanks to Piotr P. Karwasz. +o Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int). Thanks to Gary Gregory, Piotr P. Karwasz. +o IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow. Thanks to Piotr P. Karwasz. +o Javadoc general improvements. Thanks to Gary Gregory, Piotr P. Karwasz. +o IOUtils.toByteArray() now throws EOFException when not enough data is available #796. Thanks to Piotr P. Karwasz. +o Fix IOUtils.skip() usage in concurrent scenarios. Thanks to Piotr P. Karwasz. +o [javadoc] Fix XmlStreamReader Javadoc to indicate the correct class that is built #806. Thanks to J Hawkins. + +Changes +------- + +o Bump org.apache.commons:commons-parent from 85 to 91 #774, #783, #808. Thanks to Gary Gregory, Dependabot. +o [test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0. Thanks to Gary Gregory. +o [test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.8 #769. Thanks to Gary Gregory, Dependabot. +o [test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0. Thanks to Gary Gregory. + +Removed +------- + +o Inline private constant field ProxyInputStream.exceptionHandler #780. Thanks to Piotr P. Karwasz. +Commons IO 2.7 and up requires Java 8 or above. +Commons IO 2.6 requires Java 7 or above. +Commons IO 2.3 through 2.5 requires Java 6 or above. +Commons IO 2.2 requires Java 5 or above. +Commons IO 1.4 requires Java 1.3 or above. + +Historical list of changes: https://commons.apache.org/proper/commons-io/changes.html + +For complete information on Apache Commons IO, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Commons IO website: + +https://commons.apache.org/proper/commons-io/ + +Download page: https://commons.apache.org/proper/commons-io/download_io.cgi + +Have fun! +-Apache Commons Team + +------------------------------------------------------------------------------ + + Apache Commons IO 2.20.0 Release Notes +-------------------------------------- The Apache Commons IO team is pleased to announce the release of Apache Commons IO 2.20.0. @@ -78,6 +354,7 @@ Have fun! Apache Commons IO 2.19.0 Release Notes +-------------------------------------- Introduction ------------ @@ -173,6 +450,7 @@ Have fun! Apache Commons IO 2.18.0 Release Notes +-------------------------------------- Introduction ------------ @@ -251,6 +529,7 @@ Apache Commons Team Apache Commons IO 2.17.0 Release Notes +-------------------------------------- Introduction ------------ @@ -341,6 +620,7 @@ Have fun! Apache Commons IO 2.16.1 Release Notes +-------------------------------------- Introduction ------------ @@ -393,6 +673,7 @@ Have fun! Apache Commons IO 2.16.1 Release Notes +-------------------------------------- Introduction ------------ @@ -444,6 +725,7 @@ Have fun! Apache Commons IO 2.16.0 Release Notes +-------------------------------------- Introduction ------------ diff --git a/pom.xml b/pom.xml index c356baf502a..6cb9cd77537 100644 --- a/pom.xml +++ b/pom.xml @@ -19,12 +19,12 @@ org.apache.commons commons-parent - 88 + 100 4.0.0 commons-io commons-io - 2.21.0-SNAPSHOT + 2.23.0-SNAPSHOT Apache Commons IO 2002 @@ -32,10 +32,6 @@ The Apache Commons IO library contains utility classes, stream implementations, file comparators, endian transformation classes, and much more. https://commons.apache.org/proper/commons-io/ - - jira - https://issues.apache.org/jira/browse/IO - apache.website @@ -43,16 +39,6 @@ file comparators, endian transformation classes, and much more. scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-io/ - - scm:git:https://gitbox.apache.org/repos/asf/commons-io.git - scm:git:https://gitbox.apache.org/repos/asf/commons-io.git - https://gitbox.apache.org/repos/asf?p=commons-io.git - rel/commons-io-2.21.0 - - - GitHub - https://github.com/apache/commons-io/actions - org.junit.jupiter @@ -93,13 +79,13 @@ file comparators, endian transformation classes, and much more. org.apache.commons commons-lang3 - 3.19.0 + 3.20.0 test commons-codec commons-codec - 1.19.0 + 1.22.0 test @@ -115,11 +101,11 @@ file comparators, endian transformation classes, and much more. io org.apache.commons.io RC1 - 2.20.0 - 2.21.0 - 2.21.1 + 2.22.0 + 2.23.0 + 2.23.1 - 2025-07-18T21:12:04Z + 2026-04-23T10:25:32Z (requires Java 8) IO 12310477 @@ -146,11 +132,10 @@ file comparators, endian transformation classes, and much more. https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-io/ site-content - 1.17.7 + 1.18.8 false true - ${env.JACOCO_SKIP} true 0.98 0.90 @@ -170,11 +155,11 @@ file comparators, endian transformation classes, and much more. org.apache.rat apache-rat-plugin - - src/test/resources/**/*.bin - src/test/resources/dir-equals-tests/** - test/** - + + src/test/resources/**/*.bin + src/test/resources/dir-equals-tests/** + test/** + @@ -219,7 +204,7 @@ file comparators, endian transformation classes, and much more. false - ${argLine} -Xmx25M ${EnableDynamicAgentLoading} + @{argLine} -Xmx25M ${EnableDynamicAgentLoading} **/*Test*.class diff --git a/src/changes/changes.xml b/src/changes/changes.xml index fb68216e20a..67ade54a42a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -45,7 +45,68 @@ The type attribute can be add,update,fix,remove. Apache Commons IO Release Notes - + + + ThresholdingOutputStream.isThresholdExceeded() now reflects the threshold-reached state, and a failed thresholdReached() no longer leaves the stream unable to fire the event again. + Clean BufferedFileChannelInputStream's direct ByteBuffer only once #854. + BufferedFileChannelInputStream now clears its direct memory byte buffer on close (#855). + MemoryMappedFileInputStream now clears its direct memory byte buffer on close (#855). + FileChannels.contentEquals(ReadableByteChannel, ReadableByteChannel, int) now clears its direct memory byte buffer on close (#855). + BufferedFileChannelInputStream now clears its direct memory byte buffer on close (#855). + MemoryMappedFileInputStream now clears its direct memory byte buffer on close (#855). + + Add IOConsumer.accept(IOConsumer, T) (#846). + Add UnsynchronizedBufferedReader.unwrap() (#850). + Add UnsynchronizedBufferedReader.getPosition() (#851). + Add Buffers clear() methods (#853). + + Bump org.apache.commons:commons-parent from 98 to 100 (#849). + [test] Bump commons-codec:commons-codec from 1.21.0 to 1.22.0. + + + + Fix Apache RAT plugin console warnings. + ByteArraySeekableByteChannel.position(long) and truncate(long) shouldn't throw an IllegalArgumentException for a new positive position that's too large #817. + Fix malformed Javadoc comments. + ReadAheadInputStream.close() doesn't always close its filtered input stream. + ReadAheadInputStream now restores the current thread's interrupt flag when catching InterruptedException. + FileAlterationMonitor.stop(long) now restores the current thread's interrupt flag when catching InterruptedException. + FileCleaningTracker now restores the current thread's interrupt flag when catching InterruptedException. + ThreadMonitor.run() now restores the current thread's interrupt flag when catching InterruptedException. + ThrottledInputStream.throttle() now restores the current thread's interrupt flag when catching InterruptedException. + ThrottledInputStream.throttle() doesn't preserve the original InterruptedException as the cause of its InterruptedIOException. + All thread names are now prefixed with "commons-io-". + ReversedLinesFileReader does not read first line if its empty #829. + Fixed incorrect regular expression in PathUtils.RelativeSortedPaths.extractKey(String, String). + Fix typos in Javadoc of FileUtils and related test classes #833. + WriterOutputStream from a builder fails on malformed or unmappable input bytes. + BoundedReader now extends ProxyReader. + AbstractStreamBuilder.setOpenOptions(OpenOption...) now makes a defensive copy of its input array. + Path visits follow links #832. + BOMInputStream fail-fast and tracks its ByteOrderMark as a final #835. + Refactor UnixLineEndingInputStream and WindowsLineEndingInputStream for duplication. + [Javadoc] PathUtils.cleanDirectory() methods vs FileUtils. + Fix JaCoCo report generation (code coverage). + AbstractStreamBuilder.setBufferSizeDefault(int) now resets to default for input less than or equal to zero. + + Add and use IOUtils.closeQuietlySuppress(Closeable, Throwable) #818. + Add ProxyWriter.setReference(Writer). + Add ProxyWriter.unwrap(). + Add ProxyReader.setReference(Reader). + Add ProxyReader.unrwap(). + ByteArraySeekableByteChannel should optionally configure a read-only channel. + Add ByteArraySeekableByteChannel.Builder and builder(). + Add AbstractStreamBuilder.getByteArray(). + CloseShieldInputStream now supports a custom close shield as a function #836. + Add FlushShieldOutputStream to workaround issues in generic code that ends up calling third parties like like org.tukaani.xz.LZMAOutputStream.flush(). + Add filter channels. + + Bump org.apache.commons:commons-parent from 91 to 98 #816. + Bump commons-codec:commons-codec from 1.19.0 to 1.21.0 #812. + Bump commons.bytebuddy.version from 1.17.8 to 1.18.8 #814, #820, #838. + Bump commons-lang3 from 3.19.0 to 3.20.0. + + When testing on Java 21 and up, enable -XX:+EnableDynamicAgentLoading. When testing on Java 24 and up, don't fail FileUtilsListFilesTest for a different behavior in the JRE. @@ -55,6 +116,9 @@ The type attribute can be add,update,fix,remove. Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int). IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow. Javadoc general improvements. + IOUtils.toByteArray() now throws EOFException when not enough data is available #796. + Fix IOUtils.skip() usage in concurrent scenarios. + [javadoc] Fix XmlStreamReader Javadoc to indicate the correct class that is built #806. FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763. Add org.apache.commons.io.FileUtils.ONE_RB #763. @@ -71,9 +135,9 @@ The type attribute can be add,update,fix,remove. Add CloseShieldChannel to close-shielded NIO Channels #786. Added IOUtils.checkFromIndexSize as a Java 8 backport of Objects.checkFromIndexSize #790. - Bump org.apache.commons:commons-parent from 85 to 88 #774, #783. + Bump org.apache.commons:commons-parent from 85 to 91 #774, #783, #808. [test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0. - [test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.7 #769. + [test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.8 #769. [test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0. Inline private constant field ProxyInputStream.exceptionHandler #780. @@ -1340,7 +1404,7 @@ The type attribute can be add,update,fix,remove. Fixed error of copying directories between different file systems #203. - Fix Typos in JavaDoc, Comments and Tests #201. + Fix Typos in Javadoc, Comments and Tests #201. FileUtils.checksumCRC32 and FileUtils.checksum are not thread safe. diff --git a/src/changes/release-notes.vm b/src/changes/release-notes.vm index c28de3c8729..aa5a3663420 100644 --- a/src/changes/release-notes.vm +++ b/src/changes/release-notes.vm @@ -16,6 +16,7 @@ ## under the License. Apache Commons IO ${version} Release Notes +-------------------------------------- The ${developmentTeam} is pleased to announce the release of ${project.name} ${version}. diff --git a/src/conf/maven-pmd-plugin.xml b/src/conf/maven-pmd-plugin.xml index 215ac295ede..9d51e182ff9 100644 --- a/src/conf/maven-pmd-plugin.xml +++ b/src/conf/maven-pmd-plugin.xml @@ -78,7 +78,6 @@ under the License. - diff --git a/src/main/java/org/apache/commons/io/Buffers.java b/src/main/java/org/apache/commons/io/Buffers.java new file mode 100644 index 00000000000..cd4e2a2b179 --- /dev/null +++ b/src/main/java/org/apache/commons/io/Buffers.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.commons.io; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ReadOnlyBufferException; +import java.nio.ShortBuffer; +import java.util.Arrays; + +/** + * Helps use {@link Buffer} instances. + * + * @since 2.23.0 + */ +public final class Buffers { + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer, or null. + * @throws ReadOnlyBufferException If the buffer is read-only. + * @throws UnsupportedOperationException Thrown if the given buffer is not one of {@link CharBuffer}, {@link ByteBuffer}, {@link DoubleBuffer}, + * {@link FloatBuffer}, {@link IntBuffer}, {@link LongBuffer}, {@link ShortBuffer}, or null. + */ + public static Buffer clear(final Buffer buffer) { + if (buffer instanceof CharBuffer) { + return clear((CharBuffer) buffer); + } + if (buffer instanceof ByteBuffer) { + return clear((ByteBuffer) buffer); + } + if (buffer instanceof DoubleBuffer) { + return clear((DoubleBuffer) buffer); + } + if (buffer instanceof FloatBuffer) { + return clear((FloatBuffer) buffer); + } + if (buffer instanceof IntBuffer) { + return clear((IntBuffer) buffer); + } + if (buffer instanceof LongBuffer) { + return clear((LongBuffer) buffer); + } + if (buffer instanceof ShortBuffer) { + return clear((ShortBuffer) buffer); + } + if (buffer == null) { + return null; + } + throw new UnsupportedOperationException(buffer.getClass().getCanonicalName()); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static ByteBuffer clear(final ByteBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), (byte) 0); + } else { + final byte[] zeros = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static CharBuffer clear(final CharBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), (char) 0); + } else { + final char[] zeros = new char[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static DoubleBuffer clear(final DoubleBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), 0); + } else { + final double[] zeros = new double[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static FloatBuffer clear(final FloatBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), 0); + } else { + final float[] zeros = new float[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static IntBuffer clear(final IntBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), 0); + } else { + final int[] zeros = new int[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static LongBuffer clear(final LongBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), 0); + } else { + final long[] zeros = new long[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * Clears this buffer by filling it with zeros, the position is set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + * @throws ReadOnlyBufferException If the buffer is read-only. + */ + public static ShortBuffer clear(final ShortBuffer buffer) { + if (buffer == null) { + return null; + } + if (clearBuffer(buffer).hasArray()) { + Arrays.fill(buffer.array(), (short) 0); + } else { + final short[] zeros = new short[IOUtils.DEFAULT_BUFFER_SIZE]; + while (buffer.hasRemaining()) { + buffer.put(zeros, 0, Math.min(buffer.remaining(), zeros.length)); + } + } + return clearBuffer(buffer); + } + + /** + * A better typed version of {@link Buffer#clear()}. + *

+ * Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded. + *

+ *

+ * Invoke this method before using a sequence of channel-read or put operations to fill this buffer. For example: + *

+ * + *
+     * buf.clear(); // Prepare buffer for reading
+     * in.read(buf); // Read data
+     * 
+ * + *

+ * This method does not actually erase the data in the buffer, but it is named as if it did because it will most often be used in situations in which that + * might as well be the case. + *

+ * + * @param A Buffer subclass. + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + */ + private static B clearBuffer(final B buffer) { + buffer.clear(); + return buffer; + } + + /** + * Clears the given direct buffer by filling it with zeros and resetting the position to zero. The limit is set to the capacity of the buffer. + *

+ * If the given buffer is a not direct buffer, nothing happens to that buffer. + *

+ * + * @param A Buffer subclass. + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + */ + public static B clearDirect(final B buffer) { + if (buffer != null && buffer.isDirect()) { + clear(buffer); + } + return buffer; + } + + /** + * Clears the given writable buffer by filling it with zeros and resetting the position to zero. The limit is set to the capacity of the buffer. + *

+ * If the buffer is read-only, then nothing happens to that buffer. + *

+ * + * @param A Buffer subclass. + * @param buffer The buffer to clear, may be null. + * @return The given buffer. + */ + public static B clearWritable(final B buffer) { + if (buffer != null && !buffer.isReadOnly()) { + clear(buffer); + } + return buffer; + } + + /** + * No instances. + */ + private Buffers() { + // empty. + } +} diff --git a/src/main/java/org/apache/commons/io/ByteBuffers.java b/src/main/java/org/apache/commons/io/ByteBuffers.java index 91efd8ed3e2..d5126f01534 100644 --- a/src/main/java/org/apache/commons/io/ByteBuffers.java +++ b/src/main/java/org/apache/commons/io/ByteBuffers.java @@ -68,8 +68,11 @@ public static ByteBuffer littleEndian(final int capacity) { return littleEndian(ByteBuffer.allocate(capacity)); } + /** + * No instances. + */ private ByteBuffers() { - // empty, no instance. + // empty. } } diff --git a/src/main/java/org/apache/commons/io/ByteOrderMark.java b/src/main/java/org/apache/commons/io/ByteOrderMark.java index 0067f7d2d6f..b9209cb316f 100644 --- a/src/main/java/org/apache/commons/io/ByteOrderMark.java +++ b/src/main/java/org/apache/commons/io/ByteOrderMark.java @@ -40,7 +40,7 @@ * * @see org.apache.commons.io.input.BOMInputStream * @see Wikipedia: Byte Order Mark - * @see W3C: Autodetection of Character Encodings + * @see W3C: Autodetection of Character Encodings * (Non-Normative) * @since 2.0 */ @@ -128,10 +128,10 @@ public class ByteOrderMark implements Serializable { /** * Constructs a new instance. * - * @param charsetName The name of the charset the BOM represents - * @param bytes The BOM's bytes - * @throws IllegalArgumentException if the charsetName is zero length - * @throws IllegalArgumentException if the bytes are zero length + * @param charsetName The name of the charset the BOM represents. + * @param bytes The BOM's bytes. + * @throws IllegalArgumentException if the charsetName is zero length. + * @throws IllegalArgumentException if the bytes are zero length. */ public ByteOrderMark(final String charsetName, final int... bytes) { Objects.requireNonNull(charsetName, "charsetName"); @@ -149,9 +149,9 @@ public ByteOrderMark(final String charsetName, final int... bytes) { /** * Indicates if this instance's bytes equals another. * - * @param obj The object to compare to + * @param obj The object to compare to. * @return true if the bom's bytes are equal, otherwise - * false + * false. */ @Override public boolean equals(final Object obj) { @@ -173,8 +173,8 @@ public boolean equals(final Object obj) { /** * Gets the byte at the specified position. * - * @param pos The position - * @return The specified byte + * @param pos The position. + * @return The specified byte. */ public int get(final int pos) { return bytes[pos]; @@ -183,7 +183,7 @@ public int get(final int pos) { /** * Gets a copy of the BOM's bytes. * - * @return a copy of the BOM's bytes + * @return a copy of the BOM's bytes. */ public byte[] getBytes() { final byte[] copy = IOUtils.byteArray(bytes.length); @@ -196,7 +196,7 @@ public byte[] getBytes() { /** * Gets the name of the {@link java.nio.charset.Charset} the BOM represents. * - * @return the character set name + * @return the character set name. */ public String getCharsetName() { return charsetName; @@ -224,7 +224,7 @@ public int hashCode() { /** * Gets the length of the BOM's bytes. * - * @return the length of the BOM's bytes + * @return the length of the BOM's bytes. */ public int length() { return bytes.length; @@ -260,7 +260,7 @@ public boolean matches(final int[] test) { /** * Converts this instance to a String representation of the BOM. * - * @return the length of the BOM's bytes + * @return the length of the BOM's bytes. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/ByteOrderParser.java b/src/main/java/org/apache/commons/io/ByteOrderParser.java index 33a263e4966..6684c04c23f 100644 --- a/src/main/java/org/apache/commons/io/ByteOrderParser.java +++ b/src/main/java/org/apache/commons/io/ByteOrderParser.java @@ -41,8 +41,8 @@ public final class ByteOrderParser { * * * @param value - * the {@link String} containing the ByteOrder representation to be parsed - * @return the ByteOrder represented by the string argument + * the {@link String} containing the ByteOrder representation to be parsed. + * @return the ByteOrder represented by the string argument. * @throws IllegalArgumentException * if the {@link String} containing the ByteOrder representation to be parsed is unknown. */ diff --git a/src/main/java/org/apache/commons/io/Charsets.java b/src/main/java/org/apache/commons/io/Charsets.java index ef6a6264f77..0c5fa748a73 100644 --- a/src/main/java/org/apache/commons/io/Charsets.java +++ b/src/main/java/org/apache/commons/io/Charsets.java @@ -34,21 +34,38 @@ * documentation for your implementation to see if any other encodings are supported. *

* - *
    - *
  • {@code US-ASCII}
    - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • - *
  • {@code ISO-8859-1}
    - * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • - *
  • {@code UTF-8}
    - * Eight-bit Unicode Transformation Format.
  • - *
  • {@code UTF-16BE}
    - * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • - *
  • {@code UTF-16LE}
    - * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • - *
  • {@code UTF-16}
    - * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order - * accepted on input, big-endian used on output.)
  • - *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Standard Charsets
CharsetDescription
{@code US-ASCII}Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
{@code ISO-8859-1}ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
{@code UTF-8}Eight-bit Unicode Transformation Format.
{@code UTF-16BE}Sixteen-bit Unicode Transformation Format, big-endian byte order.
{@code UTF-16LE}Sixteen-bit Unicode Transformation Format, little-endian byte order.
{@code UTF-16}Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order + * accepted on input, big-endian used on output.)
* * @see Standard charsets * @since 2.3 @@ -80,7 +97,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#ISO_8859_1}. */ @Deprecated public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; @@ -94,7 +111,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#US_ASCII}. */ @Deprecated public static final Charset US_ASCII = StandardCharsets.US_ASCII; @@ -109,7 +126,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_16}. */ @Deprecated public static final Charset UTF_16 = StandardCharsets.UTF_16; @@ -123,7 +140,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_16BE}. */ @Deprecated public static final Charset UTF_16BE = StandardCharsets.UTF_16BE; @@ -137,7 +154,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_16LE}. */ @Deprecated public static final Charset UTF_16LE = StandardCharsets.UTF_16LE; @@ -151,7 +168,7 @@ public class Charsets { *

* * @see Standard charsets - * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} + * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8}. */ @Deprecated public static final Charset UTF_8 = StandardCharsets.UTF_8; @@ -210,7 +227,7 @@ public static SortedMap requiredCharsets() { * * @param charset * A charset or null. - * @return the given Charset or the default Charset if the given Charset is null + * @return the given Charset or the default Charset if the given Charset is null. * @see Charset#defaultCharset() */ public static Charset toCharset(final Charset charset) { @@ -277,7 +294,7 @@ public static Charset toCharsetDefault(final String charsetName, final Charset d /** * Construct a new instance. * - * @deprecated Will be private in 4.0 + * @deprecated Will be private in 3.0. */ @Deprecated public Charsets() { diff --git a/src/main/java/org/apache/commons/io/CopyUtils.java b/src/main/java/org/apache/commons/io/CopyUtils.java index ad426952e4e..ed816826305 100644 --- a/src/main/java/org/apache/commons/io/CopyUtils.java +++ b/src/main/java/org/apache/commons/io/CopyUtils.java @@ -29,6 +29,8 @@ import java.io.Writer; import java.nio.charset.Charset; +import org.apache.commons.io.IOUtils.ScratchChars; + /** * This class provides static utility methods for buffered * copying between sources ({@link InputStream}, {@link Reader}, @@ -44,7 +46,7 @@ * released when the associated Stream is garbage-collected. It is not a good * idea to rely on this mechanism. For a good overview of the distinction * between "memory management" and "resource management", see - * this + * this * UnixReview article. *

* For byte-to-char methods, a {@code copy} variant allows the encoding @@ -115,9 +117,10 @@ public class CopyUtils { /** * Copies bytes from a {@code byte[]} to an {@link OutputStream}. - * @param input the byte array to read from - * @param output the {@link OutputStream} to write to - * @throws IOException In case of an I/O problem + * + * @param input the byte array to read from. + * @param output the {@link OutputStream} to write to. + * @throws IOException In case of an I/O problem. */ public static void copy(final byte[] input, final OutputStream output) throws IOException { output.write(input); @@ -128,10 +131,10 @@ public static void copy(final byte[] input, final OutputStream output) throws IO * {@link Writer}. * The platform's default encoding is used for the byte-to-char conversion. * - * @param input the byte array to read from - * @param output the {@link Writer} to write to - * @throws IOException In case of an I/O problem - * @deprecated Use {@link #copy(byte[], Writer, String)} instead + * @param input the byte array to read from. + * @param output the {@link Writer} to write to. + * @throws IOException In case of an I/O problem. + * @deprecated Use {@link #copy(byte[], Writer, String)} instead. */ @Deprecated public static void copy(final byte[] input, final Writer output) throws IOException { @@ -143,12 +146,12 @@ public static void copy(final byte[] input, final Writer output) throws IOExcept * Copies and convert bytes from a {@code byte[]} to chars on a * {@link Writer}, using the specified encoding. * - * @param input the byte array to read from - * @param output the {@link Writer} to write to + * @param input the byte array to read from. + * @param output the {@link Writer} to write to. * @param encoding The name of a supported character encoding. See the - * IANA + * IANA * Charset Registry for a list of valid encoding types. - * @throws IOException In case of an I/O problem + * @throws IOException In case of an I/O problem. */ public static void copy(final byte[] input, final Writer output, final String encoding) throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); @@ -159,10 +162,10 @@ public static void copy(final byte[] input, final Writer output, final String en * Copies bytes from an {@link InputStream} to an * {@link OutputStream}. * - * @param input the {@link InputStream} to read from - * @param output the {@link OutputStream} to write to - * @return the number of bytes copied - * @throws IOException In case of an I/O problem + * @param input the {@link InputStream} to read from. + * @param output the {@link OutputStream} to write to. + * @return the number of bytes copied. + * @throws IOException In case of an I/O problem. */ public static int copy(final InputStream input, final OutputStream output) throws IOException { final byte[] buffer = IOUtils.byteArray(); @@ -179,13 +182,13 @@ public static int copy(final InputStream input, final OutputStream output) throw * Copies and convert bytes from an {@link InputStream} to chars on a * {@link Writer}. *

- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion. *

* - * @param input the {@link InputStream} to read from - * @param output the {@link Writer} to write to - * @throws IOException In case of an I/O problem - * @deprecated Use {@link #copy(InputStream, Writer, String)} instead + * @param input the {@link InputStream} to read from. + * @param output the {@link Writer} to write to. + * @throws IOException In case of an I/O problem. + * @deprecated Use {@link #copy(InputStream, Writer, String)} instead. */ @Deprecated public static void copy( @@ -201,12 +204,12 @@ public static void copy( * Copies and convert bytes from an {@link InputStream} to chars on a * {@link Writer}, using the specified encoding. * - * @param input the {@link InputStream} to read from - * @param output the {@link Writer} to write to + * @param input the {@link InputStream} to read from. + * @param output the {@link Writer} to write to. * @param encoding The name of a supported character encoding. See the - * IANA + * IANA * Charset Registry for a list of valid encoding types. - * @throws IOException In case of an I/O problem + * @throws IOException In case of an I/O problem. */ public static void copy( final InputStream input, @@ -221,13 +224,13 @@ public static void copy( * Serialize chars from a {@link Reader} to bytes on an * {@link OutputStream}, and flush the {@link OutputStream}. *

- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion. *

* - * @param input the {@link Reader} to read from - * @param output the {@link OutputStream} to write to - * @throws IOException In case of an I/O problem - * @deprecated Use {@link #copy(Reader, OutputStream, String)} instead + * @param input the {@link Reader} to read from. + * @param output the {@link OutputStream} to write to. + * @throws IOException In case of an I/O problem. + * @deprecated Use {@link #copy(Reader, OutputStream, String)} instead. */ @Deprecated public static void copy( @@ -246,12 +249,12 @@ public static void copy( * Serialize chars from a {@link Reader} to bytes on an * {@link OutputStream}, and flush the {@link OutputStream}. * - * @param input the {@link Reader} to read from - * @param output the {@link OutputStream} to write to + * @param input the {@link Reader} to read from. + * @param output the {@link OutputStream} to write to. * @param encoding The name of a supported character encoding. See the - * IANA + * IANA * Charset Registry for a list of valid encoding types. - * @throws IOException In case of an I/O problem + * @throws IOException In case of an I/O problem. * @since 2.5 */ public static void copy( @@ -269,23 +272,25 @@ public static void copy( /** * Copies chars from a {@link Reader} to a {@link Writer}. * - * @param input the {@link Reader} to read from - * @param output the {@link Writer} to write to - * @return the number of characters copied - * @throws IOException In case of an I/O problem + * @param input the {@link Reader} to read from. + * @param output the {@link Writer} to write to. + * @return the number of characters copied. + * @throws IOException In case of an I/O problem. */ public static int copy( final Reader input, final Writer output) throws IOException { - final char[] buffer = IOUtils.getScratchCharArray(); - int count = 0; - int n; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + final char[] buffer = scratch.array(); + int count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; } - return count; } /** @@ -293,13 +298,13 @@ public static int copy( * {@link OutputStream}, and * flush the {@link OutputStream}. *

- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion. *

* - * @param input the {@link String} to read from - * @param output the {@link OutputStream} to write to - * @throws IOException In case of an I/O problem - * @deprecated Use {@link #copy(String, OutputStream, String)} instead + * @param input the {@link String} to read from. + * @param output the {@link OutputStream} to write to. + * @throws IOException In case of an I/O problem. + * @deprecated Use {@link #copy(String, OutputStream, String)} instead. */ @Deprecated public static void copy( @@ -320,12 +325,12 @@ public static void copy( * {@link OutputStream}, and * flush the {@link OutputStream}. * - * @param input the {@link String} to read from - * @param output the {@link OutputStream} to write to + * @param input the {@link String} to read from. + * @param output the {@link OutputStream} to write to. * @param encoding The name of a supported character encoding. See the - * IANA + * IANA * Charset Registry for a list of valid encoding types. - * @throws IOException In case of an I/O problem + * @throws IOException In case of an I/O problem. * @since 2.5 */ public static void copy( @@ -344,9 +349,9 @@ public static void copy( /** * Copies chars from a {@link String} to a {@link Writer}. * - * @param input the {@link String} to read from - * @param output the {@link Writer} to write to - * @throws IOException In case of an I/O problem + * @param input the {@link String} to read from. + * @param output the {@link Writer} to write to. + * @throws IOException In case of an I/O problem. */ public static void copy(final String input, final Writer output) throws IOException { diff --git a/src/main/java/org/apache/commons/io/DirectoryWalker.java b/src/main/java/org/apache/commons/io/DirectoryWalker.java index ac3d8bd5fd7..1d2a7678fd4 100644 --- a/src/main/java/org/apache/commons/io/DirectoryWalker.java +++ b/src/main/java/org/apache/commons/io/DirectoryWalker.java @@ -167,7 +167,7 @@ *
    *
  • The decision logic on whether to cancel processing or not.
  • *
  • Constructing and throwing a {@link CancelException}.
  • - *
  • Custom cancel processing in the {@code handleCancelled()} method. + *
  • Custom cancel processing in the {@code handleCancelled()} method.
  • *
*

* Two possible scenarios are envisaged for cancellation: @@ -196,14 +196,14 @@ *

  * public class FooDirectoryWalker extends DirectoryWalker {
  *
- *     private volatile boolean cancelled = false;
+ *     private volatile boolean canceled = false;
  *
  *     public void cancel() {
- *         cancelled = true;
+ *         canceled = true;
  *     }
  *
  *     protected boolean handleIsCancelled(File file, int depth, Collection results) {
- *         return cancelled;
+ *         return canceled;
  *     }
  *
  *     protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
@@ -265,6 +265,7 @@ public static class CancelException extends IOException {
 
         /** The file being processed when the exception was thrown. */
         private final File file;
+
         /** The file depth when the exception was thrown. */
         private final int depth;
 
@@ -272,8 +273,8 @@ public static class CancelException extends IOException {
          * Constructs a {@link CancelException} with
          * the file and depth when cancellation occurred.
          *
-         * @param file  the file when the operation was cancelled, may be null
-         * @param depth  the depth when the operation was cancelled, may be null
+         * @param file  the file when the operation was canceled, may be null.
+         * @param depth  the depth when the operation was canceled, may be null.
          */
         public CancelException(final File file, final int depth) {
             this("Operation Cancelled", file, depth);
@@ -284,9 +285,9 @@ public CancelException(final File file, final int depth) {
          * an appropriate message and the file and depth when
          * cancellation occurred.
          *
-         * @param message  the detail message
-         * @param file  the file when the operation was cancelled
-         * @param depth  the depth when the operation was cancelled
+         * @param message  the detail message.
+         * @param file  the file when the operation was canceled.
+         * @param depth  the depth when the operation was canceled.
          */
         public CancelException(final String message, final File file, final int depth) {
             super(message);
@@ -295,23 +296,24 @@ public CancelException(final String message, final File file, final int depth) {
         }
 
         /**
-         * Returns the depth when the operation was cancelled.
+         * Returns the depth when the operation was canceled.
          *
-         * @return the depth when the operation was cancelled
+         * @return the depth when the operation was canceled.
          */
         public int getDepth() {
             return depth;
         }
 
         /**
-         * Returns the file when the operation was cancelled.
+         * Returns the file when the operation was canceled.
          *
-         * @return the file when the operation was cancelled
+         * @return the file when the operation was canceled.
          */
         public File getFile() {
             return file;
         }
     }
+
     /**
      * The file filter to use to filter files and directories.
      */
@@ -338,9 +340,9 @@ protected DirectoryWalker() {
      * filtering should occur and all files and directories will be visited.
      * 

* - * @param filter the filter to apply, null means visit all files + * @param filter the filter to apply, null means visit all files. * @param depthLimit controls how deep the hierarchy is - * navigated to (less than 0 means unlimited) + * navigated to (less than 0 means unlimited). */ protected DirectoryWalker(final FileFilter filter, final int depthLimit) { this.filter = filter; @@ -357,10 +359,10 @@ protected DirectoryWalker(final FileFilter filter, final int depthLimit) { * A {@code null} filter means that no filtering should occur. *

* - * @param directoryFilter the filter to apply to directories, null means visit all directories - * @param fileFilter the filter to apply to files, null means visit all files + * @param directoryFilter the filter to apply to directories, null means visit all directories. + * @param fileFilter the filter to apply to files, null means visit all files. * @param depthLimit controls how deep the hierarchy is - * navigated to (less than 0 means unlimited) + * navigated to (less than 0 means unlimited). */ protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) { if (directoryFilter == null && fileFilter == null) { @@ -376,7 +378,7 @@ protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, } /** - * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled}, + * Checks whether the walk has been canceled by calling {@link #handleIsCancelled}, * throwing a {@link CancelException} if it has. *

* Writers of subclasses should not normally call this method as it is called @@ -385,10 +387,10 @@ protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, * you may wish to check for cancellation by calling this method. *

* - * @param file the current file being processed - * @param depth the current file level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param file the current file being processed. + * @param depth the current file level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ protected final void checkIfCancelled(final File file, final int depth, final Collection results) throws IOException { @@ -403,11 +405,11 @@ protected final void checkIfCancelled(final File file, final int depth, final Co * This implementation returns the files unchanged *

* - * @param directory the current directory being processed - * @param depth the current directory level (starting directory = 0) + * @param directory the current directory being processed. + * @param depth the current directory level (starting directory = 0). * @param files the files (possibly filtered) in the directory, may be {@code null} - * @return the filtered list of files - * @throws IOException if an I/O Error occurs + * @return the filtered list of files. + * @throws IOException if an I/O Error occurs. * @since 2.0 */ @SuppressWarnings("unused") // Possibly thrown from subclasses. @@ -417,18 +419,18 @@ protected File[] filterDirectoryContents(final File directory, final int depth, } /** - * Overridable callback method invoked when the operation is cancelled. + * Overridable callback method invoked when the operation is canceled. * The file being processed when the cancellation occurred can be * obtained from the exception. *

* This implementation just re-throws the {@link CancelException}. *

* - * @param startDirectory the directory that the walk started from - * @param results the collection of result objects, may be updated + * @param startDirectory the directory that the walk started from. + * @param results the collection of result objects, may be updated. * @param cancel the exception throw to cancel further processing * containing details at the point of cancellation. - * @throws IOException if an I/O Error occurs + * @throws IOException if an I/O Error occurs. */ protected void handleCancelled(final File startDirectory, final Collection results, final CancelException cancel) throws IOException { @@ -447,11 +449,11 @@ protected void handleCancelled(final File startDirectory, final Collection re * This implementation does nothing and returns true. *

* - * @param directory the current directory being processed - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @return true to process this directory, false to skip this directory - * @throws IOException if an I/O Error occurs + * @param directory the current directory being processed. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @return true to process this directory, false to skip this directory. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected boolean handleDirectory(final File directory, final int depth, final Collection results) throws @@ -466,10 +468,10 @@ protected boolean handleDirectory(final File directory, final int depth, final C * This implementation does nothing. *

* - * @param directory the directory being processed - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param directory the directory being processed. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleDirectoryEnd(final File directory, final int depth, final Collection results) throws @@ -483,10 +485,10 @@ protected void handleDirectoryEnd(final File directory, final int depth, final C * This implementation does nothing. *

* - * @param directory the current directory being processed - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param directory the current directory being processed. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleDirectoryStart(final File directory, final int depth, final Collection results) throws @@ -500,8 +502,8 @@ protected void handleDirectoryStart(final File directory, final int depth, final * This implementation does nothing. *

* - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleEnd(final Collection results) throws IOException { @@ -514,10 +516,10 @@ protected void handleEnd(final Collection results) throws IOException { * This implementation does nothing. *

* - * @param file the current file being processed - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param file the current file being processed. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleFile(final File file, final int depth, final Collection results) throws IOException { @@ -526,7 +528,7 @@ protected void handleFile(final File file, final int depth, final Collection /** * Overridable callback method invoked to determine if the entire walk - * operation should be immediately cancelled. + * operation should be immediately canceled. *

* This method should be implemented by those subclasses that want to * provide a public {@code cancel()} method available from another @@ -534,13 +536,13 @@ protected void handleFile(final File file, final int depth, final Collection *

*
      *  public class FooDirectoryWalker extends DirectoryWalker {
-     *    private volatile boolean cancelled = false;
+     *    private volatile boolean canceled = false;
      *
      *    public void cancel() {
-     *        cancelled = true;
+     *        canceled = true;
      *    }
      *    private void handleIsCancelled(File file, int depth, Collection results) {
-     *        return cancelled;
+     *        return canceled;
      *    }
      *    protected void handleCancelled(File startDirectory,
      *              Collection results, CancelException cancel) {
@@ -550,23 +552,23 @@ protected void handleFile(final File file, final int depth, final Collection
      * 
*

* If this method returns true, then the directory walk is immediately - * cancelled. The next callback method will be {@link #handleCancelled}. + * canceled. The next callback method will be {@link #handleCancelled}. *

*

* This implementation returns false. *

* - * @param file the file or directory being processed - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @return true if the walk has been cancelled - * @throws IOException if an I/O Error occurs + * @param file the file or directory being processed. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @return true if the walk has been canceled. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected boolean handleIsCancelled( final File file, final int depth, final Collection results) throws IOException { // do nothing - overridable by subclass - return false; // not cancelled + return false; // not canceled } /** @@ -575,10 +577,10 @@ protected boolean handleIsCancelled( * This implementation does nothing. *

* - * @param directory the restricted directory - * @param depth the current directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param directory the restricted directory. + * @param depth the current directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleRestricted(final File directory, final int depth, final Collection results) throws @@ -592,9 +594,9 @@ protected void handleRestricted(final File directory, final int depth, final Col * This implementation does nothing. *

* - * @param startDirectory the directory to start from - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param startDirectory the directory to start from. + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ @SuppressWarnings("unused") // Possibly thrown from subclasses. protected void handleStart(final File startDirectory, final Collection results) throws IOException { @@ -613,10 +615,10 @@ protected void handleStart(final File startDirectory, final Collection result * The event methods have the prefix {@code handle}. *

* - * @param startDirectory the directory to start from, not null - * @param results the collection of result objects, may be updated - * @throws NullPointerException if the start directory is null - * @throws IOException if an I/O Error occurs + * @param startDirectory the directory to start from, not null. + * @param results the collection of result objects, may be updated. + * @throws NullPointerException if the start directory is null. + * @throws IOException if an I/O Error occurs. */ protected final void walk(final File startDirectory, final Collection results) throws IOException { Objects.requireNonNull(startDirectory, "startDirectory"); @@ -632,10 +634,10 @@ protected final void walk(final File startDirectory, final Collection results /** * Main recursive method to examine the directory hierarchy. * - * @param directory the directory to examine, not null - * @param depth the directory level (starting directory = 0) - * @param results the collection of result objects, may be updated - * @throws IOException if an I/O Error occurs + * @param directory the directory to examine, not null. + * @param depth the directory level (starting directory = 0). + * @param results the collection of result objects, may be updated. + * @throws IOException if an I/O Error occurs. */ private void walk(final File directory, final int depth, final Collection results) throws IOException { checkIfCancelled(directory, depth, results); diff --git a/src/main/java/org/apache/commons/io/EndianUtils.java b/src/main/java/org/apache/commons/io/EndianUtils.java index 6b4d58eeba3..b6cb2ebd83a 100644 --- a/src/main/java/org/apache/commons/io/EndianUtils.java +++ b/src/main/java/org/apache/commons/io/EndianUtils.java @@ -50,9 +50,10 @@ public class EndianUtils { /** * Reads the next byte from the input stream. - * @param input the stream - * @return the byte - * @throws IOException if the end of file is reached + * + * @param input the stream. + * @return the byte. + * @throws IOException if the end of file is reached. */ private static int read(final InputStream input) throws IOException { final int value = input.read(); @@ -65,10 +66,10 @@ private static int read(final InputStream input) throws IOException { /** * Reads a little-endian {@code double} value from a byte array at a given offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes. */ public static double readSwappedDouble(final byte[] data, final int offset) { return Double.longBitsToDouble(readSwappedLong(data, offset)); @@ -77,9 +78,9 @@ public static double readSwappedDouble(final byte[] data, final int offset) { /** * Reads a little-endian {@code double} value from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static double readSwappedDouble(final InputStream input) throws IOException { return Double.longBitsToDouble(readSwappedLong(input)); @@ -88,10 +89,10 @@ public static double readSwappedDouble(final InputStream input) throws IOExcepti /** * Reads a little-endian {@code float} value from a byte array at a given offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes. */ public static float readSwappedFloat(final byte[] data, final int offset) { return Float.intBitsToFloat(readSwappedInteger(data, offset)); @@ -100,9 +101,9 @@ public static float readSwappedFloat(final byte[] data, final int offset) { /** * Reads a little-endian {@code float} value from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static float readSwappedFloat(final InputStream input) throws IOException { return Float.intBitsToFloat(readSwappedInteger(input)); @@ -111,14 +112,15 @@ public static float readSwappedFloat(final InputStream input) throws IOException /** * Reads a little-endian {@code int} value from a byte array at a given offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes. */ public static int readSwappedInteger(final byte[] data, final int offset) { validateByteArrayOffset(data, offset, Integer.SIZE / Byte.SIZE); - return ((data[offset + 0] & 0xff) << 0) + + return + ((data[offset + 0] & 0xff) << 0) + ((data[offset + 1] & 0xff) << 8) + ((data[offset + 2] & 0xff) << 16) + ((data[offset + 3] & 0xff) << 24); @@ -127,25 +129,29 @@ public static int readSwappedInteger(final byte[] data, final int offset) { /** * Reads a little-endian {@code int} value from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static int readSwappedInteger(final InputStream input) throws IOException { final int value1 = read(input); final int value2 = read(input); final int value3 = read(input); final int value4 = read(input); - return ((value1 & 0xff) << 0) + ((value2 & 0xff) << 8) + ((value3 & 0xff) << 16) + ((value4 & 0xff) << 24); + return + ((value1 & 0xff) << 0) + + ((value2 & 0xff) << 8) + + ((value3 & 0xff) << 16) + + ((value4 & 0xff) << 24); } /** * Reads a little-endian {@code long} value from a byte array at a given offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes. */ public static long readSwappedLong(final byte[] data, final int offset) { validateByteArrayOffset(data, offset, Long.SIZE / Byte.SIZE); @@ -157,9 +163,9 @@ public static long readSwappedLong(final byte[] data, final int offset) { /** * Reads a little-endian {@code long} value from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static long readSwappedLong(final InputStream input) throws IOException { final byte[] bytes = new byte[8]; @@ -172,41 +178,47 @@ public static long readSwappedLong(final InputStream input) throws IOException { /** * Reads a little-endian {@code short} value from a byte array at a given offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes. */ public static short readSwappedShort(final byte[] data, final int offset) { validateByteArrayOffset(data, offset, Short.SIZE / Byte.SIZE); - return (short) (((data[offset + 0] & 0xff) << 0) + ((data[offset + 1] & 0xff) << 8)); + return (short) ( + ((data[offset + 0] & 0xff) << 0) + + ((data[offset + 1] & 0xff) << 8) + ); } /** * Reads a little-endian {@code short} value from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static short readSwappedShort(final InputStream input) throws IOException { - return (short) (((read(input) & 0xff) << 0) + ((read(input) & 0xff) << 8)); + return (short) ( + ((read(input) & 0xff) << 0) + + ((read(input) & 0xff) << 8) + ); } /** * Reads a little-endian unsigned integer (32-bit) value from a byte array at a given * offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes. */ public static long readSwappedUnsignedInteger(final byte[] data, final int offset) { validateByteArrayOffset(data, offset, Integer.SIZE / Byte.SIZE); final long low = ((data[offset + 0] & 0xff) << 0) + - ((data[offset + 1] & 0xff) << 8) + - ((data[offset + 2] & 0xff) << 16); + ((data[offset + 1] & 0xff) << 8) + + ((data[offset + 2] & 0xff) << 16); final long high = data[offset + 3] & 0xff; return (high << 24) + (0xffffffffL & low); } @@ -214,16 +226,18 @@ public static long readSwappedUnsignedInteger(final byte[] data, final int offse /** * Reads a little-endian unsigned integer (32-bit) from an InputStream. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static long readSwappedUnsignedInteger(final InputStream input) throws IOException { final int value1 = read(input); final int value2 = read(input); final int value3 = read(input); final int value4 = read(input); - final long low = ((value1 & 0xff) << 0) + ((value2 & 0xff) << 8) + ((value3 & 0xff) << 16); + final long low = ((value1 & 0xff) << 0) + + ((value2 & 0xff) << 8) + + ((value3 & 0xff) << 16); final long high = value4 & 0xff; return (high << 24) + (0xffffffffL & low); } @@ -232,28 +246,30 @@ public static long readSwappedUnsignedInteger(final InputStream input) throws IO * Reads an unsigned short (16-bit) value from a byte array in little-endian order at a given * offset. * - * @param data source byte array - * @param offset starting offset in the byte array - * @return the value read - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes + * @param data source byte array. + * @param offset starting offset in the byte array. + * @return the value read. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes. */ public static int readSwappedUnsignedShort(final byte[] data, final int offset) { validateByteArrayOffset(data, offset, Short.SIZE / Byte.SIZE); - return ((data[offset + 0] & 0xff) << 0) + ((data[offset + 1] & 0xff) << 8); + return ((data[offset + 0] & 0xff) << 0) + + ((data[offset + 1] & 0xff) << 8); } /** * Reads an unsigned short (16-bit) from an InputStream in little-endian order. * - * @param input source InputStream - * @return the value just read - * @throws IOException in case of an I/O problem + * @param input source InputStream. + * @return the value just read. + * @throws IOException in case of an I/O problem. */ public static int readSwappedUnsignedShort(final InputStream input) throws IOException { final int value1 = read(input); final int value2 = read(input); - return ((value1 & 0xff) << 0) + ((value2 & 0xff) << 8); + return ((value1 & 0xff) << 0) + + ((value2 & 0xff) << 8); } /** @@ -263,8 +279,8 @@ public static int readSwappedUnsignedShort(final InputStream input) throws IOExc * This can be useful if you have a number that was read from the * underlying source in the wrong endianness. * - * @param value value to convert - * @return the converted value + * @param value value to convert. + * @return the converted value. */ public static double swapDouble(final double value) { return Double.longBitsToDouble(swapLong(Double.doubleToLongBits(value))); @@ -273,8 +289,8 @@ public static double swapDouble(final double value) { /** * Converts a {@code float} value from big-endian to little-endian and vice versa. * - * @param value value to convert - * @return the converted value + * @param value value to convert. + * @return the converted value. */ public static float swapFloat(final float value) { return Float.intBitsToFloat(swapInteger(Float.floatToIntBits(value))); @@ -283,8 +299,8 @@ public static float swapFloat(final float value) { /** * Converts an {@code int} value from big-endian to little-endian and vice versa. * - * @param value value to convert - * @return the converted value + * @param value value to convert. + * @return the converted value. */ public static int swapInteger(final int value) { return @@ -297,8 +313,8 @@ public static int swapInteger(final int value) { /** * Converts a {@code long} value from big-endian to little-endian and vice versa. * - * @param value value to convert - * @return the converted value + * @param value value to convert. + * @return the converted value. */ public static long swapLong(final long value) { return @@ -315,21 +331,23 @@ public static long swapLong(final long value) { /** * Converts a {@code short} value from big-endian to little-endian and vice versa. * - * @param value value to convert - * @return the converted value + * @param value value to convert. + * @return the converted value. */ public static short swapShort(final short value) { - return (short) (((value >> 0 & 0xff) << 8) + - ((value >> 8 & 0xff) << 0)); + return (short) ( + ((value >> 0 & 0xff) << 8) + + ((value >> 8 & 0xff) << 0) + ); } /** * Validates if the provided byte array has enough data. * - * @param data the input byte array - * @param offset the input offset - * @param byteNeeded the needed number of bytes - * @throws IllegalArgumentException if the byte array does not have enough data + * @param data the input byte array. + * @param offset the input offset. + * @param byteNeeded the needed number of bytes. + * @throws IllegalArgumentException if the byte array does not have enough data. */ private static void validateByteArrayOffset(final byte[] data, final int offset, final int byteNeeded) { if (data.length < offset + byteNeeded) { @@ -340,10 +358,10 @@ private static void validateByteArrayOffset(final byte[] data, final int offset, /** * Writes the 8 bytes of a {@code double} to a byte array at a given offset in little-endian order. * - * @param data target byte array - * @param offset starting offset in the byte array - * @param value value to write - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes + * @param data target byte array. + * @param offset starting offset in the byte array. + * @param value value to write. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes. */ public static void writeSwappedDouble(final byte[] data, final int offset, final double value) { writeSwappedLong(data, offset, Double.doubleToLongBits(value)); @@ -352,9 +370,9 @@ public static void writeSwappedDouble(final byte[] data, final int offset, final /** * Writes the 8 bytes of a {@code double} to an output stream in little-endian order. * - * @param output target OutputStream - * @param value value to write - * @throws IOException in case of an I/O problem + * @param output target OutputStream. + * @param value value to write. + * @throws IOException in case of an I/O problem. */ public static void writeSwappedDouble(final OutputStream output, final double value) throws IOException { writeSwappedLong(output, Double.doubleToLongBits(value)); @@ -363,10 +381,10 @@ public static void writeSwappedDouble(final OutputStream output, final double va /** * Writes the 4 bytes of a {@code float} to a byte array at a given offset in little-endian order. * - * @param data target byte array - * @param offset starting offset in the byte array - * @param value value to write - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes + * @param data target byte array. + * @param offset starting offset in the byte array. + * @param value value to write. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes. */ public static void writeSwappedFloat(final byte[] data, final int offset, final float value) { writeSwappedInteger(data, offset, Float.floatToIntBits(value)); @@ -375,9 +393,9 @@ public static void writeSwappedFloat(final byte[] data, final int offset, final /** * Writes the 4 bytes of a {@code float} to an output stream in little-endian order. * - * @param output target OutputStream - * @param value value to write - * @throws IOException in case of an I/O problem + * @param output target OutputStream. + * @param value value to write. + * @throws IOException in case of an I/O problem. */ public static void writeSwappedFloat(final OutputStream output, final float value) throws IOException { writeSwappedInteger(output, Float.floatToIntBits(value)); @@ -386,10 +404,10 @@ public static void writeSwappedFloat(final OutputStream output, final float valu /** * Writes the 4 bytes of an {@code int} to a byte array at a given offset in little-endian order. * - * @param data target byte array - * @param offset starting offset in the byte array - * @param value value to write - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes + * @param data target byte array. + * @param offset starting offset in the byte array. + * @param value value to write. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 4 bytes. */ public static void writeSwappedInteger(final byte[] data, final int offset, final int value) { validateByteArrayOffset(data, offset, Integer.SIZE / Byte.SIZE); @@ -402,9 +420,9 @@ public static void writeSwappedInteger(final byte[] data, final int offset, fina /** * Writes the 4 bytes of an {@code int} to an output stream in little-endian order. * - * @param output target OutputStream - * @param value value to write - * @throws IOException in case of an I/O problem + * @param output target OutputStream. + * @param value value to write. + * @throws IOException in case of an I/O problem. */ public static void writeSwappedInteger(final OutputStream output, final int value) throws IOException { output.write((byte) (value >> 0 & 0xff)); @@ -416,10 +434,10 @@ public static void writeSwappedInteger(final OutputStream output, final int valu /** * Writes the 8 bytes of a {@code long} to a byte array at a given offset in little-endian order. * - * @param data target byte array - * @param offset starting offset in the byte array - * @param value value to write - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes + * @param data target byte array. + * @param offset starting offset in the byte array. + * @param value value to write. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 8 bytes. */ public static void writeSwappedLong(final byte[] data, final int offset, final long value) { validateByteArrayOffset(data, offset, Long.SIZE / Byte.SIZE); @@ -436,9 +454,9 @@ public static void writeSwappedLong(final byte[] data, final int offset, final l /** * Writes the 8 bytes of a {@code long} to an output stream in little-endian order. * - * @param output target OutputStream - * @param value value to write - * @throws IOException in case of an I/O problem + * @param output target OutputStream. + * @param value value to write. + * @throws IOException in case of an I/O problem. */ public static void writeSwappedLong(final OutputStream output, final long value) throws IOException { output.write((byte) (value >> 0 & 0xff)); @@ -454,10 +472,10 @@ public static void writeSwappedLong(final OutputStream output, final long value) /** * Writes the 2 bytes of a {@code short} to a byte array at a given offset in little-endian order. * - * @param data target byte array - * @param offset starting offset in the byte array - * @param value value to write - * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes + * @param data target byte array. + * @param offset starting offset in the byte array. + * @param value value to write. + * @throws IllegalArgumentException if the part of the byte array starting at offset does not have at least 2 bytes. */ public static void writeSwappedShort(final byte[] data, final int offset, final short value) { validateByteArrayOffset(data, offset, Short.SIZE / Byte.SIZE); @@ -468,9 +486,9 @@ public static void writeSwappedShort(final byte[] data, final int offset, final /** * Writes the 2 bytes of a {@code short} to an output stream using little-endian encoding. * - * @param output target OutputStream - * @param value value to write - * @throws IOException in case of an I/O problem + * @param output target OutputStream. + * @param value value to write. + * @throws IOException in case of an I/O problem. */ public static void writeSwappedShort(final OutputStream output, final short value) throws IOException { output.write((byte) (value >> 0 & 0xff)); diff --git a/src/main/java/org/apache/commons/io/FileCleaner.java b/src/main/java/org/apache/commons/io/FileCleaner.java index 5ffdd7db219..df9bc924f49 100644 --- a/src/main/java/org/apache/commons/io/FileCleaner.java +++ b/src/main/java/org/apache/commons/io/FileCleaner.java @@ -53,7 +53,7 @@ public class FileCleaner { * loader it was started from terminates. This can constitute a memory leak. *

* For example, suppose that you have developed a web application, which - * contains the commons-io jar file in your WEB-INF/lib directory. In other + * contains the Commons IO jar file in your WEB-INF/lib directory. In other * words, the FileCleaner class is loaded through the class loader of your * web application. If the web application is terminated, but the servlet * container is still running, then the file cleaner thread will still exist, @@ -63,6 +63,7 @@ public class FileCleaner { * in the resource cleanup code, such as * {@code javax.servlet.ServletContextListener.contextDestroyed(javax.servlet.ServletContextEvent)}. * One called, no new objects can be tracked by the file cleaner. + * * @deprecated Use {@link FileCleaningTracker#exitWhenFinished()}. */ @Deprecated @@ -76,7 +77,7 @@ public static synchronized void exitWhenFinished() { * {@link FileCleaningTracker} class while maintain compatibility with the * deprecated {@link FileCleaner}. * - * @return the singleton instance + * @return the singleton instance. */ public static FileCleaningTracker getInstance() { return INSTANCE; @@ -86,7 +87,7 @@ public static FileCleaningTracker getInstance() { * Gets the number of files currently being tracked, and therefore * awaiting deletion. * - * @return the number of files being tracked + * @return the number of files being tracked. * @deprecated Use {@link FileCleaningTracker#getTrackCount()}. */ @Deprecated @@ -99,9 +100,9 @@ public static int getTrackCount() { * when the marker instance is garbage collected. * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @throws NullPointerException if the file is null. * @deprecated Use {@link FileCleaningTracker#track(File, Object)}. */ @Deprecated @@ -114,10 +115,10 @@ public static void track(final File file, final Object marker) { * when the marker instance is garbage collected. * The specified deletion strategy is used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException if the file is null. * @deprecated Use {@link FileCleaningTracker#track(File, Object, FileDeleteStrategy)}. */ @Deprecated @@ -130,9 +131,9 @@ public static void track(final File file, final Object marker, final FileDeleteS * when the marker instance is garbage collected. * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. * - * @param path the full path to the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @throws NullPointerException if the path is null + * @param path the full path to the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @throws NullPointerException if the path is null. * @deprecated Use {@link FileCleaningTracker#track(String, Object)}. */ @Deprecated @@ -145,10 +146,10 @@ public static void track(final String path, final Object marker) { * when the marker instance is garbage collected. * The specified deletion strategy is used. * - * @param path the full path to the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @throws NullPointerException if the path is null + * @param path the full path to the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException if the path is null. * @deprecated Use {@link FileCleaningTracker#track(String, Object, FileDeleteStrategy)}. */ @Deprecated diff --git a/src/main/java/org/apache/commons/io/FileCleaningTracker.java b/src/main/java/org/apache/commons/io/FileCleaningTracker.java index 98edebc518e..be2746b66be 100644 --- a/src/main/java/org/apache/commons/io/FileCleaningTracker.java +++ b/src/main/java/org/apache/commons/io/FileCleaningTracker.java @@ -21,14 +21,14 @@ import java.lang.ref.ReferenceQueue; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** - * Keeps track of files awaiting deletion, and deletes them when an associated + * Tracks files awaiting deletion, and deletes them when an associated * marker object is reclaimed by the garbage collector. *

* This utility creates a background thread to handle file deletion. @@ -51,9 +51,10 @@ public class FileCleaningTracker { * The reaper thread. */ private final class Reaper extends Thread { + /** Constructs a new Reaper */ Reaper() { - super("File Reaper"); + super("commons-io-FileCleaningTracker-Reaper"); setPriority(MAX_PRIORITY); setDaemon(true); } @@ -65,16 +66,18 @@ private final class Reaper extends Thread { @Override public void run() { // thread exits when exitWhenFinished is true and there are no more tracked objects - while (!exitWhenFinished || !trackers.isEmpty()) { + while (!(exitWhenFinished && trackers.isEmpty())) { try { // Wait for a tracker to remove. - final Tracker tracker = (Tracker) q.remove(); // cannot return null + final Tracker tracker = (Tracker) refQueue.remove(); // cannot return null trackers.remove(tracker); if (!tracker.delete()) { deleteFailures.add(tracker.getPath()); } tracker.clear(); } catch (final InterruptedException e) { + // interrupted removing from the queue. + interrupt(); continue; } } @@ -99,15 +102,14 @@ private static final class Tracker extends PhantomReference { /** * Constructs an instance of this class from the supplied parameters. * - * @param path the full path to the file to be tracked, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @param marker the marker object used to track the file, not null - * @param queue the queue on to which the tracker will be pushed, not null + * @param path the full path to the file to be tracked, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @param marker the marker object used to track the file, not null. + * @param queue the queue on to which the tracker will be pushed, not null. */ - Tracker(final String path, final FileDeleteStrategy deleteStrategy, final Object marker, - final ReferenceQueue queue) { + Tracker(final String path, final FileDeleteStrategy deleteStrategy, final Object marker, final ReferenceQueue queue) { super(marker, queue); - this.path = path; + this.path = Objects.requireNonNull(path, "path"); this.deleteStrategy = deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy; } @@ -124,7 +126,7 @@ public boolean delete() { /** * Gets the path. * - * @return the path + * @return the path. */ public String getPath() { return path; @@ -134,12 +136,12 @@ public String getPath() { /** * Queue of {@link Tracker} instances being watched. */ - ReferenceQueue q = new ReferenceQueue<>(); + ReferenceQueue refQueue = new ReferenceQueue<>(); /** * Collection of {@link Tracker} instances in existence. */ - final Collection trackers = Collections.synchronizedSet(new HashSet<>()); // synchronized + final Set trackers = Collections.synchronizedSet(new HashSet<>()); // synchronized /** * Collection of File paths that failed to delete. @@ -166,13 +168,13 @@ public FileCleaningTracker() { /** * Adds a tracker to the list of trackers. * - * @param path the full path to the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal + * @param path the full path to the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException Thrown if the path is null. */ - private synchronized void addTracker(final String path, final Object marker, final FileDeleteStrategy - deleteStrategy) { - // synchronized block protects reaper + private synchronized void addTracker(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) { + // synchronized method guards reaper if (exitWhenFinished) { throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called"); } @@ -180,7 +182,7 @@ private synchronized void addTracker(final String path, final Object marker, fin reaper = new Reaper(); reaper.start(); } - trackers.add(new Tracker(path, deleteStrategy, marker, q)); + trackers.add(new Tracker(path, deleteStrategy, marker, refQueue)); } /** @@ -192,21 +194,24 @@ private synchronized void addTracker(final String path, final Object marker, fin * with multiple class loaders (such as an application server), you should be * aware that the file cleaner thread will continue running even if the class * loader it was started from terminates. This can constitute a memory leak. + *

*

* For example, suppose that you have developed a web application, which - * contains the commons-io jar file in your WEB-INF/lib directory. In other + * contains the Commons IO JAR file in your WEB-INF/lib directory. In other * words, the FileCleaner class is loaded through the class loader of your * web application. If the web application is terminated, but the servlet * container is still running, then the file cleaner thread will still exist, * posing a memory leak. + *

*

* This method allows the thread to be terminated. Simply call this method * in the resource cleanup code, such as * {@code javax.servlet.ServletContextListener.contextDestroyed(javax.servlet.ServletContextEvent)}. * Once called, no new objects can be tracked by the file cleaner. + *

*/ public synchronized void exitWhenFinished() { - // synchronized block protects reaper + // synchronized method guards reaper exitWhenFinished = true; if (reaper != null) { synchronized (reaper) { @@ -218,7 +223,7 @@ public synchronized void exitWhenFinished() { /** * Gets a copy of the file paths that failed to delete. * - * @return a copy of the file paths that failed to delete + * @return a copy of the file paths that failed to delete. * @since 2.0 */ public List getDeleteFailures() { @@ -229,7 +234,7 @@ public List getDeleteFailures() { * Gets the number of files currently being tracked, and therefore * awaiting deletion. * - * @return the number of files being tracked + * @return the number of files being tracked. */ public int getTrackCount() { return trackers.size(); @@ -240,9 +245,9 @@ public int getTrackCount() { * when the marker instance is garbage collected. * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @throws NullPointerException if the file is null. */ public void track(final File file, final Object marker) { track(file, marker, null); @@ -253,10 +258,10 @@ public void track(final File file, final Object marker) { * when the marker instance is garbage collected. * The specified deletion strategy is used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException if the file is null. */ public void track(final File file, final Object marker, final FileDeleteStrategy deleteStrategy) { Objects.requireNonNull(file, "file"); @@ -268,9 +273,9 @@ public void track(final File file, final Object marker, final FileDeleteStrategy * when the marker instance is garbage collected. * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @throws NullPointerException if the file is null. * @since 2.14.0 */ public void track(final Path file, final Object marker) { @@ -282,10 +287,10 @@ public void track(final Path file, final Object marker) { * when the marker instance is garbage collected. * The specified deletion strategy is used. * - * @param file the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @throws NullPointerException if the file is null + * @param file the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException if the file is null. * @since 2.14.0 */ public void track(final Path file, final Object marker, final FileDeleteStrategy deleteStrategy) { @@ -298,9 +303,9 @@ public void track(final Path file, final Object marker, final FileDeleteStrategy * when the marker instance is garbage collected. * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used. * - * @param path the full path to the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @throws NullPointerException if the path is null + * @param path the full path to the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @throws NullPointerException if the path is null. */ public void track(final String path, final Object marker) { track(path, marker, null); @@ -311,13 +316,12 @@ public void track(final String path, final Object marker) { * when the marker instance is garbage collected. * The specified deletion strategy is used. * - * @param path the full path to the file to be tracked, not null - * @param marker the marker object used to track the file, not null - * @param deleteStrategy the strategy to delete the file, null means normal - * @throws NullPointerException if the path is null + * @param path the full path to the file to be tracked, not null. + * @param marker the marker object used to track the file, not null. + * @param deleteStrategy the strategy to delete the file, null means normal. + * @throws NullPointerException Thrown if the path is null. */ public void track(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) { - Objects.requireNonNull(path, "path"); addTracker(path, marker, deleteStrategy); } diff --git a/src/main/java/org/apache/commons/io/FileDeleteStrategy.java b/src/main/java/org/apache/commons/io/FileDeleteStrategy.java index 62cf28c1631..a73ea719a75 100644 --- a/src/main/java/org/apache/commons/io/FileDeleteStrategy.java +++ b/src/main/java/org/apache/commons/io/FileDeleteStrategy.java @@ -37,7 +37,7 @@ public class FileDeleteStrategy { /** * Force file deletion strategy. */ - static class ForceFileDeleteStrategy extends FileDeleteStrategy { + static final class ForceFileDeleteStrategy extends FileDeleteStrategy { /** Default Constructor */ ForceFileDeleteStrategy() { @@ -51,10 +51,10 @@ static class ForceFileDeleteStrategy extends FileDeleteStrategy { * if the file exists. *

* - * @param fileToDelete the file to delete, not null + * @param fileToDelete the file to delete, not null. * @return Always returns {@code true} - * @throws NullPointerException if the file is null - * @throws IOException if an error occurs during file deletion + * @throws NullPointerException if the file is null. + * @throws IOException if an error occurs during file deletion. */ @Override protected boolean doDelete(final File fileToDelete) throws IOException { @@ -81,7 +81,7 @@ protected boolean doDelete(final File fileToDelete) throws IOException { /** * Restricted constructor. * - * @param name the name by which the strategy is known + * @param name the name by which the strategy is known. */ protected FileDeleteStrategy(final String name) { this.name = name; @@ -94,9 +94,9 @@ protected FileDeleteStrategy(final String name) { * Subclass writers should override {@link #doDelete(File)}, not this method. *

* - * @param fileToDelete the file to delete, not null - * @throws NullPointerException if the file is null - * @throws IOException if an error occurs during file deletion + * @param fileToDelete the file to delete, not null. + * @throws NullPointerException if the file is null. + * @throws IOException if an error occurs during file deletion. */ public void delete(final File fileToDelete) throws IOException { if (fileToDelete.exists() && !doDelete(fileToDelete)) { @@ -112,8 +112,8 @@ public void delete(final File fileToDelete) throws IOException { * Subclass writers should override {@link #doDelete(File)}, not this method. *

* - * @param fileToDelete the file to delete, null returns true - * @return true if the file was deleted, or there was no such file + * @param fileToDelete the file to delete, null returns true. + * @return true if the file was deleted, or there was no such file. */ public boolean deleteQuietly(final File fileToDelete) { if (fileToDelete == null || !fileToDelete.exists()) { @@ -139,10 +139,10 @@ public boolean deleteQuietly(final File fileToDelete) { * This implementation uses {@link FileUtils#delete(File)}. *

* - * @param file the file to delete, exists, not null - * @return true if the file was deleted - * @throws NullPointerException if the file is null - * @throws IOException if an error occurs during file deletion + * @param file the file to delete, exists, not null. + * @return true if the file was deleted. + * @throws NullPointerException if the file is null. + * @throws IOException if an error occurs during file deletion. */ protected boolean doDelete(final File file) throws IOException { FileUtils.delete(file); @@ -152,7 +152,7 @@ protected boolean doDelete(final File file) throws IOException { /** * Gets a string describing the delete strategy. * - * @return a string describing the delete strategy + * @return a string describing the delete strategy. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/FileExistsException.java b/src/main/java/org/apache/commons/io/FileExistsException.java index 1f733cf11ae..2028ddc5ebd 100644 --- a/src/main/java/org/apache/commons/io/FileExistsException.java +++ b/src/main/java/org/apache/commons/io/FileExistsException.java @@ -40,7 +40,7 @@ public FileExistsException() { /** * Constructs an instance with the specified file. * - * @param file The file that exists + * @param file The file that exists. */ public FileExistsException(final File file) { super("File " + file + " exists"); @@ -49,7 +49,7 @@ public FileExistsException(final File file) { /** * Constructs an instance with the specified message. * - * @param message The error message + * @param message The error message. */ public FileExistsException(final String message) { super(message); diff --git a/src/main/java/org/apache/commons/io/FileSystem.java b/src/main/java/org/apache/commons/io/FileSystem.java index 4169e1a53b1..11d3673f213 100644 --- a/src/main/java/org/apache/commons/io/FileSystem.java +++ b/src/main/java/org/apache/commons/io/FileSystem.java @@ -32,11 +32,10 @@ import java.util.Objects; /** - * Abstracts an OS' file system details, currently supporting the single use case of converting a file name String to a - * legal file name with {@link #toLegalFileName(String, char)}. + * Enumerates file system details for operating systems, currently supporting the single use case of converting a file name String to a legal file name with + * {@link #toLegalFileName(String, char)}. *

- * The starting point of any operation is {@link #getCurrent()} which gets you the enum for the file system that matches - * the OS hosting the running JVM. + * The starting point of any operation is {@link #getCurrent()} which gets you the enum for the file system that matches the OS hosting the running JVM. *

* * @since 2.7 @@ -118,6 +117,7 @@ public enum FileSystem { * Implementations measure length and can truncate to a specified limit. */ enum NameLengthStrategy { + /** Length measured as encoded bytes. */ BYTES { @Override @@ -266,7 +266,7 @@ final boolean isWithinLimit(final CharSequence value, final int limit, final Cha /** * Gets the current file system. * - * @return the current file system + * @return the current file system. */ private static FileSystem current() { if (IS_OS_LINUX) { @@ -284,7 +284,7 @@ private static FileSystem current() { /** * Gets the current file system. * - * @return the current file system + * @return the current file system. */ public static FileSystem getCurrent() { return CURRENT; @@ -294,8 +294,8 @@ public static FileSystem getCurrent() { * Decides if the operating system matches. * * @param osNamePrefix - * the prefix for the os name - * @return true if matches, or false if not or can't determine + * the prefix for the operating system name. + * @return true if matches, or false if not or can't determine. */ private static boolean getOsMatchesName(final String osNamePrefix) { return isOsNameMatch(getSystemProperty("os.name"), osNamePrefix); @@ -309,8 +309,8 @@ private static boolean getOsMatchesName(final String osNamePrefix) { *

* * @param property - * the system property name - * @return the system property value or {@code null} if a security problem occurs + * the system property name. + * @return the system property value or {@code null} if a security problem occurs. */ private static String getSystemProperty(final String property) { try { @@ -345,10 +345,10 @@ private static int indexOfFirstDot(final CharSequence cs) { *

* * @param osName - * the actual OS name + * the actual OS name. * @param osNamePrefix - * the prefix for the expected OS name - * @return true if matches, or false if not or can't determine + * the prefix for the expected OS name. + * @return true if matches, or false if not or can't determine. */ private static boolean isOsNameMatch(final String osName, final String osNamePrefix) { if (osName == null) { diff --git a/src/main/java/org/apache/commons/io/FileSystemUtils.java b/src/main/java/org/apache/commons/io/FileSystemUtils.java index 4c0af0d3671..7455dca8d09 100644 --- a/src/main/java/org/apache/commons/io/FileSystemUtils.java +++ b/src/main/java/org/apache/commons/io/FileSystemUtils.java @@ -53,12 +53,12 @@ public class FileSystemUtils { * FileSystemUtils.freeSpace("/volume"); // *nix * * - * @param path the path to get free space for, not null, not empty on Unix - * @return the amount of free drive space on the drive or volume + * @param path the path to get free space for, not null, not empty on Unix. + * @return the amount of free drive space on the drive or volume. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. * @since 1.1, enhanced OS support in 1.2 and 1.3 - * @deprecated Use freeSpaceKb(String) Deprecated from 1.3, may be removed in 2.0 + * @deprecated Use freeSpaceKb(String) Deprecated from 1.3, may be removed in 2.0. */ @Deprecated public static long freeSpace(final String path) throws IOException { @@ -75,7 +75,7 @@ public static long freeSpace(final String path) throws IOException { * freeSpaceKb(FileUtils.current().getAbsolutePath()) * * - * @return the amount of free drive space on the drive or volume in kilobytes + * @return the amount of free drive space on the drive or volume in kilobytes. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. * @since 2.0 @@ -97,7 +97,7 @@ public static long freeSpaceKb() throws IOException { * * * @param timeout ignored. - * @return the amount of free drive space on the drive or volume in kilobytes + * @return the amount of free drive space on the drive or volume in kilobytes. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. * @since 2.0 @@ -116,8 +116,8 @@ public static long freeSpaceKb(final long timeout) throws IOException { * FileSystemUtils.freeSpaceKb("/volume"); // *nix * * - * @param path the path to get free space for, not null, not empty on Unix - * @return the amount of free drive space on the drive or volume in kilobytes + * @param path the path to get free space for, not null, not empty on Unix. + * @return the amount of free drive space on the drive or volume in kilobytes. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. * @since 1.2, enhanced OS support in 1.3 @@ -136,9 +136,9 @@ public static long freeSpaceKb(final String path) throws IOException { * FileSystemUtils.freeSpaceKb("/volume"); // *nix * * - * @param path the path to get free space for, not null, not empty on Unix + * @param path the path to get free space for, not null, not empty on Unix. * @param timeout ignored. - * @return the amount of free drive space on the drive or volume in kilobytes + * @return the amount of free drive space on the drive or volume in kilobytes. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. * @since 2.0 @@ -157,8 +157,8 @@ public static long freeSpaceKb(final String path, final long timeout) throws IOE * FileSystemUtils.freeSpace("/volume"); // *nix * * - * @param pathStr the path to get free space for, not null, not empty on Unix - * @return the amount of free drive space on the drive or volume + * @param pathStr the path to get free space for, not null, not empty on Unix. + * @return the amount of free drive space on the drive or volume. * @throws IOException if an I/O error occurs. * @throws IllegalArgumentException if the path is invalid. */ diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java index 203fe84bfc5..11afd3f5d62 100644 --- a/src/main/java/org/apache/commons/io/FileUtils.java +++ b/src/main/java/org/apache/commons/io/FileUtils.java @@ -93,16 +93,16 @@ * Facilities are provided in the following areas: *

*
    - *
  • writing to a file - *
  • reading from a file - *
  • make a directory including parent directories - *
  • copying files and directories - *
  • deleting files and directories - *
  • converting to and from a URL - *
  • listing files and directories by filter and extension - *
  • comparing file content - *
  • file last changed date - *
  • calculating a checksum + *
  • writing to a file
  • + *
  • reading from a file
  • + *
  • make a directory including parent directories
  • + *
  • copying files and directories
  • + *
  • deleting files and directories
  • + *
  • converting to and from a URL
  • + *
  • listing files and directories by filter and extension
  • + *
  • comparing file content
  • + *
  • file last changed date
  • + *
  • calculating a checksum
  • *
*

* Note that a specific charset should be specified whenever possible. Relying on the platform default means that the @@ -112,7 +112,7 @@ * {@link SecurityException} are not documented in the Javadoc. *

*

- * Provenance: Excalibur, Alexandria, Commons-Utils + * Provenance: Excalibur, Alexandria, Commons-Utils. *

*/ public class FileUtils { @@ -230,8 +230,8 @@ public class FileUtils { * Similarly for the 1MB and 1KB boundaries. *

* - * @param size the number of bytes - * @return a human-readable display value (includes units - QB, RB, YB, ZB, EB, PB, TB, GB, MB, KB or bytes) + * @param size the number of bytes. + * @return a human-readable display value (includes units - QB, RB, YB, ZB, EB, PB, TB, GB, MB, KB or bytes). * @throws NullPointerException if the given {@link BigInteger} is {@code null}. * @see IO-226 - should the rounding be changed? * @since 2.4 @@ -276,8 +276,8 @@ public static String byteCountToDisplaySize(final BigInteger size) { * Similarly for the 1MB and 1KB boundaries. *

* - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @param size the number of bytes. + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes). * @see IO-226 - should the rounding be changed? */ // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? @@ -295,8 +295,8 @@ public static String byteCountToDisplaySize(final long size) { * Similarly for the 1MB and 1KB boundaries. *

* - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @param size the number of bytes. + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes). * @see IO-226 - should the rounding be changed? * @since 2.12.0 */ @@ -357,11 +357,11 @@ private static File checkIsFile(final File file, final String name) { * * @param file the file to checksum, must not be {@code null} * @param checksum the checksum object to be used, must not be {@code null} - * @return the checksum specified, updated with the content of the file + * @return the checksum specified, updated with the content of the file. * @throws NullPointerException if the given {@link File} is {@code null}. * @throws NullPointerException if the given {@link Checksum} is {@code null}. * @throws IllegalArgumentException if the given {@link File} is not a file. - * @throws FileNotFoundException if the file does not exist + * @throws FileNotFoundException if the file does not exist. * @throws IOException if an IO error occurs reading the file. * @since 1.3 */ @@ -379,7 +379,7 @@ public static Checksum checksum(final File file, final Checksum checksum) throws * The value of the checksum is returned. * * @param file the file to checksum, must not be {@code null} - * @return the checksum value + * @return the checksum value. * @throws NullPointerException if the {@code file} is {@code null}. * @throws IllegalArgumentException if the {@code file} does not exist or is not a file. * @throws IOException if an IO error occurs reading the file. @@ -392,7 +392,7 @@ public static long checksumCRC32(final File file) throws IOException { /** * Cleans a directory without deleting it. * - * @param directory directory to clean + * @param directory directory to clean. * @throws NullPointerException if the given {@link File} is {@code null}. * @throws IllegalArgumentException if the {@code directory} does not exist or is not a directory. * @throws IOException if an I/O error occurs. @@ -422,9 +422,9 @@ private static void cleanDirectoryOnExit(final File directory) throws IOExceptio * resorting to byte-by-byte comparison of the contents. *

* - * @param file1 the first file - * @param file2 the second file - * @return true if the content of the files are equal or they both don't exist, false otherwise + * @param file1 the first file. + * @param file2 the second file. + * @return true if the content of the files are equal or they both don't exist, false otherwise. * @throws IllegalArgumentException when an input is not a file. * @throws IOException If an I/O error occurs. * @see PathUtils#fileContentEquals(Path,Path) @@ -464,12 +464,12 @@ public static boolean contentEquals(final File file1, final File file2) throws I * before resorting to line-by-line comparison of the contents. *

* - * @param file1 the first file - * @param file2 the second file + * @param file1 the first file. + * @param file2 the second file. * @param charsetName the name of the requested charset. - * May be null, in which case the platform default is used + * May be null, in which case the platform default is used. * @return true if the content of the files are equal or neither exists, - * false otherwise + * false otherwise. * @throws IllegalArgumentException when an input is not a file. * @throws IOException in case of an I/O error. * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception). @@ -509,7 +509,7 @@ public static boolean contentEqualsIgnoreEOL(final File file1, final File file2, * representation. This is to account for the difference between * File.listFiles() and FileUtils.listFiles(). * - * @param files a Collection containing {@link File} instances + * @param files a Collection containing {@link File} instances. * @return an array of {@link File} */ public static File[] convertFileCollectionToFileArray(final Collection files) { @@ -545,9 +545,9 @@ public static File[] convertFileCollectionToFileArray(final Collection fil * @param destDir the new directory, must not be {@code null}. * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, - * the source and the destination directory are the same + * the source and the destination directory are the same. * @throws FileNotFoundException if the source does not exist. - * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed + * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed. * @since 1.1 */ public static void copyDirectory(final File srcDir, final File destDir) throws IOException { @@ -573,9 +573,9 @@ public static void copyDirectory(final File srcDir, final File destDir) throws I * @param destDir the new directory, must not be {@code null}. * @param preserveFileDate true if the file date of the copy should be the same as the original. * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or - * the source and the destination directory are the same + * the source and the destination directory are the same. * @throws FileNotFoundException if the source does not exist. - * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed + * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed. * @since 1.1 */ public static void copyDirectory(final File srcDir, final File destDir, final boolean preserveFileDate) @@ -623,9 +623,9 @@ public static void copyDirectory(final File srcDir, final File destDir, final bo * @param filter the filter to apply, null means copy all directories and files should be the same as the original. * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or - * the source and the destination directory are the same + * the source and the destination directory are the same. * @throws FileNotFoundException if the source does not exist. - * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed + * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed. * @since 1.4 */ public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter) @@ -675,7 +675,7 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi * @param preserveFileDate true if the file date of the copy should be the same as the original. * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, - * the source and the destination directory are the same, or the destination is not writable + * the source and the destination directory are the same, or the destination is not writable. * @throws FileNotFoundException if the source does not exist. * @throws IOException if an error occurs or setting the last-modified time didn't succeed. * @since 1.4 @@ -722,14 +722,14 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi * * @param srcDir an existing directory to copy, must not be {@code null} * @param destDir the new directory, must not be {@code null} - * @param fileFilter the filter to apply, null means copy all directories and files - * @param preserveFileDate true if the file date of the copy should be the same as the original + * @param fileFilter the filter to apply, null means copy all directories and files. + * @param preserveFileDate true if the file date of the copy should be the same as the original. * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}. * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or - * the source and the destination directory are the same + * the source and the destination directory are the same. * @throws FileNotFoundException if the source does not exist. - * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed + * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed. * @since 2.8.0 */ public static void copyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final boolean preserveFileDate, @@ -775,7 +775,7 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IllegalArgumentException if the source or destination is invalid. * @throws FileNotFoundException if the source does not exist. - * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed + * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed. * @since 1.2 */ public static void copyDirectoryToDirectory(final File sourceDir, final File destinationDir) throws IOException { @@ -831,7 +831,7 @@ public static void copyFile(final File srcFile, final File destFile) throws IOEx * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws IOException if source or destination is invalid. * @throws IOException if an error occurs or setting the last-modified time didn't succeed. - * @throws IOException if the output file length is not the same as the input file length after the copy completes + * @throws IOException if the output file length is not the same as the input file length after the copy completes. * @see #copyFile(File, File, boolean, CopyOption...) */ public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException { @@ -864,10 +864,10 @@ public static void copyFile(final File srcFile, final File destFile, final boole * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}. * @throws NullPointerException if any of the given {@link File}s are {@code null}. * @throws FileNotFoundException if the source does not exist. - * @throws IllegalArgumentException if {@code srcFile} or {@code destFile} is not a file + * @throws IllegalArgumentException if {@code srcFile} or {@code destFile} is not a file. * @throws IOException if the output file length is not the same as the input file length after the copy completes. * @throws IOException if an I/O error occurs, setting the last-modified time didn't succeed, - * or the destination is not writable + * or the destination is not writable. * @see #copyFileToDirectory(File, File, boolean) * @since 2.8.0 */ @@ -917,7 +917,7 @@ public static void copyFile(final File srcFile, final File destFile, final CopyO * * @param input the {@link File} to read. * @param output the {@link OutputStream} to write. - * @return the number of bytes copied + * @return the number of bytes copied. * @throws NullPointerException if the File is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. * @throws IOException if an I/O error occurs. @@ -995,13 +995,13 @@ public static void copyFileToDirectory(final File sourceFile, final File destina * See {@link #copyToFile(InputStream, File)} for a method that does not close the input stream. *

* - * @param source the {@link InputStream} to copy bytes from, must not be {@code null}, will be closed + * @param source the {@link InputStream} to copy bytes from, must not be {@code null}, will be closed. * @param destination the non-directory {@link File} to write bytes to * (possibly overwriting), must not be {@code null} - * @throws IOException if {@code destination} is a directory - * @throws IOException if {@code destination} cannot be written - * @throws IOException if {@code destination} needs creating but can't be - * @throws IOException if an IO error occurs during copying + * @throws IOException if {@code destination} is a directory. + * @throws IOException if {@code destination} cannot be written. + * @throws IOException if {@code destination} needs creating but can't be. + * @throws IOException if an IO error occurs during copying. * @since 2.0 */ public static void copyInputStreamToFile(final InputStream source, final File destination) throws IOException { @@ -1116,11 +1116,11 @@ public static void copyToFile(final InputStream inputStream, final File file) th * @param source the {@link URL} to copy bytes from, must not be {@code null} * @param destination the non-directory {@link File} to write bytes to * (possibly overwriting), must not be {@code null} - * @throws IOException if {@code source} URL cannot be opened - * @throws IOException if {@code destination} is a directory - * @throws IOException if {@code destination} cannot be written - * @throws IOException if {@code destination} needs creating but can't be - * @throws IOException if an IO error occurs during copying + * @throws IOException if {@code source} URL cannot be opened. + * @throws IOException if {@code destination} is a directory. + * @throws IOException if {@code destination} cannot be written. + * @throws IOException if {@code destination} needs creating but can't be. + * @throws IOException if an IO error occurs during copying. */ public static void copyURLToFile(final URL source, final File destination) throws IOException { final Path path = destination.toPath(); @@ -1140,11 +1140,11 @@ public static void copyURLToFile(final URL source, final File destination) throw * be established to the {@code source} * @param readTimeoutMillis the number of milliseconds until this method will time out if no data could be read from * the {@code source} - * @throws IOException if {@code source} URL cannot be opened - * @throws IOException if {@code destination} is a directory - * @throws IOException if {@code destination} cannot be written - * @throws IOException if {@code destination} needs creating but can't be - * @throws IOException if an IO error occurs during copying + * @throws IOException if {@code source} URL cannot be opened. + * @throws IOException if {@code destination} is a directory. + * @throws IOException if {@code destination} cannot be written. + * @throws IOException if {@code destination} needs creating but can't be. + * @throws IOException if an IO error occurs during copying. * @since 2.0 */ public static void copyURLToFile(final URL source, final File destination, final int connectionTimeoutMillis, final int readTimeoutMillis) @@ -1235,7 +1235,7 @@ static String decodeUrl(final String url) { * * @param file The file to delete. * @return the given file. - * @throws NullPointerException if the parameter is {@code null} + * @throws NullPointerException if the parameter is {@code null}. * @throws IOException if the file cannot be deleted. * @see File#delete() * @since 2.9.0 @@ -1249,10 +1249,10 @@ public static File delete(final File file) throws IOException { /** * Deletes a directory recursively. * - * @param directory directory to delete - * @throws IOException in case deletion is unsuccessful - * @throws NullPointerException if the parameter is {@code null} - * @throws IllegalArgumentException if {@code directory} is not a directory + * @param directory directory to delete. + * @throws IOException in case deletion is unsuccessful. + * @throws NullPointerException if the parameter is {@code null}. + * @throws IllegalArgumentException if {@code directory} is not a directory. */ public static void deleteDirectory(final File directory) throws IOException { Objects.requireNonNull(directory, "directory"); @@ -1269,8 +1269,8 @@ public static void deleteDirectory(final File directory) throws IOException { * Requests a directory for deletion recursively when the virtual machine terminates. * * @param directory directory to delete, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws IOException in case deletion is unsuccessful + * @throws NullPointerException if the directory is {@code null}. + * @throws IOException in case deletion is unsuccessful. */ private static void deleteDirectoryOnExit(final File directory) throws IOException { if (!directory.exists()) { @@ -1292,9 +1292,9 @@ private static void deleteDirectoryOnExit(final File directory) throws IOExcepti *
  • No exceptions are thrown when a file or directory cannot be deleted.
  • * * - * @param file file or directory to delete, can be {@code null} + * @param file file or directory to delete, can be {@code null}. * @return {@code true} if the file or directory was deleted, otherwise - * {@code false} + * {@code false}. * @since 1.4 */ public static boolean deleteQuietly(final File file) { @@ -1360,7 +1360,7 @@ public static boolean directoryContains(final File directory, final File child) * @param preserveDirDate preserve the directories last modified dates. * @param copyOptions options specifying how the copy should be done, see {@link StandardCopyOption}. * @throws IOException if the directory was not created along with all its parent directories. - * @throws IllegalArgumentException if {@code destDir} is not writable + * @throws IllegalArgumentException if {@code destDir} is not writable. * @throws SecurityException See {@link File#mkdirs()}. */ private static void doCopyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final List exclusionList, @@ -1540,9 +1540,9 @@ private static File getParentFile(final File file) { } /** - * Gets a {@link File} representing the system temporary directory. + * Gets a {@link File} representing the system temporary directory based on the Java system property {@code java.io.tmpdir}. * - * @return the system temporary directory as a File + * @return the system temporary directory as a File. * @since 2.0 */ public static File getTempDirectory() { @@ -1550,14 +1550,13 @@ public static File getTempDirectory() { } /** - * Getsv the path to the system temporary directory. - * - * WARNING: this method relies on the Java system property 'java.io.tmpdir' - * which may or may not have a trailing file separator. - * This can affect code that uses String processing to manipulate pathnames rather - * than the standard libary methods in classes such as {@link File} + * Gets the path to the system temporary directory. + *

    + * WARNING: This method reads the Java system property {@code java.io.tmpdir}, which may or may not have a trailing file separator. This can affect code + * that uses String processing to manipulate pathnames rather than the standard library methods in classes such as {@link File}. + *

    * - * @return the path to the system temporary directory as a String + * @return the path to the system temporary directory as a String. * @since 2.0 */ public static String getTempDirectoryPath() { @@ -1565,7 +1564,7 @@ public static String getTempDirectoryPath() { } /** - * Gets a {@link File} representing the user's home directory. + * Gets a {@link File} representing the user's home directory based on the Java system property {@code user.home}. * * @return the user's home directory. * @since 2.0 @@ -1575,7 +1574,7 @@ public static File getUserDirectory() { } /** - * Gets the path to the user's home directory. + * Gets the path to the user's home directory based on the Java system property {@code user.home}. * * @return the path to the user's home directory. * @since 2.0 @@ -1589,7 +1588,7 @@ public static String getUserDirectoryPath() { * null-safe delegate to {@link Files#isDirectory(Path path, LinkOption... options)}. * * @param file the path to the file. - * @param options options indicating how symbolic links are handled + * @param options options indicating how symbolic links are handled. * @return {@code true} if the file is a directory; {@code false} if * the path is null, the file does not exist, is not a directory, or it cannot * be determined if the file is a directory or not. @@ -1632,7 +1631,7 @@ public static boolean isEmptyDirectory(final File directory) throws IOException * @param chronoLocalDate the date reference. * @return true if the {@link File} exists and has been modified after the given * {@link ChronoLocalDate} at the current time. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file or local date is {@code null}. * @since 2.8.0 */ @@ -1656,7 +1655,7 @@ public static boolean isFileNewer(final File file, final ChronoLocalDate chronoL * @param localTime the time reference. * @return true if the {@link File} exists and has been modified after the given * {@link ChronoLocalDate} at the given time. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file, local date or zone ID is {@code null}. * @since 2.8.0 */ @@ -1670,13 +1669,13 @@ public static boolean isFileNewer(final File file, final ChronoLocalDate chronoL * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate} at the specified * {@link OffsetTime}. * - * @param file the {@link File} of which the modification date must be compared - * @param chronoLocalDate the date reference - * @param offsetTime the time reference + * @param file the {@link File} of which the modification date must be compared. + * @param chronoLocalDate the date reference. + * @param offsetTime the time reference. * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given * {@link OffsetTime}. - * @throws UncheckedIOException if an I/O error occurs - * @throws NullPointerException if the file, local date or zone ID is {@code null} + * @throws UncheckedIOException if an I/O error occurs. + * @throws NullPointerException if the file, local date or zone ID is {@code null}. * @since 2.12.0 */ public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) { @@ -1700,7 +1699,7 @@ public static boolean isFileNewer(final File file, final ChronoLocalDate chronoL * @param chronoLocalDateTime the date reference. * @return true if the {@link File} exists and has been modified after the given * {@link ChronoLocalDateTime} at the system-default time zone. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file or local date time is {@code null}. * @since 2.8.0 */ @@ -1717,7 +1716,7 @@ public static boolean isFileNewer(final File file, final ChronoLocalDateTime * @param zoneId the time zone. * @return true if the {@link File} exists and has been modified after the given * {@link ChronoLocalDateTime} at the given {@link ZoneId}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file, local date time or zone ID is {@code null}. * @since 2.8.0 */ @@ -1735,7 +1734,7 @@ public static boolean isFileNewer(final File file, final ChronoLocalDateTime * @return true if the {@link File} exists and has been modified after the given * {@link ChronoZonedDateTime}. * @throws NullPointerException if the file or zoned date time is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @since 2.8.0 */ public static boolean isFileNewer(final File file, final ChronoZonedDateTime chronoZonedDateTime) { @@ -1751,7 +1750,7 @@ public static boolean isFileNewer(final File file, final ChronoZonedDateTime * @param date the date reference. * @return true if the {@link File} exists and has been modified * after the given {@link Date}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file or date is {@code null}. */ public static boolean isFileNewer(final File file, final Date date) { @@ -1795,7 +1794,7 @@ public static boolean isFileNewer(final File file, final FileTime fileTime) thro * @param instant the date reference. * @return true if the {@link File} exists and has been modified after the given {@link Instant}. * @throws NullPointerException if the file or instant is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @since 2.8.0 */ public static boolean isFileNewer(final File file, final Instant instant) { @@ -1810,7 +1809,7 @@ public static boolean isFileNewer(final File file, final Instant instant) { * @param timeMillis the time reference measured in milliseconds since the * epoch (00:00:00 GMT, January 1, 1970). * @return true if the {@link File} exists and has been modified after the given time reference. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file is {@code null}. */ public static boolean isFileNewer(final File file, final long timeMillis) { @@ -1821,11 +1820,11 @@ public static boolean isFileNewer(final File file, final long timeMillis) { /** * Tests if the specified {@link File} is newer than the specified {@link OffsetDateTime}. * - * @param file the {@link File} of which the modification date must be compared - * @param offsetDateTime the date reference + * @param file the {@link File} of which the modification date must be compared. + * @param offsetDateTime the date reference. * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}. - * @throws UncheckedIOException if an I/O error occurs - * @throws NullPointerException if the file or zoned date time is {@code null} + * @throws UncheckedIOException if an I/O error occurs. + * @throws NullPointerException if the file or zoned date time is {@code null}. * @since 2.12.0 */ public static boolean isFileNewer(final File file, final OffsetDateTime offsetDateTime) { @@ -1850,7 +1849,7 @@ public static boolean isFileNewer(final File file, final OffsetDateTime offsetDa * @return true if the {@link File} exists and has been modified before the given * {@link ChronoLocalDate} at the current time. * @throws NullPointerException if the file or local date is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @see ZoneId#systemDefault() * @see LocalTime#now() * @since 2.8.0 @@ -1875,7 +1874,7 @@ public static boolean isFileOlder(final File file, final ChronoLocalDate chronoL * @param localTime the time reference. * @return true if the {@link File} exists and has been modified before the * given {@link ChronoLocalDate} at the specified time. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @throws NullPointerException if the file, local date or local time is {@code null}. * @see ZoneId#systemDefault() * @since 2.8.0 @@ -1890,13 +1889,13 @@ public static boolean isFileOlder(final File file, final ChronoLocalDate chronoL * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate} at the specified * {@link OffsetTime}. * - * @param file the {@link File} of which the modification date must be compared - * @param chronoLocalDate the date reference - * @param offsetTime the time reference + * @param file the {@link File} of which the modification date must be compared. + * @param chronoLocalDate the date reference. + * @param offsetTime the time reference. * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given * {@link OffsetTime}. - * @throws NullPointerException if the file, local date or zone ID is {@code null} - * @throws UncheckedIOException if an I/O error occurs + * @throws NullPointerException if the file, local date or zone ID is {@code null}. + * @throws UncheckedIOException if an I/O error occurs. * @since 2.12.0 */ public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) { @@ -1921,7 +1920,7 @@ public static boolean isFileOlder(final File file, final ChronoLocalDate chronoL * @return true if the {@link File} exists and has been modified before the given * {@link ChronoLocalDateTime} at the system-default time zone. * @throws NullPointerException if the file or local date time is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @see ZoneId#systemDefault() * @since 2.8.0 */ @@ -1939,7 +1938,7 @@ public static boolean isFileOlder(final File file, final ChronoLocalDateTime * @return true if the {@link File} exists and has been modified before the given * {@link ChronoLocalDateTime} at the given {@link ZoneId}. * @throws NullPointerException if the file, local date time or zone ID is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @since 2.8.0 */ public static boolean isFileOlder(final File file, final ChronoLocalDateTime chronoLocalDateTime, final ZoneId zoneId) { @@ -1956,7 +1955,7 @@ public static boolean isFileOlder(final File file, final ChronoLocalDateTime * @return true if the {@link File} exists and has been modified before the given * {@link ChronoZonedDateTime}. * @throws NullPointerException if the file or zoned date time is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. * @since 2.8.0 */ public static boolean isFileOlder(final File file, final ChronoZonedDateTime chronoZonedDateTime) { @@ -1971,7 +1970,7 @@ public static boolean isFileOlder(final File file, final ChronoZonedDateTime * @param date the date reference. * @return true if the {@link File} exists and has been modified before the given {@link Date}. * @throws NullPointerException if the file or date is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. */ public static boolean isFileOlder(final File file, final Date date) { Objects.requireNonNull(date, "date"); @@ -1986,7 +1985,7 @@ public static boolean isFileOlder(final File file, final Date date) { * @return true if the {@link File} exists and has been modified before the reference {@link File}. * @throws NullPointerException if the file or reference file is {@code null}. * @throws FileNotFoundException if the reference file doesn't exist. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. */ public static boolean isFileOlder(final File file, final File reference) throws FileNotFoundException { return Uncheck.getAsBoolean(() -> PathUtils.isOlder(file.toPath(), reference.toPath())); @@ -2029,7 +2028,7 @@ public static boolean isFileOlder(final File file, final Instant instant) { * epoch (00:00:00 GMT, January 1, 1970). * @return true if the {@link File} exists and has been modified before the given time reference. * @throws NullPointerException if the file is {@code null}. - * @throws UncheckedIOException if an I/O error occurs + * @throws UncheckedIOException if an I/O error occurs. */ public static boolean isFileOlder(final File file, final long timeMillis) { Objects.requireNonNull(file, PROTOCOL_FILE); @@ -2039,8 +2038,8 @@ public static boolean isFileOlder(final File file, final long timeMillis) { /** * Tests if the specified {@link File} is older than the specified {@link OffsetDateTime}. * - * @param file the {@link File} of which the modification date must be compared - * @param offsetDateTime the date reference + * @param file the {@link File} of which the modification date must be compared. + * @param offsetDateTime the date reference. * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}. * @throws NullPointerException if the file or zoned date time is {@code null} * @since 2.12.0 @@ -2065,7 +2064,7 @@ private static boolean isFileProtocol(final URL url) { * null-safe delegate to {@link Files#isRegularFile(Path path, LinkOption... options)}. * * @param file the path to the file. - * @param options options indicating how symbolic links are handled + * @param options options indicating how symbolic links are handled. * @return {@code true} if the file is a regular file; {@code false} if * the path is null, the file does not exist, is not a regular file, or it cannot * be determined if the file is a regular file or not. @@ -2290,7 +2289,7 @@ public static LineIterator lineIterator(final File file, final String charsetNam inputStream = Files.newInputStream(file.toPath()); return IOUtils.lineIterator(inputStream, charsetName); } catch (final IOException | RuntimeException ex) { - IOUtils.closeQuietly(inputStream, ex::addSuppressed); + IOUtils.closeQuietlySuppress(inputStream, ex); throw ex; } } @@ -2693,9 +2692,9 @@ public static FileInputStream openInputStream(final File file) throws IOExceptio *

    * * @param file the file to open for output, must not be {@code null}. - * @return a new {@link FileOutputStream} for the specified file + * @return a new {@link FileOutputStream} for the specified file. * @throws NullPointerException if the file object is {@code null}. - * @throws IllegalArgumentException if the file object is a directory + * @throws IllegalArgumentException if the file object is a directory. * @throws IllegalArgumentException if the file is not writable. * @throws IOException if the directories could not be created. * @since 1.3 @@ -2755,7 +2754,7 @@ public static byte[] readFileToByteArray(final File file) throws IOException { } /** - * Reads the contents of a file into a String using the virtual machine's {@link Charset#defaultCharset() default charset}. The + * Reads the contents of a file into a String using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. The * file is always closed. * * @param file the file to read, must not be {@code null}. @@ -2804,7 +2803,7 @@ public static String readFileToString(final File file, final String charsetName) } /** - * Reads the contents of a file line by line to a List of Strings using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Reads the contents of a file line by line to a List of Strings using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * The file is always closed. * * @param file the file to read, must not be {@code null}. @@ -3212,7 +3211,7 @@ public static boolean waitFor(final File file, final int seconds) { } /** - * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param file the file to write. * @param data the content to write to the file. @@ -3226,7 +3225,7 @@ public static void write(final File file, final CharSequence data) throws IOExce } /** - * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param file the file to write. * @param data the content to write to the file. @@ -3271,11 +3270,11 @@ public static void write(final File file, final CharSequence data, final Charset /** * Writes a CharSequence to a file creating the file if it does not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charsetName the name of the requested charset, {@code null} means platform default - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @param file the file to write. + * @param data the content to write to the file. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 2.0 */ public static void write(final File file, final CharSequence data, final String charsetName) throws IOException { @@ -3285,13 +3284,13 @@ public static void write(final File file, final CharSequence data, final String /** * Writes a CharSequence to a file creating the file if it does not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charsetName the name of the requested charset, {@code null} means platform default + * @param file the file to write. + * @param data the content to write to the file. + * @param charsetName the name of the requested charset, {@code null} means platform default. * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. + * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM. * @since 2.1 */ public static void write(final File file, final CharSequence data, final String charsetName, final boolean append) throws IOException { @@ -3304,9 +3303,9 @@ public static void write(final File file, final CharSequence data, final String * Writes a byte array to a file creating the file if it does not exist. * The parent directories of the file will be created if they do not exist. * - * @param file the file to write to - * @param data the content to write to the file - * @throws IOException in case of an I/O error + * @param file the file to write to. + * @param data the content to write to the file. + * @throws IOException in case of an I/O error. * @since 1.1 */ public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException { @@ -3316,11 +3315,11 @@ public static void writeByteArrayToFile(final File file, final byte[] data) thro /** * Writes a byte array to a file creating the file if it does not exist. * - * @param file the file to write to - * @param data the content to write to the file + * @param file the file to write to. + * @param data the content to write to the file. * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.1 */ public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append) throws IOException { @@ -3332,11 +3331,11 @@ public static void writeByteArrayToFile(final File file, final byte[] data, fina * at offset {@code off} to a file, creating the file if it does * not exist. * - * @param file the file to write to - * @param data the content to write to the file - * @param off the start offset in the data - * @param len the number of bytes to write - * @throws IOException in case of an I/O error + * @param file the file to write to. + * @param data the content to write to the file. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws IOException in case of an I/O error. * @since 2.5 */ public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len) throws IOException { @@ -3348,13 +3347,13 @@ public static void writeByteArrayToFile(final File file, final byte[] data, fina * at offset {@code off} to a file, creating the file if it does * not exist. * - * @param file the file to write to - * @param data the content to write to the file - * @param off the start offset in the data - * @param len the number of bytes to write + * @param file the file to write to. + * @param data the content to write to the file. + * @param off the start offset in the data. + * @param len the number of bytes to write. * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.5 */ public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, final boolean append) throws IOException { @@ -3368,9 +3367,9 @@ public static void writeByteArrayToFile(final File file, final byte[] data, fina * the specified {@link File} line by line. * The default VM encoding and the default line ending will be used. * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws IOException in case of an I/O error + * @param file the file to write to. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @throws IOException in case of an I/O error. * @since 1.3 */ public static void writeLines(final File file, final Collection lines) throws IOException { @@ -3382,11 +3381,11 @@ public static void writeLines(final File file, final Collection lines) throws * the specified {@link File} line by line. * The default VM encoding and the default line ending will be used. * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines + * @param file the file to write to. + * @param lines the lines to write, {@code null} entries produce blank lines. * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.1 */ public static void writeLines(final File file, final Collection lines, final boolean append) throws IOException { @@ -3398,10 +3397,10 @@ public static void writeLines(final File file, final Collection lines, final * the specified {@link File} line by line. * The default VM encoding and the specified line ending will be used. * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws IOException in case of an I/O error + * @param file the file to write to. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @param lineEnding the line separator to use, {@code null} is system default. + * @throws IOException in case of an I/O error. * @since 1.3 */ public static void writeLines(final File file, final Collection lines, final String lineEnding) throws IOException { @@ -3413,12 +3412,12 @@ public static void writeLines(final File file, final Collection lines, final * the specified {@link File} line by line. * The default VM encoding and the specified line ending will be used. * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default + * @param file the file to write to. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @param lineEnding the line separator to use, {@code null} is system default. * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.1 */ public static void writeLines(final File file, final Collection lines, final String lineEnding, final boolean append) throws IOException { @@ -3431,11 +3430,11 @@ public static void writeLines(final File file, final Collection lines, final * The specified character encoding and the default line ending will be used. * The parent directories of the file will be created if they do not exist. * - * @param file the file to write to - * @param charsetName the name of the requested charset, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @param file the file to write to. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 1.1 */ public static void writeLines(final File file, final String charsetName, final Collection lines) throws IOException { @@ -3447,13 +3446,13 @@ public static void writeLines(final File file, final String charsetName, final C * the specified {@link File} line by line, optionally appending. * The specified character encoding and the default line ending will be used. * - * @param file the file to write to - * @param charsetName the name of the requested charset, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines + * @param file the file to write to. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @param lines the lines to write, {@code null} entries produce blank lines. * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 2.1 */ public static void writeLines(final File file, final String charsetName, final Collection lines, final boolean append) throws IOException { @@ -3466,12 +3465,12 @@ public static void writeLines(final File file, final String charsetName, final C * The specified character encoding and the line ending will be used. * The parent directories of the file will be created if they do not exist. * - * @param file the file to write to - * @param charsetName the name of the requested charset, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @param file the file to write to. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @param lineEnding the line separator to use, {@code null} is system default. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 1.1 */ public static void writeLines(final File file, final String charsetName, final Collection lines, final String lineEnding) throws IOException { @@ -3483,14 +3482,14 @@ public static void writeLines(final File file, final String charsetName, final C * the specified {@link File} line by line. * The specified character encoding and the line ending will be used. * - * @param file the file to write to - * @param charsetName the name of the requested charset, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default + * @param file the file to write to. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @param lines the lines to write, {@code null} entries produce blank lines. + * @param lineEnding the line separator to use, {@code null} is system default. * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 2.1 */ public static void writeLines(final File file, final String charsetName, final Collection lines, final String lineEnding, final boolean append) @@ -3501,12 +3500,12 @@ public static void writeLines(final File file, final String charsetName, final C } /** - * Writes a String to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes a String to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * - * @param file the file to write - * @param data the content to write to the file - * @throws IOException in case of an I/O error - * @deprecated Use {@link #writeStringToFile(File, String, Charset)} instead (and specify the appropriate encoding) + * @param file the file to write. + * @param data the content to write to the file. + * @throws IOException in case of an I/O error. + * @deprecated Use {@link #writeStringToFile(File, String, Charset)} instead (and specify the appropriate encoding). */ @Deprecated public static void writeStringToFile(final File file, final String data) throws IOException { @@ -3514,14 +3513,14 @@ public static void writeStringToFile(final File file, final String data) throws } /** - * Writes a String to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes a String to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * - * @param file the file to write - * @param data the content to write to the file - * @param append if {@code true}, then the String will be added to the end of the file rather than overwriting - * @throws IOException in case of an I/O error + * @param file the file to write. + * @param data the content to write to the file. + * @param append if {@code true}, then the String will be added to the end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.1 - * @deprecated Use {@link #writeStringToFile(File, String, Charset, boolean)} instead (and specify the appropriate encoding) + * @deprecated Use {@link #writeStringToFile(File, String, Charset, boolean)} instead (and specify the appropriate encoding). */ @Deprecated public static void writeStringToFile(final File file, final String data, final boolean append) throws IOException { @@ -3532,11 +3531,11 @@ public static void writeStringToFile(final File file, final String data, final b * Writes a String to a file creating the file if it does not exist. * The parent directories of the file will be created if they do not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charset the charset to use, {@code null} means platform default - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @param file the file to write. + * @param data the content to write to the file. + * @param charset the charset to use, {@code null} means platform default. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. * @since 2.4 */ public static void writeStringToFile(final File file, final String data, final Charset charset) throws IOException { @@ -3547,12 +3546,12 @@ public static void writeStringToFile(final File file, final String data, final C * Writes a String to a file, creating the file if it does not exist. * The parent directories of the file are created if they do not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charset the charset to use, {@code null} means platform default + * @param file the file to write. + * @param data the content to write to the file. + * @param charset the charset to use, {@code null} means platform default. * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. * @since 2.3 */ public static void writeStringToFile(final File file, final String data, final Charset charset, final boolean append) throws IOException { @@ -3565,11 +3564,11 @@ public static void writeStringToFile(final File file, final String data, final C * Writes a String to a file, creating the file if it does not exist. * The parent directories of the file are created if they do not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charsetName the name of the requested charset, {@code null} means platform default - * @throws IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @param file the file to write. + * @param data the content to write to the file. + * @param charsetName the name of the requested charset, {@code null} means platform default. + * @throws IOException in case of an I/O error. + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM. */ public static void writeStringToFile(final File file, final String data, final String charsetName) throws IOException { writeStringToFile(file, data, charsetName, false); @@ -3579,13 +3578,13 @@ public static void writeStringToFile(final File file, final String data, final S * Writes a String to a file, creating the file if it does not exist. * The parent directories of the file are created if they do not exist. * - * @param file the file to write - * @param data the content to write to the file - * @param charsetName the name of the requested charset, {@code null} means platform default + * @param file the file to write. + * @param data the content to write to the file. + * @param charsetName the name of the requested charset, {@code null} means platform default. * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM + * end of the file rather than overwriting. + * @throws IOException in case of an I/O error. + * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM. * @since 2.1 */ public static void writeStringToFile(final File file, final String data, final String charsetName, final boolean append) throws IOException { diff --git a/src/main/java/org/apache/commons/io/FilenameUtils.java b/src/main/java/org/apache/commons/io/FilenameUtils.java index ec836e29421..3c8ca2f8373 100644 --- a/src/main/java/org/apache/commons/io/FilenameUtils.java +++ b/src/main/java/org/apache/commons/io/FilenameUtils.java @@ -143,12 +143,14 @@ public class FilenameUtils { /** * The extension separator character. + * * @since 1.4 */ public static final char EXTENSION_SEPARATOR = '.'; /** * The extension separator String. + * * @since 1.4 */ public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); @@ -231,10 +233,10 @@ public class FilenameUtils { * use {@link #getFullPath(String)} on the base path argument. *

    * - * @param basePath the base path to attach to, always treated as a path - * @param fullFileNameToAdd the file name (or path) to attach to the base - * @return the concatenated path, or null if invalid - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param basePath the base path to attach to, always treated as a path. + * @param fullFileNameToAdd the file name (or path) to attach to the base. + * @return the concatenated path, or null if invalid. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ public static String concat(final String basePath, final String fullFileNameToAdd) { final int prefix = getPrefixLength(fullFileNameToAdd); @@ -280,29 +282,23 @@ public static String concat(final String basePath, final String fullFileNameToAd * @see FileUtils#directoryContains(File, File) */ public static boolean directoryContains(final String canonicalParent, final String canonicalChild) { - if (isEmpty(canonicalParent) || isEmpty(canonicalChild)) { - return false; - } - - if (IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) { + if (isEmpty(canonicalParent) || isEmpty(canonicalChild) || IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) { return false; } - final char separator = toSeparator(canonicalParent.charAt(0) == UNIX_NAME_SEPARATOR); final String parentWithEndSeparator = canonicalParent.charAt(canonicalParent.length() - 1) == separator ? canonicalParent : canonicalParent + separator; - return IOCase.SYSTEM.checkStartsWith(canonicalChild, parentWithEndSeparator); } /** * Does the work of getting the path. * - * @param fileName the file name - * @param includeSeparator true to include the end separator - * @return the path - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name. + * @param includeEndSeparator true to include the end separator. + * @return the path. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ - private static String doGetFullPath(final String fileName, final boolean includeSeparator) { + private static String doGetFullPath(final String fileName, final boolean includeEndSeparator) { if (fileName == null) { return null; } @@ -311,7 +307,7 @@ private static String doGetFullPath(final String fileName, final boolean include return null; } if (prefix >= fileName.length()) { - if (includeSeparator) { + if (includeEndSeparator) { return getPrefix(fileName); // add end slash if necessary } return fileName; @@ -320,7 +316,7 @@ private static String doGetFullPath(final String fileName, final boolean include if (index < 0) { return fileName.substring(0, prefix); } - int end = index + (includeSeparator ? 1 : 0); + int end = index + (includeEndSeparator ? 1 : 0); if (end == 0) { end++; } @@ -330,10 +326,10 @@ private static String doGetFullPath(final String fileName, final boolean include /** * Does the work of getting the path. * - * @param fileName the file name - * @param separatorAdd 0 to omit the end separator, 1 to return it - * @return the path - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name. + * @param separatorAdd 0 to omit the end separator, 1 to return it. + * @return the path. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ private static String doGetPath(final String fileName, final int separatorAdd) { if (fileName == null) { @@ -354,19 +350,17 @@ private static String doGetPath(final String fileName, final int separatorAdd) { /** * Internal method to perform the normalization. * - * @param fileName the file name - * @param separator The separator character to use - * @param keepSeparator true to keep the final separator - * @return the normalized fileName - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name. + * @param separator The separator character to use. + * @param keepSeparator true to keep the final separator. + * @return the normalized fileName. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ private static String doNormalize(final String fileName, final char separator, final boolean keepSeparator) { if (fileName == null) { return null; } - requireNonNullChars(fileName); - int size = fileName.length(); if (size == 0) { return fileName; @@ -375,10 +369,8 @@ private static String doNormalize(final String fileName, final char separator, f if (prefix < 0) { return null; } - - final char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy + final char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy fileName.getChars(0, fileName.length(), array, 0); - // fix separators throughout final char otherSeparator = flipSeparator(separator); for (int i = 0; i < array.length; i++) { @@ -386,14 +378,12 @@ private static String doNormalize(final String fileName, final char separator, f array[i] = separator; } } - // add extra separator on the end to simplify code below boolean lastIsDirectory = true; if (array[size - 1] != separator) { array[size++] = separator; lastIsDirectory = false; } - // adjoining slashes // If we get here, prefix can only be 0 or greater, size 1 or greater // If prefix is 0, set loop start to 1 to prevent index errors @@ -404,11 +394,9 @@ private static String doNormalize(final String fileName, final char separator, f i--; } } - // period slash for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && - (i == prefix + 1 || array[i - 2] == separator)) { + if (array[i] == separator && array[i - 1] == '.' && (i == prefix + 1 || array[i - 2] == separator)) { if (i == size - 1) { lastIsDirectory = true; } @@ -417,12 +405,9 @@ private static String doNormalize(final String fileName, final char separator, f i--; } } - // double period slash - outer: - for (int i = prefix + 2; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && - (i == prefix + 2 || array[i - 3] == separator)) { + outer: for (int i = prefix + 2; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && (i == prefix + 2 || array[i - 3] == separator)) { if (i == prefix + 2) { return null; } @@ -430,7 +415,7 @@ private static String doNormalize(final String fileName, final char separator, f lastIsDirectory = true; } int j; - for (j = i - 4 ; j >= prefix; j--) { + for (j = i - 4; j >= prefix; j--) { if (array[j] == separator) { // remove b/../ from a/b/../c System.arraycopy(array, i + 1, array, j + 1, size - i); @@ -445,17 +430,13 @@ private static String doNormalize(final String fileName, final char separator, f i = prefix + 1; } } - - if (size <= 0) { // should never be less than 0 + if (size <= 0) { // should never be less than 0 return EMPTY_STRING; } - if (size <= prefix) { // should never be less than prefix - return new String(array, 0, size); - } - if (lastIsDirectory && keepSeparator) { - return new String(array, 0, size); // keep trailing separator + if (size <= prefix || lastIsDirectory && keepSeparator) { + return new String(array, 0, size); // keep trailing separator } - return new String(array, 0, size - 1); // lose trailing separator + return new String(array, 0, size - 1); // lose trailing separator } /** @@ -465,9 +446,9 @@ private static String doNormalize(final String fileName, final char separator, f * This is merely a null-safe case-sensitive string equality. *

    * - * @param fileName1 the first file name, may be null - * @param fileName2 the second file name, may be null - * @return true if the file names are equal, null equals null + * @param fileName1 the first file name, may be null. + * @param fileName2 the second file name, may be null. + * @return true if the file names are equal, null equals null. * @see IOCase#SENSITIVE */ public static boolean equals(final String fileName1, final String fileName2) { @@ -478,15 +459,14 @@ public static boolean equals(final String fileName1, final String fileName2) { * Checks whether two file names are equal, optionally normalizing and providing * control over the case-sensitivity. * - * @param fileName1 the first file name, may be null - * @param fileName2 the second file name, may be null - * @param normalize whether to normalize the file names - * @param ioCase what case sensitivity rule to use, null means case-sensitive - * @return true if the file names are equal, null equals null + * @param fileName1 the first file name, may be null. + * @param fileName2 the second file name, may be null. + * @param normalize whether to normalize the file names. + * @param ioCase what case sensitivity rule to use, null means case-sensitive. + * @return true if the file names are equal, null equals null. * @since 1.3 */ public static boolean equals(String fileName1, String fileName2, final boolean normalize, final IOCase ioCase) { - if (fileName1 == null || fileName2 == null) { return fileName1 == null && fileName2 == null; } @@ -510,9 +490,9 @@ public static boolean equals(String fileName1, String fileName2, final boolean n * The check is then performed in a case-sensitive manner. *

    * - * @param fileName1 the first file name, may be null - * @param fileName2 the second file name, may be null - * @return true if the file names are equal, null equals null + * @param fileName1 the first file name, may be null. + * @param fileName2 the second file name, may be null. + * @return true if the file names are equal, null equals null. * @see IOCase#SENSITIVE */ public static boolean equalsNormalized(final String fileName1, final String fileName2) { @@ -528,9 +508,9 @@ public static boolean equalsNormalized(final String fileName1, final String file * case-insensitively on Windows. *

    * - * @param fileName1 the first file name, may be null - * @param fileName2 the second file name, may be null - * @return true if the file names are equal, null equals null + * @param fileName1 the first file name, may be null. + * @param fileName2 the second file name, may be null. + * @return true if the file names are equal, null equals null. * @see IOCase#SYSTEM */ public static boolean equalsNormalizedOnSystem(final String fileName1, final String fileName2) { @@ -544,9 +524,9 @@ public static boolean equalsNormalizedOnSystem(final String fileName1, final Str * The check is case-sensitive on Unix and case-insensitive on Windows. *

    * - * @param fileName1 the first file name, may be null - * @param fileName2 the second file name, may be null - * @return true if the file names are equal, null equals null + * @param fileName1 the first file name, may be null. + * @param fileName2 the second file name, may be null. + * @return true if the file names are equal, null equals null. * @see IOCase#SYSTEM */ public static boolean equalsOnSystem(final String fileName1, final String fileName2) { @@ -572,7 +552,7 @@ static char flipSeparator(final char ch) { /** * Special handling for NTFS ADS: Don't accept colon in the file name. * - * @param fileName a file name + * @param fileName a file name. * @return ADS offsets. */ private static int getAdsCriticalOffset(final String fileName) { @@ -609,9 +589,9 @@ private static int getAdsCriticalOffset(final String fileName) { * The output will be the same irrespective of the machine that the code is running on. *

    * - * @param fileName the file name, null returns null - * @return the name of the file without the path, or an empty string if none exists - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the name of the file without the path, or an empty string if none exists. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static String getBaseName(final String fileName) { return removeExtension(getName(fileName)); @@ -682,9 +662,9 @@ public static String getExtension(final String fileName) throws IllegalArgumentE * The output will be the same irrespective of the machine that the code is running on. *

    * - * @param fileName the file name, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the path of the file, an empty string if none exists, null if invalid. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ public static String getFullPath(final String fileName) { return doGetFullPath(fileName, true); @@ -715,9 +695,9 @@ public static String getFullPath(final String fileName) { * The output will be the same irrespective of the machine that the code is running on. *

    * - * @param fileName the file name, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the path of the file, an empty string if none exists, null if invalid. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ public static String getFullPathNoEndSeparator(final String fileName) { return doGetFullPath(fileName, false); @@ -740,9 +720,9 @@ public static String getFullPathNoEndSeparator(final String fileName) { * The output will be the same irrespective of the machine that the code is running on. *

    * - * @param fileName the file name, null returns null - * @return the name of the file without the path, or an empty string if none exists - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the name of the file without the path, or an empty string if none exists. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static String getName(final String fileName) { if (fileName == null) { @@ -773,9 +753,9 @@ public static String getName(final String fileName) { * See {@link #getFullPath(String)} for the method that retains the prefix. *

    * - * @param fileName the file name, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the path of the file, an empty string if none exists, null if invalid. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ public static String getPath(final String fileName) { return doGetPath(fileName, 1); @@ -804,9 +784,9 @@ public static String getPath(final String fileName) { * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. *

    * - * @param fileName the file name, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the path of the file, an empty string if none exists, null if invalid. + * @throws IllegalArgumentException if the result path contains the null character ({@code U+0000}). */ public static String getPathNoEndSeparator(final String fileName) { return doGetPath(fileName, 0); @@ -839,9 +819,9 @@ public static String getPathNoEndSeparator(final String fileName) { * ie. both Unix and Windows prefixes are matched regardless. *

    * - * @param fileName the file name, null returns null - * @return the prefix of the file, null if invalid - * @throws IllegalArgumentException if the result contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the prefix of the file, null if invalid. + * @throws IllegalArgumentException if the result contains the null character ({@code U+0000}). */ public static String getPrefix(final String fileName) { if (fileName == null) { @@ -898,8 +878,8 @@ public static String getPrefix(final String fileName) { * to a single slash at the start of the file name. *

    * - * @param fileName the file name to find the prefix in, null returns -1 - * @return the length of the prefix, -1 if invalid or null + * @param fileName the file name to find the prefix in, null returns -1. + * @return the length of the prefix, -1 if invalid or null. */ public static int getPrefixLength(final String fileName) { if (fileName == null) { @@ -979,8 +959,8 @@ public static int getPrefixLength(final String fileName) { * an {@link IllegalArgumentException} for names like this. * * @param fileName - * the file name to find the last extension separator in, null returns -1 - * @return the index of the last extension separator character, or -1 if there is no such character + * the file name to find the last extension separator in, null returns -1. + * @return the index of the last extension separator character, or -1 if there is no such character. * @throws IllegalArgumentException Windows only: the file name parameter is, in fact, * the identifier of an Alternate Data Stream, for example "foo.exe:bar.txt". */ @@ -1008,9 +988,9 @@ public static int indexOfExtension(final String fileName) throws IllegalArgument *

    * The output will be the same irrespective of the machine that the code is running on. * - * @param fileName the file name to find the last path separator in, null returns -1 + * @param fileName the file name to find the last path separator in, null returns -1. * @return the index of the last separator character, or -1 if there - * is no such character + * is no such character. */ public static int indexOfLastSeparator(final String fileName) { if (fileName == null) { @@ -1032,17 +1012,16 @@ private static boolean isEmpty(final String string) { * after the last period. There must be no directory separator after the period. * The extension check is case-sensitive on all platforms. * - * @param fileName the file name, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the file name is one of the extensions - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name, null returns false. + * @param extensions the extensions to check for, null checks for no extension. + * @return true if the file name is one of the extensions. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static boolean isExtension(final String fileName, final Collection extensions) { if (fileName == null) { return false; } requireNonNullChars(fileName); - if (extensions == null || extensions.isEmpty()) { return indexOfExtension(fileName) == NOT_FOUND; } @@ -1056,17 +1035,16 @@ public static boolean isExtension(final String fileName, final Collection IPV4_MAX_OCTET_VALUE) { + if (iIpSegment > IPV4_MAX_OCTET_VALUE || ipSegment.length() > 1 && ipSegment.startsWith("0")) { return false; } - - if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { - return false; - } - } - return true; } @@ -1132,8 +1103,8 @@ private static boolean isIPv4Address(final String name) { /** * Checks whether a given string represents a valid IPv6 address. * - * @param inet6Address the name to validate - * @return true if the given name is a valid IPv6 address + * @param inet6Address the name to validate. + * @return true if the given name is a valid IPv6 address. */ private static boolean isIPv6Address(final String inet6Address) { final boolean containsCompressedZeroes = inet6Address.contains("::"); @@ -1200,8 +1171,8 @@ private static boolean isIPv6Address(final String inet6Address) { * RFC 3986 - not accepting IP addresses. * * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2" - * @param name the hostname to validate - * @return true if the given name is a valid host name + * @param name the hostname to validate. + * @return true if the given name is a valid host name. */ private static boolean isRFC3986HostName(final String name) { final String[] parts = name.split("\\.", -1); @@ -1220,8 +1191,8 @@ private static boolean isRFC3986HostName(final String name) { /** * Checks if the character is a separator. * - * @param ch the character to check - * @return true if it is a separator character + * @param ch the character to check. + * @return true if it is a separator character. */ private static boolean isSeparator(final char ch) { return ch == UNIX_NAME_SEPARATOR || ch == WINDOWS_NAME_SEPARATOR; @@ -1230,7 +1201,7 @@ private static boolean isSeparator(final char ch) { /** * Determines if Windows file system is in use. * - * @return true if the system is Windows + * @return true if the system is Windows. */ static boolean isSystemWindows() { return SYSTEM_NAME_SEPARATOR == WINDOWS_NAME_SEPARATOR; @@ -1245,8 +1216,8 @@ static boolean isSystemWindows() { * valid names in UNC paths.

    * * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2" - * @param name the hostname to validate - * @return true if the given name is a valid host name + * @param name the hostname to validate. + * @return true if the given name is a valid host name. */ private static boolean isValidHostName(final String name) { return isIPv6Address(name) || isRFC3986HostName(name); @@ -1288,9 +1259,9 @@ private static boolean isValidHostName(final String name) { * * (Note the file separator will be correct for Windows/Unix.) * - * @param fileName the file name to normalize, null returns null - * @return the normalized fileName, or null if invalid - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name to normalize, null returns null. + * @return the normalized fileName, or null if invalid. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static String normalize(final String fileName) { return doNormalize(fileName, SYSTEM_NAME_SEPARATOR, true); @@ -1334,11 +1305,11 @@ public static String normalize(final String fileName) { * The output will be the same on both Unix and Windows including * the separator character. * - * @param fileName the file name to normalize, null returns null + * @param fileName the file name to normalize, null returns null. * @param unixSeparator {@code true} if a Unix separator should * be used or {@code false} if a Windows separator should be used. - * @return the normalized fileName, or null if invalid - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @return the normalized fileName, or null if invalid. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). * @since 2.0 */ public static String normalize(final String fileName, final boolean unixSeparator) { @@ -1383,9 +1354,9 @@ public static String normalize(final String fileName, final boolean unixSeparato * * (Note the file separator returned will be correct for Windows/Unix) * - * @param fileName the file name to normalize, null returns null - * @return the normalized fileName, or null if invalid - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name to normalize, null returns null. + * @return the normalized fileName, or null if invalid. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static String normalizeNoEndSeparator(final String fileName) { return doNormalize(fileName, SYSTEM_NAME_SEPARATOR, false); @@ -1428,11 +1399,11 @@ public static String normalizeNoEndSeparator(final String fileName) { * ~/../bar --> null * * - * @param fileName the file name to normalize, null returns null + * @param fileName the file name to normalize, null returns null. * @param unixSeparator {@code true} if a Unix separator should * be used or {@code false} if a Windows separator should be used. - * @return the normalized fileName, or null if invalid - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @return the normalized fileName, or null if invalid. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). * @since 2.0 */ public static String normalizeNoEndSeparator(final String fileName, final boolean unixSeparator) { @@ -1455,16 +1426,15 @@ public static String normalizeNoEndSeparator(final String fileName, final boolea *

    * The output will be the same irrespective of the machine that the code is running on. * - * @param fileName the file name, null returns null - * @return the file name minus the extension - * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}) + * @param fileName the file name, null returns null. + * @return the file name minus the extension. + * @throws IllegalArgumentException if the file name contains the null character ({@code U+0000}). */ public static String removeExtension(final String fileName) { if (fileName == null) { return null; } requireNonNullChars(fileName); - final int index = indexOfExtension(fileName); if (index == NOT_FOUND) { return fileName; @@ -1477,9 +1447,9 @@ public static String removeExtension(final String fileName) { * * This may be used to defend against poison byte attacks. * - * @param path the path to check - * @return The input - * @throws IllegalArgumentException if path contains the null character ({@code U+0000}) + * @param path the path to check. + * @return The input. + * @throws IllegalArgumentException if path contains the null character ({@code U+0000}). */ private static String requireNonNullChars(final String path) { if (path.indexOf(0) >= 0) { @@ -1524,17 +1494,15 @@ public static String separatorsToWindows(final String path) { * The text is split by '?' and '*'. * Where multiple '*' occur consecutively they are collapsed into a single '*'. * - * @param text the text to split - * @return the array of tokens, never null + * @param text the text to split. + * @return the array of tokens, never null. */ static String[] splitOnTokens(final String text) { // used by wildcardMatch // package level so a unit test may run on this - if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) { return new String[] { text }; } - final char[] array = text.toCharArray(); final ArrayList list = new ArrayList<>(); final StringBuilder buffer = new StringBuilder(); @@ -1558,7 +1526,6 @@ static String[] splitOnTokens(final String text) { if (buffer.length() != 0) { list.add(buffer.toString()); } - return list.toArray(EMPTY_STRING_ARRAY); } @@ -1589,9 +1556,9 @@ private static char toSeparator(final boolean unixSeparator) { * * The sequence "*?" does not work properly at present in match strings. * - * @param fileName the file name to match on - * @param wildcardMatcher the wildcard string to match against - * @return true if the file name matches the wildcard string + * @param fileName the file name to match on, may be null. + * @param wildcardMatcher the wildcard string to match against, may be null. + * @return true if the file name matches the wildcard string. * @see IOCase#SENSITIVE */ public static boolean wildcardMatch(final String fileName, final String wildcardMatcher) { @@ -1606,10 +1573,10 @@ public static boolean wildcardMatch(final String fileName, final String wildcard * single or multiple (zero or more) wildcard characters. * The sequence "*?" does not work properly at present in match strings. * - * @param fileName the file name to match on - * @param wildcardMatcher the wildcard string to match against - * @param ioCase what case sensitivity rule to use, null means case-sensitive - * @return true if the file name matches the wildcard string + * @param fileName the file name to match on, may be null. + * @param wildcardMatcher the wildcard string to match against, may be null. + * @param ioCase what case sensitivity rule to use, null means case-sensitive. + * @return true if the file name matches the wildcard string. * @since 1.3 */ public static boolean wildcardMatch(final String fileName, final String wildcardMatcher, IOCase ioCase) { @@ -1625,7 +1592,6 @@ public static boolean wildcardMatch(final String fileName, final String wildcard int textIdx = 0; int wcsIdx = 0; final Deque backtrack = new ArrayDeque<>(wcs.length); - // loop around a backtrack stack, to handle complex * matching do { if (!backtrack.isEmpty()) { @@ -1634,10 +1600,8 @@ public static boolean wildcardMatch(final String fileName, final String wildcard textIdx = array[1]; anyChars = true; } - // loop whilst tokens and text left to process while (wcsIdx < wcs.length) { - if (wcs[wcsIdx].equals("?")) { // ? so move to next text char textIdx++; @@ -1645,14 +1609,12 @@ public static boolean wildcardMatch(final String fileName, final String wildcard break; } anyChars = false; - } else if (wcs[wcsIdx].equals("*")) { // set any chars status anyChars = true; if (wcsIdx == wcs.length - 1) { textIdx = fileName.length(); } - } else { // matching text token if (anyChars) { @@ -1664,29 +1626,24 @@ public static boolean wildcardMatch(final String fileName, final String wildcard } final int repeat = ioCase.checkIndexOf(fileName, textIdx + 1, wcs[wcsIdx]); if (repeat >= 0) { - backtrack.push(new int[] {wcsIdx, repeat}); + backtrack.push(new int[] { wcsIdx, repeat }); } } else if (!ioCase.checkRegionMatches(fileName, textIdx, wcs[wcsIdx])) { // matching from current position // couldn't match token break; } - // matched text token, move text index to end of matched token textIdx += wcs[wcsIdx].length(); anyChars = false; } - wcsIdx++; } - // full match if (wcsIdx == wcs.length && textIdx == fileName.length()) { return true; } - } while (!backtrack.isEmpty()); - return false; } @@ -1707,9 +1664,9 @@ public static boolean wildcardMatch(final String fileName, final String wildcard * * The sequence "*?" does not work properly at present in match strings. * - * @param fileName the file name to match on - * @param wildcardMatcher the wildcard string to match against - * @return true if the file name matches the wildcard string + * @param fileName the file name to match on. + * @param wildcardMatcher the wildcard string to match against. + * @return true if the file name matches the wildcard string. * @see IOCase#SYSTEM */ public static boolean wildcardMatchOnSystem(final String fileName, final String wildcardMatcher) { diff --git a/src/main/java/org/apache/commons/io/HexDump.java b/src/main/java/org/apache/commons/io/HexDump.java index cb7efa7b2f2..38f52fdd325 100644 --- a/src/main/java/org/apache/commons/io/HexDump.java +++ b/src/main/java/org/apache/commons/io/HexDump.java @@ -62,11 +62,11 @@ public class HexDump { * characters (if any) that those bytes represent printed per each line * of output. * - * @param data the byte array to be dumped - * @param appendable the Appendable to which the data is to be written + * @param data the byte array to be dumped. + * @param appendable the Appendable to which the data is to be written. * @throws IOException is thrown if anything goes wrong writing - * the data to appendable - * @throws NullPointerException if the output appendable is null + * the data to appendable. + * @throws NullPointerException if the output appendable is null. * @since 2.12.0 */ public static void dump(final byte[] data, final Appendable appendable) @@ -89,16 +89,16 @@ public static void dump(final byte[] data, final Appendable appendable) * the first byte on that line is located. *

    * - * @param data the byte array to be dumped - * @param offset offset of the byte array within a larger entity - * @param appendable the Appendable to which the data is to be written - * @param index initial index into the byte array - * @param length number of bytes to dump from the array + * @param data the byte array to be dumped. + * @param offset offset of the byte array within a larger entity. + * @param appendable the Appendable to which the data is to be written. + * @param index initial index into the byte array. + * @param length number of bytes to dump from the array. * @throws IOException is thrown if anything goes wrong writing - * the data to appendable + * the data to appendable. * @throws ArrayIndexOutOfBoundsException if the index or length is - * outside the data array's bounds - * @throws NullPointerException if the output appendable is null + * outside the data array's bounds. + * @throws NullPointerException if the output appendable is null. * @since 2.12.0 */ public static void dump(final byte[] data, final long offset, @@ -169,19 +169,19 @@ public static void dump(final byte[] data, final long offset, * data array are dumped. *

    *

    - * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * - * @param data the byte array to be dumped - * @param offset offset of the byte array within a larger entity + * @param data the byte array to be dumped. + * @param offset offset of the byte array within a larger entity. * @param stream the OutputStream to which the data is to be - * written - * @param index initial index into the byte array + * written. + * @param index initial index into the byte array. * @throws IOException is thrown if anything goes wrong writing - * the data to stream + * the data to stream. * @throws ArrayIndexOutOfBoundsException if the index is - * outside the data array's bounds - * @throws NullPointerException if the output stream is null + * outside the data array's bounds. + * @throws NullPointerException if the output stream is null. */ @SuppressWarnings("resource") // Caller closes stream public static void dump(final byte[] data, final long offset, @@ -197,8 +197,8 @@ public static void dump(final byte[] data, final long offset, /** * Dumps a byte value into a StringBuilder. * - * @param builder the StringBuilder to dump the value in - * @param value the byte value to be dumped + * @param builder the StringBuilder to dump the value in. + * @param value the byte value to be dumped. * @return StringBuilder containing the dumped value. */ private static StringBuilder dump(final StringBuilder builder, final byte value) { @@ -211,8 +211,8 @@ private static StringBuilder dump(final StringBuilder builder, final byte value) /** * Dumps a long value into a StringBuilder. * - * @param builder the StringBuilder to dump the value in - * @param value the long value to be dumped + * @param builder the StringBuilder to dump the value in. + * @param value the long value to be dumped. * @return StringBuilder containing the dumped value. */ private static StringBuilder dump(final StringBuilder builder, final long value) { diff --git a/src/main/java/org/apache/commons/io/IOCase.java b/src/main/java/org/apache/commons/io/IOCase.java index 7fee424f489..9a4d168fc3a 100644 --- a/src/main/java/org/apache/commons/io/IOCase.java +++ b/src/main/java/org/apache/commons/io/IOCase.java @@ -21,7 +21,7 @@ import java.util.stream.Stream; /** - * Enumeration of IO case sensitivity. + * Enumerates IO case sensitivity types. *

    * Different filing systems have different rules for case-sensitivity. * Windows is case-insensitive, Unix is case-sensitive. @@ -72,9 +72,9 @@ public enum IOCase { /** * Looks up an IOCase by name. * - * @param name the name to find - * @return the IOCase object - * @throws IllegalArgumentException if the name is invalid + * @param name the name to find. + * @return the IOCase object. + * @throws IllegalArgumentException if the name is invalid. */ public static IOCase forName(final String name) { return Stream.of(values()).filter(ioCase -> ioCase.getName().equals(name)).findFirst() @@ -95,7 +95,7 @@ public static boolean isCaseSensitive(final IOCase ioCase) { /** * Returns the given value if not-null, the defaultValue if null. * - * @param value the value to test. + * @param value the value to test, may be null. * @param defaultValue the default value. * @return the given value if not-null, the defaultValue if null. * @since 2.12.0 @@ -236,7 +236,7 @@ public boolean checkStartsWith(final String str, final String start) { /** * Gets the name of the constant. * - * @return the name of the constant + * @return the name of the constant. */ public String getName() { return name; diff --git a/src/main/java/org/apache/commons/io/IOExceptionList.java b/src/main/java/org/apache/commons/io/IOExceptionList.java index 740bedb6789..256cebd1360 100644 --- a/src/main/java/org/apache/commons/io/IOExceptionList.java +++ b/src/main/java/org/apache/commons/io/IOExceptionList.java @@ -126,7 +126,7 @@ public List getCauseList() { * Works around Throwable and Generics, may fail at runtime depending on the argument value. * * @param type of exception to return. - * @param clazz the target type + * @param clazz the target type. * @return The list of causes. */ public List getCauseList(final Class clazz) { diff --git a/src/main/java/org/apache/commons/io/IOExceptionWithCause.java b/src/main/java/org/apache/commons/io/IOExceptionWithCause.java index af25e8b0a40..8cb451658f7 100644 --- a/src/main/java/org/apache/commons/io/IOExceptionWithCause.java +++ b/src/main/java/org/apache/commons/io/IOExceptionWithCause.java @@ -23,7 +23,7 @@ * Subclasses IOException with the {@link Throwable} constructors missing before Java 6. * * @since 1.4 - * @deprecated (since 2.5) use {@link IOException} instead + * @deprecated (since 2.5) use {@link IOException} instead. */ @Deprecated public class IOExceptionWithCause extends IOException { @@ -41,7 +41,7 @@ public class IOExceptionWithCause extends IOException { *

    * * @param message - * the message (see {@link #getMessage()}) + * the message (see {@link #getMessage()}). * @param cause * the cause (see {@link #getCause()}). A {@code null} value is allowed. */ diff --git a/src/main/java/org/apache/commons/io/IOIndexedException.java b/src/main/java/org/apache/commons/io/IOIndexedException.java index fcb84334ff6..958d8307317 100644 --- a/src/main/java/org/apache/commons/io/IOIndexedException.java +++ b/src/main/java/org/apache/commons/io/IOIndexedException.java @@ -27,6 +27,7 @@ public class IOIndexedException extends IOException { private static final long serialVersionUID = 1L; + /** * Converts input to a suitable String for exception message. * diff --git a/src/main/java/org/apache/commons/io/IORandomAccessFile.java b/src/main/java/org/apache/commons/io/IORandomAccessFile.java index 9b4aea52dec..22d232e562d 100644 --- a/src/main/java/org/apache/commons/io/IORandomAccessFile.java +++ b/src/main/java/org/apache/commons/io/IORandomAccessFile.java @@ -37,7 +37,7 @@ public final class IORandomAccessFile extends RandomAccessFile { /** * Constructs a new instance by calling {@link RandomAccessFile#RandomAccessFile(File, String)}. * - * @param file the file object + * @param file the file object. * @param mode the access mode, as described in {@link RandomAccessFile#RandomAccessFile(File, String)}. * @throws FileNotFoundException Thrown by {@link RandomAccessFile#RandomAccessFile(File, String)}. * @see RandomAccessFile#RandomAccessFile(File, String) @@ -51,7 +51,7 @@ public IORandomAccessFile(final File file, final String mode) throws FileNotFoun /** * Constructs a new instance by calling {@link RandomAccessFile#RandomAccessFile(String, String)}. * - * @param name the file object + * @param name the file object. * @param mode the access mode, as described in {@link RandomAccessFile#RandomAccessFile(String, String)}. * @throws FileNotFoundException Thrown by {@link RandomAccessFile#RandomAccessFile(String, String)}. * @see RandomAccessFile#RandomAccessFile(String, String) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index a882d4d87cb..d4b9af073d0 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -81,25 +81,20 @@ * This class provides static utility methods for input/output operations. *

    *
      - *
    • closeQuietly - these methods close a stream ignoring nulls and exceptions - *
    • toXxx/read - these methods read data from a stream - *
    • write - these methods write data to a stream - *
    • copy - these methods copy all the data from one stream to another - *
    • contentEquals - these methods compare the content of two streams + *
    • closeQuietly - these methods close a stream ignoring nulls and exceptions
    • + *
    • toXxx/read - these methods read data from a stream
    • + *
    • write - these methods write data to a stream
    • + *
    • copy - these methods copy all the data from one stream to another
    • + *
    • contentEquals - these methods compare the content of two streams
    • *
    *

    - * The byte-to-char methods and char-to-byte methods involve a conversion step. - * Two methods are provided in each case, one that uses the platform default - * encoding and the other which allows you to specify an encoding. You are - * encouraged to always specify an encoding because relying on the platform - * default can lead to unexpected results, for example when moving from - * development to production. + * The byte-to-char methods and char-to-byte methods involve a conversion step. Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are encouraged to always specify an encoding because relying on the platform default can + * lead to unexpected results, for example when moving from development to production. *

    *

    - * All the methods in this class that read a stream are buffered internally. - * This means that there is no cause to use a {@link BufferedInputStream} - * or {@link BufferedReader}. The default buffer size of 4K has been shown - * to be efficient in tests. + * All the methods in this class that read a stream are buffered internally. This means that there is no cause to use a {@link BufferedInputStream} or + * {@link BufferedReader}. The default buffer size of 4K has been shown to be efficient in tests. *

    *

    * The various copy methods all delegate the actual copying to one of the following methods: @@ -110,18 +105,14 @@ *

  • {@link #copyLarge(Reader, Writer, char[])}
  • *
  • {@link #copyLarge(Reader, Writer, long, long, char[])}
  • * - * For example, {@link #copy(InputStream, OutputStream)} calls {@link #copyLarge(InputStream, OutputStream)} - * which calls {@link #copy(InputStream, OutputStream, int)} which creates the buffer and calls - * {@link #copyLarge(InputStream, OutputStream, byte[])}. + * For example, {@link #copy(InputStream, OutputStream)} calls {@link #copyLarge(InputStream, OutputStream)} which calls + * {@link #copy(InputStream, OutputStream, int)} which creates the buffer and calls {@link #copyLarge(InputStream, OutputStream, byte[])}. *

    - * Applications can re-use buffers by using the underlying methods directly. - * This may improve performance for applications that need to do a lot of copying. + * Applications can re-use buffers by using the underlying methods directly. This may improve performance for applications that need to do a lot of copying. *

    *

    - * Wherever possible, the methods in this class do not flush or close - * the stream. This is to avoid making non-portable assumptions about the - * streams' origin and further use. Thus the caller is still responsible for - * closing streams after use. + * Wherever possible, the methods in this class do not flush or close the stream. This is to avoid making non-portable assumptions about the streams' + * origin and further use. Thus the caller is still responsible for closing streams after use. *

    *

    * Provenance: Excalibur. @@ -132,6 +123,142 @@ public class IOUtils { // Writer. Each method should take at least one of these as a parameter, // or return one of them. + /** + * Holder for per-thread internal scratch buffer. + *

    + * Buffers are created lazily and reused within the same thread to reduce allocation overhead. In the rare case of reentrant access, a temporary buffer is + * allocated to avoid data corruption. + *

    + *

    + * Typical usage: + *

    + * + *
    {@code
    +     * try (ScratchBytes scratch = ScratchBytes.get()) {
    +     *     // use the buffer
    +     *     byte[] bytes = scratch.array();
    +     *     // ...
    +     * }
    +     * }
    + */ + static final class ScratchBytes implements AutoCloseable { + + /** + * Wraps an internal byte array. [0] boolean in use. [1] byte[] buffer. + */ + private static final ThreadLocal LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, byteArray() }); + + private static final ScratchBytes INSTANCE = new ScratchBytes(null); + + /** + * Gets the internal byte array buffer. + * + * @return the internal byte array buffer. + */ + static ScratchBytes get() { + final Object[] holder = LOCAL.get(); + // If already in use, return a new array + if ((boolean) holder[0]) { + return new ScratchBytes(byteArray()); + } + holder[0] = true; + return INSTANCE; + } + + /** + * The buffer, or null if using the thread-local buffer. + */ + private final byte[] buffer; + + private ScratchBytes(final byte[] buffer) { + this.buffer = buffer; + } + + byte[] array() { + return buffer != null ? buffer : (byte[]) LOCAL.get()[1]; + } + + /** + * If the buffer is the internal array, clear and release it for reuse. + */ + @Override + public void close() { + if (buffer == null) { + final Object[] holder = LOCAL.get(); + Arrays.fill((byte[]) holder[1], (byte) 0); + holder[0] = false; + } + } + } + + /** + * Holder for per-thread internal scratch buffer. + *

    + * Buffers are created lazily and reused within the same thread to reduce allocation overhead. In the rare case of reentrant access, a temporary buffer is + * allocated to avoid data corruption. + *

    + *

    + * Typical usage: + *

    + * + *
    {@code
    +     * try (ScratchChars scratch = ScratchChars.get()) {
    +     *     // use the buffer
    +     *     char[] bytes = scratch.array();
    +     *     // ...
    +     * }
    +     * }
    + */ + static final class ScratchChars implements AutoCloseable { + + /** + * Wraps an internal char array. [0] boolean in use. [1] char[] buffer. + */ + private static final ThreadLocal LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, charArray() }); + + private static final ScratchChars INSTANCE = new ScratchChars(null); + + /** + * Gets the internal char array buffer. + * + * @return the internal char array buffer. + */ + static ScratchChars get() { + final Object[] holder = LOCAL.get(); + // If already in use, return a new array + if ((boolean) holder[0]) { + return new ScratchChars(charArray()); + } + holder[0] = true; + return INSTANCE; + } + + /** + * The buffer, or null if using the thread-local buffer. + */ + private final char[] buffer; + + private ScratchChars(final char[] buffer) { + this.buffer = buffer; + } + + char[] array() { + return buffer != null ? buffer : (char[]) LOCAL.get()[1]; + } + + /** + * If the buffer is the internal array, clear and release it for reuse. + */ + @Override + public void close() { + if (buffer == null) { + final Object[] holder = LOCAL.get(); + Arrays.fill((char[]) holder[1], (char) 0); + holder[0] = false; + } + } + } + /** * CR char '{@value}'. * @@ -162,12 +289,13 @@ public class IOUtils { /** * A singleton empty byte array. * - * @since 2.9.0 + * @since 2.9.0 */ public static final byte[] EMPTY_BYTE_ARRAY = {}; /** * Represents the end-of-file (or stream) value {@value}. + * * @since 2.5 (made public) */ public static final int EOF = -1; @@ -201,26 +329,6 @@ public class IOUtils { */ public static final String LINE_SEPARATOR_WINDOWS = StandardLineSeparator.CRLF.getString(); - /** - * Internal byte array buffer, intended for both reading and writing. - */ - private static final ThreadLocal SCRATCH_BYTE_BUFFER_RW = ThreadLocal.withInitial(IOUtils::byteArray); - - /** - * Internal byte array buffer, intended for write only operations. - */ - private static final byte[] SCRATCH_BYTE_BUFFER_WO = byteArray(); - - /** - * Internal char array buffer, intended for both reading and writing. - */ - private static final ThreadLocal SCRATCH_CHAR_BUFFER_RW = ThreadLocal.withInitial(IOUtils::charArray); - - /** - * Internal char array buffer, intended for write only operations. - */ - private static final char[] SCRATCH_CHAR_BUFFER_WO = charArray(); - /** * The maximum size of an array in many Java VMs. *

    @@ -232,8 +340,7 @@ public class IOUtils { public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; /** - * Returns the given InputStream if it is already a {@link BufferedInputStream}, otherwise creates a - * BufferedInputStream from the given InputStream. + * Returns the given InputStream if it is already a {@link BufferedInputStream}, otherwise creates a BufferedInputStream from the given InputStream. * * @param inputStream the InputStream to wrap or return (not null). * @return the given InputStream or a new {@link BufferedInputStream} for the given InputStream. @@ -245,16 +352,14 @@ public static BufferedInputStream buffer(final InputStream inputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream Objects.requireNonNull(inputStream, "inputStream"); - return inputStream instanceof BufferedInputStream ? - (BufferedInputStream) inputStream : new BufferedInputStream(inputStream); + return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream); } /** - * Returns the given InputStream if it is already a {@link BufferedInputStream}, otherwise creates a - * BufferedInputStream from the given InputStream. + * Returns the given InputStream if it is already a {@link BufferedInputStream}, otherwise creates a BufferedInputStream from the given InputStream. * * @param inputStream the InputStream to wrap or return (not null). - * @param size the buffer size, if a new BufferedInputStream is created. + * @param size the buffer size, if a new BufferedInputStream is created. * @return the given InputStream or a new {@link BufferedInputStream} for the given InputStream. * @throws NullPointerException if the input parameter is null. * @since 2.5 @@ -264,16 +369,14 @@ public static BufferedInputStream buffer(final InputStream inputStream, final in // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream Objects.requireNonNull(inputStream, "inputStream"); - return inputStream instanceof BufferedInputStream ? - (BufferedInputStream) inputStream : new BufferedInputStream(inputStream, size); + return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream, size); } /** - * Returns the given OutputStream if it is already a {@link BufferedOutputStream}, otherwise creates a - * BufferedOutputStream from the given OutputStream. + * Returns the given OutputStream if it is already a {@link BufferedOutputStream}, otherwise creates a BufferedOutputStream from the given OutputStream. * * @param outputStream the OutputStream to wrap or return (not null). - * @return the given OutputStream or a new {@link BufferedOutputStream} for the given OutputStream + * @return the given OutputStream or a new {@link BufferedOutputStream} for the given OutputStream. * @throws NullPointerException if the input parameter is null. * @since 2.5 */ @@ -282,16 +385,14 @@ public static BufferedOutputStream buffer(final OutputStream outputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream Objects.requireNonNull(outputStream, "outputStream"); - return outputStream instanceof BufferedOutputStream ? - (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream); + return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream); } /** - * Returns the given OutputStream if it is already a {@link BufferedOutputStream}, otherwise creates a - * BufferedOutputStream from the given OutputStream. + * Returns the given OutputStream if it is already a {@link BufferedOutputStream}, otherwise creates a BufferedOutputStream from the given OutputStream. * * @param outputStream the OutputStream to wrap or return (not null). - * @param size the buffer size, if a new BufferedOutputStream is created. + * @param size the buffer size, if a new BufferedOutputStream is created. * @return the given OutputStream or a new {@link BufferedOutputStream} for the given OutputStream. * @throws NullPointerException if the input parameter is null. * @since 2.5 @@ -301,13 +402,11 @@ public static BufferedOutputStream buffer(final OutputStream outputStream, final // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream Objects.requireNonNull(outputStream, "outputStream"); - return outputStream instanceof BufferedOutputStream ? - (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream, size); + return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream, size); } /** - * Returns the given reader if it is already a {@link BufferedReader}, otherwise creates a BufferedReader from - * the given reader. + * Returns the given reader if it is already a {@link BufferedReader}, otherwise creates a BufferedReader from the given reader. * * @param reader the reader to wrap or return (not null). * @return the given reader or a new {@link BufferedReader} for the given reader. @@ -319,11 +418,10 @@ public static BufferedReader buffer(final Reader reader) { } /** - * Returns the given reader if it is already a {@link BufferedReader}, otherwise creates a BufferedReader from the - * given reader. + * Returns the given reader if it is already a {@link BufferedReader}, otherwise creates a BufferedReader from the given reader. * * @param reader the reader to wrap or return (not null). - * @param size the buffer size, if a new BufferedReader is created. + * @param size the buffer size, if a new BufferedReader is created. * @return the given reader or a new {@link BufferedReader} for the given reader. * @throws NullPointerException if the input parameter is null. * @since 2.5 @@ -333,8 +431,7 @@ public static BufferedReader buffer(final Reader reader, final int size) { } /** - * Returns the given Writer if it is already a {@link BufferedWriter}, otherwise creates a BufferedWriter from the - * given Writer. + * Returns the given Writer if it is already a {@link BufferedWriter}, otherwise creates a BufferedWriter from the given Writer. * * @param writer the Writer to wrap or return (not null). * @return the given Writer or a new {@link BufferedWriter} for the given Writer. @@ -346,11 +443,10 @@ public static BufferedWriter buffer(final Writer writer) { } /** - * Returns the given Writer if it is already a {@link BufferedWriter}, otherwise creates a BufferedWriter from the - * given Writer. + * Returns the given Writer if it is already a {@link BufferedWriter}, otherwise creates a BufferedWriter from the given Writer. * * @param writer the Writer to wrap or return (not null). - * @param size the buffer size, if a new BufferedWriter is created. + * @param size the buffer size, if a new BufferedWriter is created. * @return the given Writer or a new {@link BufferedWriter} for the given Writer. * @throws NullPointerException if the input parameter is null. * @since 2.5 @@ -370,9 +466,7 @@ public static byte[] byteArray() { } /** - * Returns a new byte array of the given size. - * - * TODO Consider guarding or warning against large allocations. + * Returns a new byte array of the given size. TODO Consider guarding or warning against large allocations. * * @param size array size. * @return a new byte array of the given size. @@ -394,9 +488,7 @@ private static char[] charArray() { } /** - * Returns a new char array of the given size. - * - * TODO Consider guarding or warning against large allocations. + * Returns a new char array of the given size. TODO Consider guarding or warning against large allocations. * * @param size array size. * @return a new char array of the given size. @@ -408,19 +500,23 @@ private static char[] charArray(final int size) { /** * Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array. - * - *

    The range is valid if all of the following hold:

    + *

    + * The range is valid if all of the following hold: + *

    *
      - *
    • {@code off >= 0}
    • - *
    • {@code len >= 0}
    • - *
    • {@code off + len <= array.length}
    • + *
    • {@code off >= 0}
    • + *
    • {@code len >= 0}
    • + *
    • {@code off + len <= array.length}
    • *
    + *

    + * If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message. + *

    + *

    + * Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations: + *

    * - *

    If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

    - * - *

    Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations:

    - * - *
    
    +     * 
    +     * 
          * public int read(byte[] b, int off, int len) throws IOException {
          *     IOUtils.checkFromIndexSize(b, off, len);
          *     if (len == 0) {
    @@ -438,13 +534,14 @@ private static char[] charArray(final int size) {
          *     ensureOpen();
          *     // perform write...
          * }
    -     * 
    + *
    + *
    * - * @param array the array against which the range is validated - * @param off the starting offset into the array (inclusive) - * @param len the number of elements to access - * @throws NullPointerException if {@code array} is {@code null} - * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array} + * @param array the array against which the range is validated. + * @param off the starting offset into the array (inclusive). + * @param len the number of elements to access. + * @throws NullPointerException if {@code array} is {@code null}. + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array}. * @see InputStream#read(byte[], int, int) * @see OutputStream#write(byte[], int, int) * @since 2.21.0 @@ -455,19 +552,23 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i /** * Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array. - * - *

    The range is valid if all of the following hold:

    + *

    + * The range is valid if all of the following hold: + *

    *
      - *
    • {@code off >= 0}
    • - *
    • {@code len >= 0}
    • - *
    • {@code off + len <= array.length}
    • + *
    • {@code off >= 0}
    • + *
    • {@code len >= 0}
    • + *
    • {@code off + len <= array.length}
    • *
    + *

    + * If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message. + *

    + *

    + * Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations: + *

    * - *

    If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

    - * - *

    Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations:

    - * - *
    
    +     * 
    +     * 
          * public int read(char[] cbuf, int off, int len) throws IOException {
          *     ensureOpen();
          *     IOUtils.checkFromIndexSize(cbuf, off, len);
    @@ -485,13 +586,14 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i
          *     }
          *     // perform write...
          * }
    -     * 
    + *
    + *
    * - * @param array the array against which the range is validated - * @param off the starting offset into the array (inclusive) - * @param len the number of characters to access - * @throws NullPointerException if {@code array} is {@code null} - * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array} + * @param array the array against which the range is validated. + * @param off the starting offset into the array (inclusive). + * @param len the number of characters to access. + * @throws NullPointerException if {@code array} is {@code null}. + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array}. * @see Reader#read(char[], int, int) * @see Writer#write(char[], int, int) * @since 2.21.0 @@ -500,21 +602,31 @@ public static void checkFromIndexSize(final char[] array, final int off, final i checkFromIndexSize(off, len, Objects.requireNonNull(array, "char array").length); } + static void checkFromIndexSize(final int off, final int len, final int arrayLength) { + if ((off | len | arrayLength) < 0 || arrayLength - len < off) { + throw new IndexOutOfBoundsException(String.format("Range [%s, %The range is valid if all of the following hold:

    + *

    + * The range is valid if all of the following hold: + *

    *
      - *
    • {@code off >= 0}
    • - *
    • {@code len >= 0}
    • - *
    • {@code off + len <= str.length()}
    • + *
    • {@code off >= 0}
    • + *
    • {@code len >= 0}
    • + *
    • {@code off + len <= str.length()}
    • *
    + *

    + * If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message. + *

    + *

    + * Typical usage in {@link Writer#write(String, int, int)} implementations: + *

    * - *

    If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

    - * - *

    Typical usage in {@link Writer#write(String, int, int)} implementations:

    - * - *
    
    +     * 
    +     * 
          * public void write(String str, int off, int len) throws IOException {
          *     IOUtils.checkFromIndexSize(str, off, len);
          *     if (len == 0) {
    @@ -522,13 +634,14 @@ public static void checkFromIndexSize(final char[] array, final int off, final i
          *     }
          *     // perform write...
          * }
    -     * 
    + *
    + *
    * - * @param str the string against which the range is validated - * @param off the starting offset into the string (inclusive) - * @param len the number of characters to write - * @throws NullPointerException if {@code str} is {@code null} - * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code str} + * @param str the string against which the range is validated. + * @param off the starting offset into the string (inclusive). + * @param len the number of characters to write. + * @throws NullPointerException if {@code str} is {@code null}. + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code str}. * @see Writer#write(String, int, int) * @since 2.21.0 */ @@ -536,40 +649,40 @@ public static void checkFromIndexSize(final String str, final int off, final int checkFromIndexSize(off, len, Objects.requireNonNull(str, "str").length()); } - static void checkFromIndexSize(final int off, final int len, final int arrayLength) { - if ((off | len | arrayLength) < 0 || arrayLength - len < off) { - throw new IndexOutOfBoundsException(String.format("Range [%s, %The sub-sequence is valid if all of the following hold:

    + *

    + * The sub-sequence is valid if all of the following hold: + *

    *
      - *
    • {@code fromIndex >= 0}
    • - *
    • {@code fromIndex <= toIndex}
    • - *
    • {@code toIndex <= seq.length()}
    • + *
    • {@code fromIndex >= 0}
    • + *
    • {@code fromIndex <= toIndex}
    • + *
    • {@code toIndex <= seq.length()}
    • *
    + *

    + * If {@code seq} is {@code null}, it is treated as the literal string {@code "null"} (length {@code 4}). + *

    + *

    + * If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message. + *

    + *

    + * Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations: + *

    * - *

    If {@code seq} is {@code null}, it is treated as the literal string {@code "null"} (length {@code 4}).

    - * - *

    If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

    - * - *

    Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations:

    - * - *
    
    +     * 
    +     * 
          * public Appendable append(CharSequence csq, int start, int end) throws IOException {
          *     IOUtils.checkFromToIndex(csq, start, end);
          *     // perform append...
          *     return this;
          * }
    -     * 
    + *
    + *
    * - * @param seq the character sequence to validate (may be {@code null}, treated as {@code "null"}) - * @param fromIndex the starting index (inclusive) - * @param toIndex the ending index (exclusive) - * @throws IndexOutOfBoundsException if the range {@code [fromIndex, toIndex)} is out of bounds for {@code seq} + * @param seq the character sequence to validate (may be {@code null}, treated as {@code "null"}). + * @param fromIndex the starting index (inclusive). + * @param toIndex the ending index (exclusive). + * @throws IndexOutOfBoundsException if the range {@code [fromIndex, toIndex)} is out of bounds for {@code seq}. * @see Appendable#append(CharSequence, int, int) * @since 2.21.0 */ @@ -589,13 +702,12 @@ static void checkFromToIndex(final int fromIndex, final int toIndex, final int l *
  • Removes the current thread's value for thread-local variables.
  • *
  • Sets static scratch arrays to 0s.
  • * + * * @see IO#clear() */ static void clear() { - SCRATCH_BYTE_BUFFER_RW.remove(); - SCRATCH_CHAR_BUFFER_RW.remove(); - Arrays.fill(SCRATCH_BYTE_BUFFER_WO, (byte) 0); - Arrays.fill(SCRATCH_CHAR_BUFFER_WO, (char) 0); + ScratchBytes.LOCAL.remove(); + ScratchChars.LOCAL.remove(); } /** @@ -626,8 +738,8 @@ public static void close(final Closeable... closeables) throws IOExceptionList { * Closes the given {@link Closeable} as a null-safe operation. * * @param closeable The resource to close, may be null. - * @param consumer Consume the IOException thrown by {@link Closeable#close()}. - * @throws IOException if an I/O error occurs. + * @param consumer Consume the IOException thrown by {@link Closeable#close()}. + * @throws IOException As thrown by the consumer. * @since 2.7 */ public static void close(final Closeable closeable, final IOConsumer consumer) throws IOException { @@ -635,13 +747,9 @@ public static void close(final Closeable closeable, final IOConsumer) null); } /** * Closes a {@link Closeable} unconditionally. - * *

    - * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is typically used in - * finally blocks. + * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    * Example code: *

    + * *
          * Closeable closeable = null;
          * try {
    @@ -691,6 +798,7 @@ private static void closeQ(final Closeable closeable) {
          * 

    * Closing all streams: *

    + * *
          * try {
          *     return IOUtils.copy(inputStream, outputStream);
    @@ -708,7 +816,7 @@ private static void closeQ(final Closeable closeable) {
          * @see Throwable#addSuppressed(Throwable)
          */
         public static void closeQuietly(final Closeable closeable) {
    -        closeQuietly(closeable, null);
    +        closeQuietly(closeable, (Consumer) null);
         }
     
         /**
    @@ -716,17 +824,17 @@ public static void closeQuietly(final Closeable closeable) {
          * 

    * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. *

    - * This is typically used in finally blocks to ensure that the closeable is closed - * even if an Exception was thrown before the normal close statement was reached. - *
    - * It should not be used to replace the close statement(s) - * which should be present for the non-exceptional case. - *
    - * It is only intended to simplify tidying up where normal processing has already failed - * and reporting close failure as well is not necessary or useful. + * This is typically used in finally blocks to ensure that the closeable is closed even if an Exception was thrown before the normal close statement was + * reached. + *

    + *

    + * It should not be used to replace the close statement(s) which should be present for the non-exceptional case. + *

    + * It is only intended to simplify tidying up where normal processing has already failed and reporting close failure as well is not necessary or useful. *

    * Example code: *

    + * *
          * Closeable closeable = null;
          * try {
    @@ -741,7 +849,8 @@ public static void closeQuietly(final Closeable closeable) {
          * 
    *

    * Closing all streams: - *
    + *

    + * *
          * try {
          *     return IOUtils.copy(inputStream, outputStream);
    @@ -752,6 +861,7 @@ public static void closeQuietly(final Closeable closeable) {
          * 

    * Also consider using a try-with-resources statement where appropriate. *

    + * * @param closeables the objects to close, may be null or already closed. * @see #closeQuietly(Closeable) * @since 2.5 @@ -767,7 +877,7 @@ public static void closeQuietly(final Closeable... closeables) { * Closes the given {@link Closeable} as a null-safe operation while consuming IOException by the given {@code consumer}. * * @param closeable The resource to close, may be null. - * @param consumer Consumes the Exception thrown by {@link Closeable#close()}. + * @param consumer Consumes the Exception thrown by {@link Closeable#close()}. * @since 2.7 */ public static void closeQuietly(final Closeable closeable, final Consumer consumer) { @@ -785,24 +895,24 @@ public static void closeQuietly(final Closeable closeable, final Consumer - * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   byte[] data = new byte[1024];
    -     *   InputStream in = null;
    -     *   try {
    -     *       in = new FileInputStream("foo.txt");
    -     *       in.read(data);
    -     *       in.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(in);
    -     *   }
    +     * byte[] data = new byte[1024];
    +     * InputStream in = null;
    +     * try {
    +     *     in = new FileInputStream("foo.txt");
    +     *     in.read(data);
    +     *     in.close(); // close errors are handled
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(in);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -834,20 +944,19 @@ public static void closeQuietly(final Iterable closeables) { /** * Closes an {@link OutputStream} unconditionally. *

    - * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
          * byte[] data = "Hello, World".getBytes();
    -     *
          * OutputStream out = null;
          * try {
          *     out = new FileOutputStream("foo.txt");
          *     out.write(data);
    -     *     out.close(); //close errors are handled
    +     *     out.close(); // close errors are handled
          * } catch (IOException e) {
          *     // error handling
          * } finally {
    @@ -868,24 +977,24 @@ public static void closeQuietly(final OutputStream output) {
         /**
          * Closes an {@link Reader} unconditionally.
          * 

    - * Equivalent to {@link Reader#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link Reader#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   char[] data = new char[1024];
    -     *   Reader in = null;
    -     *   try {
    -     *       in = new FileReader("foo.txt");
    -     *       in.read(data);
    -     *       in.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(in);
    -     *   }
    +     * char[] data = new char[1024];
    +     * Reader in = null;
    +     * try {
    +     *     in = new FileReader("foo.txt");
    +     *     in.read(data);
    +     *     in.close(); // close errors are handled
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(in);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -901,23 +1010,22 @@ public static void closeQuietly(final Reader reader) { /** * Closes a {@link Selector} unconditionally. *

    - * Equivalent to {@link Selector#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link Selector#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   Selector selector = null;
    -     *   try {
    -     *       selector = Selector.open();
    -     *       // process socket
    -     *
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(selector);
    -     *   }
    +     * Selector selector = null;
    +     * try {
    +     *     selector = Selector.open();
    +     *     // process socket
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(selector);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -934,23 +1042,23 @@ public static void closeQuietly(final Selector selector) { /** * Closes a {@link ServerSocket} unconditionally. *

    - * Equivalent to {@link ServerSocket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link ServerSocket#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   ServerSocket socket = null;
    -     *   try {
    -     *       socket = new ServerSocket();
    -     *       // process socket
    -     *       socket.close();
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(socket);
    -     *   }
    +     * ServerSocket socket = null;
    +     * try {
    +     *     socket = new ServerSocket();
    +     *     // process socket
    +     *     socket.close();
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(socket);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -967,23 +1075,23 @@ public static void closeQuietly(final ServerSocket serverSocket) { /** * Closes a {@link Socket} unconditionally. *

    - * Equivalent to {@link Socket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link Socket#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   Socket socket = null;
    -     *   try {
    -     *       socket = new Socket("http://www.foo.com/", 80);
    -     *       // process socket
    -     *       socket.close();
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(socket);
    -     *   }
    +     * Socket socket = null;
    +     * try {
    +     *     socket = new Socket("http://www.foo.com/", 80);
    +     *     // process socket
    +     *     socket.close();
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(socket);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -1016,23 +1124,23 @@ public static void closeQuietly(final Stream closeables) { /** * Closes an {@link Writer} unconditionally. *

    - * Equivalent to {@link Writer#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. + * Equivalent to {@link Writer#close()}, except any exceptions will be ignored. This is typically used in finally blocks. *

    *

    * Example code: *

    + * *
    -     *   Writer out = null;
    -     *   try {
    -     *       out = new StringWriter();
    -     *       out.write("Hello World");
    -     *       out.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(out);
    -     *   }
    +     * Writer out = null;
    +     * try {
    +     *     out = new StringWriter();
    +     *     out.write("Hello World");
    +     *     out.close(); // close errors are handled
    +     * } catch (Exception e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(out);
    +     * }
          * 
    *

    * Also consider using a try-with-resources statement where appropriate. @@ -1045,6 +1153,37 @@ public static void closeQuietly(final Writer writer) { closeQ(writer); } + /** + * Closes a {@link Closeable} unconditionally and adds any exception thrown by the {@code close()} to the given Throwable. + *

    + * For example: + *

    + * + *
    +     * Closeable closeable = ...;
    +     * try {
    +     *     // process closeable.
    +     * } catch (Exception e) {
    +     *     // Handle exception.
    +     *     throw IOUtils.closeQuietlySuppress(closeable, e);
    +     * }
    +     * 
    + *

    + * Also consider using a try-with-resources statement where appropriate. + *

    + * + * @param The Throwable type. + * @param closeable The object to close, may be null or already closed. + * @param throwable Add the exception throw by the closeable to the given Throwable. + * @return The given Throwable. + * @since 2.22.0 + * @see Throwable#addSuppressed(Throwable) + */ + public static T closeQuietlySuppress(final Closeable closeable, final T throwable) { + closeQuietly(closeable, throwable::addSuppressed); + return throwable; + } + /** * Consumes bytes from a {@link InputStream} and ignores them. *

    @@ -1054,7 +1193,7 @@ public static void closeQuietly(final Writer writer) { * @param input the {@link InputStream} to read. * @return the number of bytes copied. or {@code 0} if {@code input is null}. * @throws NullPointerException if the InputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.8.0 */ public static long consume(final InputStream input) throws IOException { @@ -1070,7 +1209,7 @@ public static long consume(final InputStream input) throws IOException { * @param input the {@link Reader} to read. * @return the number of bytes copied. or {@code 0} if {@code input is null}. * @throws NullPointerException if the Reader is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.12.0 */ public static long consume(final Reader input) throws IOException { @@ -1078,18 +1217,15 @@ public static long consume(final Reader input) throws IOException { } /** - * Compares the contents of two Streams to determine if they are equal or - * not. + * Compares the contents of two Streams to determine if they are equal or not. *

    - * This method buffers the input internally using - * {@link BufferedInputStream} if they are not already buffered. + * This method buffers the input internally using {@link BufferedInputStream} if they are not already buffered. *

    * * @param input1 the first stream. * @param input2 the second stream. - * @return true if the content of the streams are equal or they both don't. - * exist, false otherwise. - * @throws IOException if an I/O error occurs. + * @return true if the content of the streams are equal or they both don't. exist, false otherwise. + * @throws IOException if an I/O error occurs. */ @SuppressWarnings("resource") // Caller closes input streams public static boolean contentEquals(final InputStream input1, final InputStream input2) throws IOException { @@ -1107,10 +1243,7 @@ public static boolean contentEquals(final InputStream input1, final InputStream // TODO Consider making public private static boolean contentEquals(final Iterator iterator1, final Iterator iterator2) { while (iterator1.hasNext()) { - if (!iterator2.hasNext()) { - return false; - } - if (!Objects.equals(iterator1.next(), iterator2.next())) { + if (!iterator2.hasNext() || !Objects.equals(iterator1.next(), iterator2.next())) { return false; } } @@ -1127,7 +1260,7 @@ private static boolean contentEquals(final Iterator iterator1, final Iterator * @param input2 the second reader. * @return true if the content of the readers are equal or they both don't exist, false otherwise. * @throws NullPointerException if either input is null. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 1.1 */ public static boolean contentEquals(final Reader input1, final Reader input2) throws IOException { @@ -1137,39 +1270,38 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th if (input1 == null || input2 == null) { return false; } - - // reuse one - final char[] array1 = getScratchCharArray(); - // but allocate another - final char[] array2 = charArray(); - int pos1; - int pos2; - int count1; - int count2; - while (true) { - pos1 = 0; - pos2 = 0; - for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) { - if (pos1 == index) { - do { - count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1); - } while (count1 == 0); - if (count1 == EOF) { - return pos2 == index && input2.read() == EOF; + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + final char[] array1 = scratch.array(); + final char[] array2 = charArray(); + int pos1; + int pos2; + int count1; + int count2; + while (true) { + pos1 = 0; + pos2 = 0; + for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) { + if (pos1 == index) { + do { + count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1); + } while (count1 == 0); + if (count1 == EOF) { + return pos2 == index && input2.read() == EOF; + } + pos1 += count1; } - pos1 += count1; - } - if (pos2 == index) { - do { - count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2); - } while (count2 == 0); - if (count2 == EOF) { - return pos1 == index && input1.read() == EOF; + if (pos2 == index) { + do { + count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2); + } while (count2 == 0); + if (count2 == EOF) { + return pos1 == index && input1.read() == EOF; + } + pos2 += count2; + } + if (array1[index] != array2[index]) { + return false; } - pos2 += count2; - } - if (array1[index] != array2[index]) { - return false; } } } @@ -1198,16 +1330,14 @@ private static boolean contentEqualsIgnoreEOL(final BufferedReader reader1, fina } /** - * Compares the contents of two Readers to determine if they are equal or - * not, ignoring EOL characters. + * Compares the contents of two Readers to determine if they are equal or not, ignoring EOL characters. *

    - * This method buffers the input internally using - * {@link BufferedReader} if they are not already buffered. + * This method buffers the input internally using {@link BufferedReader} if they are not already buffered. *

    * * @param reader1 the first reader. * @param reader2 the second reader. - * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise. + * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise. * @throws NullPointerException if either input is null. * @throws UncheckedIOException if an I/O error occurs. * @since 2.2 @@ -1229,17 +1359,16 @@ public static boolean contentEqualsIgnoreEOL(final Reader reader1, final Reader * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    - * Large streams (over 2GB) will return a bytes copied value of {@code -1} after the copy has completed since - * the correct number of bytes cannot be returned as an int. For large streams use the - * {@link #copyLarge(InputStream, OutputStream)} method. + * Large streams (over 2GB) will return a bytes copied value of {@code -1} after the copy has completed since the correct number of bytes cannot be returned + * as an int. For large streams use the {@link #copyLarge(InputStream, OutputStream)} method. *

    * - * @param inputStream the {@link InputStream} to read. + * @param inputStream the {@link InputStream} to read. * @param outputStream the {@link OutputStream} to write. * @return the number of bytes copied, or -1 if greater than {@link Integer#MAX_VALUE}. * @throws NullPointerException if the InputStream is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 1.1 */ public static int copy(final InputStream inputStream, final OutputStream outputStream) throws IOException { @@ -1248,19 +1377,18 @@ public static int copy(final InputStream inputStream, final OutputStream outputS } /** - * Copies bytes from an {@link InputStream} to an {@link OutputStream} using an internal buffer of the - * given size. + * Copies bytes from an {@link InputStream} to an {@link OutputStream} using an internal buffer of the given size. *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param inputStream the {@link InputStream} to read. + * @param inputStream the {@link InputStream} to read. * @param outputStream the {@link OutputStream} to write to. - * @param bufferSize the bufferSize used to copy from the input to the output. + * @param bufferSize the bufferSize used to copy from the input to the output. * @return the number of bytes copied. * @throws NullPointerException if the InputStream is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.5 */ public static long copy(final InputStream inputStream, final OutputStream outputStream, final int bufferSize) throws IOException { @@ -1268,17 +1396,15 @@ public static long copy(final InputStream inputStream, final OutputStream output } /** - * Copies bytes from an {@link InputStream} to chars on a - * {@link Writer} using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Copies bytes from an {@link InputStream} to chars on a {@link Writer} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    * This method uses {@link InputStreamReader}. *

    * - * @param input the {@link InputStream} to read. + * @param input the {@link InputStream} to read. * @param writer the {@link Writer} to write to. * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. @@ -1291,18 +1417,16 @@ public static void copy(final InputStream input, final Writer writer) throws IOE } /** - * Copies bytes from an {@link InputStream} to chars on a - * {@link Writer} using the specified character encoding. + * Copies bytes from an {@link InputStream} to chars on a {@link Writer} using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    * This method uses {@link InputStreamReader}. *

    * - * @param input the {@link InputStream} to read. - * @param writer the {@link Writer} to write to. + * @param input the {@link InputStream} to read. + * @param writer the {@link Writer} to write to. * @param inputCharset the charset to use for the input stream, null means platform default. * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. @@ -1313,22 +1437,19 @@ public static void copy(final InputStream input, final Writer writer, final Char } /** - * Copies bytes from an {@link InputStream} to chars on a - * {@link Writer} using the specified character encoding. + * Copies bytes from an {@link InputStream} to chars on a {@link Writer} using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link InputStreamReader}. *

    * - * @param input the {@link InputStream} to read - * @param writer the {@link Writer} to write to + * @param input the {@link InputStream} to read. + * @param writer the {@link Writer} to write to. * @param inputCharsetName the name of the requested charset for the InputStream, null means platform default. * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. @@ -1342,8 +1463,7 @@ public static void copy(final InputStream input, final Writer writer, final Stri /** * Copies bytes from a {@link ByteArrayOutputStream} to a {@link QueueInputStream}. *

    - * Unlike using JDK {@link PipedInputStream} and {@link PipedOutputStream} for this, this - * solution works safely in a single thread environment. + * Unlike using JDK {@link PipedInputStream} and {@link PipedOutputStream} for this, this solution works safely in a single thread environment. *

    *

    * Example usage: @@ -1352,14 +1472,13 @@ public static void copy(final InputStream input, final Writer writer, final Stri *

          * ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          * outputStream.writeBytes("hello world".getBytes(StandardCharsets.UTF_8));
    -     *
          * InputStream inputStream = IOUtils.copy(outputStream);
          * 
    * * @param outputStream the {@link ByteArrayOutputStream} to read. * @return the {@link QueueInputStream} filled with the content of the outputStream. * @throws NullPointerException if the {@link ByteArrayOutputStream} is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.12 */ @SuppressWarnings("resource") // streams are closed by the caller. @@ -1373,14 +1492,11 @@ public static QueueInputStream copy(final java.io.ByteArrayOutputStream outputSt /** * Copies chars from a {@link Reader} to a {@link Appendable}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    *

    - * Large streams (over 2GB) will return a chars copied value of - * {@code -1} after the copy has completed since the correct - * number of chars cannot be returned as an int. For large streams - * use the {@link #copyLarge(Reader, Writer)} method. + * Large streams (over 2GB) will return a chars copied value of {@code -1} after the copy has completed since the correct number of chars cannot be returned + * as an int. For large streams use the {@link #copyLarge(Reader, Writer)} method. *

    * * @param reader the {@link Reader} to read. @@ -1397,8 +1513,7 @@ public static long copy(final Reader reader, final Appendable output) throws IOE /** * Copies chars from a {@link Reader} to an {@link Appendable}. *

    - * This method uses the provided buffer, so there is no need to use a - * {@link BufferedReader}. + * This method uses the provided buffer, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to read. @@ -1421,7 +1536,7 @@ public static long copy(final Reader reader, final Appendable output, final Char } /** - * Copies chars from a {@link Reader} to bytes on an {@link OutputStream} using the the virtual machine's {@link Charset#defaultCharset() default charset}, + * Copies chars from a {@link Reader} to bytes on an {@link OutputStream} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}, * and calling flush. *

    * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. @@ -1438,7 +1553,7 @@ public static long copy(final Reader reader, final Appendable output, final Char * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. * @since 1.1 - * @deprecated Use {@link #copy(Reader, OutputStream, Charset)} instead + * @deprecated Use {@link #copy(Reader, OutputStream, Charset)} instead. */ @Deprecated public static void copy(final Reader reader, final OutputStream output) throws IOException { @@ -1478,7 +1593,7 @@ public static void copy(final Reader reader, final OutputStream output, final Ch * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    *

    - * Character encoding names can be found at IANA. + * Character encoding names can be found at IANA. *

    *

    * Due to the implementation of OutputStreamWriter, this method performs a flush. @@ -1502,14 +1617,11 @@ public static void copy(final Reader reader, final OutputStream output, final St /** * Copies chars from a {@link Reader} to a {@link Writer}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    *

    - * Large streams (over 2GB) will return a chars copied value of - * {@code -1} after the copy has completed since the correct - * number of chars cannot be returned as an int. For large streams - * use the {@link #copyLarge(Reader, Writer)} method. + * Large streams (over 2GB) will return a chars copied value of {@code -1} after the copy has completed since the correct number of chars cannot be returned + * as an int. For large streams use the {@link #copyLarge(Reader, Writer)} method. *

    * * @param reader the {@link Reader} to read. @@ -1536,12 +1648,12 @@ public static int copy(final Reader reader, final Writer writer) throws IOExcept * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. *

    * - * @param url the {@link URL} to read. + * @param url the {@link URL} to read. * @param file the {@link OutputStream} to write. * @return the number of bytes copied. * @throws NullPointerException if the URL is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.9.0 */ public static long copy(final URL url, final File file) throws IOException { @@ -1559,12 +1671,12 @@ public static long copy(final URL url, final File file) throws IOException { * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. *

    * - * @param url the {@link URL} to read. + * @param url the {@link URL} to read. * @param outputStream the {@link OutputStream} to write. * @return the number of bytes copied. * @throws NullPointerException if the URL is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.9.0 */ public static long copy(final URL url, final OutputStream outputStream) throws IOException { @@ -1574,49 +1686,43 @@ public static long copy(final URL url, final OutputStream outputStream) throws I } /** - * Copies bytes from a large (over 2GB) {@link InputStream} to an - * {@link OutputStream}. + * Copies bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. *

    * - * @param inputStream the {@link InputStream} to read. + * @param inputStream the {@link InputStream} to read. * @param outputStream the {@link OutputStream} to write. * @return the number of bytes copied. * @throws NullPointerException if the InputStream is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 1.3 */ - public static long copyLarge(final InputStream inputStream, final OutputStream outputStream) - throws IOException { + public static long copyLarge(final InputStream inputStream, final OutputStream outputStream) throws IOException { return copy(inputStream, outputStream, DEFAULT_BUFFER_SIZE); } /** - * Copies bytes from a large (over 2GB) {@link InputStream} to an - * {@link OutputStream}. + * Copies bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}. *

    - * This method uses the provided buffer, so there is no need to use a - * {@link BufferedInputStream}. + * This method uses the provided buffer, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param inputStream the {@link InputStream} to read. + * @param inputStream the {@link InputStream} to read. * @param outputStream the {@link OutputStream} to write. - * @param buffer the buffer to use for the copy + * @param buffer the buffer to use for the copy. * @return the number of bytes copied. * @throws NullPointerException if the InputStream is {@code null}. * @throws NullPointerException if the OutputStream is {@code null}. - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. * @since 2.2 */ @SuppressWarnings("resource") // streams are closed by the caller. - public static long copyLarge(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) - throws IOException { + public static long copyLarge(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) throws IOException { Objects.requireNonNull(inputStream, "inputStream"); Objects.requireNonNull(outputStream, "outputStream"); long count = 0; @@ -1629,58 +1735,53 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o } /** - * Copies some or all bytes from a large (over 2GB) {@link InputStream} to an - * {@link OutputStream}, optionally skipping input bytes. + * Copies some or all bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}, optionally skipping input bytes. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    *

    - * Note that the implementation uses {@link #skip(InputStream, long)}. - * This means that the method may be considerably less efficient than using the actual skip implementation, - * this is done to guarantee that the correct number of characters are skipped. + * Note that the implementation uses {@link #skip(InputStream, long)}. This means that the method may be considerably less efficient than using the actual + * skip implementation, this is done to guarantee that the correct number of characters are skipped. *

    * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. * - * @param input the {@link InputStream} to read. - * @param output the {@link OutputStream} to write. + * @param input the {@link InputStream} to read. + * @param output the {@link OutputStream} to write. * @param inputOffset number of bytes to skip from input before copying, these bytes are ignored. - * @param length number of bytes to copy. + * @param length number of bytes to copy. * @return the number of bytes copied. * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. * @since 2.2 */ - public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, - final long length) throws IOException { - return copyLarge(input, output, inputOffset, length, getScratchByteArray()); + public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length) throws IOException { + try (ScratchBytes scratch = ScratchBytes.get()) { + return copyLarge(input, output, inputOffset, length, scratch.array()); + } } /** - * Copies some or all bytes from a large (over 2GB) {@link InputStream} to an - * {@link OutputStream}, optionally skipping input bytes. + * Copies some or all bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}, optionally skipping input bytes. *

    - * This method uses the provided buffer, so there is no need to use a - * {@link BufferedInputStream}. + * This method uses the provided buffer, so there is no need to use a {@link BufferedInputStream}. *

    *

    - * Note that the implementation uses {@link #skip(InputStream, long)}. - * This means that the method may be considerably less efficient than using the actual skip implementation, - * this is done to guarantee that the correct number of characters are skipped. + * Note that the implementation uses {@link #skip(InputStream, long)}. This means that the method may be considerably less efficient than using the actual + * skip implementation, this is done to guarantee that the correct number of characters are skipped. *

    * - * @param input the {@link InputStream} to read. - * @param output the {@link OutputStream} to write. + * @param input the {@link InputStream} to read. + * @param output the {@link OutputStream} to write. * @param inputOffset number of bytes to skip from input before copying, these bytes are ignored. - * @param length number of bytes to copy. - * @param buffer the buffer to use for the copy. + * @param length number of bytes to copy. + * @param buffer the buffer to use for the copy. * @return the number of bytes copied. * @throws NullPointerException if the input or output is null. * @throws IOException if an I/O error occurs. * @since 2.2 */ - public static long copyLarge(final InputStream input, final OutputStream output, - final long inputOffset, final long length, final byte[] buffer) throws IOException { + public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length, final byte[] buffer) + throws IOException { if (inputOffset > 0) { skipFully(input, inputOffset); } @@ -1698,7 +1799,7 @@ public static long copyLarge(final InputStream input, final OutputStream output, output.write(buffer, 0, read); totalRead += read; if (length > 0) { // only adjust length if not reading to the end - // Note the cast must work because buffer.length is an integer + // Note the cast must work because bufferLength = buffer.length is an integer bytesToRead = (int) Math.min(length - totalRead, bufferLength); } } @@ -1708,8 +1809,7 @@ public static long copyLarge(final InputStream input, final OutputStream output, /** * Copies chars from a large (over 2GB) {@link Reader} to a {@link Writer}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    *

    * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. @@ -1723,22 +1823,23 @@ public static long copyLarge(final InputStream input, final OutputStream output, * @since 1.3 */ public static long copyLarge(final Reader reader, final Writer writer) throws IOException { - return copyLarge(reader, writer, getScratchCharArray()); + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + return copyLarge(reader, writer, scratch.array()); + } } /** * Copies chars from a large (over 2GB) {@link Reader} to a {@link Writer}. *

    - * This method uses the provided buffer, so there is no need to use a - * {@link BufferedReader}. + * This method uses the provided buffer, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to source. * @param writer the {@link Writer} to target. - * @param buffer the buffer to be used for the copy - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs + * @param buffer the buffer to be used for the copy. + * @return the number of characters copied. + * @throws NullPointerException if the input or output is null. + * @throws IOException if an I/O error occurs. * @since 2.2 */ public static long copyLarge(final Reader reader, final Writer writer, final char[] buffer) throws IOException { @@ -1770,7 +1871,9 @@ public static long copyLarge(final Reader reader, final Writer writer, final cha * @since 2.2 */ public static long copyLarge(final Reader reader, final Writer writer, final long inputOffset, final long length) throws IOException { - return copyLarge(reader, writer, inputOffset, length, getScratchCharArray()); + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + return copyLarge(reader, writer, inputOffset, length, scratch.array()); + } } /** @@ -1817,84 +1920,18 @@ public static long copyLarge(final Reader reader, final Writer writer, final lon * Copies up to {@code size} bytes from the given {@link InputStream} into a new {@link UnsynchronizedByteArrayOutputStream}. * * @param input The {@link InputStream} to read; must not be {@code null}. - * @param limit The maximum number of bytes to read; must be {@code >= 0}. - * The actual bytes read are validated to equal {@code size}. + * @param limit The maximum number of bytes to read; must be {@code >= 0}. The actual bytes read are validated to equal {@code size}. * @param bufferSize The buffer size of the output stream; must be {@code > 0}. * @return a ByteArrayOutputStream containing the read bytes. */ - static UnsynchronizedByteArrayOutputStream copyToOutputStream( - final InputStream input, final long limit, final int bufferSize) throws IOException { - try (UnsynchronizedByteArrayOutputStream output = UnsynchronizedByteArrayOutputStream.builder() - .setBufferSize(bufferSize) - .get(); - InputStream boundedInput = BoundedInputStream.builder() - .setMaxCount(limit) - .setPropagateClose(false) - .setInputStream(input) - .get()) { + static UnsynchronizedByteArrayOutputStream copyToOutputStream(final InputStream input, final long limit, final int bufferSize) throws IOException { + try (UnsynchronizedByteArrayOutputStream output = UnsynchronizedByteArrayOutputStream.builder().setBufferSize(bufferSize).get(); + InputStream boundedInput = BoundedInputStream.builder().setMaxCount(limit).setPropagateClose(false).setInputStream(input).get()) { output.write(boundedInput); return output; } } - /** - * Fills the given array with 0s. - * - * @param arr The non-null array to fill. - * @return The given array. - */ - private static byte[] fill0(final byte[] arr) { - Arrays.fill(arr, (byte) 0); - return arr; - } - - /** - * Fills the given array with 0s. - * - * @param arr The non-null array to fill. - * @return The given array. - */ - private static char[] fill0(final char[] arr) { - Arrays.fill(arr, (char) 0); - return arr; - } - - /** - * Gets the internal byte array buffer, intended for both reading and writing. - * - * @return the internal byte array buffer, intended for both reading and writing. - */ - static byte[] getScratchByteArray() { - return fill0(SCRATCH_BYTE_BUFFER_RW.get()); - } - - /** - * Gets the internal byte array intended for write only operations. - * - * @return the internal byte array intended for write only operations. - */ - static byte[] getScratchByteArrayWriteOnly() { - return fill0(SCRATCH_BYTE_BUFFER_WO); - } - - /** - * Gets the char byte array buffer, intended for both reading and writing. - * - * @return the char byte array buffer, intended for both reading and writing. - */ - static char[] getScratchCharArray() { - return fill0(SCRATCH_CHAR_BUFFER_RW.get()); - } - - /** - * Gets the internal char array intended for write only operations. - * - * @return the internal char array intended for write only operations. - */ - static char[] getScratchCharArrayWriteOnly() { - return fill0(SCRATCH_CHAR_BUFFER_WO); - } - /** * Returns the length of the given array in a null-safe manner. * @@ -1940,31 +1977,29 @@ public static int length(final Object[] array) { } /** - * Returns an Iterator for the lines in an {@link InputStream}, using - * the character encoding specified (or default encoding if null). + * Returns an Iterator for the lines in an {@link InputStream}, using the character encoding specified (or default encoding if null). *

    - * {@link LineIterator} holds a reference to the open - * {@link InputStream} specified here. When you have finished with - * the iterator you should close the stream to free internal resources. - * This can be done by using a try-with-resources block, closing the stream directly, or by calling + * {@link LineIterator} holds a reference to the open {@link InputStream} specified here. When you have finished with the iterator you should close the + * stream to free internal resources. This can be done by using a try-with-resources block, closing the stream directly, or by calling * {@link LineIterator#close()}. *

    *

    * The recommended usage pattern is: *

    + * *
          * try {
    -     *   LineIterator it = IOUtils.lineIterator(stream, charset);
    -     *   while (it.hasNext()) {
    -     *     String line = it.nextLine();
    -     *     /// do something with line
    -     *   }
    +     *     LineIterator it = IOUtils.lineIterator(stream, charset);
    +     *     while (it.hasNext()) {
    +     *         String line = it.nextLine();
    +     *         /// do something with line
    +     *     }
          * } finally {
    -     *   IOUtils.closeQuietly(stream);
    +     *     IOUtils.closeQuietly(stream);
          * }
          * 
    * - * @param input the {@link InputStream} to read, not null. + * @param input the {@link InputStream} to read, not null. * @param charset the charset to use, null means platform default. * @return an Iterator of the lines in the reader, never null. * @throws IllegalArgumentException if the input is null. @@ -1975,31 +2010,29 @@ public static LineIterator lineIterator(final InputStream input, final Charset c } /** - * Returns an Iterator for the lines in an {@link InputStream}, using - * the character encoding specified (or default encoding if null). + * Returns an Iterator for the lines in an {@link InputStream}, using the character encoding specified (or default encoding if null). *

    - * {@link LineIterator} holds a reference to the open - * {@link InputStream} specified here. When you have finished with - * the iterator you should close the stream to free internal resources. - * This can be done by using a try-with-resources block, closing the stream directly, or by calling + * {@link LineIterator} holds a reference to the open {@link InputStream} specified here. When you have finished with the iterator you should close the + * stream to free internal resources. This can be done by using a try-with-resources block, closing the stream directly, or by calling * {@link LineIterator#close()}. *

    *

    * The recommended usage pattern is: *

    + * *
          * try {
    -     *   LineIterator it = IOUtils.lineIterator(stream, StandardCharsets.UTF_8.name());
    -     *   while (it.hasNext()) {
    -     *     String line = it.nextLine();
    -     *     /// do something with line
    -     *   }
    +     *     LineIterator it = IOUtils.lineIterator(stream, StandardCharsets.UTF_8.name());
    +     *     while (it.hasNext()) {
    +     *         String line = it.nextLine();
    +     *         /// do something with line
    +     *     }
          * } finally {
    -     *   IOUtils.closeQuietly(stream);
    +     *     IOUtils.closeQuietly(stream);
          * }
          * 
    * - * @param input the {@link InputStream} to read, not null. + * @param input the {@link InputStream} to read, not null. * @param charsetName the encoding to use, null means platform default. * @return an Iterator of the lines in the reader, never null. * @throws IllegalArgumentException if the input is null. @@ -2013,24 +2046,22 @@ public static LineIterator lineIterator(final InputStream input, final String ch /** * Returns an Iterator for the lines in a {@link Reader}. *

    - * {@link LineIterator} holds a reference to the open - * {@link Reader} specified here. When you have finished with the - * iterator you should close the reader to free internal resources. - * This can be done by using a try-with-resources block, closing the reader directly, or by calling - * {@link LineIterator#close()}. + * {@link LineIterator} holds a reference to the open {@link Reader} specified here. When you have finished with the iterator you should close the reader to + * free internal resources. This can be done by using a try-with-resources block, closing the reader directly, or by calling {@link LineIterator#close()}. *

    *

    * The recommended usage pattern is: *

    + * *
          * try {
    -     *   LineIterator it = IOUtils.lineIterator(reader);
    -     *   while (it.hasNext()) {
    -     *     String line = it.nextLine();
    -     *     /// do something with line
    -     *   }
    +     *     LineIterator it = IOUtils.lineIterator(reader);
    +     *     while (it.hasNext()) {
    +     *         String line = it.nextLine();
    +     *         /// do something with line
    +     *     }
          * } finally {
    -     *   IOUtils.closeQuietly(reader);
    +     *     IOUtils.closeQuietly(reader);
          * }
          * 
    * @@ -2046,15 +2077,15 @@ public static LineIterator lineIterator(final Reader reader) { /** * Reads bytes from an input stream. *

    - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link InputStream}. + * This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case for subclasses of + * {@link InputStream}. *

    * - * @param input where to read input from. + * @param input where to read input from. * @param buffer destination. * @return actual length read; may be less than requested if EOF was reached. - * @throws IOException if a read error occurs. + * @throws NullPointerException if {@code input} or {@code buffer} is null. + * @throws IOException if a read error occurs. * @since 2.2 */ public static int read(final InputStream input, final byte[] buffer) throws IOException { @@ -2064,50 +2095,26 @@ public static int read(final InputStream input, final byte[] buffer) throws IOEx /** * Reads bytes from an input stream. *

    - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link InputStream}. + * This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case for subclasses of + * {@link InputStream}. *

    * - * @param input where to read input. + * @param input where to read input. * @param buffer destination. * @param offset initial offset into buffer. * @param length length to read, must be >= 0. * @return actual length read; may be less than requested if EOF was reached. - * @throws IllegalArgumentException if length is negative. - * @throws IOException if a read error occurs. + * @throws NullPointerException if {@code input} or {@code buffer} is null. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if {@code offset + length} is greater than {@code buffer.length}. + * @throws IOException if a read error occurs. * @since 2.2 */ - public static int read(final InputStream input, final byte[] buffer, final int offset, final int length) - throws IOException { - if (length == 0) { - return 0; - } - return read(input::read, buffer, offset, length); - } - - /** - * Reads bytes from an input. This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case - * for subclasses of {@link InputStream}. - * - * @param input How to read input. - * @param buffer destination. - * @param offset initial offset into buffer. - * @param length length to read, must be >= 0. - * @return actual length read; may be less than requested if EOF was reached. - * @throws IllegalArgumentException if length is negative. - * @throws IOException if a read error occurs. - * @since 2.2 - */ - static int read(final IOTriFunction input, final byte[] buffer, final int offset, final int length) - throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } + public static int read(final InputStream input, final byte[] buffer, final int offset, final int length) throws IOException { + checkFromIndexSize(buffer, offset, length); int remaining = length; while (remaining > 0) { final int location = length - remaining; - final int count = input.apply(buffer, offset + location, remaining); + final int count = input.read(buffer, offset + location, remaining); if (EOF == count) { break; } @@ -2119,12 +2126,11 @@ static int read(final IOTriFunction input, fi /** * Reads bytes from a ReadableByteChannel. *

    - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link ReadableByteChannel}. + * This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case for subclasses of + * {@link ReadableByteChannel}. *

    * - * @param input the byte channel to read. + * @param input the byte channel to read. * @param buffer byte buffer destination. * @return the actual length read; may be less than requested if EOF was reached. * @throws IOException if a read error occurs. @@ -2144,9 +2150,8 @@ public static int read(final ReadableByteChannel input, final ByteBuffer buffer) /** * Reads characters from an input character stream. *

    - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link Reader}. + * This implementation guarantees that it will read as many characters as possible before giving up; this may not always be the case for subclasses of + * {@link Reader}. *

    * * @param reader where to read input from. @@ -2162,9 +2167,8 @@ public static int read(final Reader reader, final char[] buffer) throws IOExcept /** * Reads characters from an input character stream. *

    - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link Reader}. + * This implementation guarantees that it will read as many characters as possible before giving up; this may not always be the case for subclasses of + * {@link Reader}. *

    * * @param reader where to read input from. @@ -2172,15 +2176,13 @@ public static int read(final Reader reader, final char[] buffer) throws IOExcept * @param offset initial offset into buffer. * @param length length to read, must be >= 0. * @return actual length read; may be less than requested if EOF was reached. - * @throws IllegalArgumentException if length is negative. - * @throws IOException if a read error occurs. + * @throws NullPointerException if {@code reader} or {@code buffer} is null. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if {@code offset + length} is greater than {@code buffer.length}. + * @throws IOException if a read error occurs. * @since 2.2 */ - public static int read(final Reader reader, final char[] buffer, final int offset, final int length) - throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } + public static int read(final Reader reader, final char[] buffer, final int offset, final int length) throws IOException { + checkFromIndexSize(buffer, offset, length); int remaining = length; while (remaining > 0) { final int location = length - remaining; @@ -2196,15 +2198,15 @@ public static int read(final Reader reader, final char[] buffer, final int offse /** * Reads the requested number of bytes or fail if there are not enough left. *

    - * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may not read as many bytes as requested (most likely because of reaching + * EOF). *

    * - * @param input where to read input from. + * @param input where to read input from. * @param buffer destination. - * @throws IOException if there is a problem reading the file. - * @throws IllegalArgumentException if length is negative. - * @throws EOFException if the number of bytes read was incorrect. + * @throws NullPointerException if {@code input} or {@code buffer} is null. + * @throws EOFException if the number of bytes read was incorrect. + * @throws IOException if there is a problem reading the file. * @since 2.2 */ public static void readFully(final InputStream input, final byte[] buffer) throws IOException { @@ -2214,21 +2216,21 @@ public static void readFully(final InputStream input, final byte[] buffer) throw /** * Reads the requested number of bytes or fail if there are not enough left. *

    - * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may not read as many bytes as requested (most likely because of reaching + * EOF). *

    * - * @param input where to read input from. + * @param input where to read input from. * @param buffer destination. * @param offset initial offset into buffer. * @param length length to read, must be >= 0. - * @throws IOException if there is a problem reading the file. - * @throws IllegalArgumentException if length is negative. - * @throws EOFException if the number of bytes read was incorrect. + * @throws NullPointerException if {@code input} or {@code buffer} is null. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if {@code offset + length} is greater than {@code buffer.length}. + * @throws EOFException if the number of bytes read was incorrect. + * @throws IOException if there is a problem reading the file. * @since 2.2 */ - public static void readFully(final InputStream input, final byte[] buffer, final int offset, final int length) - throws IOException { + public static void readFully(final InputStream input, final byte[] buffer, final int offset, final int length) throws IOException { final int actual = read(input, buffer, offset, length); if (actual != length) { throw new EOFException("Length to read: " + length + " actual: " + actual); @@ -2238,11 +2240,11 @@ public static void readFully(final InputStream input, final byte[] buffer, final /** * Reads the requested number of bytes or fail if there are not enough left. *

    - * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may not read as many bytes as requested (most likely because of reaching + * EOF). *

    * - * @param input where to read input from. + * @param input where to read input from. * @param length length to read, must be >= 0. * @return the bytes read from input. * @throws IOException if there is a problem reading the file. @@ -2259,11 +2261,11 @@ public static byte[] readFully(final InputStream input, final int length) throws /** * Reads the requested number of bytes or fail if there are not enough left. *

    - * This allows for the possibility that {@link ReadableByteChannel#read(ByteBuffer)} may - * not read as many bytes as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link ReadableByteChannel#read(ByteBuffer)} may not read as many bytes as requested (most likely because of + * reaching EOF). *

    * - * @param input the byte channel to read. + * @param input the byte channel to read. * @param buffer byte buffer destination. * @throws IOException if there is a problem reading the file. * @throws EOFException if the number of bytes read was incorrect. @@ -2280,15 +2282,15 @@ public static void readFully(final ReadableByteChannel input, final ByteBuffer b /** * Reads the requested number of characters or fail if there are not enough left. *

    - * This allows for the possibility that {@link Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link Reader#read(char[], int, int)} may not read as many characters as requested (most likely because of reaching + * EOF). *

    * * @param reader where to read input from. * @param buffer destination. - * @throws IOException if there is a problem reading the file. - * @throws IllegalArgumentException if length is negative. - * @throws EOFException if the number of characters read was incorrect. + * @throws NullPointerException if {@code reader} or {@code buffer} is null. + * @throws EOFException if the number of characters read was incorrect. + * @throws IOException if there is a problem reading the file. * @since 2.2 */ public static void readFully(final Reader reader, final char[] buffer) throws IOException { @@ -2298,21 +2300,21 @@ public static void readFully(final Reader reader, final char[] buffer) throws IO /** * Reads the requested number of characters or fail if there are not enough left. *

    - * This allows for the possibility that {@link Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link Reader#read(char[], int, int)} may not read as many characters as requested (most likely because of reaching + * EOF). *

    * * @param reader where to read input from. * @param buffer destination. * @param offset initial offset into buffer. * @param length length to read, must be >= 0. - * @throws IOException if there is a problem reading the file. - * @throws IllegalArgumentException if length is negative. - * @throws EOFException if the number of characters read was incorrect. + * @throws NullPointerException if {@code reader} or {@code buffer} is null. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if {@code offset + length} is greater than {@code buffer.length}. + * @throws EOFException if the number of characters read was incorrect. + * @throws IOException if there is a problem reading the file. * @since 2.2 */ - public static void readFully(final Reader reader, final char[] buffer, final int offset, final int length) - throws IOException { + public static void readFully(final Reader reader, final char[] buffer, final int offset, final int length) throws IOException { final int actual = read(reader, buffer, offset, length); if (actual != length) { throw new EOFException("Length to read: " + length + " actual: " + actual); @@ -2334,11 +2336,10 @@ public static List readLines(final CharSequence csq) throws UncheckedIOE } /** - * Gets the contents of an {@link InputStream} as a list of Strings, - * one entry per line, using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of an {@link InputStream} as a list of Strings, one entry per line, using the virtual machine's {@linkplain Charset#defaultCharset() + * default charset}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * * @param input the {@link InputStream} to read, not null. @@ -2346,7 +2347,7 @@ public static List readLines(final CharSequence csq) throws UncheckedIOE * @throws NullPointerException if the input is null. * @throws UncheckedIOException if an I/O error occurs. * @since 1.1 - * @deprecated Use {@link #readLines(InputStream, Charset)} instead + * @deprecated Use {@link #readLines(InputStream, Charset)} instead. */ @Deprecated public static List readLines(final InputStream input) throws UncheckedIOException { @@ -2354,14 +2355,12 @@ public static List readLines(final InputStream input) throws UncheckedIO } /** - * Gets the contents of an {@link InputStream} as a list of Strings, - * one entry per line, using the specified character encoding. + * Gets the contents of an {@link InputStream} as a list of Strings, one entry per line, using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input the {@link InputStream} to read, not null. + * @param input the {@link InputStream} to read, not null. * @param charset the charset to use, null means platform default. * @return the list of Strings, never null. * @throws NullPointerException if the input is null. @@ -2373,18 +2372,15 @@ public static List readLines(final InputStream input, final Charset char } /** - * Gets the contents of an {@link InputStream} as a list of Strings, - * one entry per line, using the specified character encoding. + * Gets the contents of an {@link InputStream} as a list of Strings, one entry per line, using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input the {@link InputStream} to read, not null. + * @param input the {@link InputStream} to read, not null. * @param charsetName the name of the requested charset, null means platform default. * @return the list of Strings, never null. * @throws NullPointerException if the input is null. @@ -2397,11 +2393,9 @@ public static List readLines(final InputStream input, final String chars } /** - * Gets the contents of a {@link Reader} as a list of Strings, - * one entry per line. + * Gets the contents of a {@link Reader} as a list of Strings, one entry per line. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to read, not null. @@ -2437,7 +2431,7 @@ public static byte[] resourceToByteArray(final String name) throws IOException { * Delegates to {@link #resourceToURL(String, ClassLoader)}. *

    * - * @param name The resource name. + * @param name The resource name. * @param classLoader the class loader that the resolution of the resource is delegated to. * @return the requested byte array. * @throws IOException if an I/O error occurs or the resource is not found. @@ -2454,7 +2448,7 @@ public static byte[] resourceToByteArray(final String name, final ClassLoader cl * Delegates to {@link #resourceToString(String, Charset, ClassLoader) resourceToString(String, Charset, null)}. *

    * - * @param name The resource name. + * @param name The resource name. * @param charset the charset to use, null means platform default. * @return the requested String. * @throws IOException if an I/O error occurs or the resource is not found. @@ -2471,8 +2465,8 @@ public static String resourceToString(final String name, final Charset charset) * Delegates to {@link #resourceToURL(String, ClassLoader)}. *

    * - * @param name The resource name. - * @param charset the Charset to use, null means platform default. + * @param name The resource name. + * @param charset the Charset to use, null means platform default. * @param classLoader the class loader that the resolution of the resource is delegated to. * @return the requested String. * @throws IOException if an I/O error occurs. @@ -2501,11 +2495,11 @@ public static URL resourceToURL(final String name) throws IOException { /** * Gets a URL pointing to the given resource. *

    - * If the {@code classLoader} is not null, call {@link ClassLoader#getResource(String)}, otherwise call - * {@link Class#getResource(String) IOUtils.class.getResource(name)}. + * If the {@code classLoader} is not null, call {@link ClassLoader#getResource(String)}, otherwise call {@link Class#getResource(String) + * IOUtils.class.getResource(name)}. *

    * - * @param name The resource name. + * @param name The resource name. * @param classLoader Delegate to this class loader if not null. * @return A URL object for reading the resource. * @throws IOException if the resource is not found. @@ -2543,7 +2537,9 @@ public static URL resourceToURL(final String name, final ClassLoader classLoader * @since 2.0 */ public static long skip(final InputStream input, final long skip) throws IOException { - return skip(input, skip, IOUtils::getScratchByteArrayWriteOnly); + try (ScratchBytes scratch = ScratchBytes.get()) { + return skip(input, skip, scratch::array); + } } /** @@ -2563,7 +2559,7 @@ public static long skip(final InputStream input, final long skip) throws IOExcep *

    * * @param input byte stream to skip. - * @param skip number of bytes to skip. + * @param skip number of bytes to skip. * @param skipBufferSupplier Supplies the buffer to use for reading. * @return number of bytes actually skipped. * @throws IOException if there is a problem reading the file. @@ -2595,11 +2591,9 @@ public static long skip(final InputStream input, final long skip, final Supplier } /** - * Skips bytes from a ReadableByteChannel. - * This implementation guarantees that it will read as many bytes - * as possible before giving up. + * Skips bytes from a ReadableByteChannel. This implementation guarantees that it will read as many bytes as possible before giving up. * - * @param input ReadableByteChannel to skip. + * @param input ReadableByteChannel to skip. * @param toSkip number of bytes to skip. * @return number of bytes actually skipped. * @throws IOException if there is a problem reading the ReadableByteChannel. @@ -2625,15 +2619,12 @@ public static long skip(final ReadableByteChannel input, final long toSkip) thro } /** - * Skips characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * skip() implementations in subclasses of {@link Reader}. + * Skips characters from an input character stream. This implementation guarantees that it will read as many characters as possible before giving up; this + * may not always be the case for skip() implementations in subclasses of {@link Reader}. *

    - * Note that the implementation uses {@link Reader#read(char[], int, int)} rather - * than delegating to {@link Reader#skip(long)}. - * This means that the method may be considerably less efficient than using the actual skip implementation, - * this is done to guarantee that the correct number of characters are skipped. + * Note that the implementation uses {@link Reader#read(char[], int, int)} rather than delegating to {@link Reader#skip(long)}. This means that the method + * may be considerably less efficient than using the actual skip implementation, this is done to guarantee that the correct number of characters are + * skipped. *

    * * @param reader character stream to skip. @@ -2650,14 +2641,16 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); } long remain = toSkip; - while (remain > 0) { - // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip() - final char[] charArray = getScratchCharArrayWriteOnly(); - final long n = reader.read(charArray, 0, (int) Math.min(remain, charArray.length)); - if (n < 0) { // EOF - break; + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + final char[] chars = scratch.array(); + while (remain > 0) { + // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip() + final long n = reader.read(chars, 0, (int) Math.min(remain, chars.length)); + if (n < 0) { // EOF + break; + } + remain -= n; } - remain -= n; } return toSkip - remain; } @@ -2665,16 +2658,14 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti /** * Skips the requested number of bytes or fail if there are not enough left. *

    - * This allows for the possibility that {@link InputStream#skip(long)} may - * not skip as many bytes as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link InputStream#skip(long)} may not skip as many bytes as requested (most likely because of reaching EOF). *

    *

    - * Note that the implementation uses {@link #skip(InputStream, long)}. - * This means that the method may be considerably less efficient than using the actual skip implementation, - * this is done to guarantee that the correct number of characters are skipped. + * Note that the implementation uses {@link #skip(InputStream, long)}. This means that the method may be considerably less efficient than using the actual + * skip implementation, this is done to guarantee that the correct number of characters are skipped. *

    * - * @param input stream to skip. + * @param input stream to skip. * @param toSkip the number of bytes to skip. * @throws IOException if there is a problem reading the file. * @throws IllegalArgumentException if toSkip is negative. @@ -2683,7 +2674,7 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti * @since 2.0 */ public static void skipFully(final InputStream input, final long toSkip) throws IOException { - final long skipped = skip(input, toSkip, IOUtils::getScratchByteArrayWriteOnly); + final long skipped = skip(input, toSkip); if (skipped != toSkip) { throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); } @@ -2725,7 +2716,7 @@ public static void skipFully(final InputStream input, final long toSkip, final S /** * Skips the requested number of bytes or fail if there are not enough left. * - * @param input ReadableByteChannel to skip. + * @param input ReadableByteChannel to skip. * @param toSkip the number of bytes to skip. * @throws IOException if there is a problem reading the ReadableByteChannel. * @throws IllegalArgumentException if toSkip is negative. @@ -2745,13 +2736,11 @@ public static void skipFully(final ReadableByteChannel input, final long toSkip) /** * Skips the requested number of characters or fail if there are not enough left. *

    - * This allows for the possibility that {@link Reader#skip(long)} may - * not skip as many characters as requested (most likely because of reaching EOF). + * This allows for the possibility that {@link Reader#skip(long)} may not skip as many characters as requested (most likely because of reaching EOF). *

    *

    - * Note that the implementation uses {@link #skip(Reader, long)}. - * This means that the method may be considerably less efficient than using the actual skip implementation, - * this is done to guarantee that the correct number of characters are skipped. + * Note that the implementation uses {@link #skip(Reader, long)}. This means that the method may be considerably less efficient than using the actual skip + * implementation, this is done to guarantee that the correct number of characters are skipped. *

    * * @param reader stream to skip. @@ -2780,7 +2769,9 @@ public static void skipFully(final Reader reader, final long toSkip) throws IOEx *
  • It has network timeout associated.
  • * *

    - * It can be used in favor of {@link #toByteArray(InputStream)}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray(InputStream)}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -2804,7 +2795,9 @@ public static InputStream toBufferedInputStream(final InputStream input) throws *
  • It has network timeout associated.
  • * *

    - * It can be used in favor of {@link #toByteArray(InputStream)}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray(InputStream)}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -2819,8 +2812,7 @@ public static InputStream toBufferedInputStream(final InputStream input, final i } /** - * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a BufferedReader from the given - * reader. + * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a BufferedReader from the given reader. * * @param reader the reader to wrap or return (not null). * @return the given reader or a new {@link BufferedReader} for the given reader. @@ -2833,11 +2825,10 @@ public static BufferedReader toBufferedReader(final Reader reader) { } /** - * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a BufferedReader from the given - * reader. + * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a BufferedReader from the given reader. * * @param reader the reader to wrap or return (not null). - * @param size the buffer size, if a new BufferedReader is created. + * @param size the buffer size, if a new BufferedReader is created. * @return the given reader or a new {@link BufferedReader} for the given reader. * @throws NullPointerException if the input parameter is null. * @see #buffer(Reader) @@ -2849,16 +2840,15 @@ public static BufferedReader toBufferedReader(final Reader reader, final int siz /** * Reads all the bytes from an input stream in a byte array. - * - *

    The memory used by this method is proportional to the number - * of bytes read, which is only limited by {@link Integer#MAX_VALUE}. Only streams - * which fit into a single byte array with roughly 2 GiB limit can be processed - * with this method.

    + *

    + * The memory used by this method is proportional to the number of bytes read, which is only limited by {@link Integer#MAX_VALUE}. Only + * streams which fit into a single byte array with roughly 2 GiB limit can be processed with this method. + *

    * * @param inputStream The {@link InputStream} to read; must not be {@code null}. * @return A new byte array. - * @throws IOException If an I/O error occurs while reading or if the maximum array size is exceeded. - * @throws NullPointerException If {@code inputStream} is {@code null}. + * @throws IOException If an I/O error occurs while reading or if the maximum array size is exceeded. + * @throws NullPointerException If {@code inputStream} is {@code null}. */ public static byte[] toByteArray(final InputStream inputStream) throws IOException { // Using SOFT_MAX_ARRAY_LENGTH guarantees that size() will not overflow @@ -2871,10 +2861,10 @@ public static byte[] toByteArray(final InputStream inputStream) throws IOExcepti /** * Reads exactly {@code size} bytes from the given {@link InputStream} into a new {@code byte[]}. - * - *

    This variant always allocates the whole requested array size, - * for a dynamic growing variant use {@link #toByteArray(InputStream, int, int)}, - * which enforces stricter memory usage constraints.

    + *

    + * This variant always allocates the whole requested array size, for a dynamic growing variant use {@link #toByteArray(InputStream, int, int)}, which + * enforces stricter memory usage constraints. + *

    * * @param input the {@link InputStream} to read; must not be {@code null}. * @param size the exact number of bytes to read; must be {@code >= 0}. @@ -2891,19 +2881,17 @@ public static byte[] toByteArray(final InputStream input, final int size) throws /** * Reads exactly {@code size} bytes from the given {@link InputStream} into a new {@code byte[]}. + *

    + * The memory used by this method is proportional to the number of bytes read and limited by the specified {@code size}. This makes it + * suitable for processing large input streams, provided that sufficient heap space is available. + *

    + *

    + * This method processes the input stream in successive chunks of up to {@code chunkSize} bytes. + *

    * - *

    The memory used by this method is proportional to the number - * of bytes read and limited by the specified {@code size}. This makes it suitable for - * processing large input streams, provided that sufficient heap space is - * available.

    - * - *

    This method processes the input stream in successive chunks of up to - * {@code chunkSize} bytes.

    - * - * @param input the {@link InputStream} to read; must not be {@code null}. - * @param size the exact number of bytes to read; must be {@code >= 0}. - * The actual bytes read are validated to equal {@code size}. - * @param chunkSize The chunk size for incremental reading; must be {@code > 0}. + * @param input the {@link InputStream} to read; must not be {@code null}. + * @param size the exact number of bytes to read; must be {@code >= 0}. The actual bytes read are validated to equal {@code size}. + * @param chunkSize The chunk size for incremental reading; must be {@code > 0}. * @return a new byte array of length {@code size}. * @throws IllegalArgumentException if {@code size} is negative or {@code chunkSize <= 0}. * @throws EOFException if the stream ends before {@code size} bytes are read. @@ -2914,25 +2902,26 @@ public static byte[] toByteArray(final InputStream input, final int size) throws public static byte[] toByteArray(final InputStream input, final int size, final int chunkSize) throws IOException { Objects.requireNonNull(input, "input"); if (chunkSize <= 0) { - throw new IllegalArgumentException("Chunk size must be greater than zero: " + chunkSize); + throw new IllegalArgumentException(String.format("chunkSize <= 0, chunkSize = %,d", chunkSize)); } if (size <= chunkSize) { // throws if size < 0 return toByteArray(input::read, size); } final UnsynchronizedByteArrayOutputStream output = copyToOutputStream(input, size, chunkSize); - if (output.size() != size) { - throw new EOFException("Unexpected read size, current: " + output.size() + ", expected: " + size); + final int outSize = output.size(); + if (outSize != size) { + throw new EOFException(String.format("Expected read size: %,d, actual: %,d", size, outSize)); } return output.toByteArray(); } /** * Reads exactly {@code size} bytes from the given {@link InputStream} into a new {@code byte[]}. - * - *

    This variant always allocates the whole requested array size, - * for a dynamic growing variant use {@link #toByteArray(InputStream, int, int)}, - * which enforces stricter memory usage constraints.

    + *

    + * This variant always allocates the whole requested array size, for a dynamic growing variant use {@link #toByteArray(InputStream, int, int)}, which + * enforces stricter memory usage constraints. + *

    * * @param input the {@link InputStream} to read; must not be {@code null}. * @param size the exact number of bytes to read; must be {@code >= 0} and {@code <= Integer.MAX_VALUE}. @@ -2946,7 +2935,7 @@ public static byte[] toByteArray(final InputStream input, final int size, final */ public static byte[] toByteArray(final InputStream input, final long size) throws IOException { if (size > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); + throw new IllegalArgumentException(String.format("size > Integer.MAX_VALUE, size = %,d", size)); } return toByteArray(input, (int) size); } @@ -2955,14 +2944,15 @@ public static byte[] toByteArray(final InputStream input, final long size) throw * Gets the contents of an input as a {@code byte[]}. * * @param input the input to read, not null. - * @param size the size of the input to read, where 0 < {@code size} <= length of input. + * @param size the size of the input to read, where 0 < {@code size} <= length of input. * @return byte [] of length {@code size}. - * @throws IOException if an I/O error occurs or input length is smaller than parameter {@code size}. + * @throws EOFException if the end of the input is reached before reading {@code size} bytes. + * @throws IOException if an I/O error occurs or input length is smaller than parameter {@code size}. * @throws IllegalArgumentException if {@code size} is less than zero. */ static byte[] toByteArray(final IOTriFunction input, final int size) throws IOException { if (size < 0) { - throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + throw new IllegalArgumentException(String.format("size < 0, size = %,d", size)); } if (size == 0) { return EMPTY_BYTE_ARRAY; @@ -2974,17 +2964,15 @@ static byte[] toByteArray(final IOTriFunction offset += read; } if (offset != size) { - throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size); + throw new EOFException(String.format("Expected read size: %,d, actual: %,d", size, offset)); } return data; } /** - * Gets the contents of a {@link Reader} as a {@code byte[]} - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of a {@link Reader} as a {@code byte[]} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to read. @@ -2999,14 +2987,12 @@ public static byte[] toByteArray(final Reader reader) throws IOException { } /** - * Gets the contents of a {@link Reader} as a {@code byte[]} - * using the specified character encoding. + * Gets the contents of a {@link Reader} as a {@code byte[]} using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * - * @param reader the {@link Reader} to read. + * @param reader the {@link Reader} to read. * @param charset the charset to use, null means platform default. * @return the requested byte array. * @throws NullPointerException if the input is null. @@ -3021,18 +3007,15 @@ public static byte[] toByteArray(final Reader reader, final Charset charset) thr } /** - * Gets the contents of a {@link Reader} as a {@code byte[]} - * using the specified character encoding. + * Gets the contents of a {@link Reader} as a {@code byte[]} using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * - * @param reader the {@link Reader} to read. + * @param reader the {@link Reader} to read. * @param charsetName the name of the requested charset, null means platform default. * @return the requested byte array. * @throws NullPointerException if the input is null. @@ -3045,8 +3028,7 @@ public static byte[] toByteArray(final Reader reader, final String charsetName) } /** - * Gets the contents of a {@link String} as a {@code byte[]} - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of a {@link String} as a {@code byte[]} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * This is the same as {@link String#getBytes()}. *

    @@ -3096,7 +3078,7 @@ public static byte[] toByteArray(final URL url) throws IOException { * @param urlConnection the {@link URLConnection} to read. * @return the requested byte array. * @throws NullPointerException if the urlConn is null. - * @throws IOException if an I/O exception occurs. + * @throws IOException if an I/O exception occurs. * @since 2.4 */ public static byte[] toByteArray(final URLConnection urlConnection) throws IOException { @@ -3106,11 +3088,9 @@ public static byte[] toByteArray(final URLConnection urlConnection) throws IOExc } /** - * Gets the contents of an {@link InputStream} as a character array - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of an {@link InputStream} as a character array using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * * @param inputStream the {@link InputStream} to read. @@ -3118,7 +3098,7 @@ public static byte[] toByteArray(final URLConnection urlConnection) throws IOExc * @throws NullPointerException if the input is null. * @throws IOException if an I/O error occurs. * @since 1.1 - * @deprecated Use {@link #toCharArray(InputStream, Charset)} instead + * @deprecated Use {@link #toCharArray(InputStream, Charset)} instead. */ @Deprecated public static char[] toCharArray(final InputStream inputStream) throws IOException { @@ -3126,37 +3106,31 @@ public static char[] toCharArray(final InputStream inputStream) throws IOExcepti } /** - * Gets the contents of an {@link InputStream} as a character array - * using the specified character encoding. + * Gets the contents of an {@link InputStream} as a character array using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * * @param inputStream the {@link InputStream} to read. - * @param charset the charset to use, null means platform default. + * @param charset the charset to use, null means platform default. * @return the requested character array. * @throws NullPointerException if the input is null. * @throws IOException if an I/O error occurs. * @since 2.3 */ - public static char[] toCharArray(final InputStream inputStream, final Charset charset) - throws IOException { + public static char[] toCharArray(final InputStream inputStream, final Charset charset) throws IOException { final CharArrayWriter writer = new CharArrayWriter(); copy(inputStream, writer, charset); return writer.toCharArray(); } /** - * Gets the contents of an {@link InputStream} as a character array - * using the specified character encoding. + * Gets the contents of an {@link InputStream} as a character array using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * * @param inputStream the {@link InputStream} to read. @@ -3174,8 +3148,7 @@ public static char[] toCharArray(final InputStream inputStream, final String cha /** * Gets the contents of a {@link Reader} as a character array. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to read. @@ -3191,8 +3164,8 @@ public static char[] toCharArray(final Reader reader) throws IOException { } /** - * Converts the specified CharSequence to an input stream, encoded as bytes - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Converts the specified CharSequence to an input stream, encoded as bytes using the virtual machine's {@linkplain Charset#defaultCharset() default + * charset}. * * @param input the CharSequence to convert. * @return an input stream. @@ -3205,10 +3178,9 @@ public static InputStream toInputStream(final CharSequence input) { } /** - * Converts the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. + * Converts the specified CharSequence to an input stream, encoded as bytes using the specified character encoding. * - * @param input the CharSequence to convert. + * @param input the CharSequence to convert. * @param charset the charset to use, null means platform default. * @return an input stream. * @since 2.3 @@ -3218,14 +3190,12 @@ public static InputStream toInputStream(final CharSequence input, final Charset } /** - * Converts the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. + * Converts the specified CharSequence to an input stream, encoded as bytes using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    * - * @param input the CharSequence to convert. + * @param input the CharSequence to convert. * @param charsetName the name of the requested charset, null means platform default. * @return an input stream. * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. @@ -3236,8 +3206,7 @@ public static InputStream toInputStream(final CharSequence input, final String c } /** - * Converts the specified string to an input stream, encoded as bytes - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Converts the specified string to an input stream, encoded as bytes using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param input the string to convert. * @return an input stream. @@ -3250,10 +3219,9 @@ public static InputStream toInputStream(final String input) { } /** - * Converts the specified string to an input stream, encoded as bytes - * using the specified character encoding. + * Converts the specified string to an input stream, encoded as bytes using the specified character encoding. * - * @param input the string to convert. + * @param input the string to convert. * @param charset the charset to use, null means platform default. * @return an input stream. * @since 2.3 @@ -3263,14 +3231,12 @@ public static InputStream toInputStream(final String input, final Charset charse } /** - * Converts the specified string to an input stream, encoded as bytes - * using the specified character encoding. + * Converts the specified string to an input stream, encoded as bytes using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    * - * @param input the string to convert. + * @param input the string to convert. * @param charsetName the name of the requested charset, null means platform default. * @return an input stream. * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. @@ -3281,8 +3247,7 @@ public static InputStream toInputStream(final String input, final String charset } /** - * Gets the contents of a {@code byte[]} as a String - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of a {@code byte[]} as a String using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param input the byte array to read. * @return the requested String. @@ -3296,14 +3261,12 @@ public static String toString(final byte[] input) { } /** - * Gets the contents of a {@code byte[]} as a String - * using the specified character encoding. + * Gets the contents of a {@code byte[]} as a String using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    * - * @param input the byte array to read. + * @param input the byte array to read. * @param charsetName the name of the requested charset, null means platform default. * @return the requested String. * @throws NullPointerException if the input is null. @@ -3313,11 +3276,9 @@ public static String toString(final byte[] input, final String charsetName) { } /** - * Gets the contents of an {@link InputStream} as a String - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents of an {@link InputStream} as a String using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * * @param input the {@link InputStream} to read. @@ -3332,14 +3293,12 @@ public static String toString(final InputStream input) throws IOException { } /** - * Gets the contents of an {@link InputStream} as a String - * using the specified character encoding. + * Gets the contents of an {@link InputStream} as a String using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input the {@link InputStream} to read. + * @param input the {@link InputStream} to read. * @param charset the charset to use, null means platform default. * @return the requested String. * @throws NullPointerException if the input is null. @@ -3354,38 +3313,32 @@ public static String toString(final InputStream input, final Charset charset) th } /** - * Gets the contents of an {@link InputStream} as a String - * using the specified character encoding. + * Gets the contents of an {@link InputStream} as a String using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input the {@link InputStream} to read. + * @param input the {@link InputStream} to read. * @param charsetName the name of the requested charset, null means platform default. * @return the requested String. * @throws NullPointerException if the input is null. * @throws IOException if an I/O error occurs. * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. */ - public static String toString(final InputStream input, final String charsetName) - throws IOException { + public static String toString(final InputStream input, final String charsetName) throws IOException { return toString(input, Charsets.toCharset(charsetName)); } /** - * Gets the contents of an {@link InputStream} from a supplier as a String - * using the specified character encoding. + * Gets the contents of an {@link InputStream} from a supplier as a String using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input supplies the {@link InputStream} to read. + * @param input supplies the {@link InputStream} to read. * @param charset the charset to use, null means platform default. * @return the requested String. * @throws NullPointerException if the input is null. @@ -3399,15 +3352,13 @@ public static String toString(final IOSupplier input, final Charset } /** - * Gets the contents of an {@link InputStream} from a supplier as a String - * using the specified character encoding. + * Gets the contents of an {@link InputStream} from a supplier as a String using the specified character encoding. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedInputStream}. + * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * - * @param input supplies the {@link InputStream} to read. - * @param charset the charset to use, null means platform default. + * @param input supplies the {@link InputStream} to read. + * @param charset the charset to use, null means platform default. * @param defaultString the default return value if the supplier or its value is null. * @return the requested String. * @throws NullPointerException if the input is null. @@ -3426,8 +3377,7 @@ public static String toString(final IOSupplier input, final Charset /** * Gets the contents of a {@link Reader} as a String. *

    - * This method buffers the input internally, so there is no need to use a - * {@link BufferedReader}. + * This method buffers the input internally, so there is no need to use a {@link BufferedReader}. *

    * * @param reader the {@link Reader} to read. @@ -3443,7 +3393,7 @@ public static String toString(final Reader reader) throws IOException { } /** - * Gets the contents at the given URI using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents at the given URI using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param uri The URI source. * @return The contents of the URL as a String. @@ -3459,7 +3409,7 @@ public static String toString(final URI uri) throws IOException { /** * Gets the contents at the given URI. * - * @param uri The URI source. + * @param uri The URI source. * @param encoding The encoding name for the URL contents. * @return The contents of the URL as a String. * @throws IOException if an I/O exception occurs. @@ -3472,7 +3422,7 @@ public static String toString(final URI uri, final Charset encoding) throws IOEx /** * Gets the contents at the given URI. * - * @param uri The URI source. + * @param uri The URI source. * @param charsetName The encoding name for the URL contents. * @return The contents of the URL as a String. * @throws IOException if an I/O exception occurs. @@ -3484,7 +3434,7 @@ public static String toString(final URI uri, final String charsetName) throws IO } /** - * Gets the contents at the given URL using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the contents at the given URL using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @param url The URL source. * @return The contents of the URL as a String. @@ -3500,7 +3450,7 @@ public static String toString(final URL url) throws IOException { /** * Gets the contents at the given URL. * - * @param url The URL source. + * @param url The URL source. * @param encoding The encoding name for the URL contents. * @return The contents of the URL as a String. * @throws IOException if an I/O exception occurs. @@ -3513,7 +3463,7 @@ public static String toString(final URL url, final Charset encoding) throws IOEx /** * Gets the contents at the given URL. * - * @param url The URL source. + * @param url The URL source. * @param charsetName The encoding name for the URL contents. * @return The contents of the URL as a String. * @throws IOException if an I/O exception occurs. @@ -3533,22 +3483,19 @@ public static String toString(final URL url, final String charsetName) throws IO * @throws IOException if an I/O error occurs. * @since 1.1 */ - public static void write(final byte[] data, final OutputStream output) - throws IOException { + public static void write(final byte[] data, final OutputStream output) throws IOException { if (data != null) { output.write(data); } } /** - * Writes bytes from a {@code byte[]} to chars on a {@link Writer} - * using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes bytes from a {@code byte[]} to chars on a {@link Writer} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * This method uses {@link String#String(byte[])}. *

    * - * @param data the byte array to write, do not modify during output, - * null ignored + * @param data the byte array to write, do not modify during output, null ignored. * @param writer the {@link Writer} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3561,15 +3508,13 @@ public static void write(final byte[] data, final Writer writer) throws IOExcept } /** - * Writes bytes from a {@code byte[]} to chars on a {@link Writer} - * using the specified character encoding. + * Writes bytes from a {@code byte[]} to chars on a {@link Writer} using the specified character encoding. *

    * This method uses {@link String#String(byte[], String)}. *

    * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param writer the {@link Writer} to write to. + * @param data the byte array to write, do not modify during output, null ignored. + * @param writer the {@link Writer} to write to. * @param charset the charset to use, null means platform default. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3584,7 +3529,7 @@ public static void write(final byte[] data, final Writer writer, final Charset c /** * Writes bytes from a {@code byte[]} to chars on a {@link Writer} using the specified character encoding. *

    - * Character encoding names can be found at IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link String#String(byte[], String)}. @@ -3605,7 +3550,7 @@ public static void write(final byte[] data, final Writer writer, final String ch /** * Writes chars from a {@code char[]} to bytes on an {@link OutputStream}. *

    - * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * * @param data the char array to write, do not modify during output, null ignored. @@ -3616,8 +3561,7 @@ public static void write(final byte[] data, final Writer writer, final String ch * @deprecated Use {@link #write(char[], OutputStream, Charset)} instead. */ @Deprecated - public static void write(final char[] data, final OutputStream output) - throws IOException { + public static void write(final char[] data, final OutputStream output) throws IOException { write(data, output, Charset.defaultCharset()); } @@ -3643,7 +3587,7 @@ public static void write(final char[] data, final OutputStream output, final Cha /** * Writes chars from a {@code char[]} to bytes on an {@link OutputStream} using the specified character encoding. *

    - * Character encoding names can be found at IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link String#String(char[])} and {@link String#getBytes(String)}. @@ -3657,8 +3601,7 @@ public static void write(final char[] data, final OutputStream output, final Cha * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @since 1.1 */ - public static void write(final char[] data, final OutputStream output, final String charsetName) - throws IOException { + public static void write(final char[] data, final OutputStream output, final String charsetName) throws IOException { write(data, output, Charsets.toCharset(charsetName)); } @@ -3692,8 +3635,7 @@ public static void write(final char[] data, final Writer writer) throws IOExcept * @deprecated Use {@link #write(CharSequence, OutputStream, Charset)} instead. */ @Deprecated - public static void write(final CharSequence data, final OutputStream output) - throws IOException { + public static void write(final CharSequence data, final OutputStream output) throws IOException { write(data, output, Charset.defaultCharset()); } @@ -3710,8 +3652,7 @@ public static void write(final CharSequence data, final OutputStream output) * @throws IOException if an I/O error occurs. * @since 2.3 */ - public static void write(final CharSequence data, final OutputStream output, final Charset charset) - throws IOException { + public static void write(final CharSequence data, final OutputStream output, final Charset charset) throws IOException { if (data != null) { write(data.toString(), output, charset); } @@ -3720,7 +3661,7 @@ public static void write(final CharSequence data, final OutputStream output, fin /** * Writes chars from a {@link CharSequence} to bytes on an {@link OutputStream} using the specified character encoding. *

    - * Character encoding names can be found at IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link String#getBytes(String)}. @@ -3734,15 +3675,14 @@ public static void write(final CharSequence data, final OutputStream output, fin * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @since 2.0 */ - public static void write(final CharSequence data, final OutputStream output, final String charsetName) - throws IOException { + public static void write(final CharSequence data, final OutputStream output, final String charsetName) throws IOException { write(data, output, Charsets.toCharset(charsetName)); } /** * Writes chars from a {@link CharSequence} to a {@link Writer}. * - * @param data the {@link CharSequence} to write, null ignored. + * @param data the {@link CharSequence} to write, null ignored. * @param writer the {@link Writer} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3755,34 +3695,31 @@ public static void write(final CharSequence data, final Writer writer) throws IO } /** - * Writes chars from a {@link String} to bytes on an - * {@link OutputStream} using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Writes chars from a {@link String} to bytes on an {@link OutputStream} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * This method uses {@link String#getBytes()}. *

    * - * @param data the {@link String} to write, null ignored. + * @param data the {@link String} to write, null ignored. * @param output the {@link OutputStream} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. * @since 1.1 - * @deprecated Use {@link #write(String, OutputStream, Charset)} instead + * @deprecated Use {@link #write(String, OutputStream, Charset)} instead. */ @Deprecated - public static void write(final String data, final OutputStream output) - throws IOException { + public static void write(final String data, final OutputStream output) throws IOException { write(data, output, Charset.defaultCharset()); } /** - * Writes chars from a {@link String} to bytes on an - * {@link OutputStream} using the specified character encoding. + * Writes chars from a {@link String} to bytes on an {@link OutputStream} using the specified character encoding. *

    * This method uses {@link String#getBytes(String)}. *

    * - * @param data the {@link String} to write, null ignored. - * @param output the {@link OutputStream} to write to. + * @param data the {@link String} to write, null ignored. + * @param output the {@link OutputStream} to write to. * @param charset the charset to use, null means platform default. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3799,33 +3736,30 @@ public static void write(final String data, final OutputStream output, final Cha } /** - * Writes chars from a {@link String} to bytes on an - * {@link OutputStream} using the specified character encoding. + * Writes chars from a {@link String} to bytes on an {@link OutputStream} using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link String#getBytes(String)}. *

    * - * @param data the {@link String} to write, null ignored. - * @param output the {@link OutputStream} to write to. + * @param data the {@link String} to write, null ignored. + * @param output the {@link OutputStream} to write to. * @param charsetName the name of the requested charset, null means platform default. - * @throws NullPointerException if output is null. - * @throws IOException if an I/O error occurs. + * @throws NullPointerException if output is null. + * @throws IOException if an I/O error occurs. * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @since 1.1 */ - public static void write(final String data, final OutputStream output, final String charsetName) - throws IOException { + public static void write(final String data, final OutputStream output, final String charsetName) throws IOException { write(data, output, Charsets.toCharset(charsetName)); } /** * Writes chars from a {@link String} to a {@link Writer}. * - * @param data the {@link String} to write, null ignored. + * @param data the {@link String} to write, null ignored. * @param writer the {@link Writer} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3838,49 +3772,45 @@ public static void write(final String data, final Writer writer) throws IOExcept } /** - * Writes chars from a {@link StringBuffer} to bytes on an - * {@link OutputStream} using the default character encoding of the - * platform. + * Writes chars from a {@link StringBuffer} to bytes on an {@link OutputStream} using the default character encoding of the platform. *

    * This method uses {@link String#getBytes()}. *

    * - * @param data the {@link StringBuffer} to write, null ignored. + * @param data the {@link StringBuffer} to write, null ignored. * @param output the {@link OutputStream} to write to. * @throws NullPointerException if output is null. - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs. * @since 1.1 * @deprecated Use {@link #write(CharSequence, OutputStream)}. */ @Deprecated - public static void write(final StringBuffer data, final OutputStream output) //NOSONAR + public static void write(final StringBuffer data, final OutputStream output) // NOSONAR throws IOException { write(data, output, (String) null); } /** - * Writes chars from a {@link StringBuffer} to bytes on an - * {@link OutputStream} using the specified character encoding. + * Writes chars from a {@link StringBuffer} to bytes on an {@link OutputStream} using the specified character encoding. *

    - * Character encoding names can be found at - * IANA. + * Character encoding names can be found at IANA. *

    *

    * This method uses {@link String#getBytes(String)}. *

    * - * @param data the {@link StringBuffer} to write, null ignored. - * @param output the {@link OutputStream} to write to. + * @param data the {@link StringBuffer} to write, null ignored. + * @param output the {@link OutputStream} to write to. * @param charsetName the name of the requested charset, null means platform default. - * @throws NullPointerException if output is null. - * @throws IOException if an I/O error occurs. + * @throws NullPointerException if output is null. + * @throws IOException if an I/O error occurs. * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @since 1.1 * @deprecated Use {@link #write(CharSequence, OutputStream, String)}. */ @Deprecated - public static void write(final StringBuffer data, final OutputStream output, final String charsetName) //NOSONAR - throws IOException { + public static void write(final StringBuffer data, final OutputStream output, final String charsetName) // NOSONAR + throws IOException { if (data != null) { write(data.toString(), output, Charsets.toCharset(charsetName)); } @@ -3889,7 +3819,7 @@ public static void write(final StringBuffer data, final OutputStream output, fin /** * Writes chars from a {@link StringBuffer} to a {@link Writer}. * - * @param data the {@link StringBuffer} to write, null ignored. + * @param data the {@link StringBuffer} to write, null ignored. * @param writer the {@link Writer} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. @@ -3897,7 +3827,7 @@ public static void write(final StringBuffer data, final OutputStream output, fin * @deprecated Use {@link #write(CharSequence, Writer)}. */ @Deprecated - public static void write(final StringBuffer data, final Writer writer) //NOSONAR + public static void write(final StringBuffer data, final Writer writer) // NOSONAR throws IOException { if (data != null) { writer.write(data.toString()); @@ -3905,19 +3835,16 @@ public static void write(final StringBuffer data, final Writer writer) //NOSONAR } /** - * Writes bytes from a {@code byte[]} to an {@link OutputStream} using chunked writes. - * This is intended for writing very large byte arrays which might otherwise cause excessive - * memory usage if the native code has to allocate a copy. + * Writes bytes from a {@code byte[]} to an {@link OutputStream} using chunked writes. This is intended for writing very large byte arrays which might + * otherwise cause excessive memory usage if the native code has to allocate a copy. * - * @param data the byte array to write, do not modify during output, - * null ignored. + * @param data the byte array to write, do not modify during output, null ignored. * @param output the {@link OutputStream} to write to. * @throws NullPointerException if output is null. * @throws IOException if an I/O error occurs. * @since 2.5 */ - public static void writeChunked(final byte[] data, final OutputStream output) - throws IOException { + public static void writeChunked(final byte[] data, final OutputStream output) throws IOException { if (data != null) { int bytes = data.length; int offset = 0; @@ -3955,7 +3882,7 @@ public static void writeChunked(final char[] data, final Writer writer) throws I /** * Writes the {@link #toString()} value of each item in a collection to an {@link OutputStream} line by line, using the virtual machine's - * {@link Charset#defaultCharset() default charset} and the specified line ending. + * {@linkplain Charset#defaultCharset() default charset} and the specified line ending. * * @param lines the lines to write, null entries produce blank lines. * @param lineEnding the line separator to use, null is system default. @@ -3963,7 +3890,7 @@ public static void writeChunked(final char[] data, final Writer writer) throws I * @throws NullPointerException if the output is null. * @throws IOException if an I/O error occurs. * @since 1.1 - * @deprecated Use {@link #writeLines(Collection, String, OutputStream, Charset)} instead + * @deprecated Use {@link #writeLines(Collection, String, OutputStream, Charset)} instead. */ @Deprecated public static void writeLines(final Collection lines, final String lineEnding, final OutputStream output) throws IOException { @@ -4009,7 +3936,7 @@ public static void writeLines(final Collection lines, String lineEnding, fina * Writes the {@link #toString()} value of each item in a collection to an {@link OutputStream} line by line, using the specified character encoding and the * specified line ending. *

    - * Character encoding names can be found at IANA. + * Character encoding names can be found at IANA. *

    * * @param lines the lines to write, null entries produce blank lines. @@ -4051,11 +3978,10 @@ public static void writeLines(final Collection lines, String lineEnding, fina } /** - * Returns the given Appendable if it is already a {@link Writer}, otherwise creates a Writer wrapper around the - * given Appendable. + * Returns the given Appendable if it is already a {@link Writer}, otherwise creates a Writer wrapper around the given Appendable. * * @param appendable the Appendable to wrap or return (not null). - * @return the given Appendable or a Writer wrapper around the given Appendable. + * @return the given Appendable or a Writer wrapper around the given Appendable. * @throws NullPointerException if the input parameter is null. * @since 2.7 */ @@ -4076,8 +4002,7 @@ public static Writer writer(final Appendable appendable) { * @deprecated TODO Make private in 3.0. */ @Deprecated - public IOUtils() { //NOSONAR + public IOUtils() { // NOSONAR // empty } - } diff --git a/src/main/java/org/apache/commons/io/LineIterator.java b/src/main/java/org/apache/commons/io/LineIterator.java index 293847e7a72..92a8bc7c035 100644 --- a/src/main/java/org/apache/commons/io/LineIterator.java +++ b/src/main/java/org/apache/commons/io/LineIterator.java @@ -56,9 +56,9 @@ public class LineIterator implements Iterator, Closeable { * Closes a {@link LineIterator} quietly. * * @param iterator The iterator to close, or {@code null}. + * @see Throwable#addSuppressed(Throwable) * @deprecated As of 2.6 deprecated without replacement. Please use the try-with-resources statement or handle * suppressed exceptions manually. - * @see Throwable#addSuppressed(Throwable) */ @Deprecated public static void closeQuietly(final LineIterator iterator) { @@ -77,17 +77,12 @@ public static void closeQuietly(final LineIterator iterator) { /** * Constructs an iterator of the lines for a {@link Reader}. * - * @param reader the {@link Reader} to read from, not null - * @throws NullPointerException if the reader is null + * @param reader the {@link Reader} to read from, not null. + * @throws NullPointerException if the reader is null. */ @SuppressWarnings("resource") // Caller closes Reader public LineIterator(final Reader reader) { - Objects.requireNonNull(reader, "reader"); - if (reader instanceof BufferedReader) { - bufferedReader = (BufferedReader) reader; - } else { - bufferedReader = new BufferedReader(reader); - } + bufferedReader = IOUtils.buffer(Objects.requireNonNull(reader, "reader")); } /** @@ -111,8 +106,8 @@ public void close() throws IOException { * If there is an {@link IOException} then {@link #close()} will * be called on this instance. * - * @return {@code true} if the Reader has more lines - * @throws IllegalStateException if an IO exception occurs + * @return {@code true} if the Reader has more lines. + * @throws IllegalStateException if an IO exception occurs. */ @Override public boolean hasNext() { @@ -135,16 +130,16 @@ public boolean hasNext() { } } } catch (final IOException ioe) { - IOUtils.closeQuietly(this, ioe::addSuppressed); - throw new IllegalStateException(ioe); + throw new IllegalStateException(IOUtils.closeQuietlySuppress(this, ioe)); } } /** * Overridable method to validate each line that is returned. * This implementation always returns true. - * @param line the line that is to be validated - * @return true if valid, false to remove from the iterator + * + * @param line the line that is to be validated. + * @return true if valid, false to remove from the iterator. */ protected boolean isValidLine(final String line) { return true; @@ -153,8 +148,8 @@ protected boolean isValidLine(final String line) { /** * Returns the next line in the wrapped {@link Reader}. * - * @return the next line from the input - * @throws NoSuchElementException if there is no line to return + * @return the next line from the input. + * @throws NoSuchElementException if there is no line to return. */ @Override public String next() { @@ -164,8 +159,8 @@ public String next() { /** * Returns the next line in the wrapped {@link Reader}. * - * @return the next line from the input - * @throws NoSuchElementException if there is no line to return + * @return the next line from the input. + * @throws NoSuchElementException if there is no line to return. * @deprecated Use {@link #next()}. */ @Deprecated @@ -181,7 +176,7 @@ public String nextLine() { /** * Unsupported. * - * @throws UnsupportedOperationException always + * @throws UnsupportedOperationException always. */ @Override public void remove() { diff --git a/src/main/java/org/apache/commons/io/RandomAccessFileMode.java b/src/main/java/org/apache/commons/io/RandomAccessFileMode.java index 88f499d120b..ea44bfa6cba 100644 --- a/src/main/java/org/apache/commons/io/RandomAccessFileMode.java +++ b/src/main/java/org/apache/commons/io/RandomAccessFileMode.java @@ -195,8 +195,8 @@ public T apply(final Path file, final IOFunction functi * Prefer {@link #create(Path)} over this. *

    * - * @param file the file object - * @return a random access file + * @param file the file object. + * @return a random access file. * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}. */ public RandomAccessFile create(final File file) throws FileNotFoundException { @@ -206,8 +206,8 @@ public RandomAccessFile create(final File file) throws FileNotFoundException { /** * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument. * - * @param file the file object - * @return a random access file + * @param file the file object. + * @return a random access file. * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}. */ public RandomAccessFile create(final Path file) throws FileNotFoundException { @@ -220,8 +220,8 @@ public RandomAccessFile create(final Path file) throws FileNotFoundException { * Prefer {@link #create(Path)} over this. *

    * - * @param name the file object - * @return a random access file + * @param name the file object. + * @return a random access file. * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}. */ public RandomAccessFile create(final String name) throws FileNotFoundException { @@ -282,8 +282,8 @@ public boolean implies(final RandomAccessFileMode other) { /** * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument. * - * @param name the file object - * @return a random access file + * @param name the file object. + * @return a random access file. * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}. * @since 2.18.0 */ diff --git a/src/main/java/org/apache/commons/io/TaggedIOException.java b/src/main/java/org/apache/commons/io/TaggedIOException.java index 5eb0b5df596..d997ecc0433 100644 --- a/src/main/java/org/apache/commons/io/TaggedIOException.java +++ b/src/main/java/org/apache/commons/io/TaggedIOException.java @@ -57,8 +57,8 @@ public class TaggedIOException extends IOExceptionWithCause { * } *
    * - * @param throwable The Throwable object to check - * @param tag tag object + * @param throwable The Throwable object to check. + * @param tag tag object. * @return {@code true} if the throwable has the specified tag, * otherwise {@code false} */ @@ -86,9 +86,9 @@ public static boolean isTaggedWith(final Throwable throwable, final Object tag) * } *
    * - * @param throwable an exception - * @param tag tag object - * @throws IOException original exception from the tagged decorator, if any + * @param throwable an exception. + * @param tag tag object. + * @throws IOException original exception from the tagged decorator, if any. */ public static void throwCauseIfTaggedWith(final Throwable throwable, final Object tag) throws IOException { @@ -105,8 +105,8 @@ public static void throwCauseIfTaggedWith(final Throwable throwable, final Objec /** * Constructs a tagged wrapper for the given exception. * - * @param original the exception to be tagged - * @param tag tag of this exception + * @param original the exception to be tagged. + * @param tag tag of this exception. */ public TaggedIOException(final IOException original, final Serializable tag) { super(original.getMessage(), original); @@ -117,7 +117,7 @@ public TaggedIOException(final IOException original, final Serializable tag) { * Returns the wrapped exception. The only difference to the overridden * {@link Throwable#getCause()} method is the narrower return type. * - * @return wrapped exception + * @return wrapped exception. */ @Override public synchronized IOException getCause() { @@ -127,7 +127,7 @@ public synchronized IOException getCause() { /** * Returns the serializable tag object. * - * @return tag object + * @return tag object. */ public Serializable getTag() { return tag; diff --git a/src/main/java/org/apache/commons/io/ThreadMonitor.java b/src/main/java/org/apache/commons/io/ThreadMonitor.java index 4c2c37a2d3b..f9fe24957c5 100644 --- a/src/main/java/org/apache/commons/io/ThreadMonitor.java +++ b/src/main/java/org/apache/commons/io/ThreadMonitor.java @@ -52,7 +52,7 @@ static Thread start(final Duration timeout) { /** * Starts monitoring the specified thread. * - * @param thread The thread to monitor + * @param thread The thread to monitor. * @param timeout The timeout amount. or no timeout if the value is zero or less. * @return The monitor thread or {@code null} if the timeout amount is not greater than zero. */ @@ -60,7 +60,7 @@ static Thread start(final Thread thread, final Duration timeout) { if (timeout.isZero() || timeout.isNegative()) { return null; } - final Thread monitor = new Thread(new ThreadMonitor(thread, timeout), ThreadMonitor.class.getSimpleName()); + final Thread monitor = new Thread(new ThreadMonitor(thread, timeout), "commons-io-ThreadMonitor"); monitor.setDaemon(true); monitor.start(); return monitor; @@ -104,6 +104,7 @@ public void run() { thread.interrupt(); } catch (final InterruptedException ignored) { // timeout not reached + Thread.currentThread().interrupt(); } } } diff --git a/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java b/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java index f31043769b7..6a5075c52ca 100644 --- a/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java +++ b/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java @@ -47,6 +47,7 @@ public static UncheckedIOException create(final Object message) { *

    * This method exists because there is no String constructor in {@link UncheckedIOException}. *

    + * * @param e cause the {@link IOException}. * @param message the detail message. * @return a new {@link UncheckedIOException}. diff --git a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java index d84aefdb79a..e64f4ae732d 100644 --- a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java +++ b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java @@ -31,6 +31,7 @@ import java.io.Reader; import java.io.Writer; import java.net.URI; +import java.net.URLConnection; import java.nio.channels.Channel; import java.nio.channels.Channels; import java.nio.channels.FileChannel; @@ -43,8 +44,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.Duration; import java.util.Arrays; import java.util.Objects; +import java.util.stream.Stream; import org.apache.commons.io.Charsets; import org.apache.commons.io.IORandomAccessFile; @@ -366,6 +369,7 @@ public abstract static class AbstractRandomAccessFileOrigin * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public AbstractRandomAccessFileOrigin(final T origin) { super(origin); @@ -437,6 +441,7 @@ public static class ByteArrayOrigin extends AbstractOrigin * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public ChannelOrigin(final Channel origin) { super(origin); @@ -563,6 +569,7 @@ public static class CharSequenceOrigin extends AbstractOrigin { * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public FileOrigin(final File origin) { super(origin); @@ -689,6 +697,7 @@ public static class InputStreamOrigin extends AbstractOrigin { * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public PathOrigin(final Path origin) { super(origin); @@ -892,6 +903,7 @@ public static class ReaderOrigin extends AbstractOrigin { * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public ReaderOrigin(final Reader origin) { super(origin); @@ -952,6 +964,80 @@ public Reader getReader(final Charset charset) throws IOException { */ public static class URIOrigin extends AbstractOrigin { + /** + * Options for connect and read from a URI. + * + * @since 2.22.0 + */ + public static final class URIOpenOption implements OpenOption { + + /** + * Builds URIOpenOption. + */ + public static class Builder extends AbstractSupplier { + + private Duration connectTimeout; + private Duration readTimeout; + + /** + * Constructs a new instance. + */ + public Builder() { + // empty + } + + @Override + public URIOpenOption get() { + return new URIOpenOption(this); + } + + /** + * Sets the connect timeout duration. + * + * @param connectTimeout the connect timeout duration. + * @return {@code this instance}. + */ + public Builder setConnectTimeout(final Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return asThis(); + } + + /** + * Sets the read timeout duration. + * + * @param readTimeout the read timeout duration. + * @return {@code this instance}. + */ + public Builder setReadTimeout(final Duration readTimeout) { + this.readTimeout = readTimeout; + return asThis(); + } + } + + /** + * Creates a new builder. + * + * @return a new builder. + */ + public static Builder builder() { + return new Builder(); + } + + private final Duration connectTimeout; + + private final Duration readTimeout; + + private URIOpenOption(final Builder builder) { + connectTimeout = builder.connectTimeout; + readTimeout = builder.readTimeout; + } + + @Override + public String toString() { + return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]"; + } + } + private static final String SCHEME_HTTPS = "https"; private static final String SCHEME_HTTP = "http"; @@ -959,17 +1045,23 @@ public static class URIOrigin extends AbstractOrigin { * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public URIOrigin(final URI origin) { super(origin); } + /** + * {@inheritDoc} + * + * @see URIOpenOption + */ @Override protected Channel getChannel(final OpenOption... options) throws IOException { final URI uri = get(); final String scheme = uri.getScheme(); if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) { - return Channels.newChannel(uri.toURL().openStream()); + return Channels.newChannel(getInputStream(uri, options)); } return Files.newByteChannel(getPath(), options); } @@ -979,20 +1071,52 @@ public File getFile() { return getPath().toFile(); } + /** + * {@inheritDoc} + *

    + * Set timeouts with a {@link URIOpenOption}. + *

    + * + * @see URIOpenOption + * @see URLConnection#setConnectTimeout(int) + * @see URLConnection#setReadTimeout(int) + */ @Override public InputStream getInputStream(final OpenOption... options) throws IOException { final URI uri = get(); final String scheme = uri.getScheme(); if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) { - return uri.toURL().openStream(); + return getInputStream(uri, options); } return Files.newInputStream(getPath(), options); } + private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException { + final URLConnection connection = uri.toURL().openConnection(); + if (options != null) { + Stream.of(options).forEach(option -> { + if (option instanceof URIOpenOption) { + final URIOpenOption connOption = (URIOpenOption) option; + if (connOption.connectTimeout != null) { + connection.setConnectTimeout(toMillis(connOption.connectTimeout)); + } + if (connOption.readTimeout != null) { + connection.setReadTimeout(toMillis(connOption.readTimeout)); + } + } + }); + } + return connection.getInputStream(); + } + @Override public Path getPath() { return Paths.get(get()); } + + private int toMillis(final Duration duration) { + return Math.toIntExact(duration.toMillis()); + } } /** @@ -1007,6 +1131,7 @@ public static class WriterOrigin extends AbstractOrigin { * Constructs a new instance for the given origin. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ public WriterOrigin(final Writer origin) { super(origin); @@ -1057,15 +1182,16 @@ public Writer getWriter(final Charset charset, final OpenOption... options) thro * Constructs a new instance for subclasses. * * @param origin The origin, not null. + * @throws NullPointerException if {@code origin} is {@code null}. */ protected AbstractOrigin(final T origin) { this.origin = Objects.requireNonNull(origin, "origin"); } /** - * Gets the origin. + * Gets the origin, never null. * - * @return the origin. + * @return the origin, never null. */ @Override public T get() { @@ -1090,7 +1216,7 @@ public byte[] getByteArray() throws IOException { * @param length How many bytes to copy. * @return this origin as a byte array, if possible. * @throws UnsupportedOperationException if the origin cannot be converted to a Path. - * @throws ArithmeticException if the {@code position} overflows an int + * @throws ArithmeticException if the {@code position} overflows an int. * @throws IOException if an I/O error occurs. * @since 2.13.0 */ @@ -1113,6 +1239,7 @@ public byte[] getByteArray(final long position, final int length) throws IOExcep * @param The type of channel to return. * @throws IOException If an I/O error occurs. * @throws UnsupportedOperationException If this origin cannot be converted to a channel of the given type. + * @see #getChannel(OpenOption...) * @since 2.21.0 */ public final C getChannel(final Class channelType, final OpenOption... options) throws IOException { @@ -1131,6 +1258,7 @@ public final C getChannel(final Class channelType, final * @return A new Channel on the origin. * @throws IOException If an I/O error occurs. * @throws UnsupportedOperationException If this origin cannot be converted to a channel. + * @see #getChannel(Class, OpenOption...) * @since 2.21.0 */ protected Channel getChannel(final OpenOption... options) throws IOException { @@ -1162,7 +1290,7 @@ public File getFile() { /** * Gets this origin as an InputStream, if possible. * - * @param options options specifying how the file is opened + * @param options options specifying how the file is opened. * @return this origin as an InputStream, if possible. * @throws IOException if an I/O error occurs. * @throws UnsupportedOperationException if the origin cannot be converted to a Path. @@ -1174,7 +1302,7 @@ public InputStream getInputStream(final OpenOption... options) throws IOExceptio /** * Gets this origin as an OutputStream, if possible. * - * @param options options specifying how the file is opened + * @param options options specifying how the file is opened. * @return this origin as an OutputStream, if possible. * @throws IOException if an I/O error occurs. * @throws UnsupportedOperationException if the origin cannot be converted to a Path. @@ -1217,6 +1345,11 @@ public Reader getReader(final Charset charset) throws IOException { return Files.newBufferedReader(getPath(), Charsets.toCharset(charset)); } + /** + * Gets simple name of the underlying class. + * + * @return The simple name of the underlying class. + */ private String getSimpleClassName() { return getClass().getSimpleName(); } @@ -1224,8 +1357,8 @@ private String getSimpleClassName() { /** * Gets a new Writer on the origin, buffered by default. * - * @param charset the charset to use for encoding - * @param options options specifying how the file is opened + * @param charset the charset to use for encoding. + * @param options options specifying how the file is opened. * @return a new Writer on the origin. * @throws IOException if an I/O error occurs opening or creating the file. * @throws UnsupportedOperationException if the origin cannot be converted to a Path. diff --git a/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java index 541548c7295..37986693b48 100644 --- a/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java +++ b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java @@ -122,6 +122,20 @@ public int getBufferSizeDefault() { return bufferSizeDefault; } + /** + * Gets a byte array from the origin. + * + * @return A byte array. + * @throws IllegalStateException if the {@code origin} is {@code null}. + * @throws UnsupportedOperationException if the origin cannot be converted to a byte array. + * @throws IOException if an I/O error occurs. + * @see AbstractOrigin#getByteArray() + * @since 2.22.0 + */ + public byte[] getByteArray() throws IOException { + return checkOrigin().getByteArray(); + } + /** * Gets a Channel from the origin with OpenOption[]. * @@ -142,7 +156,7 @@ public C getChannel(final Class channelType) throws IOExc /** * Gets a CharSequence from the origin with a Charset. * - * @return An input stream + * @return An input stream. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to a CharSequence. * @throws IOException if an I/O error occurs. @@ -174,7 +188,7 @@ public Charset getCharsetDefault() { /** * Gets a File from the origin. * - * @return A File + * @return A File. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}. * @see AbstractOrigin#getPath() @@ -187,7 +201,7 @@ public File getFile() { /** * Gets an InputStream from the origin with OpenOption[]. * - * @return An input stream + * @return An input stream. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. * @throws IOException if an I/O error occurs. @@ -202,7 +216,7 @@ public InputStream getInputStream() throws IOException { /** * Gets the OpenOption array. * - * @return the OpenOption array. + * @return the OpenOption array, this is not a defensive copy, modify at your own risk. */ public OpenOption[] getOpenOptions() { return openOptions; @@ -211,7 +225,7 @@ public OpenOption[] getOpenOptions() { /** * Gets an OutputStream from the origin with OpenOption[]. * - * @return An OutputStream + * @return An OutputStream. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}. * @throws IOException if an I/O error occurs. @@ -226,7 +240,7 @@ public OutputStream getOutputStream() throws IOException { /** * Gets a Path from the origin. * - * @return A Path + * @return A Path. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Path}. * @see AbstractOrigin#getPath() @@ -239,7 +253,7 @@ public Path getPath() { /** * Gets a RandomAccessFile from the origin. * - * @return A RandomAccessFile + * @return A RandomAccessFile. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}. * @throws IOException if an I/O error occurs. @@ -252,7 +266,7 @@ public RandomAccessFile getRandomAccessFile() throws IOException { /** * Gets a Reader from the origin with a Charset. * - * @return A Reader + * @return A Reader. * @throws IllegalStateException if the {@code origin} is {@code null}. * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Reader}. * @throws IOException if an I/O error occurs. @@ -285,7 +299,7 @@ public Writer getWriter() throws IOException { * Subclasses may ignore this setting. *

    * - * @param bufferSize the buffer size. + * @param bufferSize the buffer size, 0 resets to the default from {@link #getBufferSizeDefault()}. * @return {@code this} instance. */ public B setBufferSize(final int bufferSize) { @@ -299,7 +313,7 @@ public B setBufferSize(final int bufferSize) { * Subclasses may ignore this setting. *

    * - * @param bufferSize the buffer size, null resets to the default. + * @param bufferSize the buffer size, null resets to the default from {@link #getBufferSizeDefault()}. * @return {@code this} instance. */ public B setBufferSize(final Integer bufferSize) { @@ -325,11 +339,11 @@ public B setBufferSizeChecker(final IntUnaryOperator bufferSizeChecker) { * Subclasses may ignore this setting. *

    * - * @param bufferSizeDefault the buffer size, null resets to the default. + * @param bufferSizeDefault the buffer size, 0 resets to the default {@link IOUtils#DEFAULT_BUFFER_SIZE}. * @return {@code this} instance. */ protected B setBufferSizeDefault(final int bufferSizeDefault) { - this.bufferSizeDefault = bufferSizeDefault; + this.bufferSizeDefault = checkBufferSize(bufferSizeDefault > 0 ? bufferSizeDefault : IOUtils.DEFAULT_BUFFER_SIZE); return asThis(); } @@ -388,7 +402,7 @@ protected B setCharsetDefault(final Charset defaultCharset) { } /** - * Sets the OpenOption[]. + * Sets the OpenOption array. *

    * Normally used with InputStream, OutputStream, and Writer. *

    @@ -396,7 +410,7 @@ protected B setCharsetDefault(final Charset defaultCharset) { * Subclasses may ignore this setting. *

    * - * @param openOptions the OpenOption[] name, null resets to the default. + * @param openOptions the OpenOption[] name, null resets to the default, a defensive copy is made. * @return {@code this} instance. * @since 2.13.0 * @see #setInputStream(InputStream) @@ -404,7 +418,7 @@ protected B setCharsetDefault(final Charset defaultCharset) { * @see #setWriter(Writer) */ public B setOpenOptions(final OpenOption... openOptions) { - this.openOptions = openOptions != null ? openOptions : DEFAULT_OPEN_OPTIONS; + this.openOptions = openOptions != null ? openOptions.clone() : DEFAULT_OPEN_OPTIONS; return asThis(); } diff --git a/src/main/java/org/apache/commons/io/build/package-info.java b/src/main/java/org/apache/commons/io/build/package-info.java index bda177c354c..5eed609d544 100644 --- a/src/main/java/org/apache/commons/io/build/package-info.java +++ b/src/main/java/org/apache/commons/io/build/package-info.java @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /** * Provides classes to implement the builder pattern for IO classes. * @@ -49,5 +50,4 @@ * * @since 2.12.0 */ - package org.apache.commons.io.build; diff --git a/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java b/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java index 4135a815e83..57c38124875 100644 --- a/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java +++ b/src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java @@ -24,12 +24,16 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; +import java.nio.channels.NonWritableChannelException; import java.nio.channels.SeekableByteChannel; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.build.AbstractStreamBuilder; /** * A {@link SeekableByteChannel} implementation backed by a byte array. @@ -38,25 +42,79 @@ * and it's not possible to {@link #position(long) set the position} or {@link #truncate(long) truncate} to a value bigger than that. The raw internal buffer is * accessed via {@link ByteArraySeekableByteChannel#array()}. *

    + *

    + * Building a read-only channel from an existing byte array is supported with: + *

    + *
    {@code
    + * try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
    + *               .setByteArray(...)
    + *               .setOpenOptions(StandardOpenOption.READ)
    + *               .get()) {
    + *               // read from channel
    + * }
    + * }
    * * @since 2.21.0 */ public class ByteArraySeekableByteChannel implements SeekableByteChannel { + /** + * Builds for {@link ByteArraySeekableByteChannel}. + *

    + * Building a read-only channel from an existing byte array is supported with: + *

    + *
    {@code
    +     * try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
    +     *               .setByteArray(...)
    +     *               .setOpenOptions(StandardOpenOption.READ)
    +     *               .get()) {
    +     *               // read from channel
    +     * }
    +     * }
    + * + * @since 2.22.0 + */ + public static class Builder extends AbstractStreamBuilder { + + /** + * Constructs a new builder for {@link ByteArraySeekableByteChannel}. + */ + public Builder() { + setByteArray(IOUtils.EMPTY_BYTE_ARRAY); + } + + @Override + public ByteArraySeekableByteChannel get() throws IOException { + return new ByteArraySeekableByteChannel(this); + } + } + private static final int RESIZE_LIMIT = Integer.MAX_VALUE >> 1; + /** + * Constructs a new builder for {@link ByteArraySeekableByteChannel}. + * + * @return a new builder for {@link ByteArraySeekableByteChannel}. + * @since 2.22.0 + */ + public static Builder builder() { + return new Builder(); + } + /** * Constructs a new channel backed directly by the given byte array. * - *

    The channel initially contains the full contents of the array, with its - * size set to {@code bytes.length} and its position set to {@code 0}.

    + *

    + * The channel initially contains the full contents of the array, with its size set to {@code bytes.length} and its position set to {@code 0}. + *

    * - *

    Reads and writes operate on the shared array. - * If a write operation extends beyond the current capacity, the channel will - * automatically allocate a larger backing array and copy the existing contents.

    + *

    + * Reads and writes operate on the shared array. If a write operation extends beyond the current capacity, the channel will automatically allocate a larger + * backing array and copy the existing contents. + *

    * * @param bytes The byte array to wrap, must not be {@code null} - * @return A new channel that uses the given array as its initial backing store + * @return A new channel that uses the given array as its initial backing store. * @throws NullPointerException If {@code bytes} is {@code null} * @see #array() * @see ByteArrayInputStream#ByteArrayInputStream(byte[]) @@ -65,11 +123,11 @@ public static ByteArraySeekableByteChannel wrap(final byte[] bytes) { Objects.requireNonNull(bytes, "bytes"); return new ByteArraySeekableByteChannel(bytes); } - private byte[] data; private volatile boolean closed; - private int position; + private long position; private int size; + private final boolean isWritable; private final ReentrantLock lock = new ReentrantLock(); /** @@ -84,10 +142,19 @@ public ByteArraySeekableByteChannel() { this(IOUtils.DEFAULT_BUFFER_SIZE); } + private ByteArraySeekableByteChannel(final Builder builder) throws IOException { + this.data = builder.getByteArray(); + this.size = data.length; + final OpenOption[] openOptions = builder.getOpenOptions(); + Arrays.sort(openOptions); + this.isWritable = openOptions.length == 0 || Arrays.binarySearch(openOptions, StandardOpenOption.WRITE) >= 0 + || Arrays.binarySearch(openOptions, StandardOpenOption.APPEND) >= 0; + } + private ByteArraySeekableByteChannel(final byte[] data) { this.data = data; - this.position = 0; this.size = data.length; + this.isWritable = true; } /** @@ -104,8 +171,7 @@ public ByteArraySeekableByteChannel(final int size) { throw new IllegalArgumentException("Size must be non-negative"); } this.data = new byte[size]; - this.position = 0; - this.size = 0; + this.isWritable = true; } /** @@ -126,11 +192,16 @@ private void checkOpen() throws ClosedChannelException { } } - private int checkRange(final long newSize, final String method) { - if (newSize < 0L || newSize > IOUtils.SOFT_MAX_ARRAY_LENGTH) { - throw new IllegalArgumentException(String.format("%s must be in range [0..%,d]: %,d", method, IOUtils.SOFT_MAX_ARRAY_LENGTH, newSize)); + private void checkRange(final long newSize, final String method) { + if (newSize < 0L) { + throw new IllegalArgumentException(String.format("%s must be positive: %,d", method, newSize)); + } + } + + private void checkWritable() { + if (!isWritable) { + throw new NonWritableChannelException(); } - return (int) newSize; } @Override @@ -166,10 +237,10 @@ public long position() throws ClosedChannelException { @Override public SeekableByteChannel position(final long newPosition) throws IOException { checkOpen(); - final int intPos = checkRange(newPosition, "position()"); + checkRange(newPosition, "position()"); lock.lock(); try { - position = intPos; + position = newPosition; } finally { lock.unlock(); } @@ -181,15 +252,18 @@ public int read(final ByteBuffer buf) throws IOException { checkOpen(); lock.lock(); try { + if (position > Integer.MAX_VALUE) { + return IOUtils.EOF; + } int wanted = buf.remaining(); - final int possible = size - position; + final int possible = size - (int) position; if (possible <= 0) { return IOUtils.EOF; } if (wanted > possible) { wanted = possible; } - buf.put(data, position, wanted); + buf.put(data, (int) position, wanted); position += wanted; return wanted; } finally { @@ -238,14 +312,15 @@ public byte[] toByteArray() { @Override public SeekableByteChannel truncate(final long newSize) throws ClosedChannelException { checkOpen(); - final int intSize = checkRange(newSize, "truncate()"); + checkWritable(); + checkRange(newSize, "truncate()"); lock.lock(); try { - if (size > intSize) { - size = intSize; + if (size > newSize) { + size = (int) newSize; } - if (position > intSize) { - position = intSize; + if (position > newSize) { + position = newSize; } } finally { lock.unlock(); @@ -256,21 +331,30 @@ public SeekableByteChannel truncate(final long newSize) throws ClosedChannelExce @Override public int write(final ByteBuffer b) throws IOException { checkOpen(); + checkWritable(); + // + if (position > Integer.MAX_VALUE) { + throw new IOException("position > Integer.MAX_VALUE"); + } lock.lock(); try { final int wanted = b.remaining(); - final int possibleWithoutResize = Math.max(0, size - position); - if (wanted > possibleWithoutResize) { - final int newSize = position + wanted; - if (newSize < 0 || newSize > IOUtils.SOFT_MAX_ARRAY_LENGTH) { // overflow - throw new OutOfMemoryError("required array size " + Integer.toUnsignedString(newSize) + " too large"); - } - resize(newSize); + // intPos <= Integer.MAX_VALUE + final int intPos = (int) position; + final long newPosition = position + wanted; + if (newPosition > IOUtils.SOFT_MAX_ARRAY_LENGTH) { + throw new IOException(String.format("Requested array size %,d is too large.", newPosition)); } - b.get(data, position, wanted); - position += wanted; - if (size < position) { - size = position; + if (newPosition > size) { + final int newPositionInt = (int) newPosition; + // Ensure that newPositionInt ≤ data.length + resize(newPositionInt); + size = newPositionInt; + } + b.get(data, intPos, wanted); + position = newPosition; + if (size < intPos) { + size = intPos; } return wanted; } finally { diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java index a9a462da73b..987e8427c7c 100644 --- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java +++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java @@ -19,7 +19,16 @@ import java.io.Closeable; import java.lang.reflect.Proxy; +import java.nio.channels.AsynchronousChannel; +import java.nio.channels.ByteChannel; import java.nio.channels.Channel; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.InterruptibleChannel; +import java.nio.channels.NetworkChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; @@ -28,8 +37,24 @@ * Creates a close-shielding proxy for a {@link Channel}. * *

    - * The returned proxy will implement all {@link Channel} sub-interfaces that the delegate implements. + * The returned proxy implements all {@link Channel} sub-interfaces that are both supported by this implementation and actually implemented by the given + * delegate. *

    + *

    + * The following interfaces are supported: + *

    + *
      + *
    • {@link AsynchronousChannel}
    • + *
    • {@link ByteChannel}
    • + *
    • {@link Channel}
    • + *
    • {@link GatheringByteChannel}
    • + *
    • {@link InterruptibleChannel}
    • + *
    • {@link NetworkChannel}
    • + *
    • {@link ReadableByteChannel}
    • + *
    • {@link ScatteringByteChannel}
    • + *
    • {@link SeekableByteChannel}
    • + *
    • {@link WritableByteChannel}
    • + *
    * * @see Channel * @see Closeable @@ -40,11 +65,15 @@ public final class CloseShieldChannel { private static final Class[] EMPTY = {}; private static Set> collectChannelInterfaces(final Class type, final Set> out) { + Class currentType = type; // Visit interfaces - for (final Class iface : type.getInterfaces()) { - if (Channel.class.isAssignableFrom(iface) && out.add(iface)) { - collectChannelInterfaces(iface, out); + while (currentType != null) { + for (final Class iface : currentType.getInterfaces()) { + if (CloseShieldChannelHandler.isSupported(iface) && out.add(iface)) { + collectChannelInterfaces(iface, out); + } } + currentType = currentType.getSuperclass(); } return out; } @@ -53,8 +82,10 @@ private static Set> collectChannelInterfaces(final Class type, final * Wraps a channel to shield it from being closed. * * @param channel The underlying channel to shield, not {@code null}. - * @param Any Channel type (interface or class). + * @param A supported channel type. * @return A proxy that shields {@code close()} and enforces closed semantics on other calls. + * @throws ClassCastException if {@code T} is not a supported channel type. + * @throws NullPointerException if {@code channel} is {@code null}. */ @SuppressWarnings({ "unchecked", "resource" }) // caller closes public static T wrap(final T channel) { diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java index f13b101c197..822c6b6186b 100644 --- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java +++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java @@ -21,14 +21,44 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.nio.channels.AsynchronousChannel; +import java.nio.channels.ByteChannel; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.InterruptibleChannel; import java.nio.channels.NetworkChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.ScatteringByteChannel; import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +/** + * An {@link InvocationHandler} supporting the implementation of {@link CloseShieldChannel}. + */ final class CloseShieldChannelHandler implements InvocationHandler { + private static final Set> SUPPORTED_INTERFACES; + + static { + final Set> interfaces = new HashSet<>(); + interfaces.add(AsynchronousChannel.class); + interfaces.add(ByteChannel.class); + interfaces.add(Channel.class); + interfaces.add(GatheringByteChannel.class); + interfaces.add(InterruptibleChannel.class); + interfaces.add(NetworkChannel.class); + interfaces.add(ReadableByteChannel.class); + interfaces.add(ScatteringByteChannel.class); + interfaces.add(SeekableByteChannel.class); + interfaces.add(WritableByteChannel.class); + SUPPORTED_INTERFACES = Collections.unmodifiableSet(interfaces); + } + /** * Tests whether the given method is allowed to be called after the shield is closed. * @@ -42,6 +72,10 @@ private static boolean isAllowedAfterClose(final Class declaringClass, final return parameterCount == 0 && name.equals("supportedOptions") && NetworkChannel.class.equals(declaringClass); } + static boolean isSupported(final Class interfaceClass) { + return SUPPORTED_INTERFACES.contains(interfaceClass); + } + /** * Tests whether the given method returns 'this' (the channel) as per JDK spec. * @@ -123,7 +157,7 @@ private Object invokeObjectMethod(final Object proxy, final Method method, final return false; } default: - // Not possible, all non-final Object methods are handled above + // Not possible, all non-final Object methods are handled above. return null; } } diff --git a/src/main/java/org/apache/commons/io/channels/FileChannels.java b/src/main/java/org/apache/commons/io/channels/FileChannels.java index 30a80e8ab6b..7ef9e3f82c9 100644 --- a/src/main/java/org/apache/commons/io/channels/FileChannels.java +++ b/src/main/java/org/apache/commons/io/channels/FileChannels.java @@ -24,6 +24,7 @@ import java.nio.channels.SeekableByteChannel; import java.util.Objects; +import org.apache.commons.io.Buffers; import org.apache.commons.io.IOUtils; /** @@ -67,36 +68,38 @@ public static boolean contentEquals(final ReadableByteChannel channel1, final Re // Don't use ByteBuffer#compact() to avoid extra copying. final ByteBuffer c1Buffer = ByteBuffer.allocateDirect(bufferCapacity); final ByteBuffer c2Buffer = ByteBuffer.allocateDirect(bufferCapacity); - int c1NumRead = 0; - int c2NumRead = 0; - boolean c1Read0 = false; - boolean c2Read0 = false; - // If a channel is a non-blocking channel, it may return 0 bytes read for any given call. - while (true) { - if (!c2Read0) { - c1NumRead = readToLimit(channel1, c1Buffer); - c1Buffer.clear(); - c1Read0 = c1NumRead == 0; - } - if (!c1Read0) { - c2NumRead = readToLimit(channel2, c2Buffer); - c2Buffer.clear(); - c2Read0 = c2NumRead == 0; - } - if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) { - return c1Buffer.equals(c2Buffer); - } - if (c1NumRead == 0 || c2NumRead == 0) { - // 0 may be returned from a non-blocking channel. - Thread.yield(); - continue; - } - if (c1NumRead != c2NumRead) { - return false; - } - if (!c1Buffer.equals(c2Buffer)) { - return false; + try { + int c1NumRead = 0; + int c2NumRead = 0; + boolean c1Read0 = false; + boolean c2Read0 = false; + // If a channel is a non-blocking channel, it may return 0 bytes read for any given call. + while (true) { + if (!c2Read0) { + c1NumRead = readToLimit(channel1, c1Buffer); + c1Buffer.clear(); + c1Read0 = c1NumRead == 0; + } + if (!c1Read0) { + c2NumRead = readToLimit(channel2, c2Buffer); + c2Buffer.clear(); + c2Read0 = c2NumRead == 0; + } + if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) { + return c1Buffer.equals(c2Buffer); + } + if (c1NumRead == 0 || c2NumRead == 0) { + // 0 may be returned from a non-blocking channel. + Thread.yield(); + continue; + } + if (c1NumRead != c2NumRead || !c1Buffer.equals(c2Buffer)) { + return false; + } } + } finally { + Buffers.clear(c1Buffer); + Buffers.clear(c2Buffer); } } @@ -135,7 +138,7 @@ public static boolean contentEquals(final SeekableByteChannel channel1, final Se * * @param channel The source channel. * @param dst The buffer into which bytes are to be transferred. - * @return The number of bytes read, never zero, or {@code -1} if the channel has reached end-of-stream + * @return The number of bytes read, never zero, or {@code -1} if the channel has reached end-of-stream. * @throws IOException If some other I/O error occurs. * @throws IllegalArgumentException If there is room in the given buffer. */ diff --git a/src/main/java/org/apache/commons/io/channels/FilterByteChannel.java b/src/main/java/org/apache/commons/io/channels/FilterByteChannel.java new file mode 100644 index 00000000000..bcce45486b5 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FilterByteChannel.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.apache.commons.io.input.ProxyInputStream; +import org.apache.commons.io.input.ProxyReader; +import org.apache.commons.io.output.ProxyOutputStream; +import org.apache.commons.io.output.ProxyWriter; + +/** + * A {@link ByteChannel} filter which delegates to the wrapped {@link ByteChannel}. + *

    + * A {@code FilterByteChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or providing + * additional functionality. The class {@code FilterByteChannel} itself simply overrides methods of {@code ByteChannel} with versions that pass all requests to + * the wrapped channel. Subclasses of {@code FilterByteChannel} may of course override any methods declared or inherited by {@code FilterByteChannel}, and may + * also provide additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterByteChannel#FilterByteChannel(ByteChannel) channel constructor} and more advanced instances through the + * {@link Builder}. + *

    + * + * @param the {@link ByteChannel} type. + * @see FilterInputStream + * @see FilterOutputStream + * @see FilterReader + * @see FilterWritableByteChannel + * @see ProxyInputStream + * @see ProxyOutputStream + * @see ProxyReader + * @see ProxyWriter + * @since 2.22.0 + */ +public class FilterByteChannel extends FilterChannel implements ByteChannel { + + /** + * Builds instances of {@link FilterByteChannel} for subclasses. + * + * @param The {@link FilterByteChannel} type. + * @param The {@link ByteChannel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder, C extends ByteChannel, B extends AbstractBuilder> + extends FilterChannel.AbstractBuilder { + + /** + * Constructs a new builder for {@link FilterByteChannel}. + */ + protected AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterByteChannel}. + */ + public static class Builder extends AbstractBuilder, ByteChannel, Builder> { + + /** + * Builds instances of {@link FilterByteChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterByteChannel get() throws IOException { + return new FilterByteChannel<>(this); + } + } + + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forByteChannel() { + return new Builder(); + } + + FilterByteChannel(final AbstractBuilder builder) throws IOException { + super(builder); + } + + /** + * Constructs a new instance. + * + * @param byteChannel The channel to wrap. + */ + public FilterByteChannel(final C byteChannel) { + super(byteChannel); + } + + @Override + public int read(final ByteBuffer dst) throws IOException { + return channel.read(dst); + } + + @Override + public int write(final ByteBuffer src) throws IOException { + return channel.write(src); + } +} diff --git a/src/main/java/org/apache/commons/io/channels/FilterChannel.java b/src/main/java/org/apache/commons/io/channels/FilterChannel.java new file mode 100644 index 00000000000..e76dd962082 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FilterChannel.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.nio.channels.Channel; + +import org.apache.commons.io.build.AbstractStreamBuilder; +import org.apache.commons.io.input.ProxyInputStream; +import org.apache.commons.io.input.ProxyReader; +import org.apache.commons.io.output.ProxyOutputStream; +import org.apache.commons.io.output.ProxyWriter; + +/** + * A {@link Channel} filter which delegates to the wrapped {@link Channel}. + *

    + * A {@code FilterChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or providing + * additional functionality. The class {@code FilterChannel} itself simply overrides methods of {@code Channel} with versions that pass all requests to the + * wrapped channel. Subclasses of {@code FilterChannel} may of course override any methods declared or inherited by {@code FilterChannel}, and may also provide + * additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterChannel#FilterChannel(Channel) channel constructor} and more advanced instances through the + * {@link Builder}. + *

    + * + * @param the {@link Channel} type. + * @see FilterInputStream + * @see FilterOutputStream + * @see FilterReader + * @see FilterWritableByteChannel + * @see ProxyInputStream + * @see ProxyOutputStream + * @see ProxyReader + * @see ProxyWriter + * @since 2.22.0 + */ +public class FilterChannel implements Channel { + + /** + * Builds instances of {@link FilterChannel} for subclasses. + * + * @param The {@link FilterChannel} type. + * @param The {@link Channel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder, C extends Channel, B extends AbstractBuilder> + extends AbstractStreamBuilder> { + + /** + * Constructs instance for subclasses. + */ + protected AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterChannel}. + */ + public static class Builder extends AbstractBuilder, Channel, Builder> { + + /** + * Builds instances of {@link FilterChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterChannel get() throws IOException { + return new FilterChannel<>(this); + } + } + + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forChannel() { + return new Builder(); + } + + final C channel; + + /** + * Constructs a new instance. + * + * @param builder The source builder. + * @throws IOException if an I/O error occurs. + */ + @SuppressWarnings("unchecked") + FilterChannel(final AbstractBuilder builder) throws IOException { + channel = (C) builder.getChannel(Channel.class); + } + + /** + * Constructs a new instance. + * + * @param channel The channel to wrap. + */ + public FilterChannel(final C channel) { + this.channel = channel; + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + /** + * Unwraps this instance by returning the underlying {@link Channel} of type {@code C}. + *

    + * Use with caution. + *

    + * + * @return the underlying channel of type {@code C}. + */ + public C unwrap() { + return channel; + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FileChannelProxy.java b/src/main/java/org/apache/commons/io/channels/FilterFileChannel.java similarity index 57% rename from src/test/java/org/apache/commons/io/channels/FileChannelProxy.java rename to src/main/java/org/apache/commons/io/channels/FilterFileChannel.java index ce7c0daddf7..c37d30dbf23 100644 --- a/src/test/java/org/apache/commons/io/channels/FileChannelProxy.java +++ b/src/main/java/org/apache/commons/io/channels/FilterFileChannel.java @@ -20,20 +20,90 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; +import java.nio.channels.Channel; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.util.Objects; + +import org.apache.commons.io.build.AbstractStreamBuilder; /** - * Proxies a FileChannel. + * Filters a {@link FileChannel}. + *

    + * A {@code FilterFileChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or providing + * additional functionality. The class {@code FilterFileChannel} itself simply overrides methods of {@code FileChannel} with versions that pass all requests to + * the wrapped channel. Subclasses of {@code FilterFileChannel} may of course override any methods declared or inherited by {@code FilterFileChannel}, and may + * also provide additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterFileChannel#FilterFileChannel(FileChannel) channel constructor} and more advanced instances through the + * {@link Builder}. + *

    + * + * @since 2.22.0 */ -class FileChannelProxy extends FileChannel { +public class FilterFileChannel extends FileChannel { + + /** + * Builds instances of {@link FilterFileChannel} for subclasses. + * + * @param The {@link FilterFileChannel} type. + * @param The {@link Channel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder> + extends AbstractStreamBuilder> { + + /** + * Constructs instance for subclasses. + */ + protected AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterFileChannel}. + */ + public static class Builder extends AbstractBuilder { - FileChannel fileChannel; + /** + * Builds instances of {@link FilterChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterFileChannel get() throws IOException { + return new FilterFileChannel(this); + } + } - FileChannelProxy(final FileChannel fileChannel) { - this.fileChannel = fileChannel; + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forFilterFileChannel() { + return new Builder(); + } + + final FileChannel fileChannel; + + private FilterFileChannel(final Builder builder) throws IOException { + this.fileChannel = builder.getChannel(FileChannel.class); + } + + /** + * Constructs a new instance. + * + * @param fileChannel the file channel to wrap. + */ + public FilterFileChannel(final FileChannel fileChannel) { + this.fileChannel = Objects.requireNonNull(fileChannel, "fileChannel"); } @Override @@ -121,6 +191,18 @@ public FileLock tryLock(final long position, final long size, final boolean shar return fileChannel.tryLock(position, size, shared); } + /** + * Unwraps this instance by returning the underlying {@link FileChannel}. + *

    + * Use with caution. + *

    + * + * @return the underlying {@link FileChannel}. + */ + public FileChannel unwrap() { + return fileChannel; + } + @Override public int write(final ByteBuffer src) throws IOException { return fileChannel.write(src); diff --git a/src/main/java/org/apache/commons/io/channels/FilterReadableByteChannel.java b/src/main/java/org/apache/commons/io/channels/FilterReadableByteChannel.java new file mode 100644 index 00000000000..377d3b1d043 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FilterReadableByteChannel.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +import org.apache.commons.io.input.ProxyInputStream; +import org.apache.commons.io.input.ProxyReader; +import org.apache.commons.io.output.ProxyOutputStream; +import org.apache.commons.io.output.ProxyWriter; + +/** + * A {@link ReadableByteChannel} filter which delegates to the wrapped {@link ReadableByteChannel}. + *

    + * A {@code FilterReadableByteChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or + * providing additional functionality. The class {@code FilterReadableByteChannel} itself simply overrides methods of {@code ReadableByteChannel} with versions + * that pass all requests to the wrapped channel. Subclasses of {@code FilterReadableByteChannel} may of course override any methods declared or inherited by + * {@code FilterReadableByteChannel}, and may also provide additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterReadableByteChannel#FilterReadableByteChannel(ReadableByteChannel) channel constructor} and more + * advanced instances through the {@link Builder}. + *

    + * + * @param the {@link ReadableByteChannel} type. + * @see FilterInputStream + * @see FilterOutputStream + * @see FilterReader + * @see FilterWritableByteChannel + * @see ProxyInputStream + * @see ProxyOutputStream + * @see ProxyReader + * @see ProxyWriter + * @since 2.22.0 + */ +public class FilterReadableByteChannel extends FilterChannel implements ReadableByteChannel { + + /** + * Builds instances of {@link FilterReadableByteChannel} for subclasses. + * + * @param The {@link FilterReadableByteChannel} type. + * @param The {@link ReadableByteChannel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder, C extends ReadableByteChannel, B extends AbstractBuilder> + extends FilterChannel.AbstractBuilder { + + /** + * Constructs a new builder for {@link FilterReadableByteChannel}. + */ + public AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterByteChannel}. + */ + public static class Builder extends AbstractBuilder, ReadableByteChannel, Builder> { + + /** + * Builds instances of {@link FilterByteChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterReadableByteChannel get() throws IOException { + return new FilterReadableByteChannel<>(this); + } + } + + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forReadableByteChannel() { + return new Builder(); + } + + FilterReadableByteChannel(final Builder builder) throws IOException { + super(builder); + } + + /** + * Constructs a new instance. + * + * @param channel The channel to wrap. + */ + public FilterReadableByteChannel(final C channel) { + super(channel); + } + + @Override + public int read(final ByteBuffer dst) throws IOException { + return channel.read(dst); + } +} diff --git a/src/main/java/org/apache/commons/io/channels/FilterSeekableByteChannel.java b/src/main/java/org/apache/commons/io/channels/FilterSeekableByteChannel.java new file mode 100644 index 00000000000..11fbadc2c15 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FilterSeekableByteChannel.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.nio.channels.SeekableByteChannel; + +import org.apache.commons.io.input.ProxyInputStream; +import org.apache.commons.io.input.ProxyReader; +import org.apache.commons.io.output.ProxyOutputStream; +import org.apache.commons.io.output.ProxyWriter; + +/** + * A {@link SeekableByteChannel} filter which delegates to the wrapped {@link SeekableByteChannel}. + *

    + * A {@code FilterSeekableByteChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or + * providing additional functionality. The class {@code FilterSeekableByteChannel} itself simply overrides methods of {@code SeekableByteChannel} with versions + * that pass all requests to the wrapped channel. Subclasses of {@code FilterSeekableByteChannel} may of course override any methods declared or inherited by + * {@code FilterSeekableByteChannel}, and may also provide additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterSeekableByteChannel#FilterSeekableByteChannel(SeekableByteChannel) Channel constructor} and more + * advanced instances through the {@link Builder}. + *

    + * + * @param the {@link SeekableByteChannel} type. + * @see FilterInputStream + * @see FilterOutputStream + * @see FilterReader + * @see FilterWritableByteChannel + * @see ProxyInputStream + * @see ProxyOutputStream + * @see ProxyReader + * @see ProxyWriter + * @since 2.22.0 + */ +public class FilterSeekableByteChannel extends FilterByteChannel implements SeekableByteChannel { + + /** + * Builds instances of {@link FilterSeekableByteChannel} for subclasses. + * + * @param The {@link FilterSeekableByteChannel} type. + * @param The {@link SeekableByteChannel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder, C extends SeekableByteChannel, B extends AbstractBuilder> + extends FilterByteChannel.AbstractBuilder { + + /** + * Constructs a new builder for {@link FilterSeekableByteChannel}. + */ + public AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterSeekableByteChannel}. + */ + public static class Builder extends AbstractBuilder, SeekableByteChannel, Builder> { + + /** + * Builds instances of {@link FilterSeekableByteChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterSeekableByteChannel get() throws IOException { + return new FilterSeekableByteChannel<>(this); + } + } + + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forSeekableByteChannel() { + return new Builder(); + } + + FilterSeekableByteChannel(final Builder builder) throws IOException { + super(builder); + } + + /** + * Constructs a new instance. + * + * @param channel The channel to wrap. + */ + public FilterSeekableByteChannel(final C channel) { + super(channel); + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public SeekableByteChannel position(final long newPosition) throws IOException { + return channel.position(newPosition); + } + + @Override + public long size() throws IOException { + return channel.size(); + } + + @Override + public SeekableByteChannel truncate(final long size) throws IOException { + return channel.truncate(size); + } +} diff --git a/src/main/java/org/apache/commons/io/channels/FilterWritableByteChannel.java b/src/main/java/org/apache/commons/io/channels/FilterWritableByteChannel.java new file mode 100644 index 00000000000..f07e8988c91 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FilterWritableByteChannel.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.FilterReader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +import org.apache.commons.io.input.ProxyInputStream; +import org.apache.commons.io.input.ProxyReader; +import org.apache.commons.io.output.ProxyOutputStream; +import org.apache.commons.io.output.ProxyWriter; + +/** + * A {@link WritableByteChannel} filter which delegates to the wrapped {@link WritableByteChannel}. + *

    + * A {@code FilterWritableByteChannel} wraps some other channel, which it uses as its basic source of data, possibly transforming the data along the way or + * providing additional functionality. The class {@code FilterWritableByteChannel} itself simply overrides methods of {@code WritableByteChannel} with versions + * that pass all requests to the wrapped channel. Subclasses of {@code FilterWritableByteChannel} may of course override any methods declared or inherited by + * {@code WritableByteChannel}, and may also provide additional fields and methods. + *

    + *

    + * You construct s simple instance with the {@link FilterWritableByteChannel#FilterWritableByteChannel(WritableByteChannel) Channel constructor} and more + * advanced instances through the {@link Builder}. + *

    + * + * @param the {@link WritableByteChannel} type. + * @see FilterInputStream + * @see FilterOutputStream + * @see FilterReader + * @see FilterWritableByteChannel + * @see ProxyInputStream + * @see ProxyOutputStream + * @see ProxyReader + * @see ProxyWriter + * @since 2.22.0 + */ +public class FilterWritableByteChannel extends FilterChannel implements WritableByteChannel { + + /** + * Builds instances of {@link FilterWritableByteChannel} for subclasses. + * + * @param The {@link FilterWritableByteChannel} type. + * @param The {@link WritableByteChannel} type wrapped by the FilterChannel. + * @param The builder type. + */ + public abstract static class AbstractBuilder, C extends WritableByteChannel, B extends AbstractBuilder> + extends FilterChannel.AbstractBuilder { + + /** + * Constructs a new builder for {@link FilterWritableByteChannel}. + */ + public AbstractBuilder() { + // empty + } + } + + /** + * Builds instances of {@link FilterByteChannel}. + */ + public static class Builder extends AbstractBuilder, WritableByteChannel, Builder> { + + /** + * Builds instances of {@link FilterByteChannel}. + */ + protected Builder() { + // empty + } + + @Override + public FilterWritableByteChannel get() throws IOException { + return new FilterWritableByteChannel<>(this); + } + } + + /** + * Creates a new {@link Builder}. + * + * @return a new {@link Builder}. + */ + public static Builder forWritableByteChannel() { + return new Builder(); + } + + FilterWritableByteChannel(final Builder builder) throws IOException { + super(builder); + } + + /** + * Constructs a new instance. + * + * @param channel The channel to wrap. + */ + public FilterWritableByteChannel(final C channel) { + super(channel); + } + + @Override + public int write(final ByteBuffer src) throws IOException { + return channel.write(src); + } +} diff --git a/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java b/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java index 8595be79e8a..a8aa9a3d50b 100644 --- a/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java +++ b/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java @@ -30,7 +30,7 @@ public final class CharsetDecoders { /** * Returns the given non-null CharsetDecoder or a new default CharsetDecoder. *

    - * Null input maps to the virtual machine's {@link Charset#defaultCharset() default charset} decoder. + * Null input maps to the virtual machine's {@linkplain Charset#defaultCharset() default charset} decoder. *

    * * @param charsetDecoder The CharsetDecoder to test. diff --git a/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java b/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java index d0f9cd04be1..a1fff5cf1f3 100644 --- a/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java +++ b/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java @@ -31,7 +31,7 @@ public final class CharsetEncoders { /** * Returns the given non-null CharsetEncoder or a new default CharsetEncoder. *

    - * Null input maps to the virtual machine's {@link Charset#defaultCharset() default charset} decoder. + * Null input maps to the virtual machine's {@linkplain Charset#defaultCharset() default charset} decoder. *

    * * @param charsetEncoder The CharsetEncoder to test. diff --git a/src/main/java/org/apache/commons/io/comparator/CompositeFileComparator.java b/src/main/java/org/apache/commons/io/comparator/CompositeFileComparator.java index 0934f895f7a..dcc2d20428e 100644 --- a/src/main/java/org/apache/commons/io/comparator/CompositeFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/CompositeFileComparator.java @@ -58,7 +58,7 @@ public class CompositeFileComparator extends AbstractFileComparator implements S /** * Constructs a composite comparator for the set of delegate comparators. * - * @param delegates The delegate file comparators + * @param delegates The delegate file comparators. */ public CompositeFileComparator(@SuppressWarnings("unchecked") final Comparator... delegates) { this.delegates = delegates == null ? emptyArray() : delegates.clone(); @@ -67,7 +67,7 @@ public CompositeFileComparator(@SuppressWarnings("unchecked") final Comparator> delegates) { this.delegates = delegates == null ? emptyArray() @@ -77,8 +77,8 @@ public CompositeFileComparator(final Iterable> delegates) { /** * Compares the two files using delegate comparators. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return the first non-zero result returned from the delegate comparators or zero. */ @Override @@ -94,7 +94,7 @@ private Comparator[] emptyArray() { /** * String representation of this file comparator. * - * @return String representation of this file comparator + * @return String representation of this file comparator. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/comparator/DefaultFileComparator.java b/src/main/java/org/apache/commons/io/comparator/DefaultFileComparator.java index c447f085198..db580a1a2e1 100644 --- a/src/main/java/org/apache/commons/io/comparator/DefaultFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/DefaultFileComparator.java @@ -69,8 +69,8 @@ public DefaultFileComparator() { /** * Compares the two files using the {@link File#compareTo(File)} method. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return the result of calling file1's * {@link File#compareTo(File)} with file2 as the parameter. */ diff --git a/src/main/java/org/apache/commons/io/comparator/ExtensionFileComparator.java b/src/main/java/org/apache/commons/io/comparator/ExtensionFileComparator.java index 3b4e1e2e115..1d57ec8164f 100644 --- a/src/main/java/org/apache/commons/io/comparator/ExtensionFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/ExtensionFileComparator.java @@ -93,7 +93,7 @@ public ExtensionFileComparator() { /** * Constructs a file extension comparator instance with the specified case-sensitivity. * - * @param ioCase how to handle case sensitivity, null means case-sensitive + * @param ioCase how to handle case sensitivity, null means case-sensitive. */ public ExtensionFileComparator(final IOCase ioCase) { this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE); @@ -102,8 +102,8 @@ public ExtensionFileComparator(final IOCase ioCase) { /** * Compares the extensions of two files the specified case sensitivity. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return a negative value if the first file's extension * is less than the second, zero if the extensions are the * same and a positive value if the first files extension @@ -119,7 +119,7 @@ public int compare(final File file1, final File file2) { /** * String representation of this file comparator. * - * @return String representation of this file comparator + * @return String representation of this file comparator. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/comparator/NameFileComparator.java b/src/main/java/org/apache/commons/io/comparator/NameFileComparator.java index 41590fa6636..7e92a7fcb74 100644 --- a/src/main/java/org/apache/commons/io/comparator/NameFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/NameFileComparator.java @@ -89,7 +89,7 @@ public NameFileComparator() { /** * Constructs a file name comparator instance with the specified case-sensitivity. * - * @param ioCase how to handle case sensitivity, null means case-sensitive + * @param ioCase how to handle case sensitivity, null means case-sensitive. */ public NameFileComparator(final IOCase ioCase) { this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE); @@ -98,8 +98,8 @@ public NameFileComparator(final IOCase ioCase) { /** * Compares the names of two files with the specified case sensitivity. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return a negative value if the first file's name * is less than the second, zero if the names are the * same and a positive value if the first files name @@ -113,7 +113,7 @@ public int compare(final File file1, final File file2) { /** * String representation of this file comparator. * - * @return String representation of this file comparator + * @return String representation of this file comparator. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/comparator/PathFileComparator.java b/src/main/java/org/apache/commons/io/comparator/PathFileComparator.java index 1f41eb74185..4ae379add58 100644 --- a/src/main/java/org/apache/commons/io/comparator/PathFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/PathFileComparator.java @@ -51,6 +51,7 @@ *

    * Serialization is deprecated and will be removed in 3.0. *

    + * * @since 1.4 */ public class PathFileComparator extends AbstractFileComparator implements Serializable { @@ -88,7 +89,7 @@ public PathFileComparator() { /** * Constructs a file path comparator instance with the specified case-sensitivity. * - * @param ioCase how to handle case sensitivity, null means case-sensitive + * @param ioCase how to handle case sensitivity, null means case-sensitive. */ public PathFileComparator(final IOCase ioCase) { this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE); @@ -97,8 +98,8 @@ public PathFileComparator(final IOCase ioCase) { /** * Compares the paths of two files the specified case sensitivity. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return a negative value if the first file's path * is less than the second, zero if the paths are the * same and a positive value if the first files path @@ -112,7 +113,7 @@ public int compare(final File file1, final File file2) { /** * String representation of this file comparator. * - * @return String representation of this file comparator + * @return String representation of this file comparator. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/comparator/SizeFileComparator.java b/src/main/java/org/apache/commons/io/comparator/SizeFileComparator.java index 5154d9b3256..a5ff78c0e59 100644 --- a/src/main/java/org/apache/commons/io/comparator/SizeFileComparator.java +++ b/src/main/java/org/apache/commons/io/comparator/SizeFileComparator.java @@ -107,8 +107,8 @@ public SizeFileComparator(final boolean sumDirectoryContents) { /** * Compares the length of two files. * - * @param file1 The first file to compare - * @param file2 The second file to compare + * @param file1 The first file to compare. + * @param file2 The second file to compare. * @return a negative value if the first file's length * is less than the second, zero if the lengths are the * same and a positive value if the first files length @@ -141,7 +141,7 @@ public int compare(final File file1, final File file2) { /** * String representation of this file comparator. * - * @return String representation of this file comparator + * @return String representation of this file comparator. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/doc-files/leaf.svg b/src/main/java/org/apache/commons/io/doc-files/leaf.svg new file mode 100644 index 00000000000..71de588c648 --- /dev/null +++ b/src/main/java/org/apache/commons/io/doc-files/leaf.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/apache/commons/io/doc-files/logo.png b/src/main/java/org/apache/commons/io/doc-files/logo.png new file mode 100644 index 00000000000..02a758f0ed8 Binary files /dev/null and b/src/main/java/org/apache/commons/io/doc-files/logo.png differ diff --git a/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java index 1551231cfaf..3be7a325315 100644 --- a/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java +++ b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java @@ -195,7 +195,6 @@ public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter f * @since 2.12.0 * @deprecated Use {@link #builder()}. */ - @SuppressWarnings("deprecation") @Deprecated public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter, final IOBiFunction visitFileFailed) { @@ -251,10 +250,10 @@ public int hashCode() { * Relativizes each directory path with {@link Path#relativize(Path)} against the given {@code parent}, optionally * sorting the result. * - * @param parent A parent path - * @param sort Whether to sort + * @param parent A parent path. + * @param sort Whether to sort. * @param comparator How to sort, null uses default sorting. - * @return A new list + * @return A new list. */ public List relativizeDirectories(final Path parent, final boolean sort, final Comparator comparator) { @@ -265,10 +264,10 @@ public List relativizeDirectories(final Path parent, final boolean sort, * Relativizes each file path with {@link Path#relativize(Path)} against the given {@code parent}, optionally * sorting the result. * - * @param parent A parent path - * @param sort Whether to sort + * @param parent A parent path. + * @param sort Whether to sort. * @param comparator How to sort, null uses default sorting. - * @return A new list + * @return A new list. */ public List relativizeFiles(final Path parent, final boolean sort, final Comparator comparator) { diff --git a/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java b/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java index 7172b2d0671..45b761c971c 100644 --- a/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java +++ b/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java @@ -48,8 +48,8 @@ private static CopyOption[] toCopyOption(final CopyOption... copyOptions) { * Constructs an instance that copies all files. * * @param pathCounter How to count visits. - * @param sourceDirectory The source directory - * @param targetDirectory The target directory + * @param sourceDirectory The source directory. + * @param targetDirectory The target directory. * @param copyOptions Specifies how the copying should be done. */ public CopyDirectoryVisitor(final PathCounters pathCounter, final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) { @@ -65,8 +65,8 @@ public CopyDirectoryVisitor(final PathCounters pathCounter, final Path sourceDir * @param pathCounter How to count visits. * @param fileFilter How to filter file paths. * @param dirFilter How to filter directory paths. - * @param sourceDirectory The source directory - * @param targetDirectory The target directory + * @param sourceDirectory The source directory. + * @param targetDirectory The target directory. * @param copyOptions Specifies how the copying should be done. * @since 2.9.0 */ diff --git a/src/main/java/org/apache/commons/io/file/Counters.java b/src/main/java/org/apache/commons/io/file/Counters.java index b1abd721d9c..10e05dc48f7 100644 --- a/src/main/java/org/apache/commons/io/file/Counters.java +++ b/src/main/java/org/apache/commons/io/file/Counters.java @@ -455,7 +455,7 @@ public static PathCounters noopPathCounters() { /** * Construct a new instance. * - * @deprecated Will be private in 4.0 + * @deprecated Will be private in 3.0. */ @Deprecated public Counters() { diff --git a/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java b/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java index ab02ad073e2..3f445010e26 100644 --- a/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java +++ b/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java @@ -28,7 +28,6 @@ import org.apache.commons.io.file.Counters.PathCounters; import org.apache.commons.io.filefilter.IOFileFilter; -import org.apache.commons.io.filefilter.SymbolicLinkFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.io.function.IOBiFunction; @@ -152,7 +151,7 @@ static UnaryOperator defaultDirectoryTransformer() { } static IOFileFilter defaultFileFilter() { - return new SymbolicLinkFileFilter(FileVisitResult.TERMINATE, FileVisitResult.CONTINUE); + return TrueFileFilter.INSTANCE; } static PathCounters defaultPathCounters() { diff --git a/src/main/java/org/apache/commons/io/file/PathFilter.java b/src/main/java/org/apache/commons/io/file/PathFilter.java index dbeef906261..ee54de29deb 100644 --- a/src/main/java/org/apache/commons/io/file/PathFilter.java +++ b/src/main/java/org/apache/commons/io/file/PathFilter.java @@ -34,7 +34,7 @@ public interface PathFilter { * * @param path The Path to test. * @param attributes the path's basic attributes (may be null). - * @return a FileVisitResult + * @return a FileVisitResult. */ FileVisitResult accept(Path path, BasicFileAttributes attributes); } diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java index 055bbe06a0b..305af952a49 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -137,9 +137,15 @@ private static boolean equalsIgnoreFileSystem(final Path path1, final Path path2 return extractKey(separator1, string1).equals(extractKey(separator2, string2)); } + /** + * Replaces the file separator in a path string with a string that is not legal in a path on Windows, Linux, and macOS. + * + * @param separator the file separator. + * @param string a path. + * @return a key. + */ static String extractKey(final String separator, final String string) { - // Replace the file separator in a path string with a string that is not legal in a path on Windows, Linux, and macOS. - return string.replaceAll("\\" + separator, ">"); + return string.replace(separator, ">"); } final boolean equals; @@ -200,32 +206,38 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING }; private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND }; + /** * Empty {@link CopyOption} array. * * @since 2.8.0 */ public static final CopyOption[] EMPTY_COPY_OPTIONS = {}; + /** * Empty {@link DeleteOption} array. * * @since 2.8.0 */ public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {}; + /** * Empty {@link FileAttribute} array. * * @since 2.13.0 */ public static final FileAttribute[] EMPTY_FILE_ATTRIBUTE_ARRAY = {}; + /** * Empty {@link FileVisitOption} array. */ public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {}; + /** * Empty {@link LinkOption} array. */ public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {}; + /** * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}. * @@ -234,16 +246,19 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth */ @Deprecated public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS }; + /** * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}. * * @since 2.12.0 */ static final LinkOption NULL_LINK_OPTION = null; + /** * Empty {@link OpenOption} array. */ public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {}; + /** * Empty {@link Path} array. * @@ -266,7 +281,10 @@ private static AccumulatorPathVisitor accumulate(final Path directory, final int } /** - * Cleans a directory by only deleting files, including in subdirectories, but without deleting the directories. + * Cleans a directory by only deleting files, including in subdirectories, without deleting directories. + *

    + * This leaves a directory empty of files but the directory and any subdirectories remain. + *

    * * @param directory directory to clean. * @return The visitation path counters. @@ -277,7 +295,10 @@ public static PathCounters cleanDirectory(final Path directory) throws IOExcepti } /** - * Cleans a directory by only deleting files, including in subdirectories, but without deleting the directories. + * Cleans a directory by only deleting files, including in subdirectories, without deleting directories. + *

    + * This leaves a directory empty of files but the directory and any subdirectories remain. + *

    * * @param directory directory to clean. * @param deleteOptions How to handle deletion. @@ -378,7 +399,7 @@ public static PathCounters copyDirectory(final Path sourceDirectory, final Path * @param sourceFile The source URL. * @param targetFile The target file. * @param copyOptions Specifies how the copying should be done. - * @return The target file + * @return The target file. * @throws IOException if an I/O error occurs. * @see Files#copy(InputStream, Path, CopyOption...) */ @@ -393,7 +414,7 @@ public static Path copyFile(final URL sourceFile, final Path targetFile, final C * @param sourceFile The source file. * @param targetDirectory The target directory. * @param copyOptions Specifies how the copying should be done. - * @return The target file + * @return The target file. * @throws IOException if an I/O error occurs. * @see Files#copy(Path, Path, CopyOption...) */ @@ -410,7 +431,7 @@ public static Path copyFileToDirectory(final Path sourceFile, final Path targetD * @param sourceFile The source URL. * @param targetDirectory The target directory. * @param copyOptions Specifies how the copying should be done. - * @return The target file + * @return The target file. * @throws IOException if an I/O error occurs. * @see Files#copy(InputStream, Path, CopyOption...) */ @@ -499,7 +520,7 @@ public static Path current() { *

    *
      *
    • A directory to delete does not have to be empty.
    • - *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean. + *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
    • *
    * * @param path file or directory to delete, must not be {@code null} @@ -518,7 +539,7 @@ public static PathCounters delete(final Path path) throws IOException { *

    *
      *
    • A directory to delete does not have to be empty.
    • - *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean. + *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
    • *
    * * @param path file or directory to delete, must not be {@code null} @@ -540,7 +561,7 @@ public static PathCounters delete(final Path path, final DeleteOption... deleteO *

    *
      *
    • A directory to delete does not have to be empty.
    • - *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean. + *
    • You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
    • *
    * * @param path file or directory to delete, must not be {@code null} @@ -603,7 +624,7 @@ public static PathCounters deleteDirectory(final Path directory, final LinkOptio * @param file The file to delete. * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. * @throws IOException if an I/O error occurs. - * @throws NoSuchFileException if the file is a directory + * @throws NoSuchFileException if the file is a directory. */ public static PathCounters deleteFile(final Path file) throws IOException { return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY); @@ -771,7 +792,7 @@ public static boolean directoryContentEquals(final Path path1, final Path path2) * @param path2 The second directory. * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. * @param linkOptions options to follow links. - * @param fileVisitOptions options to configure the traversal + * @param fileVisitOptions options to configure the traversal. * @return Whether the two directories contain the same files without considering file contents. * @throws IOException if an I/O error is thrown by a visitor method. */ @@ -944,7 +965,7 @@ public static AclFileAttributeView getAclFileAttributeView(final Path path, fina * Will return the file name itself if it doesn't contain any periods. All leading directories of the {@code file name} parameter are skipped. *

    * - * @return the base name of file name + * @return the base name of file name. * @param path the path of the file to obtain the base name of. * @since 2.16.0 */ @@ -1136,7 +1157,7 @@ public static Path getTempDirectory() { * {@code Files.isDirectory(Path path, LinkOption... options)}. * * @param path the path to the file. - * @param options options indicating how to handle symbolic links + * @param options options indicating how to handle symbolic links. * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be * determined if the file is a directory or not. * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String) @@ -1240,7 +1261,7 @@ public static boolean isNewer(final Path file, final Instant instant, final Link * Tests if the given {@link Path} is newer than the given time reference. * * @param file the {@link Path} to test. - * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970) + * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970). * @param options options indicating how to handle symbolic links. * @return true if the {@link Path} exists and has been modified after the given time reference. * @throws IOException if an I/O error occurs. @@ -1301,7 +1322,7 @@ public static boolean isOlder(final Path file, final Instant instant, final Link * Tests if the given {@link Path} is older than the given time reference. * * @param file the {@link Path} to test. - * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970) + * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970). * @param options options indicating how to handle symbolic links. * @return true if the {@link Path} exists and has been modified before the given time reference. * @throws IOException if an I/O error occurs. @@ -1412,7 +1433,7 @@ private static boolean notExists(final Path path, final LinkOption... options) { /** * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. * - * @param deleteOptions the array to test + * @param deleteOptions the array to test. * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. */ private static boolean overrideReadOnly(final DeleteOption... deleteOptions) { @@ -1425,7 +1446,7 @@ private static boolean overrideReadOnly(final DeleteOption... deleteOptions) { /** * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read. * - * @param The {@link BasicFileAttributes} type + * @param The {@link BasicFileAttributes} type. * @param path The Path to test. * @param type the {@link Class} of the file attributes required to read. * @param options options indicating how to handle symbolic links. @@ -1827,7 +1848,7 @@ private static List toSortedList(final Iterable rootDirectories) { * @param file the file to touch. * @return The given file. * @throws NullPointerException if the parameter is {@code null}. - * @throws IOException if setting the last-modified time failed or an I/O problem occurs.\ + * @throws IOException if setting the last-modified time failed or an I/O problem occurs. * @since 2.12.0 */ public static Path touch(final Path file) throws IOException { @@ -1957,8 +1978,8 @@ public static boolean waitFor(final Path file, final Duration timeout, final Lin * closed stream causes a {@link IllegalStateException}. *

    * - * @param start the start path - * @param pathFilter the path filter + * @param start the start path. + * @param pathFilter the path filter. * @param maxDepth the maximum depth of directories to walk. * @param readAttributes whether to call the filters with file attributes (false passes null). * @param options the options to configure the walk. diff --git a/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java b/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java index e66dff6473e..0465b77b41b 100644 --- a/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java +++ b/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java @@ -22,7 +22,7 @@ import org.apache.commons.io.IOUtils; /** - * Defines the standard delete options. + * Enumerates the standard delete options. * * @since 2.8.0 */ @@ -38,7 +38,7 @@ public enum StandardDeleteOption implements DeleteOption { * * For now, assume the array is not sorted. * - * @param options the array to test + * @param options the array to test. * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. */ public static boolean overrideReadOnly(final DeleteOption[] options) { diff --git a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java index 35725357f9c..2878d8752d7 100644 --- a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java +++ b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java @@ -308,7 +308,7 @@ static long toNtfsTime(final Instant instant) { * An NTFS file time is a 64-bit value for the number of 100-nanosecond intervals since 12:00 A.M. January 1, 1601 Coordinated Universal Time (UTC). *

    * - * @param javaTime the Java time + * @param javaTime the Java time. * @return the NTFS time, 100-nanosecond units since 1 January 1601. * @since 2.16.0 */ diff --git a/src/main/java/org/apache/commons/io/file/spi/FileSystemProviders.java b/src/main/java/org/apache/commons/io/file/spi/FileSystemProviders.java index 2483cef0a20..4e83ffe1e8c 100644 --- a/src/main/java/org/apache/commons/io/file/spi/FileSystemProviders.java +++ b/src/main/java/org/apache/commons/io/file/spi/FileSystemProviders.java @@ -39,7 +39,7 @@ public class FileSystemProviders { // NOPMD Class will be final in 3.0. /** * Gets the {@link FileSystemProvider} for the given Path. * - * @param path The Path to query + * @param path The Path to query. * @return the {@link FileSystemProvider} for the given Path. */ @SuppressWarnings("resource") // FileSystem is not allocated here. @@ -86,7 +86,7 @@ public FileSystemProvider getFileSystemProvider(final String scheme) { /** * Gets the {@link FileSystemProvider} for the given URI. * - * @param uri The URI to query + * @param uri The URI to query. * @return the {@link FileSystemProvider} for the given URI or null. */ public FileSystemProvider getFileSystemProvider(final URI uri) { @@ -96,7 +96,7 @@ public FileSystemProvider getFileSystemProvider(final URI uri) { /** * Gets the {@link FileSystemProvider} for the given URL. * - * @param url The URL to query + * @param url The URL to query. * @return the {@link FileSystemProvider} for the given URI or null. */ public FileSystemProvider getFileSystemProvider(final URL url) { diff --git a/src/main/java/org/apache/commons/io/filefilter/AbstractFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/AbstractFileFilter.java index c71867d14da..b16e86eaf14 100644 --- a/src/main/java/org/apache/commons/io/filefilter/AbstractFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/AbstractFileFilter.java @@ -77,8 +77,8 @@ protected AbstractFileFilter(final FileVisitResult onAccept, final FileVisitResu /** * Tests to see if the File should be accepted by this filter. * - * @param file the File to check - * @return true if this file matches the test + * @param file the File to check. + * @return true if this file matches the test. */ @Override public boolean accept(final File file) { @@ -89,9 +89,9 @@ public boolean accept(final File file) { /** * Tests to see if the File should be accepted by this filter. * - * @param dir the directory File to check - * @param name the file name within the directory to check - * @return true if this file matches the test + * @param dir the directory File to check. + * @param name the file name within the directory to check. + * @return true if this file matches the test. */ @Override public boolean accept(final File dir, final String name) { @@ -167,7 +167,7 @@ FileVisitResult toFileVisitResult(final boolean accept) { /** * Provides a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java index 35542cfd486..9d95794ce28 100644 --- a/src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java @@ -89,7 +89,7 @@ public class AgeFileFilter extends AbstractFileFilter implements Serializable { /** * Constructs a new age file filter for files older than (at or before) a certain cutoff date. * - * @param cutoffDate the threshold age of the files + * @param cutoffDate the threshold age of the files. */ public AgeFileFilter(final Date cutoffDate) { this(cutoffDate, true); @@ -98,7 +98,7 @@ public AgeFileFilter(final Date cutoffDate) { /** * Constructs a new age file filter for files on any one side of a certain cutoff date. * - * @param cutoffDate the threshold age of the files + * @param cutoffDate the threshold age of the files. * @param acceptOlder if true, older files (at or before the cutoff) are accepted, else newer ones (after the * cutoff). */ @@ -110,7 +110,7 @@ public AgeFileFilter(final Date cutoffDate, final boolean acceptOlder) { * Constructs a new age file filter for files older than (at or before) a certain File (whose last modification time * will be used as reference). * - * @param cutoffReference the file whose last modification time is used as the threshold age of the files + * @param cutoffReference the file whose last modification time is used as the threshold age of the files. */ public AgeFileFilter(final File cutoffReference) { this(cutoffReference, true); @@ -120,7 +120,7 @@ public AgeFileFilter(final File cutoffReference) { * Constructs a new age file filter for files on any one side of a certain File (whose last modification time will * be used as reference). * - * @param cutoffReference the file whose last modification time is used as the threshold age of the files + * @param cutoffReference the file whose last modification time is used as the threshold age of the files. * @param acceptOlder if true, older files (at or before the cutoff) are accepted, else newer ones (after the * cutoff). */ @@ -179,8 +179,8 @@ public AgeFileFilter(final long cutoffMillis, final boolean acceptOlder) { * modification time equals cutoff and older files are required, file IS selected. *

    * - * @param file the File to check - * @return true if the file name matches + * @param file the File to check. + * @return true if the file name matches. */ @Override public boolean accept(final File file) { @@ -194,9 +194,9 @@ public boolean accept(final File file) { * modification time equals cutoff and older files are required, file IS selected. *

    * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name matches + * @return true if the file name matches. * @since 2.9.0 */ @Override @@ -207,7 +207,7 @@ public FileVisitResult accept(final Path file, final BasicFileAttributes attribu /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java index f8da16bcb0e..5b9a142f70c 100644 --- a/src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java @@ -17,7 +17,6 @@ package org.apache.commons.io.filefilter; import java.io.File; -import java.io.FileFilter; import java.io.Serializable; import java.nio.file.FileVisitResult; import java.nio.file.Path; @@ -29,7 +28,7 @@ import java.util.stream.Stream; /** - * A {@link FileFilter} providing conditional AND logic across a list of + * A {@link ConditionalFileFilter} providing conditional AND logic across a list of * file filters. This filter returns {@code true} if all filters in the * list return {@code true}. Otherwise, it returns {@code false}. * Checking of the file filter list stops when the first filter returns @@ -90,9 +89,9 @@ public AndFileFilter(final IOFileFilter... fileFilters) { /** * Constructs a new file filter that ANDs the result of other filters. * - * @param filter1 the first filter, must second be null - * @param filter2 the first filter, must not be null - * @throws IllegalArgumentException if either filter is null + * @param filter1 the first filter, must second be null. + * @param filter2 the first filter, must not be null. + * @throws IllegalArgumentException if either filter is null. */ public AndFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) { this(2); @@ -129,6 +128,7 @@ public boolean accept(final File file, final String name) { /** * {@inheritDoc} + * * @since 2.9.0 */ @Override @@ -187,7 +187,7 @@ public void setFileFilters(final List fileFilters) { /** * Builds a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/CanExecuteFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/CanExecuteFileFilter.java index 3941a9d74a8..8f262806119 100644 --- a/src/main/java/org/apache/commons/io/filefilter/CanExecuteFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/CanExecuteFileFilter.java @@ -24,7 +24,7 @@ import java.nio.file.attribute.BasicFileAttributes; /** - * This filter accepts {@link File}s that can be executed. + * This filter accepts {@link File}s that are executable. *

    * Example, showing how to print out a list of the * current directory's executable files: diff --git a/src/main/java/org/apache/commons/io/filefilter/CanReadFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/CanReadFileFilter.java index da376e0f4f5..58d2ad7a50c 100644 --- a/src/main/java/org/apache/commons/io/filefilter/CanReadFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/CanReadFileFilter.java @@ -24,7 +24,7 @@ import java.nio.file.attribute.BasicFileAttributes; /** - * This filter accepts {@link File}s that can be read. + * This filter accepts {@link File}s that are readable. *

    * Example, showing how to print out a list of the current directory's readable files: *

    diff --git a/src/main/java/org/apache/commons/io/filefilter/CanWriteFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/CanWriteFileFilter.java index 899481c6465..db209b4a50e 100644 --- a/src/main/java/org/apache/commons/io/filefilter/CanWriteFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/CanWriteFileFilter.java @@ -24,7 +24,7 @@ import java.nio.file.attribute.BasicFileAttributes; /** - * This filter accepts {@link File}s that can be written to. + * This filter accepts {@link File}s that are writable. *

    * Example, showing how to print out a list of the current directory's writable files: *

    @@ -54,6 +54,7 @@ * Serialization is deprecated and will be removed in 3.0. *

    * + * @see Files#isWritable(Path) * @since 1.3 */ public class CanWriteFileFilter extends AbstractFileFilter implements Serializable { @@ -75,7 +76,7 @@ protected CanWriteFileFilter() { /** * Tests to see if the file can be written to. * - * @param file the File to check + * @param file the File to check. * @return {@code true} if the file can be written to, otherwise {@code false}. */ @Override @@ -86,7 +87,7 @@ public boolean accept(final File file) { /** * Tests to see if the file can be written to. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). * @return {@code true} if the file can be written to, otherwise {@code false}. * @since 2.9.0 diff --git a/src/main/java/org/apache/commons/io/filefilter/ConditionalFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/ConditionalFileFilter.java index d59996a0461..96ff4947b5c 100644 --- a/src/main/java/org/apache/commons/io/filefilter/ConditionalFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/ConditionalFileFilter.java @@ -29,7 +29,7 @@ public interface ConditionalFileFilter { * Adds the specified file filter to the list of file filters at the end of * the list. * - * @param ioFileFilter the filter to be added + * @param ioFileFilter the filter to be added. * @since 1.1 */ void addFileFilter(IOFileFilter ioFileFilter); @@ -37,7 +37,7 @@ public interface ConditionalFileFilter { /** * Gets this conditional file filter's list of file filters. * - * @return the file filter list + * @return the file filter list. * @since 1.1 */ List getFileFilters(); @@ -45,9 +45,9 @@ public interface ConditionalFileFilter { /** * Removes the specified file filter. * - * @param ioFileFilter filter to be removed + * @param ioFileFilter filter to be removed. * @return {@code true} if the filter was found in the list, - * {@code false} otherwise + * {@code false} otherwise. * @since 1.1 */ boolean removeFileFilter(IOFileFilter ioFileFilter); @@ -56,7 +56,7 @@ public interface ConditionalFileFilter { * Sets the list of file filters, replacing any previously configured * file filters on this filter. * - * @param fileFilters the list of filters + * @param fileFilters the list of filters. * @since 1.1 */ void setFileFilters(List fileFilters); diff --git a/src/main/java/org/apache/commons/io/filefilter/DelegateFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/DelegateFileFilter.java index 4137644ca1d..dd8674f3916 100644 --- a/src/main/java/org/apache/commons/io/filefilter/DelegateFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/DelegateFileFilter.java @@ -39,13 +39,14 @@ public class DelegateFileFilter extends AbstractFileFilter implements Serializab /** The File filter */ private final transient FileFilter fileFilter; + /** The Filename filter */ private final transient FilenameFilter fileNameFilter; /** * Constructs a delegate file filter around an existing FileFilter. * - * @param fileFilter the filter to decorate + * @param fileFilter the filter to decorate. */ public DelegateFileFilter(final FileFilter fileFilter) { Objects.requireNonNull(fileFilter, "filter"); @@ -56,7 +57,7 @@ public DelegateFileFilter(final FileFilter fileFilter) { /** * Constructs a delegate file filter around an existing FilenameFilter. * - * @param fileNameFilter the filter to decorate + * @param fileNameFilter the filter to decorate. */ public DelegateFileFilter(final FilenameFilter fileNameFilter) { Objects.requireNonNull(fileNameFilter, "filter"); @@ -67,8 +68,8 @@ public DelegateFileFilter(final FilenameFilter fileNameFilter) { /** * Tests the filter. * - * @param file the file to check - * @return true if the filter matches + * @param file the file to check. + * @return true if the filter matches. */ @Override public boolean accept(final File file) { @@ -81,9 +82,9 @@ public boolean accept(final File file) { /** * Tests the filter. * - * @param dir the directory - * @param name the file name in the directory - * @return true if the filter matches + * @param dir the directory. + * @param name the file name in the directory. + * @return true if the filter matches. */ @Override public boolean accept(final File dir, final String name) { @@ -96,7 +97,7 @@ public boolean accept(final File dir, final String name) { /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/DirectoryFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/DirectoryFileFilter.java index bb3d615bd43..b0c54b42ec4 100644 --- a/src/main/java/org/apache/commons/io/filefilter/DirectoryFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/DirectoryFileFilter.java @@ -92,8 +92,8 @@ protected DirectoryFileFilter() { /** * Tests to see if the file is a directory. * - * @param file the File to check - * @return true if the file is a directory + * @param file the File to check. + * @return true if the file is a directory. */ @Override public boolean accept(final File file) { @@ -103,9 +103,9 @@ public boolean accept(final File file) { /** * Tests to see if the file is a directory. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file is a directory + * @return true if the file is a directory. * @since 2.9.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/filefilter/EmptyFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/EmptyFileFilter.java index a5cdd676bb1..392d2cf0f12 100644 --- a/src/main/java/org/apache/commons/io/filefilter/EmptyFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/EmptyFileFilter.java @@ -99,7 +99,7 @@ protected EmptyFileFilter() { /** * Tests to see if the file is empty. * - * @param file the file or directory to check + * @param file the file or directory to check. * @return {@code true} if the file or directory is empty, otherwise {@code false}. */ @Override @@ -117,7 +117,7 @@ public boolean accept(final File file) { /** * Tests to see if the file is empty. * - * @param file the file or directory to check + * @param file the file or directory to check. * @param attributes the path's basic attributes (may be null). * @return {@code true} if the file or directory is empty, otherwise {@code false}. * @since 2.9.0 diff --git a/src/main/java/org/apache/commons/io/filefilter/FalseFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/FalseFileFilter.java index ebaa77b2007..7054a7c68f2 100644 --- a/src/main/java/org/apache/commons/io/filefilter/FalseFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/FalseFileFilter.java @@ -60,8 +60,8 @@ protected FalseFileFilter() { /** * Returns false. * - * @param file the file to check (ignored) - * @return false + * @param file the file to check (ignored). + * @return false. */ @Override public boolean accept(final File file) { @@ -71,9 +71,9 @@ public boolean accept(final File file) { /** * Returns false. * - * @param dir the directory to check (ignored) - * @param name the file name (ignored) - * @return false + * @param dir the directory to check (ignored). + * @param name the file name (ignored). + * @return false. */ @Override public boolean accept(final File dir, final String name) { @@ -83,9 +83,9 @@ public boolean accept(final File dir, final String name) { /** * Returns false. * - * @param file the file to check (ignored) + * @param file the file to check (ignored). * @param attributes the path's basic attributes (may be null). - * @return false + * @return {@link FileVisitResult#TERMINATE}. * @since 2.9.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/filefilter/FileFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/FileFileFilter.java index 801d39f07b4..4bab6adf49e 100644 --- a/src/main/java/org/apache/commons/io/filefilter/FileFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/FileFileFilter.java @@ -92,8 +92,8 @@ protected FileFileFilter() { /** * Tests to see if the file is a file. * - * @param file the File to check - * @return true if the file is a file + * @param file the File to check. + * @return true if the file is a file. */ @Override public boolean accept(final File file) { @@ -103,9 +103,9 @@ public boolean accept(final File file) { /** * Tests to see if the file is a file. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file is a file + * @return true if the file is a file. * @since 2.9.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java b/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java index 5e37676ac9c..74715ef6158 100644 --- a/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java +++ b/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java @@ -55,8 +55,8 @@ public class FileFilterUtils { * Returns a filter that returns true if the file was last modified before * or at the specified cutoff date. * - * @param cutoffDate the time threshold - * @return an appropriately configured age file filter + * @param cutoffDate the time threshold. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -67,9 +67,9 @@ public static IOFileFilter ageFileFilter(final Date cutoffDate) { /** * Returns a filter that filters files based on a cutoff date. * - * @param cutoffDate the time threshold - * @param acceptOlder if true, older files get accepted, if false, newer - * @return an appropriately configured age file filter + * @param cutoffDate the time threshold. + * @param acceptOlder if true, older files get accepted, if false, newer. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -82,8 +82,8 @@ public static IOFileFilter ageFileFilter(final Date cutoffDate, final boolean ac * or at the same time as the specified reference file. * * @param cutoffReference the file whose last modification - * time is used as the threshold age of the files - * @return an appropriately configured age file filter + * time is used as the threshold age of the files. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -95,9 +95,9 @@ public static IOFileFilter ageFileFilter(final File cutoffReference) { * Returns a filter that filters files based on a cutoff reference file. * * @param cutoffReference the file whose last modification - * time is used as the threshold age of the files - * @param acceptOlder if true, older files get accepted, if false, newer - * @return an appropriately configured age file filter + * time is used as the threshold age of the files. + * @param acceptOlder if true, older files get accepted, if false, newer. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -109,8 +109,8 @@ public static IOFileFilter ageFileFilter(final File cutoffReference, final boole * Returns a filter that returns true if the file was last modified before * or at the specified cutoff time. * - * @param cutoffMillis the time threshold - * @return an appropriately configured age file filter + * @param cutoffMillis the time threshold. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -121,9 +121,9 @@ public static IOFileFilter ageFileFilter(final long cutoffMillis) { /** * Returns a filter that filters files based on a cutoff time. * - * @param cutoffMillis the time threshold - * @param acceptOlder if true, older files get accepted, if false, newer - * @return an appropriately configured age file filter + * @param cutoffMillis the time threshold. + * @param acceptOlder if true, older files get accepted, if false, newer. + * @return an appropriately configured age file filter. * @see AgeFileFilter * @since 1.2 */ @@ -135,7 +135,7 @@ public static IOFileFilter ageFileFilter(final long cutoffMillis, final boolean * Returns a filter that ANDs the specified filters. * * @param filters the IOFileFilters that will be ANDed together. - * @return a filter that ANDs the specified filters + * @return a filter that ANDs the specified filters. * @throws IllegalArgumentException if the filters are null or contain a * null value. * @see AndFileFilter @@ -148,12 +148,12 @@ public static IOFileFilter and(final IOFileFilter... filters) { /** * Returns a filter that ANDs the two specified filters. * - * @param filter1 the first filter - * @param filter2 the second filter - * @return a filter that ANDs the two specified filters + * @param filter1 the first filter. + * @param filter2 the second filter. + * @return a filter that ANDs the two specified filters. * @see #and(IOFileFilter...) * @see AndFileFilter - * @deprecated use {@link #and(IOFileFilter...)} + * @deprecated Use {@link #and(IOFileFilter...)} */ @Deprecated public static IOFileFilter andFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) { @@ -164,8 +164,8 @@ public static IOFileFilter andFileFilter(final IOFileFilter filter1, final IOFil * Returns an {@link IOFileFilter} that wraps the * {@link FileFilter} instance. * - * @param filter the filter to be wrapped - * @return a new filter that implements IOFileFilter + * @param filter the filter to be wrapped. + * @return a new filter that implements IOFileFilter. * @see DelegateFileFilter */ public static IOFileFilter asFileFilter(final FileFilter filter) { @@ -176,8 +176,8 @@ public static IOFileFilter asFileFilter(final FileFilter filter) { * Returns an {@link IOFileFilter} that wraps the * {@link FilenameFilter} instance. * - * @param filter the filter to be wrapped - * @return a new filter that implements IOFileFilter + * @param filter the filter to be wrapped. + * @return a new filter that implements IOFileFilter. * @see DelegateFileFilter */ public static IOFileFilter asFileFilter(final FilenameFilter filter) { @@ -187,7 +187,7 @@ public static IOFileFilter asFileFilter(final FilenameFilter filter) { /** * Returns a filter that checks if the file is a directory. * - * @return file filter that accepts only directories and not files + * @return file filter that accepts only directories and not files. * @see DirectoryFileFilter#DIRECTORY */ public static IOFileFilter directoryFileFilter() { @@ -197,7 +197,7 @@ public static IOFileFilter directoryFileFilter() { /** * Returns a filter that always returns false. * - * @return a false filter + * @return a false filter. * @see FalseFileFilter#FALSE */ public static IOFileFilter falseFileFilter() { @@ -207,7 +207,7 @@ public static IOFileFilter falseFileFilter() { /** * Returns a filter that checks if the file is a file (and not a directory). * - * @return file filter that accepts only files and not directories + * @return file filter that accepts only files and not directories. * @see FileFileFilter#INSTANCE */ public static IOFileFilter fileFileFilter() { @@ -226,13 +226,13 @@ public static IOFileFilter fileFileFilter() { * Set<File> javaFiles = FileFilterUtils.filterSet(allFiles, * FileFilterUtils.suffixFileFilter(".java")); *
    + * * @param filter the filter to apply to the set of files. * @param files the array of files to apply the filter to. * @return a subset of {@code files} that is accepted by the * file filter. * @throws NullPointerException if the filter is {@code null} * or {@code files} contains a {@code null} value. - * * @since 2.0 */ public static File[] filter(final IOFileFilter filter, final File... files) { @@ -259,13 +259,13 @@ public static File[] filter(final IOFileFilter filter, final File... files) { * Set<File> javaFiles = FileFilterUtils.filterSet(allFiles, * FileFilterUtils.suffixFileFilter(".java")); *
    + * * @param filter the filter to apply to the set of files. * @param files the array of files to apply the filter to. * @return a subset of {@code files} that is accepted by the * file filter. * @throws IllegalArgumentException if the filter is {@code null} * or {@code files} contains a {@code null} value. - * * @since 2.0 */ public static File[] filter(final IOFileFilter filter, final Iterable files) { @@ -281,7 +281,7 @@ public static File[] filter(final IOFileFilter filter, final Iterable file * @param stream the stream of files on which to apply the filter. * @param collector how to collect the end result. * @param the return type. - * @param the mutable accumulation type of the reduction operation (often hidden as an implementation detail) + * @param the mutable accumulation type of the reduction operation (often hidden as an implementation detail). * @return a subset of files from the stream that is accepted by the filter. * @throws NullPointerException if the filter is {@code null}. */ @@ -311,6 +311,7 @@ private static R filterFiles(final IOFileFilter filter, final Stream + * * @param filter the filter to apply to each files in the list. * @param files the collection of files to apply the filter to. * @return a subset of {@code files} that is accepted by the @@ -339,6 +340,7 @@ public static List filterList(final IOFileFilter filter, final File... fil * List<File> directories = FileFilterUtils.filterList(filesAndDirectories, * FileFilterUtils.directoryFileFilter()); * + * * @param filter the filter to apply to each files in the list. * @param files the collection of files to apply the filter to. * @return a subset of {@code files} that is accepted by the @@ -369,6 +371,7 @@ public static List filterList(final IOFileFilter filter, final Iterable + * * @param filter the filter to apply to the set of files. * @param files the collection of files to apply the filter to. * @return a subset of {@code files} that is accepted by the @@ -398,6 +401,7 @@ public static Set filterSet(final IOFileFilter filter, final File... files * Set<File> javaFiles = FileFilterUtils.filterSet(allFiles, * FileFilterUtils.suffixFileFilter(".java")); * + * * @param filter the filter to apply to the set of files. * @param files the collection of files to apply the filter to. * @return a subset of {@code files} that is accepted by the @@ -495,8 +499,8 @@ public static IOFileFilter magicNumberFileFilter(final String magicNumber, final * Passing in {@code null} will return a filter that accepts everything * except CVS directories. * - * @param filter the filter to decorate, null means an unrestricted filter - * @return the decorated filter, never null + * @param filter the filter to decorate, null means an unrestricted filter. + * @return the decorated filter, never null. * @since 1.1 (method existed but had a bug in 1.0) */ public static IOFileFilter makeCVSAware(final IOFileFilter filter) { @@ -506,8 +510,8 @@ public static IOFileFilter makeCVSAware(final IOFileFilter filter) { /** * Decorates a filter so that it only applies to directories and not to files. * - * @param filter the filter to decorate, null means an unrestricted filter - * @return the decorated filter, never null + * @param filter the filter to decorate, null means an unrestricted filter. + * @return the decorated filter, never null. * @see DirectoryFileFilter#DIRECTORY * @since 1.3 */ @@ -521,8 +525,8 @@ public static IOFileFilter makeDirectoryOnly(final IOFileFilter filter) { /** * Decorates a filter so that it only applies to files and not to directories. * - * @param filter the filter to decorate, null means an unrestricted filter - * @return the decorated filter, never null + * @param filter the filter to decorate, null means an unrestricted filter. + * @return the decorated filter, never null. * @see FileFileFilter#INSTANCE * @since 1.3 */ @@ -538,8 +542,8 @@ public static IOFileFilter makeFileOnly(final IOFileFilter filter) { * Passing in {@code null} will return a filter that accepts everything * except SVN directories. * - * @param filter the filter to decorate, null means an unrestricted filter - * @return the decorated filter, never null + * @param filter the filter to decorate, null means an unrestricted filter. + * @return the decorated filter, never null. * @since 1.1 */ public static IOFileFilter makeSVNAware(final IOFileFilter filter) { @@ -549,8 +553,8 @@ public static IOFileFilter makeSVNAware(final IOFileFilter filter) { /** * Returns a filter that returns true if the file name matches the specified text. * - * @param name the file name - * @return a name checking filter + * @param name the file name. + * @return a name checking filter. * @see NameFileFilter */ public static IOFileFilter nameFileFilter(final String name) { @@ -560,9 +564,9 @@ public static IOFileFilter nameFileFilter(final String name) { /** * Returns a filter that returns true if the file name matches the specified text. * - * @param name the file name - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @return a name checking filter + * @param name the file name. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @return a name checking filter. * @see NameFileFilter * @since 2.0 */ @@ -573,8 +577,8 @@ public static IOFileFilter nameFileFilter(final String name, final IOCase ioCase /** * Returns a filter that NOTs the specified filter. * - * @param filter the filter to invert - * @return a filter that NOTs the specified filter + * @param filter the filter to invert. + * @return a filter that NOTs the specified filter. * @see NotFileFilter */ public static IOFileFilter notFileFilter(final IOFileFilter filter) { @@ -585,7 +589,7 @@ public static IOFileFilter notFileFilter(final IOFileFilter filter) { * Returns a filter that ORs the specified filters. * * @param filters the IOFileFilters that will be ORed together. - * @return a filter that ORs the specified filters + * @return a filter that ORs the specified filters. * @throws IllegalArgumentException if the filters are null or contain a * null value. * @see OrFileFilter @@ -598,12 +602,12 @@ public static IOFileFilter or(final IOFileFilter... filters) { /** * Returns a filter that ORs the two specified filters. * - * @param filter1 the first filter - * @param filter2 the second filter - * @return a filter that ORs the two specified filters + * @param filter1 the first filter. + * @param filter2 the second filter. + * @return a filter that ORs the two specified filters. * @see #or(IOFileFilter...) * @see OrFileFilter - * @deprecated use {@link #or(IOFileFilter...)} + * @deprecated Use {@link #or(IOFileFilter...)} */ @Deprecated public static IOFileFilter orFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) { @@ -613,8 +617,8 @@ public static IOFileFilter orFileFilter(final IOFileFilter filter1, final IOFile /** * Returns a filter that returns true if the file name starts with the specified text. * - * @param prefix the file name prefix - * @return a prefix checking filter + * @param prefix the file name prefix. + * @return a prefix checking filter. * @see PrefixFileFilter */ public static IOFileFilter prefixFileFilter(final String prefix) { @@ -624,9 +628,9 @@ public static IOFileFilter prefixFileFilter(final String prefix) { /** * Returns a filter that returns true if the file name starts with the specified text. * - * @param prefix the file name prefix - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @return a prefix checking filter + * @param prefix the file name prefix. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @return a prefix checking filter. * @see PrefixFileFilter * @since 2.0 */ @@ -637,8 +641,8 @@ public static IOFileFilter prefixFileFilter(final String prefix, final IOCase io /** * Returns a filter that returns true if the file is bigger than a certain size. * - * @param threshold the file size threshold - * @return an appropriately configured SizeFileFilter + * @param threshold the file size threshold. + * @return an appropriately configured SizeFileFilter. * @see SizeFileFilter * @since 1.2 */ @@ -649,9 +653,9 @@ public static IOFileFilter sizeFileFilter(final long threshold) { /** * Returns a filter that filters based on file size. * - * @param threshold the file size threshold - * @param acceptLarger if true, larger files get accepted, if false, smaller - * @return an appropriately configured SizeFileFilter + * @param threshold the file size threshold. + * @param acceptLarger if true, larger files get accepted, if false, smaller. + * @return an appropriately configured SizeFileFilter. * @see SizeFileFilter * @since 1.2 */ @@ -663,9 +667,9 @@ public static IOFileFilter sizeFileFilter(final long threshold, final boolean ac * Returns a filter that accepts files whose size is >= minimum size * and <= maximum size. * - * @param minSizeInclusive the minimum file size (inclusive) - * @param maxSizeInclusive the maximum file size (inclusive) - * @return an appropriately configured IOFileFilter + * @param minSizeInclusive the minimum file size (inclusive). + * @param maxSizeInclusive the maximum file size (inclusive). + * @return an appropriately configured IOFileFilter. * @see SizeFileFilter * @since 1.3 */ @@ -678,8 +682,8 @@ public static IOFileFilter sizeRangeFileFilter(final long minSizeInclusive, fina /** * Returns a filter that returns true if the file name ends with the specified text. * - * @param suffix the file name suffix - * @return a suffix checking filter + * @param suffix the file name suffix. + * @return a suffix checking filter. * @see SuffixFileFilter */ public static IOFileFilter suffixFileFilter(final String suffix) { @@ -689,9 +693,9 @@ public static IOFileFilter suffixFileFilter(final String suffix) { /** * Returns a filter that returns true if the file name ends with the specified text. * - * @param suffix the file name suffix - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @return a suffix checking filter + * @param suffix the file name suffix. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @return a suffix checking filter. * @see SuffixFileFilter * @since 2.0 */ @@ -702,8 +706,8 @@ public static IOFileFilter suffixFileFilter(final String suffix, final IOCase io /** * Create a List of file filters. * - * @param filters The file filters - * @return The list of file filters + * @param filters The file filters. + * @return The list of file filters. * @throws NullPointerException if the filters are null or contain a * null value. * @since 2.0 @@ -715,7 +719,7 @@ public static List toList(final IOFileFilter... filters) { /** * Returns a filter that always returns true. * - * @return a true filter + * @return a true filter. * @see TrueFileFilter#TRUE */ public static IOFileFilter trueFileFilter() { diff --git a/src/main/java/org/apache/commons/io/filefilter/HiddenFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/HiddenFileFilter.java index 6fff2241ab1..a8c49bbae8e 100644 --- a/src/main/java/org/apache/commons/io/filefilter/HiddenFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/HiddenFileFilter.java @@ -95,7 +95,7 @@ protected HiddenFileFilter() { /** * Tests to see if the file is hidden. * - * @param file the File to check + * @param file the File to check. * @return {@code true} if the file is * hidden, otherwise {@code false}. */ @@ -107,7 +107,7 @@ public boolean accept(final File file) { /** * Tests to see if the file is hidden. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). * @return {@code true} if the file is hidden, otherwise {@code false}. * @since 2.9.0 diff --git a/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java index 79aa55d68c1..3f28a7661cd 100644 --- a/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java @@ -218,7 +218,7 @@ public MagicNumberFileFilter(final String magicNumber) { * MagicNumberFileFilter tarFileFilter = MagicNumberFileFilter("ustar", 257); * *

    - * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}. + * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * * @param magicNumber the magic number to look for in the file. @@ -270,6 +270,7 @@ public boolean accept(final File file) { * be rejected. * *

    + * * @param file the file to accept or reject. * @param attributes the path's basic attributes (may be null). * @return {@code true} if the file contains the filter's magic number diff --git a/src/main/java/org/apache/commons/io/filefilter/NameFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/NameFileFilter.java index 7eda934d906..5aa3c38a21b 100644 --- a/src/main/java/org/apache/commons/io/filefilter/NameFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/NameFileFilter.java @@ -83,9 +83,9 @@ public class NameFileFilter extends AbstractFileFilter implements Serializable { /** * Constructs a new case-sensitive name file filter for a list of names. * - * @param names the names to allow, must not be null - * @throws IllegalArgumentException if the name list is null - * @throws ClassCastException if the list does not contain Strings + * @param names the names to allow, must not be null. + * @throws IllegalArgumentException if the name list is null. + * @throws ClassCastException if the list does not contain Strings. */ public NameFileFilter(final List names) { this(names, null); @@ -94,10 +94,10 @@ public NameFileFilter(final List names) { /** * Constructs a new name file filter for a list of names specifying case-sensitivity. * - * @param names the names to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the name list is null - * @throws ClassCastException if the list does not contain Strings + * @param names the names to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the name list is null. + * @throws ClassCastException if the list does not contain Strings. */ public NameFileFilter(final List names, final IOCase ioCase) { Objects.requireNonNull(names, "names"); @@ -108,8 +108,8 @@ public NameFileFilter(final List names, final IOCase ioCase) { /** * Constructs a new case-sensitive name file filter for a single name. * - * @param name the name to allow, must not be null - * @throws IllegalArgumentException if the name is null + * @param name the name to allow, must not be null. + * @throws IllegalArgumentException if the name is null. */ public NameFileFilter(final String name) { this(name, IOCase.SENSITIVE); @@ -122,8 +122,8 @@ public NameFileFilter(final String name) { * instance. This would be inadvisable however. *

    * - * @param names the names to allow, must not be null - * @throws IllegalArgumentException if the names array is null + * @param names the names to allow, must not be null. + * @throws IllegalArgumentException if the names array is null. */ public NameFileFilter(final String... names) { this(names, IOCase.SENSITIVE); @@ -132,9 +132,9 @@ public NameFileFilter(final String... names) { /** * Constructs a new name file filter specifying case-sensitivity. * - * @param name the name to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the name is null + * @param name the name to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the name is null. */ public NameFileFilter(final String name, final IOCase ioCase) { Objects.requireNonNull(name, "name"); @@ -145,9 +145,9 @@ public NameFileFilter(final String name, final IOCase ioCase) { /** * Constructs a new name file filter for an array of names specifying case-sensitivity. * - * @param names the names to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the names array is null + * @param names the names to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the names array is null. */ public NameFileFilter(final String[] names, final IOCase ioCase) { Objects.requireNonNull(names, "names"); @@ -158,8 +158,8 @@ public NameFileFilter(final String[] names, final IOCase ioCase) { /** * Tests to see if the file name matches. * - * @param file the File to check - * @return true if the file name matches + * @param file the File to check. + * @return true if the file name matches. */ @Override public boolean accept(final File file) { @@ -169,9 +169,9 @@ public boolean accept(final File file) { /** * Tests to see if the file name matches. * - * @param dir the File directory (ignored) - * @param name the file name - * @return true if the file name matches + * @param dir the File directory (ignored). + * @param name the file name. + * @return true if the file name matches. */ @Override public boolean accept(final File dir, final String name) { @@ -181,9 +181,9 @@ public boolean accept(final File dir, final String name) { /** * Checks to see if the file name matches. * - * @param path the File to check + * @param path the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name matches + * @return true if the file name matches. * @since 2.9.0 */ @Override @@ -202,7 +202,7 @@ private IOCase toIOCase(final IOCase ioCase) { /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/NotFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/NotFileFilter.java index 8592e402129..4e92f9a47ac 100644 --- a/src/main/java/org/apache/commons/io/filefilter/NotFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/NotFileFilter.java @@ -43,8 +43,8 @@ public class NotFileFilter extends AbstractFileFilter implements Serializable { /** * Constructs a new file filter that NOTs the result of another filter. * - * @param filter the filter, must not be null - * @throws NullPointerException if the filter is null + * @param filter the filter, must not be null. + * @throws NullPointerException if the filter is null. */ public NotFileFilter(final IOFileFilter filter) { Objects.requireNonNull(filter, "filter"); @@ -54,8 +54,8 @@ public NotFileFilter(final IOFileFilter filter) { /** * Returns the logical NOT of the underlying filter's return value for the same File. * - * @param file the File to check - * @return true if the filter returns false + * @param file the File to check. + * @return true if the filter returns false. */ @Override public boolean accept(final File file) { @@ -65,9 +65,9 @@ public boolean accept(final File file) { /** * Returns the logical NOT of the underlying filter's return value for the same arguments. * - * @param file the File directory - * @param name the file name - * @return true if the filter returns false + * @param file the File directory. + * @param name the file name. + * @return true if the filter returns false. */ @Override public boolean accept(final File file, final String name) { @@ -77,9 +77,9 @@ public boolean accept(final File file, final String name) { /** * Returns the logical NOT of the underlying filter's return value for the same File. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the filter returns false + * @return true if the filter returns false. * @since 2.9.0 */ @Override @@ -94,7 +94,7 @@ private FileVisitResult not(final FileVisitResult accept) { /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/OrFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/OrFileFilter.java index 57b8bcf0560..4017ce8f070 100644 --- a/src/main/java/org/apache/commons/io/filefilter/OrFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/OrFileFilter.java @@ -76,6 +76,7 @@ private OrFileFilter(final int initialCapacity) { /** * Constructs a new instance for the give filters. + * * @param fileFilters filters to OR. * @since 2.9.0 */ @@ -87,9 +88,9 @@ public OrFileFilter(final IOFileFilter... fileFilters) { /** * Constructs a new file filter that ORs the result of other filters. * - * @param filter1 the first filter, must not be null - * @param filter2 the second filter, must not be null - * @throws IllegalArgumentException if either filter is null + * @param filter1 the first filter, must not be null. + * @param filter2 the second filter, must not be null. + * @throws IllegalArgumentException if either filter is null. */ public OrFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) { this(2); @@ -177,7 +178,7 @@ public void setFileFilters(final List fileFilters) { /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/PrefixFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/PrefixFileFilter.java index 3a85c9e8e18..a1db0ae1203 100644 --- a/src/main/java/org/apache/commons/io/filefilter/PrefixFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/PrefixFileFilter.java @@ -83,9 +83,9 @@ public class PrefixFileFilter extends AbstractFileFilter implements Serializable /** * Constructs a new Prefix file filter for a list of prefixes. * - * @param prefixes the prefixes to allow, must not be null - * @throws NullPointerException if the prefix list is null - * @throws ClassCastException if the list does not contain Strings + * @param prefixes the prefixes to allow, must not be null. + * @throws NullPointerException if the prefix list is null. + * @throws ClassCastException if the list does not contain Strings. */ public PrefixFileFilter(final List prefixes) { this(prefixes, IOCase.SENSITIVE); @@ -95,10 +95,10 @@ public PrefixFileFilter(final List prefixes) { * Constructs a new Prefix file filter for a list of prefixes * specifying case-sensitivity. * - * @param prefixes the prefixes to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the prefix list is null - * @throws ClassCastException if the list does not contain Strings + * @param prefixes the prefixes to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the prefix list is null. + * @throws ClassCastException if the list does not contain Strings. * @since 1.4 */ public PrefixFileFilter(final List prefixes, final IOCase ioCase) { @@ -110,8 +110,8 @@ public PrefixFileFilter(final List prefixes, final IOCase ioCase) { /** * Constructs a new Prefix file filter for a single prefix. * - * @param prefix the prefix to allow, must not be null - * @throws IllegalArgumentException if the prefix is null + * @param prefix the prefix to allow, must not be null. + * @throws IllegalArgumentException if the prefix is null. */ public PrefixFileFilter(final String prefix) { this(prefix, IOCase.SENSITIVE); @@ -124,8 +124,8 @@ public PrefixFileFilter(final String prefix) { * instance. This would be inadvisable however. *

    * - * @param prefixes the prefixes to allow, must not be null - * @throws IllegalArgumentException if the prefix array is null + * @param prefixes the prefixes to allow, must not be null. + * @throws IllegalArgumentException if the prefix array is null. */ public PrefixFileFilter(final String... prefixes) { this(prefixes, IOCase.SENSITIVE); @@ -135,9 +135,9 @@ public PrefixFileFilter(final String... prefixes) { * Constructs a new Prefix file filter for a single prefix * specifying case-sensitivity. * - * @param prefix the prefix to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws IllegalArgumentException if the prefix is null + * @param prefix the prefix to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws IllegalArgumentException if the prefix is null. * @since 1.4 */ public PrefixFileFilter(final String prefix, final IOCase ioCase) { @@ -150,9 +150,9 @@ public PrefixFileFilter(final String prefix, final IOCase ioCase) { * Constructs a new Prefix file filter for any of an array of prefixes * specifying case-sensitivity. * - * @param prefixes the prefixes to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws IllegalArgumentException if the prefix is null + * @param prefixes the prefixes to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws IllegalArgumentException if the prefix is null. * @since 1.4 */ public PrefixFileFilter(final String[] prefixes, final IOCase ioCase) { @@ -164,8 +164,8 @@ public PrefixFileFilter(final String[] prefixes, final IOCase ioCase) { /** * Tests to see if the file name starts with the prefix. * - * @param file the File to check - * @return true if the file name starts with one of our prefixes + * @param file the File to check. + * @return true if the file name starts with one of our prefixes. */ @Override public boolean accept(final File file) { @@ -175,9 +175,9 @@ public boolean accept(final File file) { /** * Tests to see if the file name starts with the prefix. * - * @param file the File directory - * @param name the file name - * @return true if the file name starts with one of our prefixes + * @param file the File directory. + * @param name the file name. + * @return true if the file name starts with one of our prefixes. */ @Override public boolean accept(final File file, final String name) { @@ -187,9 +187,9 @@ public boolean accept(final File file, final String name) { /** * Tests to see if the file name starts with the prefix. * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name starts with one of our prefixes + * @return true if the file name starts with one of our prefixes. * @since 2.9.0 */ @Override @@ -204,7 +204,7 @@ private boolean accept(final String name) { /** * Provides a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/RegexFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/RegexFileFilter.java index f044800692c..db699d9b912 100644 --- a/src/main/java/org/apache/commons/io/filefilter/RegexFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/RegexFileFilter.java @@ -132,8 +132,8 @@ public RegexFileFilter(final Pattern pattern, final Function pathT /** * Constructs a new regular expression filter. * - * @param pattern regular string expression to match - * @throws NullPointerException if the pattern is null + * @param pattern regular string expression to match. + * @throws NullPointerException if the pattern is null. */ public RegexFileFilter(final String pattern) { this(pattern, 0); @@ -142,9 +142,9 @@ public RegexFileFilter(final String pattern) { /** * Constructs a new regular expression filter with the specified flags. * - * @param pattern regular string expression to match + * @param pattern regular string expression to match. * @param flags pattern flags - e.g. {@link Pattern#CASE_INSENSITIVE} - * @throws IllegalArgumentException if the pattern is null + * @throws IllegalArgumentException if the pattern is null. */ public RegexFileFilter(final String pattern, final int flags) { this(compile(pattern, flags)); @@ -153,9 +153,9 @@ public RegexFileFilter(final String pattern, final int flags) { /** * Constructs a new regular expression filter with the specified flags case sensitivity. * - * @param pattern regular string expression to match - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws IllegalArgumentException if the pattern is null + * @param pattern regular string expression to match. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws IllegalArgumentException if the pattern is null. */ public RegexFileFilter(final String pattern, final IOCase ioCase) { this(compile(pattern, toFlags(ioCase))); @@ -164,9 +164,9 @@ public RegexFileFilter(final String pattern, final IOCase ioCase) { /** * Tests to see if the file name matches one of the regular expressions. * - * @param dir the file directory (ignored) - * @param name the file name - * @return true if the file name matches one of the regular expressions + * @param dir the file directory (ignored). + * @param name the file name. + * @return true if the file name matches one of the regular expressions. */ @Override public boolean accept(final File dir, final String name) { @@ -176,9 +176,9 @@ public boolean accept(final File dir, final String name) { /** * Tests to see if the file name matches one of the regular expressions. * - * @param path the path + * @param path the path. * @param attributes the path's basic attributes (may be null). - * @return true if the file name matches one of the regular expressions + * @return true if the file name matches one of the regular expressions. */ @Override public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) { diff --git a/src/main/java/org/apache/commons/io/filefilter/SizeFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/SizeFileFilter.java index 24b0b57f939..185f47bf2ef 100644 --- a/src/main/java/org/apache/commons/io/filefilter/SizeFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/SizeFileFilter.java @@ -82,8 +82,8 @@ public class SizeFileFilter extends AbstractFileFilter implements Serializable { * Constructs a new size file filter for files equal to or * larger than a certain size. * - * @param size the threshold size of the files - * @throws IllegalArgumentException if the size is negative + * @param size the threshold size of the files. + * @throws IllegalArgumentException if the size is negative. */ public SizeFileFilter(final long size) { this(size, true); @@ -93,10 +93,10 @@ public SizeFileFilter(final long size) { * Constructs a new size file filter for files based on a certain size * threshold. * - * @param size the threshold size of the files + * @param size the threshold size of the files. * @param acceptLarger if true, files equal to or larger are accepted, - * otherwise smaller ones (but not equal to) - * @throws IllegalArgumentException if the size is negative + * otherwise smaller ones (but not equal to). + * @throws IllegalArgumentException if the size is negative. */ public SizeFileFilter(final long size, final boolean acceptLarger) { if (size < 0) { @@ -115,8 +115,8 @@ public SizeFileFilter(final long size, final boolean acceptLarger) { * file IS selected. *

    * - * @param file the File to check - * @return true if the file name matches + * @param file the File to check. + * @return true if the file name matches. */ @Override public boolean accept(final File file) { @@ -134,9 +134,9 @@ private boolean accept(final long length) { * file IS selected. *

    * - * @param file the File to check + * @param file the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name matches + * @return true if the file name matches. */ @Override public FileVisitResult accept(final Path file, final BasicFileAttributes attributes) { @@ -146,7 +146,7 @@ public FileVisitResult accept(final Path file, final BasicFileAttributes attribu /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/SuffixFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/SuffixFileFilter.java index d375433495a..e4780baf704 100644 --- a/src/main/java/org/apache/commons/io/filefilter/SuffixFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/SuffixFileFilter.java @@ -84,9 +84,9 @@ public class SuffixFileFilter extends AbstractFileFilter implements Serializable /** * Constructs a new Suffix file filter for a list of suffixes. * - * @param suffixes the suffixes to allow, must not be null - * @throws IllegalArgumentException if the suffix list is null - * @throws ClassCastException if the list does not contain Strings + * @param suffixes the suffixes to allow, must not be null. + * @throws IllegalArgumentException if the suffix list is null. + * @throws ClassCastException if the list does not contain Strings. */ public SuffixFileFilter(final List suffixes) { this(suffixes, IOCase.SENSITIVE); @@ -96,10 +96,10 @@ public SuffixFileFilter(final List suffixes) { * Constructs a new Suffix file filter for a list of suffixes * specifying case-sensitivity. * - * @param suffixes the suffixes to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws IllegalArgumentException if the suffix list is null - * @throws ClassCastException if the list does not contain Strings + * @param suffixes the suffixes to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws IllegalArgumentException if the suffix list is null. + * @throws ClassCastException if the list does not contain Strings. * @since 1.4 */ public SuffixFileFilter(final List suffixes, final IOCase ioCase) { @@ -111,8 +111,8 @@ public SuffixFileFilter(final List suffixes, final IOCase ioCase) { /** * Constructs a new Suffix file filter for a single extension. * - * @param suffix the suffix to allow, must not be null - * @throws IllegalArgumentException if the suffix is null + * @param suffix the suffix to allow, must not be null. + * @throws IllegalArgumentException if the suffix is null. */ public SuffixFileFilter(final String suffix) { this(suffix, IOCase.SENSITIVE); @@ -125,8 +125,8 @@ public SuffixFileFilter(final String suffix) { * instance. This would be inadvisable however. *

    * - * @param suffixes the suffixes to allow, must not be null - * @throws NullPointerException if the suffix array is null + * @param suffixes the suffixes to allow, must not be null. + * @throws NullPointerException if the suffix array is null. */ public SuffixFileFilter(final String... suffixes) { this(suffixes, IOCase.SENSITIVE); @@ -136,9 +136,9 @@ public SuffixFileFilter(final String... suffixes) { * Constructs a new Suffix file filter for a single extension * specifying case-sensitivity. * - * @param suffix the suffix to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the suffix is null + * @param suffix the suffix to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the suffix is null. * @since 1.4 */ public SuffixFileFilter(final String suffix, final IOCase ioCase) { @@ -151,9 +151,9 @@ public SuffixFileFilter(final String suffix, final IOCase ioCase) { * Constructs a new Suffix file filter for an array of suffixes * specifying case-sensitivity. * - * @param suffixes the suffixes to allow, must not be null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the suffix array is null + * @param suffixes the suffixes to allow, must not be null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the suffix array is null. * @since 1.4 */ public SuffixFileFilter(final String[] suffixes, final IOCase ioCase) { @@ -165,8 +165,8 @@ public SuffixFileFilter(final String[] suffixes, final IOCase ioCase) { /** * Tests to see if the file name ends with the suffix. * - * @param file the File to check - * @return true if the file name ends with one of our suffixes + * @param file the File to check. + * @return true if the file name ends with one of our suffixes. */ @Override public boolean accept(final File file) { @@ -176,9 +176,9 @@ public boolean accept(final File file) { /** * Tests to see if the file name ends with the suffix. * - * @param file the File directory - * @param name the file name - * @return true if the file name ends with one of our suffixes + * @param file the File directory. + * @param name the file name. + * @return true if the file name ends with one of our suffixes. */ @Override public boolean accept(final File file, final String name) { @@ -188,9 +188,9 @@ public boolean accept(final File file, final String name) { /** * Tests to see if the file name ends with the suffix. * - * @param path the File to check + * @param path the File to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name ends with one of our suffixes + * @return true if the file name ends with one of our suffixes. * @since 2.9.0 */ @Override @@ -205,7 +205,7 @@ private boolean accept(final String name) { /** * Provides a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilter.java index 5c0d85a79a7..3869b0bcda5 100644 --- a/src/main/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilter.java @@ -107,7 +107,7 @@ public SymbolicLinkFileFilter(final FileVisitResult onAccept, final FileVisitRes /** * Tests to see if the file is a symbolic link. * - * @param file the File to check + * @param file the File to check. * @return true if the file exists and is a symbolic link to either another file or a directory, * false otherwise. */ @@ -119,7 +119,7 @@ public boolean accept(final File file) { /** * Tests to see if the file is a symbolic link. * - * @param path the File Path to check + * @param path the File Path to check. * @param attributes the path's basic attributes (may be null). * @return {@code onAccept} from {@link #SymbolicLinkFileFilter(FileVisitResult, FileVisitResult)} if the file exists and is a symbolic link to either * another file or a directory; returns {@code onReject} otherwise. @@ -136,7 +136,7 @@ public FileVisitResult accept(final Path path, final BasicFileAttributes attribu * test for why.) *

    * - * @param filePath The filePath to test + * @param filePath The filePath to test. * @return true if the file exists and is a symbolic link to either a file or directory, false otherwise. */ boolean isSymbolicLink(final Path filePath) { diff --git a/src/main/java/org/apache/commons/io/filefilter/TrueFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/TrueFileFilter.java index 70e27da55dc..9552e27a545 100644 --- a/src/main/java/org/apache/commons/io/filefilter/TrueFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/TrueFileFilter.java @@ -60,8 +60,8 @@ protected TrueFileFilter() { /** * Returns true. * - * @param file the file to check (ignored) - * @return true + * @param file the file to check (ignored). + * @return true. */ @Override public boolean accept(final File file) { @@ -71,9 +71,9 @@ public boolean accept(final File file) { /** * Returns true. * - * @param dir the directory to check (ignored) - * @param name the file name (ignored) - * @return true + * @param dir the directory to check (ignored). + * @param name the file name (ignored). + * @return true. */ @Override public boolean accept(final File dir, final String name) { @@ -83,9 +83,9 @@ public boolean accept(final File dir, final String name) { /** * Returns true. * - * @param file the file to check (ignored) + * @param file the file to check (ignored). * @param attributes the path's basic attributes (may be null). - * @return true + * @return true. * @since 2.9.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java index 72a01c3789e..7e895e254e1 100644 --- a/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java @@ -174,9 +174,9 @@ private WildcardFileFilter(final Builder builder) { /** * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity. * - * @param wildcards the array of wildcards to match, not null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the pattern array is null + * @param wildcards the array of wildcards to match, not null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the pattern array is null. */ private WildcardFileFilter(final IOCase ioCase, final String... wildcards) { this.wildcards = requireWildcards(wildcards).clone(); @@ -186,10 +186,10 @@ private WildcardFileFilter(final IOCase ioCase, final String... wildcards) { /** * Constructs a new case-sensitive wildcard filter for a list of wildcards. * - * @param wildcards the list of wildcards to match, not null - * @throws IllegalArgumentException if the pattern list is null - * @throws ClassCastException if the list does not contain Strings - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcards the list of wildcards to match, not null. + * @throws IllegalArgumentException if the pattern list is null. + * @throws ClassCastException if the list does not contain Strings. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final List wildcards) { @@ -199,11 +199,11 @@ public WildcardFileFilter(final List wildcards) { /** * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity. * - * @param wildcards the list of wildcards to match, not null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws IllegalArgumentException if the pattern list is null - * @throws ClassCastException if the list does not contain Strings - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcards the list of wildcards to match, not null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws IllegalArgumentException if the pattern list is null. + * @throws ClassCastException if the list does not contain Strings. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final List wildcards, final IOCase ioCase) { @@ -213,9 +213,9 @@ public WildcardFileFilter(final List wildcards, final IOCase ioCase) { /** * Constructs a new case-sensitive wildcard filter for a single wildcard. * - * @param wildcard the wildcard to match - * @throws IllegalArgumentException if the pattern is null - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcard the wildcard to match. + * @throws IllegalArgumentException if the pattern is null. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final String wildcard) { @@ -225,9 +225,9 @@ public WildcardFileFilter(final String wildcard) { /** * Constructs a new case-sensitive wildcard filter for an array of wildcards. * - * @param wildcards the array of wildcards to match - * @throws NullPointerException if the pattern array is null - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcards the array of wildcards to match. + * @throws NullPointerException if the pattern array is null. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final String... wildcards) { @@ -237,10 +237,10 @@ public WildcardFileFilter(final String... wildcards) { /** * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity. * - * @param wildcard the wildcard to match, not null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the pattern is null - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcard the wildcard to match, not null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the pattern is null. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final String wildcard, final IOCase ioCase) { @@ -250,10 +250,10 @@ public WildcardFileFilter(final String wildcard, final IOCase ioCase) { /** * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity. * - * @param wildcards the array of wildcards to match, not null - * @param ioCase how to handle case sensitivity, null means case-sensitive - * @throws NullPointerException if the pattern array is null - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param wildcards the array of wildcards to match, not null. + * @param ioCase how to handle case sensitivity, null means case-sensitive. + * @throws NullPointerException if the pattern array is null. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) { @@ -263,8 +263,8 @@ public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) { /** * Tests to see if the file name matches one of the wildcards. * - * @param file the file to check - * @return true if the file name matches one of the wildcards + * @param file the file to check. + * @return true if the file name matches one of the wildcards. */ @Override public boolean accept(final File file) { @@ -274,9 +274,9 @@ public boolean accept(final File file) { /** * Tests to see if the file name matches one of the wildcards. * - * @param dir the file directory (ignored) - * @param name the file name - * @return true if the file name matches one of the wildcards + * @param dir the file directory (ignored). + * @param name the file name. + * @return true if the file name matches one of the wildcards. */ @Override public boolean accept(final File dir, final String name) { @@ -286,7 +286,7 @@ public boolean accept(final File dir, final String name) { /** * Tests to see if the file name matches one of the wildcards. * - * @param path the file to check + * @param path the file to check. * @param attributes the path's basic attributes (may be null). * @return true if the file name matches one of the wildcards. * @since 2.9.0 @@ -303,7 +303,7 @@ private boolean accept(final String name) { /** * Provide a String representation of this file filter. * - * @return a String representation + * @return a String representation. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/filefilter/WildcardFilter.java b/src/main/java/org/apache/commons/io/filefilter/WildcardFilter.java index e435101fbd4..a28cd56c634 100644 --- a/src/main/java/org/apache/commons/io/filefilter/WildcardFilter.java +++ b/src/main/java/org/apache/commons/io/filefilter/WildcardFilter.java @@ -93,9 +93,9 @@ public class WildcardFilter extends AbstractFileFilter implements Serializable { /** * Constructs a new case-sensitive wildcard filter for a list of wildcards. * - * @param wildcards the list of wildcards to match - * @throws NullPointerException if the pattern list is null - * @throws ClassCastException if the list does not contain Strings + * @param wildcards the list of wildcards to match. + * @throws NullPointerException if the pattern list is null. + * @throws ClassCastException if the list does not contain Strings. */ public WildcardFilter(final List wildcards) { Objects.requireNonNull(wildcards, "wildcards"); @@ -105,8 +105,8 @@ public WildcardFilter(final List wildcards) { /** * Constructs a new case-sensitive wildcard filter for a single wildcard. * - * @param wildcard the wildcard to match - * @throws NullPointerException if the pattern is null + * @param wildcard the wildcard to match. + * @throws NullPointerException if the pattern is null. */ public WildcardFilter(final String wildcard) { Objects.requireNonNull(wildcard, "wildcard"); @@ -116,8 +116,8 @@ public WildcardFilter(final String wildcard) { /** * Constructs a new case-sensitive wildcard filter for an array of wildcards. * - * @param wildcards the array of wildcards to match - * @throws NullPointerException if the pattern array is null + * @param wildcards the array of wildcards to match. + * @throws NullPointerException if the pattern array is null. */ public WildcardFilter(final String... wildcards) { Objects.requireNonNull(wildcards, "wildcards"); @@ -127,8 +127,8 @@ public WildcardFilter(final String... wildcards) { /** * Tests to see if the file name matches one of the wildcards. * - * @param file the file to check - * @return true if the file name matches one of the wildcards + * @param file the file to check. + * @return true if the file name matches one of the wildcards. */ @Override public boolean accept(final File file) { @@ -141,9 +141,9 @@ public boolean accept(final File file) { /** * Tests to see if the file name matches one of the wildcards. * - * @param dir the file directory - * @param name the file name - * @return true if the file name matches one of the wildcards + * @param dir the file directory. + * @param name the file name. + * @return true if the file name matches one of the wildcards. */ @Override public boolean accept(final File dir, final String name) { @@ -156,9 +156,9 @@ public boolean accept(final File dir, final String name) { /** * Tests to see if the file name matches one of the wildcards. * - * @param path the file to check + * @param path the file to check. * @param attributes the path's basic attributes (may be null). - * @return true if the file name matches one of the wildcards + * @return true if the file name matches one of the wildcards. * @since 2.9.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/function/IOBiConsumer.java b/src/main/java/org/apache/commons/io/function/IOBiConsumer.java index a8e1bdf397d..ec217eafa0a 100644 --- a/src/main/java/org/apache/commons/io/function/IOBiConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOBiConsumer.java @@ -25,8 +25,8 @@ /** * Like {@link BiConsumer} but throws {@link IOException}. * - * @param the type of the first argument to the operation - * @param the type of the second argument to the operation + * @param the type of the first argument to the operation. + * @param the type of the second argument to the operation. * @see BiConsumer * @since 2.12.0 */ @@ -36,8 +36,8 @@ public interface IOBiConsumer { /** * Returns the no-op singleton. * - * @param the type of the first argument to the operation - * @param the type of the second argument to the operation + * @param the type of the first argument to the operation. + * @param the type of the second argument to the operation. * @return The no-op singleton. */ @SuppressWarnings("unchecked") @@ -48,8 +48,8 @@ static IOBiConsumer noop() { /** * Performs this operation on the given arguments. * - * @param t the first input argument - * @param u the second input argument + * @param t the first input argument. + * @param u the second input argument. * @throws IOException if an I/O error occurs. */ void accept(T t, U u) throws IOException; @@ -59,10 +59,10 @@ static IOBiConsumer noop() { * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation. * If performing this operation throws an exception, the {@code after} operation will not be performed. * - * @param after the operation to perform after this operation + * @param after the operation to perform after this operation. * @return a composed {@link IOBiConsumer} that performs in sequence this operation followed by the {@code after} - * operation - * @throws NullPointerException if {@code after} is null + * operation. + * @throws NullPointerException if {@code after} is null. */ default IOBiConsumer andThen(final IOBiConsumer after) { Objects.requireNonNull(after); diff --git a/src/main/java/org/apache/commons/io/function/IOBiFunction.java b/src/main/java/org/apache/commons/io/function/IOBiFunction.java index a72b73f3581..fa5bd4c5db1 100644 --- a/src/main/java/org/apache/commons/io/function/IOBiFunction.java +++ b/src/main/java/org/apache/commons/io/function/IOBiFunction.java @@ -30,9 +30,9 @@ * {@link #apply(Object, Object)}. *

    * - * @param the type of the first argument to the function - * @param the type of the second argument to the function - * @param the type of the result of the function + * @param the type of the first argument to the function. + * @param the type of the second argument to the function. + * @param the type of the result of the function. * @see BiFunction * @since 2.12.0 */ @@ -44,10 +44,10 @@ public interface IOBiFunction { * function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param the type of output of the {@code after} function, and of the composed function - * @param after the function to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} function - * @throws NullPointerException if after is null + * @param the type of output of the {@code after} function, and of the composed function. + * @param after the function to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} function. + * @throws NullPointerException if after is null. */ default IOBiFunction andThen(final IOFunction after) { Objects.requireNonNull(after); @@ -57,9 +57,9 @@ default IOBiFunction andThen(final IOFunction extends IOBiFunction { * Creates a {@link IOBinaryOperator} which returns the greater of two elements according to the specified * {@code Comparator}. * - * @param the type of the input arguments of the comparator - * @param comparator a {@code Comparator} for comparing the two values + * @param the type of the input arguments of the comparator. + * @param comparator a {@code Comparator} for comparing the two values. * @return a {@code BinaryOperator} which returns the greater of its operands, according to the supplied * {@code Comparator} - * @throws NullPointerException if the argument is null + * @throws NullPointerException if the argument is null. */ static IOBinaryOperator maxBy(final IOComparator comparator) { Objects.requireNonNull(comparator); @@ -52,11 +52,11 @@ static IOBinaryOperator maxBy(final IOComparator comparator) { * Creates a {@link IOBinaryOperator} which returns the lesser of two elements according to the specified * {@code Comparator}. * - * @param the type of the input arguments of the comparator - * @param comparator a {@code Comparator} for comparing the two values + * @param the type of the input arguments of the comparator. + * @param comparator a {@code Comparator} for comparing the two values. * @return a {@code BinaryOperator} which returns the lesser of its operands, according to the supplied * {@code Comparator} - * @throws NullPointerException if the argument is null + * @throws NullPointerException if the argument is null. */ static IOBinaryOperator minBy(final IOComparator comparator) { Objects.requireNonNull(comparator); diff --git a/src/main/java/org/apache/commons/io/function/IOBooleanSupplier.java b/src/main/java/org/apache/commons/io/function/IOBooleanSupplier.java index 0b8b1dcfd8b..f82f40adb8e 100644 --- a/src/main/java/org/apache/commons/io/function/IOBooleanSupplier.java +++ b/src/main/java/org/apache/commons/io/function/IOBooleanSupplier.java @@ -42,7 +42,7 @@ default BooleanSupplier asBooleanSupplier() { /** * Gets a result. * - * @return a result + * @return a result. * @throws IOException if an I/O error occurs. */ boolean getAsBoolean() throws IOException; diff --git a/src/main/java/org/apache/commons/io/function/IOComparator.java b/src/main/java/org/apache/commons/io/function/IOComparator.java index eb31ef818ef..4d8e891eaa0 100644 --- a/src/main/java/org/apache/commons/io/function/IOComparator.java +++ b/src/main/java/org/apache/commons/io/function/IOComparator.java @@ -24,7 +24,7 @@ /** * Like {@link Comparator} but throws {@link IOException}. * - * @param the type of objects that may be compared by this comparator + * @param the type of objects that may be compared by this comparator. * @see Comparator * @since 2.12.0 */ @@ -48,7 +48,7 @@ default Comparator asComparator() { * @param o2 the second object to be compared. * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than * the second. - * @throws NullPointerException if an argument is null and this comparator does not permit null arguments + * @throws NullPointerException if an argument is null and this comparator does not permit null arguments. * @throws ClassCastException if the arguments' types prevent them from being compared by this comparator. * @throws IOException if an I/O error occurs. */ diff --git a/src/main/java/org/apache/commons/io/function/IOConsumer.java b/src/main/java/org/apache/commons/io/function/IOConsumer.java index 016b643f95f..ba85becc6f9 100644 --- a/src/main/java/org/apache/commons/io/function/IOConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOConsumer.java @@ -42,6 +42,21 @@ public interface IOConsumer { // noop }; + /** + * Applies the given {@link IOConsumer} action to the object if the consumer is not {@code null}. Otherwise, does nothing. + * + * @param consumer the consumer to consume. + * @param object the object to be consumed. + * @param the type of the argument the consumer accepts. + * @throws IOException Thrown when the consumer fails. + * @since 2.23.0 + */ + static void accept(final IOConsumer consumer, final T object) throws IOException { + if (consumer != null) { + consumer.accept(object); + } + } + /** * Performs an action for each element of the collection gathering any exceptions. * @@ -136,7 +151,7 @@ static IOConsumer noop() { /** * Performs this operation on the given argument. * - * @param t the input argument + * @param t the input argument. * @throws IOException if an I/O error occurs. */ void accept(T t) throws IOException; @@ -146,9 +161,9 @@ static IOConsumer noop() { * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation. * If performing this operation throws an exception, the {@code after} operation will not be performed. * - * @param after the operation to perform after this operation - * @return a composed {@link Consumer} that performs in sequence this operation followed by the {@code after} operation - * @throws NullPointerException if {@code after} is null + * @param after the operation to perform after this operation. + * @return a composed {@link Consumer} that performs in sequence this operation followed by the {@code after} operation. + * @throws NullPointerException if {@code after} is null. */ default IOConsumer andThen(final IOConsumer after) { Objects.requireNonNull(after, "after"); diff --git a/src/main/java/org/apache/commons/io/function/IOFunction.java b/src/main/java/org/apache/commons/io/function/IOFunction.java index 07e9d539a99..92f1335fbb6 100644 --- a/src/main/java/org/apache/commons/io/function/IOFunction.java +++ b/src/main/java/org/apache/commons/io/function/IOFunction.java @@ -37,8 +37,8 @@ public interface IOFunction { /** * Returns a {@link IOFunction} that always returns its input argument. * - * @param the type of the input and output objects to the function - * @return a function that always returns its input argument + * @param the type of the input and output objects to the function. + * @return a function that always returns its input argument. */ @SuppressWarnings("unchecked") static IOFunction identity() { @@ -50,9 +50,9 @@ static IOFunction identity() { * {@code after} consumer to the result. If evaluation of either function throws an exception, it is relayed to the * caller of the composed function. * - * @param after the consumer to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} consumer - * @throws NullPointerException if after is null + * @param after the consumer to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} consumer. + * @throws NullPointerException if after is null. * @see #compose(IOFunction) */ default IOConsumer andThen(final Consumer after) { @@ -65,10 +65,10 @@ default IOConsumer andThen(final Consumer after) { * {@code after} function to the result. If evaluation of either function throws an exception, it is relayed to the * caller of the composed function. * - * @param the type of output of the {@code after} function, and of the composed function - * @param after the function to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} function - * @throws NullPointerException if after is null + * @param the type of output of the {@code after} function, and of the composed function. + * @param after the function to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} function. + * @throws NullPointerException if after is null. * @see #compose(IOFunction) */ default IOFunction andThen(final Function after) { @@ -81,9 +81,9 @@ default IOFunction andThen(final Function afte * {@code after} consumer to the result. If evaluation of either function throws an exception, it is relayed to the * caller of the composed function. * - * @param after the consumer to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} consumer - * @throws NullPointerException if after is null + * @param after the consumer to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} consumer. + * @throws NullPointerException if after is null. * @see #compose(IOFunction) */ default IOConsumer andThen(final IOConsumer after) { @@ -96,10 +96,10 @@ default IOConsumer andThen(final IOConsumer after) { * {@code after} function to the result. If evaluation of either function throws an exception, it is relayed to the * caller of the composed function. * - * @param the type of output of the {@code after} function, and of the composed function - * @param after the function to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} function - * @throws NullPointerException if after is null + * @param the type of output of the {@code after} function, and of the composed function. + * @param after the function to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} function. + * @throws NullPointerException if after is null. * @see #compose(IOFunction) */ default IOFunction andThen(final IOFunction after) { @@ -110,8 +110,8 @@ default IOFunction andThen(final IOFunction af /** * Applies this function to the given argument. * - * @param t the function argument - * @return the function result + * @param t the function argument. + * @return the function result. * @throws IOException if an I/O error occurs. */ R apply(T t) throws IOException; @@ -131,10 +131,10 @@ default Function asFunction() { * this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param the type of input to the {@code before} function, and to the composed function - * @param before the function to apply before this function is applied - * @return a composed function that first applies the {@code before} function and then applies this function - * @throws NullPointerException if before is null + * @param the type of input to the {@code before} function, and to the composed function. + * @param before the function to apply before this function is applied. + * @return a composed function that first applies the {@code before} function and then applies this function. + * @throws NullPointerException if before is null. * @see #andThen(IOFunction) */ default IOFunction compose(final Function before) { @@ -147,10 +147,10 @@ default IOFunction compose(final Function befo * this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param the type of input to the {@code before} function, and to the composed function - * @param before the function to apply before this function is applied - * @return a composed function that first applies the {@code before} function and then applies this function - * @throws NullPointerException if before is null + * @param the type of input to the {@code before} function, and to the composed function. + * @param before the function to apply before this function is applied. + * @return a composed function that first applies the {@code before} function and then applies this function. + * @throws NullPointerException if before is null. * @see #andThen(IOFunction) */ default IOFunction compose(final IOFunction before) { @@ -163,9 +163,9 @@ default IOFunction compose(final IOFunction be * this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param before the supplier which feeds the application of this function - * @return a composed function that first applies the {@code before} function and then applies this function - * @throws NullPointerException if before is null + * @param before the supplier which feeds the application of this function. + * @return a composed function that first applies the {@code before} function and then applies this function. + * @throws NullPointerException if before is null. * @see #andThen(IOFunction) */ default IOSupplier compose(final IOSupplier before) { @@ -178,9 +178,9 @@ default IOSupplier compose(final IOSupplier before) { * this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param before the supplier which feeds the application of this function - * @return a composed function that first applies the {@code before} function and then applies this function - * @throws NullPointerException if before is null + * @param before the supplier which feeds the application of this function. + * @return a composed function that first applies the {@code before} function and then applies this function. + * @throws NullPointerException if before is null. * @see #andThen(IOFunction) */ default IOSupplier compose(final Supplier before) { diff --git a/src/main/java/org/apache/commons/io/function/IOIntConsumer.java b/src/main/java/org/apache/commons/io/function/IOIntConsumer.java index 9c45b29a011..8f81eff6342 100644 --- a/src/main/java/org/apache/commons/io/function/IOIntConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOIntConsumer.java @@ -41,7 +41,7 @@ public interface IOIntConsumer { /** * Performs this operation on the given argument. * - * @param value the input argument + * @param value the input argument. * @throws IOException if an I/O error occurs. */ void accept(int value) throws IOException; @@ -51,9 +51,9 @@ public interface IOIntConsumer { * operation throws an exception, it is relayed to the caller of the composed operation. If performing this operation throws an exception, the {@code after} * operation will not be performed. * - * @param after the operation to perform after this operation - * @return a composed {@code IOIntConsumer} that performs in sequence this operation followed by the {@code after} operation - * @throws NullPointerException if {@code after} is null + * @param after the operation to perform after this operation. + * @return a composed {@code IOIntConsumer} that performs in sequence this operation followed by the {@code after} operation. + * @throws NullPointerException if {@code after} is null. */ default IOIntConsumer andThen(final IOIntConsumer after) { Objects.requireNonNull(after); diff --git a/src/main/java/org/apache/commons/io/function/IOIntSupplier.java b/src/main/java/org/apache/commons/io/function/IOIntSupplier.java index 02e770cc3a3..058763d0a18 100644 --- a/src/main/java/org/apache/commons/io/function/IOIntSupplier.java +++ b/src/main/java/org/apache/commons/io/function/IOIntSupplier.java @@ -42,7 +42,7 @@ default IntSupplier asIntSupplier() { /** * Gets a result. * - * @return a result + * @return a result. * @throws IOException if an I/O error occurs. */ int getAsInt() throws IOException; diff --git a/src/main/java/org/apache/commons/io/function/IOIterable.java b/src/main/java/org/apache/commons/io/function/IOIterable.java index 238040d1d65..dd656dff7dd 100644 --- a/src/main/java/org/apache/commons/io/function/IOIterable.java +++ b/src/main/java/org/apache/commons/io/function/IOIterable.java @@ -44,8 +44,8 @@ default Iterable asIterable() { * Like {@link Iterable#iterator()}. * * @param action The action to be performed for each element. - * @throws NullPointerException if the specified action is null. - * @throws IOException thrown by the given action. + * @throws NullPointerException Thrown if the specified action is null. + * @throws IOException Thrown if an I/O error occurs for a remaining element, or the given action throws. * @see Iterable#iterator() */ default void forEach(final IOConsumer action) throws IOException { @@ -75,6 +75,7 @@ default IOSpliterator spliterator() { *

    * Implementations may not have anything to unwrap and that behavior is undefined for now. *

    + * * @return the underlying Iterable. */ Iterable unwrap(); diff --git a/src/main/java/org/apache/commons/io/function/IOIterator.java b/src/main/java/org/apache/commons/io/function/IOIterator.java index 42cc235fc39..85727a6e431 100644 --- a/src/main/java/org/apache/commons/io/function/IOIterator.java +++ b/src/main/java/org/apache/commons/io/function/IOIterator.java @@ -36,8 +36,8 @@ public interface IOIterator { * Adapts the given Iterable as an IOIterator. * * @param the type of the stream elements. - * @param iterable The iterable to adapt - * @return A new IOIterator + * @param iterable The iterable to adapt. + * @return A new IOIterator. * @since 2.17.0 */ static IOIterator adapt(final Iterable iterable) { @@ -48,8 +48,8 @@ static IOIterator adapt(final Iterable iterable) { * Adapts the given Iterator as an IOIterator. * * @param the type of the stream elements. - * @param iterator The iterator to adapt - * @return A new IOIterator + * @param iterator The iterator to adapt. + * @return A new IOIterator. */ static IOIterator adapt(final Iterator iterator) { return IOIteratorAdapter.adapt(iterator); @@ -91,7 +91,7 @@ default void forEachRemaining(final IOConsumer action) throws IOExcep * * @return See delegate. * @throws IOException if an I/O error occurs. - * @throws NoSuchElementException if the iteration has no more elements + * @throws NoSuchElementException if the iteration has no more elements. */ E next() throws IOException; @@ -110,6 +110,7 @@ default void remove() throws IOException { *

    * Implementations may not have anything to unwrap and that behavior is undefined for now. *

    + * * @return the underlying Iterator. */ Iterator unwrap(); diff --git a/src/main/java/org/apache/commons/io/function/IOLongSupplier.java b/src/main/java/org/apache/commons/io/function/IOLongSupplier.java index 8e90866273c..707d3b548a5 100644 --- a/src/main/java/org/apache/commons/io/function/IOLongSupplier.java +++ b/src/main/java/org/apache/commons/io/function/IOLongSupplier.java @@ -42,7 +42,7 @@ default LongSupplier asSupplier() { /** * Gets a result. * - * @return a result + * @return a result. * @throws IOException if an I/O error occurs. */ long getAsLong() throws IOException; diff --git a/src/main/java/org/apache/commons/io/function/IOPredicate.java b/src/main/java/org/apache/commons/io/function/IOPredicate.java index fda3fddb6be..6186d85b4e5 100644 --- a/src/main/java/org/apache/commons/io/function/IOPredicate.java +++ b/src/main/java/org/apache/commons/io/function/IOPredicate.java @@ -25,7 +25,7 @@ /** * Like {@link Predicate} but throws {@link IOException}. * - * @param the type of the input to the predicate + * @param the type of the input to the predicate. * @since 2.12.0 */ @FunctionalInterface @@ -34,7 +34,7 @@ public interface IOPredicate { /** * Always false. * - * @param the type of the input to the predicate + * @param the type of the input to the predicate. * @return a constant predicate that tests always false. */ @SuppressWarnings("unchecked") @@ -45,7 +45,7 @@ static IOPredicate alwaysFalse() { /** * Always true. * - * @param the type of the input to the predicate + * @param the type of the input to the predicate. * @return a constant predicate that tests always true. */ @SuppressWarnings("unchecked") @@ -56,7 +56,7 @@ static IOPredicate alwaysTrue() { /** * Creates a predicate that tests if two arguments are equal using {@link Objects#equals(Object, Object)}. * - * @param the type of arguments to the predicate + * @param the type of arguments to the predicate. * @param target the object to compare for equality, may be {@code null} * @return a predicate that tests if two arguments are equal using {@link Objects#equals(Object, Object)} */ @@ -74,10 +74,10 @@ static IOPredicate isEqual(final Object target) { * predicate throws an exception, the {@code other} predicate will not be evaluated. *

    * - * @param other a predicate that will be logically-ANDed with this predicate + * @param other a predicate that will be logically-ANDed with this predicate. * @return a composed predicate that represents the short-circuiting logical AND of this predicate and the {@code other} - * predicate - * @throws NullPointerException if other is null + * predicate. + * @throws NullPointerException if other is null. */ default IOPredicate and(final IOPredicate other) { Objects.requireNonNull(other); @@ -97,7 +97,7 @@ default Predicate asPredicate() { /** * Creates a predicate that represents the logical negation of this predicate. * - * @return a predicate that represents the logical negation of this predicate + * @return a predicate that represents the logical negation of this predicate. */ default IOPredicate negate() { return t -> !test(t); @@ -113,10 +113,10 @@ default IOPredicate negate() { * predicate throws an exception, the {@code other} predicate will not be evaluated. *

    * - * @param other a predicate that will be logically-ORed with this predicate + * @param other a predicate that will be logically-ORed with this predicate. * @return a composed predicate that represents the short-circuiting logical OR of this predicate and the {@code other} - * predicate - * @throws NullPointerException if other is null + * predicate. + * @throws NullPointerException if other is null. */ default IOPredicate or(final IOPredicate other) { Objects.requireNonNull(other); @@ -126,7 +126,7 @@ default IOPredicate or(final IOPredicate other) { /** * Evaluates this predicate on the given argument. * - * @param t the input argument + * @param t the input argument. * @return {@code true} if the input argument matches the predicate, otherwise {@code false} * @throws IOException if an I/O error occurs. */ diff --git a/src/main/java/org/apache/commons/io/function/IOQuadFunction.java b/src/main/java/org/apache/commons/io/function/IOQuadFunction.java index 78db4284b67..f5852c09948 100644 --- a/src/main/java/org/apache/commons/io/function/IOQuadFunction.java +++ b/src/main/java/org/apache/commons/io/function/IOQuadFunction.java @@ -29,11 +29,11 @@ * {@link #apply(Object, Object, Object, Object)}. *

    * - * @param the type of the first argument to the function - * @param the type of the second argument to the function - * @param the type of the third argument to the function - * @param the type of the fourth argument to the function - * @param the type of the result of the function + * @param the type of the first argument to the function. + * @param the type of the second argument to the function. + * @param the type of the third argument to the function. + * @param the type of the fourth argument to the function. + * @param the type of the result of the function. * @see Function * @since 2.12.0 */ @@ -45,10 +45,10 @@ public interface IOQuadFunction { * function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param the type of output of the {@code after} function, and of the composed function - * @param after the function to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} function - * @throws NullPointerException if after is null + * @param the type of output of the {@code after} function, and of the composed function. + * @param after the function to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} function. + * @throws NullPointerException if after is null. */ default IOQuadFunction andThen(final IOFunction after) { Objects.requireNonNull(after); @@ -58,11 +58,11 @@ default IOQuadFunction andThen(final IOFunction { * Adapts the given Spliterator as an IOSpliterator. * * @param the type of the stream elements. - * @param iterator The iterator to adapt - * @return A new IOSpliterator + * @param iterator The iterator to adapt. + * @return A new IOSpliterator. */ static IOSpliterator adapt(final Spliterator iterator) { return IOSpliteratorAdapter.adapt(iterator); @@ -55,7 +55,7 @@ default Spliterator asSpliterator() { /** * Like {@link Spliterator#characteristics()}. * - * @return a representation of characteristics + * @return a representation of characteristics. */ default int characteristics() { return unwrap().characteristics(); @@ -74,8 +74,8 @@ default long estimateSize() { /** * Like {@link Spliterator#forEachRemaining(Consumer)}. * - * @param action The action - * @throws NullPointerException if the specified action is null + * @param action The action. + * @throws NullPointerException if the specified action is null. */ default void forEachRemaining(final IOConsumer action) { while (tryAdvance(action)) { // NOPMD @@ -105,7 +105,7 @@ default long getExactSizeIfKnown() { /** * Like {@link Spliterator#hasCharacteristics(int)}. * - * @param characteristics the characteristics to check for + * @param characteristics the characteristics to check for. * @return {@code true} if all the specified characteristics are present, else {@code false} */ default boolean hasCharacteristics(final int characteristics) { @@ -115,9 +115,9 @@ default boolean hasCharacteristics(final int characteristics) { /** * Like {@link Spliterator#tryAdvance(Consumer)}. * - * @param action The action + * @param action The action. * @return {@code false} if no remaining elements existed upon entry to this method, else {@code true}. - * @throws NullPointerException if the specified action is null + * @throws NullPointerException if the specified action is null. */ default boolean tryAdvance(final IOConsumer action) { return unwrap().tryAdvance(Objects.requireNonNull(action, "action").asConsumer()); @@ -127,7 +127,7 @@ default boolean tryAdvance(final IOConsumer action) { * Like {@link Spliterator#trySplit()}. * * @return a {@code Spliterator} covering some portion of the elements, or {@code null} if this spliterator cannot be - * split + * split. */ default IOSpliterator trySplit() { return adapt(unwrap().trySplit()); diff --git a/src/main/java/org/apache/commons/io/function/IOStream.java b/src/main/java/org/apache/commons/io/function/IOStream.java index fb5ee13b58c..87c6c832baf 100644 --- a/src/main/java/org/apache/commons/io/function/IOStream.java +++ b/src/main/java/org/apache/commons/io/function/IOStream.java @@ -66,7 +66,7 @@ static IOStream adapt(final Stream stream) { /** * This class' version of {@link Stream#empty()}. * - * @param the type of the stream elements + * @param the type of the stream elements. * @return an empty sequential {@code IOStreamImpl}. * @see Stream#empty() */ @@ -135,9 +135,9 @@ static IOStream of(final T... values) { /** * Returns a sequential {@code IOStreamImpl} containing a single element. * - * @param t the single element - * @param the type of stream elements - * @return a singleton sequential stream + * @param t the single element. + * @param the type of stream elements. + * @return a singleton sequential stream. */ static IOStream of(final T t) { return adapt(Stream.of(t)); diff --git a/src/main/java/org/apache/commons/io/function/IOTriConsumer.java b/src/main/java/org/apache/commons/io/function/IOTriConsumer.java index f1e78d2de35..304e94440f4 100644 --- a/src/main/java/org/apache/commons/io/function/IOTriConsumer.java +++ b/src/main/java/org/apache/commons/io/function/IOTriConsumer.java @@ -24,9 +24,9 @@ /** * Like {@link BiConsumer} but throws {@link IOException}. * - * @param the type of the first argument to the operation - * @param the type of the second argument to the operation - * @param the type of the third argument to the operation + * @param the type of the first argument to the operation. + * @param the type of the second argument to the operation. + * @param the type of the third argument to the operation. * @see BiConsumer * @since 2.12.0 */ @@ -36,9 +36,9 @@ public interface IOTriConsumer { /** * Returns the no-op singleton. * - * @param the type of the first argument to the operation - * @param the type of the second argument to the operation - * @param the type of the third argument to the operation + * @param the type of the first argument to the operation. + * @param the type of the second argument to the operation. + * @param the type of the third argument to the operation. * @return The no-op singleton. */ @SuppressWarnings("unchecked") @@ -49,9 +49,9 @@ static IOTriConsumer noop() { /** * Performs this operation on the given arguments. * - * @param t the first input argument - * @param u the second input argument - * @param v the second third argument + * @param t the first input argument. + * @param u the second input argument. + * @param v the second third argument. * @throws IOException if an I/O error occurs. */ void accept(T t, U u, V v) throws IOException; @@ -61,10 +61,10 @@ static IOTriConsumer noop() { * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation. * If performing this operation throws an exception, the {@code after} operation will not be performed. * - * @param after the operation to perform after this operation + * @param after the operation to perform after this operation. * @return a composed {@link IOTriConsumer} that performs in sequence this operation followed by the {@code after} - * operation - * @throws NullPointerException if {@code after} is null + * operation. + * @throws NullPointerException if {@code after} is null. */ default IOTriConsumer andThen(final IOTriConsumer after) { Objects.requireNonNull(after); diff --git a/src/main/java/org/apache/commons/io/function/IOTriFunction.java b/src/main/java/org/apache/commons/io/function/IOTriFunction.java index 1459c3962e8..d84069df1df 100644 --- a/src/main/java/org/apache/commons/io/function/IOTriFunction.java +++ b/src/main/java/org/apache/commons/io/function/IOTriFunction.java @@ -29,10 +29,10 @@ * {@link #apply(Object, Object, Object)}. *

    * - * @param the type of the first argument to the function - * @param the type of the second argument to the function - * @param the type of the third argument to the function - * @param the type of the result of the function + * @param the type of the first argument to the function. + * @param the type of the second argument to the function. + * @param the type of the third argument to the function. + * @param the type of the result of the function. * @see Function * @since 2.12.0 */ @@ -44,10 +44,10 @@ public interface IOTriFunction { * function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the * composed function. * - * @param the type of output of the {@code after} function, and of the composed function - * @param after the function to apply after this function is applied - * @return a composed function that first applies this function and then applies the {@code after} function - * @throws NullPointerException if after is null + * @param the type of output of the {@code after} function, and of the composed function. + * @param after the function to apply after this function is applied. + * @return a composed function that first applies this function and then applies the {@code after} function. + * @throws NullPointerException if after is null. */ default IOTriFunction andThen(final IOFunction after) { Objects.requireNonNull(after); @@ -57,10 +57,10 @@ default IOTriFunction andThen(final IOFunction implements Iterable { /** * Constructs a new instance. * - * @param delegate The delegate + * @param delegate The delegate. */ UncheckedIOIterable(final IOIterable delegate) { this.delegate = Objects.requireNonNull(delegate, "delegate"); diff --git a/src/main/java/org/apache/commons/io/function/UncheckedIOIterator.java b/src/main/java/org/apache/commons/io/function/UncheckedIOIterator.java index ec0bfb9867e..0fbbafb31dc 100644 --- a/src/main/java/org/apache/commons/io/function/UncheckedIOIterator.java +++ b/src/main/java/org/apache/commons/io/function/UncheckedIOIterator.java @@ -37,7 +37,7 @@ final class UncheckedIOIterator implements Iterator { /** * Constructs a new instance. * - * @param delegate The delegate + * @param delegate The delegate. */ UncheckedIOIterator(final IOIterator delegate) { this.delegate = Objects.requireNonNull(delegate, "delegate"); diff --git a/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java b/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java index 53974b060e5..b9851d93b1d 100644 --- a/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java +++ b/src/main/java/org/apache/commons/io/input/AbstractCharacterFilterReader.java @@ -40,7 +40,7 @@ public abstract class AbstractCharacterFilterReader extends FilterReader { /** * Constructs a new reader. * - * @param reader the reader to filter + * @param reader the reader to filter. */ protected AbstractCharacterFilterReader(final Reader reader) { this(reader, SKIP_NONE); diff --git a/src/main/java/org/apache/commons/io/input/AbstractLineEndingInputStream.java b/src/main/java/org/apache/commons/io/input/AbstractLineEndingInputStream.java new file mode 100644 index 00000000000..e42462cf12a --- /dev/null +++ b/src/main/java/org/apache/commons/io/input/AbstractLineEndingInputStream.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.input; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Abstracts {@link UnixLineEndingInputStream} and {@link WindowsLineEndingInputStream} to reduce duplication. + */ +abstract class AbstractLineEndingInputStream extends InputStream { + + boolean atEos; + + boolean atSlashCr; + + boolean atSlashLf; + + final InputStream in; + + final boolean lineFeedAtEos; + + /** + * Constructs an input stream that filters another stream + * + * @param inputStream The input stream to wrap. + * @param lineFeedAtEos true to ensure that the file ends with LF. + */ + AbstractLineEndingInputStream(final InputStream inputStream, final boolean lineFeedAtEos) { + this.in = inputStream; + this.lineFeedAtEos = lineFeedAtEos; + } + + /** + * Closes the stream. Also closes the underlying stream. + * + * @throws IOException If an I/O error occurs. + */ + @Override + public void close() throws IOException { + super.close(); + in.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void mark(final int readLimit) { + throw UnsupportedOperationExceptions.mark(); + } +} diff --git a/src/main/java/org/apache/commons/io/input/AutoCloseInputStream.java b/src/main/java/org/apache/commons/io/input/AutoCloseInputStream.java index 81f6c6e771d..4659f3f7038 100644 --- a/src/main/java/org/apache/commons/io/input/AutoCloseInputStream.java +++ b/src/main/java/org/apache/commons/io/input/AutoCloseInputStream.java @@ -112,7 +112,7 @@ private AutoCloseInputStream(final Builder builder) throws IOException { /** * Constructs an automatically closing proxy for the given input stream. * - * @param in underlying input stream + * @param in underlying input stream. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @SuppressWarnings("resource") // ClosedInputStream.nonNull() doesn't allocate @@ -124,8 +124,8 @@ public AutoCloseInputStream(final InputStream in) { /** * Automatically closes the stream if the end of stream was reached. * - * @param n number of bytes read, or -1 if no more bytes are available - * @throws IOException if the stream could not be closed + * @param n number of bytes read, or -1 if no more bytes are available. + * @throws IOException if the stream could not be closed. * @since 2.0 */ @Override @@ -146,7 +146,7 @@ protected void afterRead(final int n) throws IOException { * first called. *

    * - * @throws IOException if the underlying input stream cannot be closed + * @throws IOException if the underlying input stream cannot be closed. */ @Override public void close() throws IOException { @@ -158,7 +158,7 @@ public void close() throws IOException { * Ensures that the stream is closed before it gets garbage-collected. As mentioned in {@link #close()}, this is a no-op if the stream has already been * closed. * - * @throws Throwable if an error occurs + * @throws Throwable if an error occurs. */ @Override protected void finalize() throws Throwable { diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java index acfe8d9ddbb..488860cc412 100644 --- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io.input; import static org.apache.commons.io.IOUtils.EOF; @@ -31,8 +32,7 @@ /** * This class is used to wrap a stream that includes an encoded {@link ByteOrderMark} as its first bytes. *

    - * This class detects these bytes and, if required, can automatically skip them and return the subsequent byte as the - * first byte in the stream. + * This class detects these bytes and, if required, can automatically skip them and return the subsequent byte as the first byte in the stream. *

    *

    * The {@link ByteOrderMark} implementation has the following predefined BOMs: @@ -60,10 +60,7 @@ * *

    + *

    + * If an {@link ExecutorService} is not set, then a single-threaded daemon executor service is used. + *

    * * @see #get() * @since 2.12.0 @@ -90,7 +92,7 @@ public Builder() { *
      *
    • {@link #getInputStream()} gets the target aspect.
    • *
    • {@link #getBufferSize()}
    • - *
    • {@link ExecutorService}
    • + *
    • {@link ExecutorService}, if not set, a single-threaded daemon executor service is used.
    • *
    * * @return a new instance. @@ -108,8 +110,11 @@ public ReadAheadInputStream get() throws IOException { /** * Sets the executor service for the read-ahead thread. + *

    + * If not set, a single-threaded daemon executor service is used. + *

    * - * @param executorService the executor service for the read-ahead thread. + * @param executorService the executor service for the read-ahead thread, may be {@code null}. * @return {@code this} instance. */ public Builder setExecutorService(final ExecutorService executorService) { @@ -203,7 +208,7 @@ private ReadAheadInputStream(final Builder builder) throws IOException { } /** - * Constructs an instance with the specified buffer size and read-ahead threshold + * Constructs an instance with the specified buffer size and read-ahead threshold. * * @param inputStream The underlying input stream. * @param bufferSizeInBytes The buffer size. @@ -215,7 +220,7 @@ public ReadAheadInputStream(final InputStream inputStream, final int bufferSizeI } /** - * Constructs an instance with the specified buffer size and read-ahead threshold + * Constructs an instance with the specified buffer size and read-ahead threshold. * * @param inputStream The underlying input stream. * @param bufferSizeInBytes The buffer size. @@ -228,7 +233,7 @@ public ReadAheadInputStream(final InputStream inputStream, final int bufferSizeI } /** - * Constructs an instance with the specified buffer size and read-ahead threshold + * Constructs an instance with the specified buffer size and read-ahead threshold. * * @param inputStream The underlying input stream. * @param bufferSizeInBytes The buffer size. @@ -239,7 +244,7 @@ private ReadAheadInputStream(final InputStream inputStream, final int bufferSize final boolean shutdownExecutorService) { super(Objects.requireNonNull(inputStream, "inputStream")); if (bufferSizeInBytes <= 0) { - throw new IllegalArgumentException("bufferSizeInBytes should be greater than 0, but the value is " + bufferSizeInBytes); + throw new IllegalArgumentException(String.format("bufferSizeInBytes <= 0, bufferSizeInBytes = %,d", bufferSizeInBytes)); } this.executorService = Objects.requireNonNull(executorService, "executorService"); this.shutdownExecutorService = shutdownExecutorService; @@ -287,21 +292,21 @@ public void close() throws IOException { } finally { stateChangeLock.unlock(); } - if (shutdownExecutorService) { try { - executorService.shutdownNow(); - executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + shutdownAwait(); } catch (final InterruptedException e) { - final InterruptedIOException iio = new InterruptedIOException(e.getMessage()); - iio.initCause(e); - throw iio; + Thread.currentThread().interrupt(); + throw Input.toInterruptedIOException(e); } finally { if (isSafeToCloseUnderlyingInputStream) { super.close(); } } } + if (isSafeToCloseUnderlyingInputStream) { + super.close(); + } } private void closeUnderlyingInputStreamIfNecessary() { @@ -346,7 +351,6 @@ public int read(final byte[] b, final int offset, int len) throws IOException { if (len == 0) { return 0; } - if (!activeBuffer.hasRemaining()) { // No remaining in active buffer - lock and switch to write ahead buffer. stateChangeLock.lock(); @@ -459,6 +463,11 @@ private void readAsync() throws IOException { }); } + boolean shutdownAwait() throws InterruptedException { + executorService.shutdownNow(); + return executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } + private void signalAsyncReadComplete() { stateChangeLock.lock(); try { @@ -532,7 +541,7 @@ private long skipInternal(final long n) throws IOException { } /** - * Flips the active and read ahead buffer + * Flips the active and read ahead buffers. */ private void swapBuffers() { final ByteBuffer temp = activeBuffer; @@ -550,9 +559,8 @@ private void waitForAsyncReadComplete() throws IOException { asyncReadComplete.await(); } } catch (final InterruptedException e) { - final InterruptedIOException iio = new InterruptedIOException(e.getMessage()); - iio.initCause(e); - throw iio; + Thread.currentThread().interrupt(); + throw Input.toInterruptedIOException(e); } finally { try { isWaiting.set(false); diff --git a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java index 4d22d28f891..7e7379e9216 100644 --- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java @@ -209,6 +209,7 @@ private static CharsetEncoder newEncoder(final Charset charset) { * CharBuffer used as input for the decoder. It should be reasonably large as we read data from the underlying Reader into this buffer. */ private final CharBuffer encoderIn; + /** * ByteBuffer used as output for the decoder. This buffer can be small as it is only used to transfer data from the decoder to the buffer provided by the * caller. @@ -225,11 +226,11 @@ private ReaderInputStream(final Builder builder) throws IOException { } /** - * Constructs a new {@link ReaderInputStream} that uses the virtual machine's {@link Charset#defaultCharset() default charset} with a default input buffer - * size of {@value IOUtils#DEFAULT_BUFFER_SIZE} characters. + * Constructs a new {@link ReaderInputStream} that uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} with a default input + * buffer size of {@value IOUtils#DEFAULT_BUFFER_SIZE} characters. * * @param reader the target {@link Reader} - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader) { @@ -244,7 +245,7 @@ public ReaderInputStream(final Reader reader) { *

    * * @param reader the target {@link Reader} - * @param charset the charset encoding + * @param charset the charset encoding. * @deprecated Use {@link ReaderInputStream#builder()} instead, will be protected for subclasses. */ @Deprecated @@ -262,17 +263,11 @@ public ReaderInputStream(final Reader reader, final Charset charset) { * @param reader the target {@link Reader}. * @param charset the charset encoding. * @param bufferSize the size of the input buffer in number of characters. - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) { - // @formatter:off - this(reader, - Charsets.toCharset(charset).newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE), - bufferSize); - // @formatter:on + this(reader, newEncoder(charset), bufferSize); } /** @@ -284,9 +279,9 @@ public ReaderInputStream(final Reader reader, final Charset charset, final int b *

    * * @param reader the target {@link Reader} - * @param charsetEncoder the charset encoder + * @param charsetEncoder the charset encoder. * @since 2.1 - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder) { @@ -303,9 +298,9 @@ public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncode * * @param reader the target {@link Reader} * @param charsetEncoder the charset encoder, null defaults to the default Charset encoder. - * @param bufferSize the size of the input buffer in number of characters + * @param bufferSize the size of the input buffer in number of characters. * @since 2.1 - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder, final int bufferSize) { @@ -325,8 +320,8 @@ public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncode *

    * * @param reader the target {@link Reader} - * @param charsetName the name of the charset encoding - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @param charsetName the name of the charset encoding. + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader, final String charsetName) { @@ -342,8 +337,8 @@ public ReaderInputStream(final Reader reader, final String charsetName) { * * @param reader the target {@link Reader} * @param charsetName the name of the charset encoding, null maps to the default Charset. - * @param bufferSize the size of the input buffer in number of characters - * @deprecated Use {@link ReaderInputStream#builder()} instead + * @param bufferSize the size of the input buffer in number of characters. + * @deprecated Use {@link ReaderInputStream#builder()} instead. */ @Deprecated public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) { @@ -372,7 +367,7 @@ public void close() throws IOException { /** * Fills the internal char buffer from the reader. * - * @throws IOException If an I/O error occurs + * @throws IOException If an I/O error occurs. */ private void fillBuffer() throws IOException { if (endOfInput) { @@ -415,7 +410,7 @@ CharsetEncoder getCharsetEncoder() { /** * Reads a single byte. * - * @return either the byte read or {@code -1} if the end of the stream has been reached + * @return either the byte read or {@code -1} if the end of the stream has been reached. * @throws IOException if an I/O error occurs. */ @Override @@ -436,7 +431,7 @@ public int read() throws IOException { * Reads the specified number of bytes into an array. * * @param b the byte array to read into, must not be {@code null} - * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @return the number of bytes read or {@code -1} if the end of the stream has been reached. * @throws NullPointerException if the byte array is {@code null}. * @throws IOException if an I/O error occurs. */ @@ -448,10 +443,10 @@ public int read(final byte[] b) throws IOException { /** * Reads the specified number of bytes into an array. * - * @param array the byte array to read into - * @param off the offset to start reading bytes into - * @param len the number of bytes to read - * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @param array the byte array to read into. + * @param off the offset to start reading bytes into. + * @param len the number of bytes to read. + * @return the number of bytes read or {@code -1} if the end of the stream has been reached. * @throws NullPointerException if the byte array is {@code null}. * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code array.length}. * @throws IOException if an I/O error occurs. diff --git a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java index 949ff8cbd8e..43ad4da1040 100644 --- a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java +++ b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java @@ -25,7 +25,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; @@ -93,6 +92,7 @@ public static class Builder extends AbstractStreamBuilder -1) { if (!isLastFilePart && i < avoidNewlineSplitBufferSize) { @@ -234,6 +241,10 @@ private String readLine() { //NOPMD Bug in PMD line = new String(lineData, charset); currentLastBytePos = i - newLineMatchByteCount; + + if (isLastFilePart && currentLastBytePos == -1 && i == 0) { + leftOver = new byte[0]; + } break; // found line } @@ -242,14 +253,22 @@ private String readLine() { //NOPMD Bug in PMD // end of file part handling if (i < 0) { - createLeftOver(); + if (isLastFilePart) { + final int lineLengthBytes = currentLastBytePos + 1; + if (lineLengthBytes > 0) { + final byte[] lineData = Arrays.copyOf(data, lineLengthBytes); + line = new String(lineData, charset); + } + currentLastBytePos = -1; + } else { + createLeftOver(); + } break; // end of file part } } - // last file part handling - if (isLastFilePart && leftOver != null) { - // there will be partNumber line break anymore, this is the first line of the file + // there will be partNumber line break anymore, this is the first line of the file + if (line == null && isLastFilePart && leftOver != null) { line = new String(leftOver, charset); leftOver = null; } @@ -260,8 +279,8 @@ private String readLine() { //NOPMD Bug in PMD /** * Handles block rollover * - * @return the new FilePart or null - * @throws IOException if there was a problem reading the file + * @return the new FilePart or null. + * @throws IOException if there was a problem reading the file. */ private FilePart rollOver() throws IOException { @@ -311,42 +330,42 @@ private ReversedLinesFileReader(final Builder builder) throws IOException { this.blockSize = builder.getBufferSize(); this.charset = Charsets.toCharset(builder.getCharset()); // check & prepare encoding - final CharsetEncoder charsetEncoder = this.charset.newEncoder(); + final CharsetEncoder charsetEncoder = charset.newEncoder(); final float maxBytesPerChar = charsetEncoder.maxBytesPerChar(); - if (maxBytesPerChar == 1f || this.charset == StandardCharsets.UTF_8) { + if (maxBytesPerChar == 1f || charset == StandardCharsets.UTF_8) { // all one byte encodings are partNumber problem byteDecrement = 1; - } else if (this.charset == Charset.forName("Shift_JIS") || // Same as for UTF-8 + } else if (charset == Charset.forName("Shift_JIS") || // Same as for UTF-8 // http://www.herongyang.com/Unicode/JIS-Shift-JIS-Encoding.html - this.charset == Charset.forName("windows-31j") || // Windows code page 932 (Japanese) - this.charset == Charset.forName("x-windows-949") || // Windows code page 949 (Korean) - this.charset == Charset.forName("gbk") || // Windows code page 936 (Simplified Chinese) - this.charset == Charset.forName("x-windows-950")) { // Windows code page 950 (Traditional Chinese) + charset == Charset.forName("windows-31j") || // Windows code page 932 (Japanese) + charset == Charset.forName("x-windows-949") || // Windows code page 949 (Korean) + charset == Charset.forName("gbk") || // Windows code page 936 (Simplified Chinese) + charset == Charset.forName("x-windows-950")) { // Windows code page 950 (Traditional Chinese) byteDecrement = 1; - } else if (this.charset == StandardCharsets.UTF_16BE || this.charset == StandardCharsets.UTF_16LE) { + } else if (charset == StandardCharsets.UTF_16BE || charset == StandardCharsets.UTF_16LE) { // UTF-16 new line sequences are not allowed as second tuple of four byte // sequences, // however byte order has to be specified byteDecrement = 2; - } else if (this.charset == StandardCharsets.UTF_16) { + } else if (charset == StandardCharsets.UTF_16) { throw new UnsupportedEncodingException("For UTF-16, you need to specify the byte order (use UTF-16BE or UTF-16LE)"); } else { throw new UnsupportedEncodingException("Encoding " + charset + " is not supported yet (feel free to submit a patch)"); } // NOTE: The new line sequences are matched in the order given, so it is // important that \r\n is BEFORE \n - this.newLineSequences = new byte[][] { StandardLineSeparator.CRLF.getBytes(this.charset), StandardLineSeparator.LF.getBytes(this.charset), - StandardLineSeparator.CR.getBytes(this.charset) }; + this.newLineSequences = new byte[][] { StandardLineSeparator.CRLF.getBytes(charset), StandardLineSeparator.LF.getBytes(charset), + StandardLineSeparator.CR.getBytes(charset) }; this.avoidNewlineSplitBufferSize = newLineSequences[0].length; // Open file - this.channel = Files.newByteChannel(builder.getPath(), StandardOpenOption.READ); + this.channel = builder.getChannel(SeekableByteChannel.class); this.totalByteLength = channel.size(); - int lastBlockLength = (int) (this.totalByteLength % blockSize); + int lastBlockLength = (int) (totalByteLength % blockSize); if (lastBlockLength > 0) { - this.totalBlockCount = this.totalByteLength / blockSize + 1; + this.totalBlockCount = totalByteLength / blockSize + 1; } else { - this.totalBlockCount = this.totalByteLength / blockSize; - if (this.totalByteLength > 0) { + this.totalBlockCount = totalByteLength / blockSize; + if (totalByteLength > 0) { lastBlockLength = blockSize; } } @@ -354,9 +373,9 @@ private ReversedLinesFileReader(final Builder builder) throws IOException { } /** - * Constructs a ReversedLinesFileReader with default block size of 4KB and the virtual machine's {@link Charset#defaultCharset() default charset}. + * Constructs a ReversedLinesFileReader with default block size of 4KB and the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * - * @param file the file to be read + * @param file the file to be read. * @throws IOException if an I/O error occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @@ -369,7 +388,7 @@ public ReversedLinesFileReader(final File file) throws IOException { * Constructs a ReversedLinesFileReader with default block size of 4KB and the * specified encoding. * - * @param file the file to be read + * @param file the file to be read. * @param charset the charset to use, null uses the default Charset. * @throws IOException if an I/O error occurs. * @since 2.5 @@ -383,7 +402,7 @@ public ReversedLinesFileReader(final File file, final Charset charset) throws IO /** * Constructs a ReversedLinesFileReader with the given block size and encoding. * - * @param file the file to be read + * @param file the file to be read. * @param blockSize size of the internal buffer (for ideal performance this * should match with the block size of the underlying file * system). @@ -400,13 +419,13 @@ public ReversedLinesFileReader(final File file, final int blockSize, final Chars /** * Constructs a ReversedLinesFileReader with the given block size and encoding. * - * @param file the file to be read + * @param file the file to be read. * @param blockSize size of the internal buffer (for ideal performance this * should match with the block size of the underlying file * system). * @param charsetName the encoding of the file, null uses the default Charset. - * @throws IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported + * @throws IOException if an I/O error occurs. + * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated @@ -418,7 +437,7 @@ public ReversedLinesFileReader(final File file, final int blockSize, final Strin * Constructs a ReversedLinesFileReader with default block size of 4KB and the * specified encoding. * - * @param file the file to be read + * @param file the file to be read. * @param charset the charset to use, null uses the default Charset. * @throws IOException if an I/O error occurs. * @since 2.7 @@ -432,7 +451,7 @@ public ReversedLinesFileReader(final Path file, final Charset charset) throws IO /** * Constructs a ReversedLinesFileReader with the given block size and encoding. * - * @param file the file to be read + * @param file the file to be read. * @param blockSize size of the internal buffer (for ideal performance this * should match with the block size of the underlying file * system). @@ -449,13 +468,13 @@ public ReversedLinesFileReader(final Path file, final int blockSize, final Chars /** * Constructs a ReversedLinesFileReader with the given block size and encoding. * - * @param file the file to be read + * @param file the file to be read. * @param blockSize size of the internal buffer (for ideal performance this * should match with the block size of the underlying file * system). * @param charsetName the encoding of the file, null uses the default Charset. - * @throws IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported + * @throws IOException if an I/O error occurs. + * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported. * @since 2.7 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @@ -509,7 +528,7 @@ public Iterator unwrap() { /** * Returns the lines of the file from bottom to top. * - * @return the next line or null if the start of the file is reached + * @return the next line or null if the start of the file is reached. * @throws IOException if an I/O error occurs. */ public String readLine() throws IOException { @@ -541,7 +560,7 @@ public String readLine() throws IOException { *

    * * @param lineCount How many lines to read. - * @return A new list + * @return A new list. * @throws IOException if an I/O error occurs. * @since 2.8.0 */ diff --git a/src/main/java/org/apache/commons/io/input/SequenceReader.java b/src/main/java/org/apache/commons/io/input/SequenceReader.java index daeb71faa92..269ce1cb8be 100644 --- a/src/main/java/org/apache/commons/io/input/SequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/SequenceReader.java @@ -44,7 +44,7 @@ public class SequenceReader extends Reader { /** * Constructs a new instance with readers * - * @param readers the readers to read + * @param readers the readers to read. */ public SequenceReader(final Iterable readers) { this.readers = Objects.requireNonNull(readers, "readers").iterator(); @@ -54,7 +54,7 @@ public SequenceReader(final Iterable readers) { /** * Constructs a new instance with readers * - * @param readers the readers to read + * @param readers the readers to read. */ public SequenceReader(final Reader... readers) { this(Arrays.asList(readers)); diff --git a/src/main/java/org/apache/commons/io/input/SwappedDataInputStream.java b/src/main/java/org/apache/commons/io/input/SwappedDataInputStream.java index a901ec6bc74..72e46367490 100644 --- a/src/main/java/org/apache/commons/io/input/SwappedDataInputStream.java +++ b/src/main/java/org/apache/commons/io/input/SwappedDataInputStream.java @@ -37,7 +37,7 @@ public class SwappedDataInputStream extends ProxyInputStream implements DataInpu /** * Constructs a SwappedDataInputStream. * - * @param input InputStream to read from + * @param input InputStream to read from. */ public SwappedDataInputStream(final InputStream input) { super(input); @@ -46,9 +46,9 @@ public SwappedDataInputStream(final InputStream input) { /** * Return {@code {@link #readByte()} != 0} * - * @return false if the byte read is zero, otherwise true + * @return false if the byte read is zero, otherwise true. * @throws IOException if an I/O error occurs. - * @throws EOFException if an end of file is reached unexpectedly + * @throws EOFException if an end of file is reached unexpectedly. */ @Override public boolean readBoolean() throws IOException, EOFException { @@ -58,9 +58,9 @@ public boolean readBoolean() throws IOException, EOFException { /** * Invokes the delegate's {@code read()} method. * - * @return the byte read or -1 if the end of stream + * @return the byte read or -1 if the end of stream. * @throws IOException if an I/O error occurs. - * @throws EOFException if an end of file is reached unexpectedly + * @throws EOFException if an end of file is reached unexpectedly. */ @Override public byte readByte() throws IOException, EOFException { @@ -70,9 +70,9 @@ public byte readByte() throws IOException, EOFException { /** * Reads a 2 byte, unsigned, little-endian UTF-16 code point. * - * @return the UTF-16 code point read or -1 if the end of stream + * @return the UTF-16 code point read or -1 if the end of stream. * @throws IOException if an I/O error occurs. - * @throws EOFException if an end of file is reached unexpectedly + * @throws EOFException if an end of file is reached unexpectedly. */ @Override public char readChar() throws IOException, EOFException { @@ -82,9 +82,9 @@ public char readChar() throws IOException, EOFException { /** * Reads an 8 byte, two's complement, little-endian long. * - * @return the read long + * @return the read long. * @throws IOException if an I/O error occurs. - * @throws EOFException if an end of file is reached unexpectedly + * @throws EOFException if an end of file is reached unexpectedly. */ @Override public double readDouble() throws IOException, EOFException { @@ -94,9 +94,9 @@ public double readDouble() throws IOException, EOFException { /** * Reads a 4 byte, IEEE 754, little-endian float. * - * @return the read float + * @return the read float. * @throws IOException if an I/O error occurs. - * @throws EOFException if an end of file is reached unexpectedly + * @throws EOFException if an end of file is reached unexpectedly. */ @Override public float readFloat() throws IOException, EOFException { @@ -106,8 +106,8 @@ public float readFloat() throws IOException, EOFException { /** * Invokes the delegate's {@code read(byte[] data, int, int)} method. * - * @param data the buffer to read the bytes into - * @throws EOFException if an end of file is reached unexpectedly + * @param data the buffer to read the bytes into. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -118,10 +118,10 @@ public void readFully(final byte[] data) throws IOException, EOFException { /** * Invokes the delegate's {@code read(byte[] data, int, int)} method. * - * @param data the buffer to read the bytes into - * @param offset The start offset - * @param length The number of bytes to read - * @throws EOFException if an end of file is reached unexpectedly + * @param data the buffer to read the bytes into. + * @param offset The start offset. + * @param length The number of bytes to read. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -143,8 +143,8 @@ public void readFully(final byte[] data, final int offset, final int length) thr /** * Reads a 4 byte, two's complement little-endian integer. * - * @return the read int - * @throws EOFException if an end of file is reached unexpectedly + * @return the read int. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -155,10 +155,10 @@ public int readInt() throws IOException, EOFException { /** * Not currently supported - throws {@link UnsupportedOperationException}. * - * @return the line read - * @throws EOFException if an end of file is reached unexpectedly - * @throws IOException if an I/O error occurs - * @throws UnsupportedOperationException always + * @return the line read. + * @throws EOFException if an end of file is reached unexpectedly. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedOperationException always. */ @Override public String readLine() throws IOException, EOFException { @@ -168,8 +168,8 @@ public String readLine() throws IOException, EOFException { /** * Reads an 8 byte, two's complement little-endian integer. * - * @return the read long - * @throws EOFException if an end of file is reached unexpectedly + * @return the read long. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -180,8 +180,8 @@ public long readLong() throws IOException, EOFException { /** * Reads a 2 byte, two's complement, little-endian integer. * - * @return the read short - * @throws EOFException if an end of file is reached unexpectedly + * @return the read short. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -192,8 +192,8 @@ public short readShort() throws IOException, EOFException { /** * Invokes the delegate's {@code read()} method. * - * @return the byte read or -1 if the end of stream - * @throws EOFException if an end of file is reached unexpectedly + * @return the byte read or -1 if the end of stream. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -204,8 +204,8 @@ public int readUnsignedByte() throws IOException, EOFException { /** * Reads a 2 byte, unsigned, little-endian integer. * - * @return the read short - * @throws EOFException if an end of file is reached unexpectedly + * @return the read short. + * @throws EOFException if an end of file is reached unexpectedly. * @throws IOException if an I/O error occurs. */ @Override @@ -216,10 +216,10 @@ public int readUnsignedShort() throws IOException, EOFException { /** * Not currently supported - throws {@link UnsupportedOperationException}. * - * @return never - * @throws EOFException if an end of file is reached unexpectedly - * @throws IOException if an I/O error occurs - * @throws UnsupportedOperationException always + * @return never. + * @throws EOFException if an end of file is reached unexpectedly. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedOperationException always. */ @Override public String readUTF() throws IOException, EOFException { @@ -229,9 +229,9 @@ public String readUTF() throws IOException, EOFException { /** * Invokes the delegate's {@code skip(int)} method. * - * @param count the number of bytes to skip - * @return the number of bytes skipped or -1 if the end of stream - * @throws IOException if an I/O error occurs + * @param count the number of bytes to skip. + * @return the number of bytes skipped or -1 if the end of stream. + * @throws IOException if an I/O error occurs. */ @Override public int skipBytes(final int count) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/TaggedInputStream.java b/src/main/java/org/apache/commons/io/input/TaggedInputStream.java index 141f9c54602..88ad492246d 100644 --- a/src/main/java/org/apache/commons/io/input/TaggedInputStream.java +++ b/src/main/java/org/apache/commons/io/input/TaggedInputStream.java @@ -76,7 +76,7 @@ public class TaggedInputStream extends ProxyInputStream { /** * Constructs a tagging decorator for the given input stream. * - * @param proxy input stream to be decorated + * @param proxy input stream to be decorated. */ public TaggedInputStream(final InputStream proxy) { super(proxy); @@ -85,7 +85,7 @@ public TaggedInputStream(final InputStream proxy) { /** * Tags any IOExceptions thrown, wrapping and re-throwing. * - * @param e The IOException thrown + * @param e The IOException thrown. * @throws IOException if an I/O error occurs. */ @Override @@ -96,9 +96,9 @@ protected void handleIOException(final IOException e) throws IOException { /** * Tests if the given exception was caused by this stream. * - * @param exception an exception + * @param exception an exception. * @return {@code true} if the exception was thrown by this stream, - * {@code false} otherwise + * {@code false} otherwise. */ public boolean isCauseOf(final Throwable exception) { return TaggedIOException.isTaggedWith(exception, tag); @@ -111,8 +111,8 @@ public boolean isCauseOf(final Throwable exception) { * original wrapped exception. Returns normally if the exception was * not thrown by this stream. * - * @param throwable an exception - * @throws IOException original exception, if any, thrown by this stream + * @param throwable an exception. + * @throws IOException original exception, if any, thrown by this stream. */ public void throwIfCauseOf(final Throwable throwable) throws IOException { TaggedIOException.throwCauseIfTaggedWith(throwable, tag); diff --git a/src/main/java/org/apache/commons/io/input/TaggedReader.java b/src/main/java/org/apache/commons/io/input/TaggedReader.java index 97297a9fd28..2573873d271 100644 --- a/src/main/java/org/apache/commons/io/input/TaggedReader.java +++ b/src/main/java/org/apache/commons/io/input/TaggedReader.java @@ -75,7 +75,7 @@ public class TaggedReader extends ProxyReader { /** * Constructs a tagging decorator for the given reader. * - * @param proxy reader to be decorated + * @param proxy reader to be decorated. */ public TaggedReader(final Reader proxy) { super(proxy); @@ -84,7 +84,7 @@ public TaggedReader(final Reader proxy) { /** * Tags any IOExceptions thrown, wrapping and re-throwing. * - * @param e The IOException thrown + * @param e The IOException thrown. * @throws IOException if an I/O error occurs. */ @Override @@ -95,8 +95,8 @@ protected void handleIOException(final IOException e) throws IOException { /** * Tests if the given exception was caused by this reader. * - * @param exception an exception - * @return {@code true} if the exception was thrown by this reader, {@code false} otherwise + * @param exception an exception. + * @return {@code true} if the exception was thrown by this reader, {@code false} otherwise. */ public boolean isCauseOf(final Throwable exception) { return TaggedIOException.isTaggedWith(exception, tag); @@ -107,8 +107,8 @@ public boolean isCauseOf(final Throwable exception) { * {@link TaggedIOException} wrapper created by this decorator, and then unwraps and throws the original wrapped * exception. Returns normally if the exception was not thrown by this reader. * - * @param throwable an exception - * @throws IOException original exception, if any, thrown by this reader + * @param throwable an exception. + * @throws IOException original exception, if any, thrown by this reader. */ public void throwIfCauseOf(final Throwable throwable) throws IOException { TaggedIOException.throwCauseIfTaggedWith(throwable, tag); diff --git a/src/main/java/org/apache/commons/io/input/Tailer.java b/src/main/java/org/apache/commons/io/input/Tailer.java index 4d9fe9c4751..d8ef0db0818 100644 --- a/src/main/java/org/apache/commons/io/input/Tailer.java +++ b/src/main/java/org/apache/commons/io/input/Tailer.java @@ -275,7 +275,7 @@ protected Builder setOrigin(final AbstractOrigin origin) { /** * Sets the re-open behavior. * - * @param reOpen whether to close/reopen the file between chunks + * @param reOpen whether to close/reopen the file between chunks. * @return {@code this} instance. */ public Builder setReOpen(final boolean reOpen) { @@ -495,7 +495,7 @@ public String toString() { private static final String RAF_READ_ONLY_MODE = "r"; /** - * The the virtual machine's {@link Charset#defaultCharset() default charset} used for reading files. + * The the virtual machine's {@linkplain Charset#defaultCharset() default charset} used for reading files. */ private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); @@ -740,12 +740,12 @@ private Tailer(final Builder builder) { * Creates a Tailer for the given file, with a specified buffer size. * * @param file the file to follow. - * @param charset the Charset to be used for reading the file + * @param charset the Charset to be used for reading the file. * @param tailerListener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. - * @param reOpen if true, close and reopen the file between reading chunks - * @param bufSize Buffer size + * @param reOpen if true, close and reopen the file between reading chunks. + * @param bufSize Buffer size. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -800,7 +800,7 @@ public Tailer(final File file, final TailerListener tailerListener, final long d * @param tailerListener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. - * @param reOpen if true, close and reopen the file between reading chunks + * @param reOpen if true, close and reopen the file between reading chunks. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -815,8 +815,8 @@ public Tailer(final File file, final TailerListener tailerListener, final long d * @param tailerListener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. - * @param reOpen if true, close and reopen the file between reading chunks - * @param bufferSize Buffer size + * @param reOpen if true, close and reopen the file between reading chunks. + * @param bufferSize Buffer size. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -831,7 +831,7 @@ public Tailer(final File file, final TailerListener tailerListener, final long d * @param tailerListener the TailerListener to use. * @param delayMillis the delay between checks of the file for new content in milliseconds. * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. - * @param bufferSize Buffer size + * @param bufferSize Buffer size. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -843,13 +843,13 @@ public Tailer(final File file, final TailerListener tailerListener, final long d * Creates a Tailer for the given file, with a specified buffer size. * * @param tailable the file to follow. - * @param charset the Charset to be used for reading the file + * @param charset the Charset to be used for reading the file. * @param tailerListener the TailerListener to use. * @param delayDuration the delay between checks of the file for new content in milliseconds. * @param tailAtEnd Set to true to tail from the end of the file, false to tail from the beginning of the file. - * @param reOpen if true, close and reopen the file between reading chunks - * @param ignoreTouch if true, file timestamp changes without content change get ignored - * @param bufferSize Buffer size + * @param reOpen if true, close and reopen the file between reading chunks. + * @param ignoreTouch if true, file timestamp changes without content change get ignored. + * @param bufferSize Buffer size. */ private Tailer(final Tailable tailable, final Charset charset, final TailerListener tailerListener, final Duration delayDuration, final boolean tailAtEnd, final boolean reOpen, final int bufferSize, final boolean ignoreTouch) { @@ -897,8 +897,8 @@ public Duration getDelayDuration() { /** * Gets the file. * - * @return the file - * @throws IllegalStateException if constructed using a user provided {@link Tailable} implementation + * @return the file. + * @throws IllegalStateException if constructed using a user provided {@link Tailable} implementation. */ public File getFile() { if (tailable instanceof TailablePath) { @@ -920,7 +920,7 @@ protected boolean getRun() { /** * Gets the Tailable. * - * @return the Tailable + * @return the Tailable. * @since 2.12.0 */ public Tailable getTailable() { @@ -930,8 +930,8 @@ public Tailable getTailable() { /** * Reads new lines. * - * @param reader The file to read - * @return The new position after the lines have been read + * @param reader The file to read. + * @return The new position after the lines have been read. * @throws IOException if an I/O error occurs. */ private long readLines(final RandomAccessResourceBridge reader) throws IOException { @@ -1043,7 +1043,7 @@ public void run() { * - gets "touched" * - Files.getLastModifiedTime returns a new timestamp but newer data is not yet there ( * was reported to happen on busy systems or samba network shares, see IO-279) - * The default behaviour is to replay the whole file. If this is unsdesired in your usecase, + * The default behavior is to replay the whole file. If this is undesired in your usecase, * use the ignoreTouch builder flag */ if (!ignoreTouch) { diff --git a/src/main/java/org/apache/commons/io/input/TailerListenerAdapter.java b/src/main/java/org/apache/commons/io/input/TailerListenerAdapter.java index ac9380f3e79..1a9dfd76c4a 100644 --- a/src/main/java/org/apache/commons/io/input/TailerListenerAdapter.java +++ b/src/main/java/org/apache/commons/io/input/TailerListenerAdapter.java @@ -35,7 +35,7 @@ public TailerListenerAdapter() { * * Note: this is called from the tailer thread. * - * Note: a future version of commons-io will pull this method up to the TailerListener interface, + * Note: a future version of Commons IO will pull this method up to the TailerListener interface, * for now clients must subclass this class to use this feature. * * @since 2.5 diff --git a/src/main/java/org/apache/commons/io/input/TeeInputStream.java b/src/main/java/org/apache/commons/io/input/TeeInputStream.java index 1949f977e09..0c852cd5f88 100644 --- a/src/main/java/org/apache/commons/io/input/TeeInputStream.java +++ b/src/main/java/org/apache/commons/io/input/TeeInputStream.java @@ -55,8 +55,8 @@ public class TeeInputStream extends ProxyInputStream { * and copies all read bytes to the given {@link OutputStream}. The given * output stream will not be closed when this stream gets closed. * - * @param input input stream to be proxied - * @param branch output stream that will receive a copy of all bytes read + * @param input input stream to be proxied. + * @param branch output stream that will receive a copy of all bytes read. */ public TeeInputStream(final InputStream input, final OutputStream branch) { this(input, branch, false); @@ -68,10 +68,10 @@ public TeeInputStream(final InputStream input, final OutputStream branch) { * output stream will be closed when this stream gets closed if the * closeBranch parameter is {@code true}. * - * @param input input stream to be proxied - * @param branch output stream that will receive a copy of all bytes read + * @param input input stream to be proxied. + * @param branch output stream that will receive a copy of all bytes read. * @param closeBranch flag for closing also the output stream when this - * stream is closed + * stream is closed. */ public TeeInputStream( final InputStream input, final OutputStream branch, final boolean closeBranch) { @@ -85,7 +85,7 @@ public TeeInputStream( * output stream. An exception thrown from one stream will not prevent * closing of the other stream. * - * @throws IOException if either of the streams could not be closed + * @throws IOException if either of the streams could not be closed. */ @Override public void close() throws IOException { @@ -102,8 +102,8 @@ public void close() throws IOException { * Reads a single byte from the proxied input stream and writes it to * the associated output stream. * - * @return next byte from the stream, or -1 if the stream has ended - * @throws IOException if the stream could not be read (or written) + * @return next byte from the stream, or -1 if the stream has ended. + * @throws IOException if the stream could not be read (or written). */ @Override public int read() throws IOException { @@ -118,9 +118,9 @@ public int read() throws IOException { * Reads bytes from the proxied input stream and writes the read bytes * to the associated output stream. * - * @param bts byte buffer - * @return number of bytes read, or -1 if the stream has ended - * @throws IOException if the stream could not be read (or written) + * @param bts byte buffer. + * @return number of bytes read, or -1 if the stream has ended. + * @throws IOException if the stream could not be read (or written). */ @Override public int read(final byte[] bts) throws IOException { @@ -135,11 +135,11 @@ public int read(final byte[] bts) throws IOException { * Reads bytes from the proxied input stream and writes the read bytes * to the associated output stream. * - * @param bts byte buffer - * @param st start offset within the buffer - * @param end maximum number of bytes to read - * @return number of bytes read, or -1 if the stream has ended - * @throws IOException if the stream could not be read (or written) + * @param bts byte buffer. + * @param st start offset within the buffer. + * @param end maximum number of bytes to read. + * @return number of bytes read, or -1 if the stream has ended. + * @throws IOException if the stream could not be read (or written). */ @Override public int read(final byte[] bts, final int st, final int end) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/TeeReader.java b/src/main/java/org/apache/commons/io/input/TeeReader.java index 72fa4ecb2f5..892f1d575bf 100644 --- a/src/main/java/org/apache/commons/io/input/TeeReader.java +++ b/src/main/java/org/apache/commons/io/input/TeeReader.java @@ -50,8 +50,8 @@ public class TeeReader extends ProxyReader { * Constructs a TeeReader that proxies the given {@link Reader} and copies all read characters to the given * {@link Writer}. The given writer will not be closed when this reader gets closed. * - * @param input reader to be proxied - * @param branch writer that will receive a copy of all characters read + * @param input reader to be proxied. + * @param branch writer that will receive a copy of all characters read. */ public TeeReader(final Reader input, final Writer branch) { this(input, branch, false); @@ -62,9 +62,9 @@ public TeeReader(final Reader input, final Writer branch) { * {@link Writer}. The given writer will be closed when this reader gets closed if the closeBranch parameter is * {@code true}. * - * @param input reader to be proxied - * @param branch writer that will receive a copy of all characters read - * @param closeBranch flag for closing also the writer when this reader is closed + * @param input reader to be proxied. + * @param branch writer that will receive a copy of all characters read. + * @param closeBranch flag for closing also the writer when this reader is closed. */ public TeeReader(final Reader input, final Writer branch, final boolean closeBranch) { super(input); @@ -76,7 +76,7 @@ public TeeReader(final Reader input, final Writer branch, final boolean closeBra * Closes the proxied reader and, if so configured, the associated writer. An exception thrown from the reader will * not prevent closing of the writer. * - * @throws IOException if either the reader or writer could not be closed + * @throws IOException if either the reader or writer could not be closed. */ @Override public void close() throws IOException { @@ -92,8 +92,8 @@ public void close() throws IOException { /** * Reads a single character from the proxied reader and writes it to the associated writer. * - * @return next character from the reader, or -1 if the reader has ended - * @throws IOException if the reader could not be read (or written) + * @return next character from the reader, or -1 if the reader has ended. + * @throws IOException if the reader could not be read (or written). */ @Override public int read() throws IOException { @@ -107,9 +107,9 @@ public int read() throws IOException { /** * Reads characters from the proxied reader and writes the read characters to the associated writer. * - * @param chr character buffer - * @return number of characters read, or -1 if the reader has ended - * @throws IOException if the reader could not be read (or written) + * @param chr character buffer. + * @return number of characters read, or -1 if the reader has ended. + * @throws IOException if the reader could not be read (or written). */ @Override public int read(final char[] chr) throws IOException { @@ -123,11 +123,11 @@ public int read(final char[] chr) throws IOException { /** * Reads characters from the proxied reader and writes the read characters to the associated writer. * - * @param chr character buffer - * @param st start offset within the buffer - * @param end maximum number of characters to read - * @return number of characters read, or -1 if the reader has ended - * @throws IOException if the reader could not be read (or written) + * @param chr character buffer. + * @param st start offset within the buffer. + * @param end maximum number of characters to read. + * @return number of characters read, or -1 if the reader has ended. + * @throws IOException if the reader could not be read (or written). */ @Override public int read(final char[] chr, final int st, final int end) throws IOException { @@ -141,9 +141,9 @@ public int read(final char[] chr, final int st, final int end) throws IOExceptio /** * Reads characters from the proxied reader and writes the read characters to the associated writer. * - * @param target character buffer - * @return number of characters read, or -1 if the reader has ended - * @throws IOException if the reader could not be read (or written) + * @param target character buffer. + * @return number of characters read, or -1 if the reader has ended. + * @throws IOException if the reader could not be read (or written). */ @Override public int read(final CharBuffer target) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/ThrottledInputStream.java b/src/main/java/org/apache/commons/io/input/ThrottledInputStream.java index b081db3c6e5..e07cdc3eadb 100644 --- a/src/main/java/org/apache/commons/io/input/ThrottledInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ThrottledInputStream.java @@ -128,7 +128,7 @@ public ThrottledInputStream get() throws IOException { * To test idle timeouts for example, use 1 byte per minute, 1 byte per 30 seconds, and so on. *

    * - * @param value the maximum bytes + * @param value the maximum bytes. * @param chronoUnit a duration scale goal. * @return {@code this} instance. * @throws IllegalArgumentException Thrown if maxBytesPerSecond <= 0. @@ -151,7 +151,7 @@ public Builder setMaxBytes(final long value, final ChronoUnit chronoUnit) { * To test idle timeouts for example, use 1 byte per minute, 1 byte per 30 seconds, and so on. *

    * - * @param value the maximum bytes + * @param value the maximum bytes. * @param duration a duration goal. * @return {@code this} instance. * @throws IllegalArgumentException Thrown if maxBytesPerSecond <= 0. @@ -245,21 +245,38 @@ private long getBytesPerSecond() { return getByteCount() / elapsedSeconds; } - // package private for testing. + /** + * Gets the maximum bytes per second. + *

    + * Package private for testing. + *

    + * + * @return The maximum bytes per second. + */ double getMaxBytesPerSecond() { return maxBytesPerSecond; } - private long getSleepMillis() { + /** + * Gets the number of milliseconds to sleep to match to the maximum bytes per second. + *

    + * Package private for testing. + *

    + * + * @return the number of milliseconds to sleep to match to the maximum bytes per second. + */ + long getSleepMillis() { return toSleepMillis(getByteCount(), System.currentTimeMillis() - startTime, maxBytesPerSecond); } /** * Gets the total duration spent in sleep. + *

    + * Package private for testing + *

    * * @return Duration spent in sleep. */ - // package private for testing Duration getTotalSleepDuration() { return totalSleepDuration; } @@ -271,7 +288,8 @@ private void throttle() throws InterruptedIOException { try { TimeUnit.MILLISECONDS.sleep(sleepMillis); } catch (final InterruptedException e) { - throw new InterruptedIOException("Thread aborted"); + Thread.currentThread().interrupt(); + throw Input.toInterruptedIOException(e); } } } diff --git a/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java b/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java index ac6e003ab1f..eb6cf216251 100644 --- a/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java +++ b/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java @@ -110,7 +110,7 @@ public static Builder builder() { * Constructs a {@link UncheckedFilterInputStream}. * * @param builder A builder providing the underlying input stream. - * @throws IOException + * @throws IOException if an I/O error occurs. */ @SuppressWarnings("resource") // caller closes private UncheckedFilterInputStream(final Builder builder) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/UnixLineEndingInputStream.java b/src/main/java/org/apache/commons/io/input/UnixLineEndingInputStream.java index 3d0400fca71..88f31b7854f 100644 --- a/src/main/java/org/apache/commons/io/input/UnixLineEndingInputStream.java +++ b/src/main/java/org/apache/commons/io/input/UnixLineEndingInputStream.java @@ -28,37 +28,16 @@ * * @since 2.5 */ -public class UnixLineEndingInputStream extends InputStream { - - private boolean atEos; - - private boolean atSlashCr; - - private boolean atSlashLf; - - private final InputStream in; - - private final boolean lineFeedAtEndOfFile; +public class UnixLineEndingInputStream extends AbstractLineEndingInputStream { /** * Constructs an input stream that filters another stream * - * @param inputStream The input stream to wrap - * @param ensureLineFeedAtEndOfFile true to ensure that the file ends with LF - */ - public UnixLineEndingInputStream(final InputStream inputStream, final boolean ensureLineFeedAtEndOfFile) { - this.in = inputStream; - this.lineFeedAtEndOfFile = ensureLineFeedAtEndOfFile; - } - - /** - * Closes the stream. Also closes the underlying stream. - * @throws IOException If an I/O error occurs. + * @param inputStream The input stream to wrap. + * @param lineFeedAtEos true to ensure that the file ends with LF. */ - @Override - public void close() throws IOException { - super.close(); - in.close(); + public UnixLineEndingInputStream(final InputStream inputStream, final boolean lineFeedAtEos) { + super(inputStream, lineFeedAtEos); } /** @@ -68,7 +47,7 @@ public void close() throws IOException { * @return The next char to output to the stream. */ private int handleEos(final boolean previousWasSlashCr) { - if (previousWasSlashCr || !lineFeedAtEndOfFile) { + if (previousWasSlashCr || !lineFeedAtEos) { return EOF; } if (!atSlashLf) { @@ -78,14 +57,6 @@ private int handleEos(final boolean previousWasSlashCr) { return EOF; } - /** - * {@inheritDoc} - */ - @Override - public synchronized void mark(final int readLimit) { - throw UnsupportedOperationExceptions.mark(); - } - /** * {@inheritDoc} */ @@ -95,7 +66,7 @@ public synchronized int read() throws IOException { if (atEos) { return handleEos(previousWasSlashR); } - final int target = readWithUpdate(); + final int target = readUpdate(); if (atEos) { return handleEos(previousWasSlashR); } @@ -112,10 +83,11 @@ public synchronized int read() throws IOException { /** * Reads the next item from the target, updating internal flags in the process - * @return the next int read from the target stream + * + * @return the next int read from the target stream. * @throws IOException If an I/O error occurs. */ - private int readWithUpdate() throws IOException { + private int readUpdate() throws IOException { final int target = this.in.read(); atEos = target == EOF; if (atEos) { diff --git a/src/main/java/org/apache/commons/io/input/UnsupportedOperationExceptions.java b/src/main/java/org/apache/commons/io/input/UnsupportedOperationExceptions.java index 63874faabb7..fea13e7c4cc 100644 --- a/src/main/java/org/apache/commons/io/input/UnsupportedOperationExceptions.java +++ b/src/main/java/org/apache/commons/io/input/UnsupportedOperationExceptions.java @@ -31,7 +31,7 @@ final class UnsupportedOperationExceptions { /** * Constructs a new instance of UnsupportedOperationException for a {@code mark} method. * - * @return a new instance of UnsupportedOperationException + * @return a new instance of UnsupportedOperationException. */ static UnsupportedOperationException mark() { // Use the same message as in java.io.InputStream.reset() in OpenJDK 8.0.275-1. @@ -41,8 +41,8 @@ static UnsupportedOperationException mark() { /** * Constructs a new instance of UnsupportedOperationException for the given unsupported a {@code method} name. * - * @param method A method name - * @return a new instance of UnsupportedOperationException + * @param method A method name. + * @return a new instance of UnsupportedOperationException. */ static UnsupportedOperationException method(final String method) { return new UnsupportedOperationException(method + " not supported"); @@ -51,7 +51,7 @@ static UnsupportedOperationException method(final String method) { /** * Constructs a new instance of UnsupportedOperationException for a {@code reset} method. * - * @return a new instance of UnsupportedOperationException + * @return a new instance of UnsupportedOperationException. */ static UnsupportedOperationException reset() { // Use the same message as in java.io.InputStream.reset() in OpenJDK 8.0.275-1. diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java index bde55873f24..b67d9f5254a 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java @@ -77,7 +77,7 @@ public class UnsynchronizedBufferedReader extends UnsynchronizedReader { */ private char[] buf; - private int pos; + private int bufPos; private int end; @@ -85,6 +85,9 @@ public class UnsynchronizedBufferedReader extends UnsynchronizedReader { private int markLimit = -1; + private long pos; + private long posMark; + /** * Constructs a new BufferedReader on the Reader {@code in}. The buffer gets the default size (8 KB). * @@ -113,8 +116,8 @@ public UnsynchronizedBufferedReader(final Reader in, final int size) { * Peeks at the next input character, refilling the buffer if necessary. If this character is a newline character ("\n"), it is discarded. */ final void chompNewline() throws IOException { - if ((pos != end || fillBuf() != EOF) && buf[pos] == LF) { - pos++; + if ((bufPos != end || fillBuf() != EOF) && buf[bufPos] == LF) { + bufPos++; } } @@ -134,24 +137,22 @@ public void close() throws IOException { } /** - * Populates the buffer with data. It is an error to call this method when the buffer still contains data; ie. if {@code pos < end}. + * Fills the buffer with characters. It is an error to call this method when the buffer still contains data, that is, if {@code pos < end}. * * @return the number of bytes read into the buffer, or -1 if the end of the source stream has been reached. */ private int fillBuf() throws IOException { // assert(pos == end); - - if (mark == EOF || pos - mark >= markLimit) { + if (mark == EOF || bufPos - mark >= markLimit) { /* mark isn't set or has exceeded its limit. use the whole buffer */ final int result = in.read(buf, 0, buf.length); if (result > 0) { mark = -1; - pos = 0; + bufPos = 0; end = result; } return result; } - if (mark == 0 && markLimit > buf.length) { /* the only way to make room when mark=0 is by growing the buffer */ int newLength = buf.length * 2; @@ -164,19 +165,48 @@ private int fillBuf() throws IOException { } else if (mark > 0) { /* make room by shifting the buffered data to left mark positions */ System.arraycopy(buf, mark, buf, 0, buf.length - mark); - pos -= mark; + bufPos -= mark; end -= mark; mark = 0; } - /* Set the new position and mark position */ - final int count = in.read(buf, pos, buf.length - pos); + final int count = in.read(buf, bufPos, buf.length - bufPos); if (count != EOF) { end += count; } return count; } + /** + * Gets the character position in the reader. + *

    + * Returns {@link Long#MAX_VALUE} instead of overflowing. + *

    + *

    + * Since this is a Reader, this is the character position, not the byte position (use an InputStream to count bytes). + *

    + * + * @return the character position in the reader. + * @since 2.23.0 + */ + public long getPosition() { + return pos; + } + + /** + * Increments the position by a given value, overflow sets the value to {@link Long#MAX_VALUE}; + * + * @param v a value. + * @return the new position. + * @since 2.23.0 + */ + protected long incPos(final long v) { + // Like Math.addExact(long, long) but clamp to Long.MAX_VALUE. + final long r = pos + v; + // Overflow iff both arguments have the opposite sign of the result. + return pos = ((pos ^ r) & (v ^ r)) < 0 ? Long.MAX_VALUE : r; + } + /** * Sets a mark position in this reader. The parameter {@code markLimit} indicates how many characters can be read before the mark is invalidated. Calling * {@link #reset()} will reposition the reader back to the marked position if {@code markLimit} has not been surpassed. @@ -194,7 +224,8 @@ public void mark(final int markLimit) throws IOException { } checkOpen(); this.markLimit = markLimit; - mark = pos; + mark = bufPos; + posMark = pos; } /** @@ -212,8 +243,8 @@ public boolean markSupported() { /** * Returns the next character in the current reader without consuming it. So the next call to {@link #read()} will still return this value. * - * @return the next character - * @throws IOException If an I/O error occurs + * @return the next character. + * @throws IOException If an I/O error occurs. */ public int peek() throws IOException { mark(1); @@ -227,8 +258,8 @@ public int peek() throws IOException { * still return the next value. * * @param buf the buffer to fill for the look ahead. - * @return the buffer itself - * @throws IOException If an I/O error occurs + * @return the buffer itself. + * @throws IOException If an I/O error occurs. */ public int peek(final char[] buf) throws IOException { final int n = buf.length; @@ -250,8 +281,9 @@ public int peek(final char[] buf) throws IOException { public int read() throws IOException { checkOpen(); /* Are there buffered characters available? */ - if (pos < end || fillBuf() != EOF) { - return buf[pos++]; + if (bufPos < end || fillBuf() != EOF) { + incPos(1); + return buf[bufPos++]; } return EOF; } @@ -281,22 +313,19 @@ public int read(final char[] buffer, int offset, final int length) throws IOExce if (length == 0) { return 0; } - int outstanding = length; while (outstanding > 0) { - /* * If there are bytes in the buffer, grab those first. */ - final int available = end - pos; + final int available = end - bufPos; if (available > 0) { final int count = available >= outstanding ? outstanding : available; - System.arraycopy(buf, pos, buffer, offset, count); - pos += count; + System.arraycopy(buf, bufPos, buffer, offset, count); + bufPos += count; offset += count; outstanding -= count; } - /* * Before attempting to read from the underlying stream, make sure we really, really want to. We won't bother if we're done, or if we've already got * some bytes and reading from the underlying stream would block. @@ -304,35 +333,31 @@ public int read(final char[] buffer, int offset, final int length) throws IOExce if (outstanding == 0 || outstanding < length && !in.ready()) { break; } - // assert(pos == end); - /* * If we're unmarked and the requested size is greater than our buffer, read the bytes directly into the caller's buffer. We don't read into smaller * buffers because that could result in a many reads. */ - if ((mark == -1 || pos - mark >= markLimit) && outstanding >= buf.length) { + if ((mark == -1 || bufPos - mark >= markLimit) && outstanding >= buf.length) { final int count = in.read(buffer, offset, outstanding); if (count > 0) { outstanding -= count; mark = -1; } - break; // assume the source stream gave us all that it could } - if (fillBuf() == EOF) { break; // source is exhausted } } - final int count = length - outstanding; + incPos(count); return count > 0 || count == length ? count : EOF; } /** * Returns the next line of text available from this reader. A line is represented by zero or more characters followed by {@code LF}, {@code CR}, - * {@code "\r\n"} or the end of the reader. The string does not include the newline sequence. + * {@code "\r\n"}, or the end of the reader. The string does not include the newline sequence. * * @return the contents of the line or {@code null} if no characters were read before the end of the reader has been reached. * @throws IOException if this reader is closed or some other I/O error occurs. @@ -340,37 +365,37 @@ public int read(final char[] buffer, int offset, final int length) throws IOExce public String readLine() throws IOException { checkOpen(); /* has the underlying stream been exhausted? */ - if (pos == end && fillBuf() == EOF) { + if (bufPos == end && fillBuf() == EOF) { return null; } - for (int charPos = pos; charPos < end; charPos++) { + final int startPos = bufPos; + for (int charPos = bufPos; charPos < end; charPos++) { final char ch = buf[charPos]; if (ch > CR) { continue; } if (ch == LF) { - final String res = new String(buf, pos, charPos - pos); - pos = charPos + 1; + final String res = new String(buf, bufPos, charPos - bufPos); + bufPos = charPos + 1; + incPos(bufPos - startPos); return res; } if (ch == CR) { - final String res = new String(buf, pos, charPos - pos); - pos = charPos + 1; - if ((pos < end || fillBuf() != EOF) && buf[pos] == LF) { - pos++; + final String res = new String(buf, bufPos, charPos - bufPos); + bufPos = charPos + 1; + if ((bufPos < end || fillBuf() != EOF) && buf[bufPos] == LF) { + bufPos++; } + incPos(bufPos - startPos); return res; } } - char eol = NUL; + // Typical Line Length final StringBuilder result = new StringBuilder(80); - /* Typical Line Length */ - - result.append(buf, pos, end - pos); + result.append(buf, bufPos, end - bufPos); while (true) { - pos = end; - + bufPos = end; /* Are there buffered characters available? */ if (eol == LF) { return result.toString(); @@ -380,19 +405,19 @@ public String readLine() throws IOException { // characters or null. return result.length() > 0 || eol != NUL ? result.toString() : null; } - for (int charPos = pos; charPos < end; charPos++) { + for (int charPos = bufPos; charPos < end; charPos++) { final char c = buf[charPos]; if (eol != NUL) { if (eol == CR && c == LF) { - if (charPos > pos) { - result.append(buf, pos, charPos - pos - 1); + if (charPos > bufPos) { + result.append(buf, bufPos, charPos - bufPos - 1); } - pos = charPos + 1; + bufPos = charPos + 1; } else { - if (charPos > pos) { - result.append(buf, pos, charPos - pos - 1); + if (charPos > bufPos) { + result.append(buf, bufPos, charPos - bufPos - 1); } - pos = charPos; + bufPos = charPos; } return result.toString(); } @@ -401,9 +426,9 @@ public String readLine() throws IOException { } } if (eol == NUL) { - result.append(buf, pos, end - pos); + result.append(buf, bufPos, end - bufPos); } else { - result.append(buf, pos, end - pos - 1); + result.append(buf, bufPos, end - bufPos - 1); } } } @@ -420,7 +445,7 @@ public String readLine() throws IOException { @Override public boolean ready() throws IOException { checkOpen(); - return end - pos > 0 || in.ready(); + return end - bufPos > 0 || in.ready(); } /** @@ -436,7 +461,8 @@ public void reset() throws IOException { if (mark == -1) { throw new IOException("mark == -1"); } - pos = mark; + bufPos = mark; + pos = posMark; } /** @@ -460,26 +486,39 @@ public long skip(final long amount) throws IOException { if (amount < 1) { return 0; } - if (end - pos >= amount) { - pos += Math.toIntExact(amount); - return amount; + if (end - bufPos >= amount) { + bufPos += Math.toIntExact(amount); + return incPos(amount); } - - long read = end - pos; - pos = end; + long read = end - bufPos; + bufPos = end; while (read < amount) { if (fillBuf() == EOF) { - return read; + return incPos(read); } - if (end - pos >= amount - read) { - pos += Math.toIntExact(amount - read); - return amount; + if (end - bufPos >= amount - read) { + bufPos += Math.toIntExact(amount - read); + return incPos(amount); } // Couldn't get all the characters, skip what we read - read += end - pos; - pos = end; + read += end - bufPos; + bufPos = end; } - return amount; + return incPos(amount); + } + + + /** + * Unwraps this instance by returning the underlying {@link Reader}. + *

    + * Use with caution. + *

    + * + * @return the underlying {@link Reader}. + * @since 2.23.0 + */ + public Reader unwrap() { + return in; } } diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java index a56dec7206c..96c47e7b9af 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java @@ -92,10 +92,6 @@ public Builder() { // empty } - private byte[] checkOriginByteArray() throws IOException { - return checkOrigin().getByteArray(); - } - /** * Builds a new {@link UnsynchronizedByteArrayInputStream}. *

    @@ -207,13 +203,13 @@ private static int requireNonNegative(final int value, final String name) { private int markedOffset; private UnsynchronizedByteArrayInputStream(final Builder builder) throws IOException { - this(builder.checkOriginByteArray(), builder.offset, builder.length); + this(builder.getByteArray(), builder.offset, builder.length); } /** * Constructs a new byte array input stream. * - * @param data the buffer + * @param data the buffer. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -224,9 +220,9 @@ public UnsynchronizedByteArrayInputStream(final byte[] data) { /** * Constructs a new byte array input stream. * - * @param data the buffer - * @param offset the offset into the buffer - * @throws IllegalArgumentException if the offset is less than zero + * @param data the buffer. + * @param offset the offset into the buffer. + * @throws IllegalArgumentException if the offset is less than zero. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated @@ -237,10 +233,10 @@ public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) { /** * Constructs a new byte array input stream. * - * @param data the buffer - * @param offset the offset into the buffer - * @param length the length of the buffer - * @throws IllegalArgumentException if the offset or length less than zero + * @param data the buffer. + * @param offset the offset into the buffer. + * @param length the length of the buffer. + * @throws IllegalArgumentException if the offset or length less than zero. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated diff --git a/src/main/java/org/apache/commons/io/input/WindowsLineEndingInputStream.java b/src/main/java/org/apache/commons/io/input/WindowsLineEndingInputStream.java index b09deec0d3e..24285adb6d3 100644 --- a/src/main/java/org/apache/commons/io/input/WindowsLineEndingInputStream.java +++ b/src/main/java/org/apache/commons/io/input/WindowsLineEndingInputStream.java @@ -28,20 +28,10 @@ * * @since 2.5 */ -public class WindowsLineEndingInputStream extends InputStream { - - private boolean atEos; - - private boolean atSlashCr; - - private boolean atSlashLf; - - private final InputStream in; +public class WindowsLineEndingInputStream extends AbstractLineEndingInputStream { private boolean injectSlashLf; - private final boolean lineFeedAtEos; - /** * Constructs an input stream that filters another stream. * @@ -49,19 +39,7 @@ public class WindowsLineEndingInputStream extends InputStream { * @param lineFeedAtEos true to ensure that the stream ends with CRLF. */ public WindowsLineEndingInputStream(final InputStream in, final boolean lineFeedAtEos) { - this.in = in; - this.lineFeedAtEos = lineFeedAtEos; - } - - /** - * Closes the stream. Also closes the underlying stream. - * - * @throws IOException If an I/O error occurs. - */ - @Override - public void close() throws IOException { - super.close(); - in.close(); + super(in, lineFeedAtEos); } /** @@ -85,14 +63,6 @@ private int handleEos() { return EOF; } - /** - * {@inheritDoc} - */ - @Override - public synchronized void mark(final int readLimit) { - throw UnsupportedOperationExceptions.mark(); - } - /** * {@inheritDoc} */ diff --git a/src/main/java/org/apache/commons/io/input/XmlStreamReader.java b/src/main/java/org/apache/commons/io/input/XmlStreamReader.java index 7f5102e8218..3b667ef1655 100644 --- a/src/main/java/org/apache/commons/io/input/XmlStreamReader.java +++ b/src/main/java/org/apache/commons/io/input/XmlStreamReader.java @@ -42,7 +42,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractStreamBuilder; import org.apache.commons.io.function.IOConsumer; -import org.apache.commons.io.output.XmlStreamWriter; /** * Character stream that handles all the necessary Voodoo to figure out the charset encoding of the XML document within the stream. @@ -76,7 +75,7 @@ public class XmlStreamReader extends Reader { // @formatter:off /** - * Builds a new {@link XmlStreamWriter}. + * Builds a new {@link XmlStreamReader}. * * Constructs a Reader using an InputStream and the associated content-type header. This constructor is lenient regarding the encoding detection. *

    @@ -131,7 +130,7 @@ public Builder() { } /** - * Builds a new {@link XmlStreamWriter}. + * Builds a new {@link XmlStreamReader}. *

    * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception. *

    @@ -278,8 +277,8 @@ public static Builder builder() { /** * Gets the charset parameter value, {@code null} if not present, {@code null} if httpContentType is {@code null}. * - * @param httpContentType the HTTP content type - * @return The content type encoding (upcased) + * @param httpContentType the HTTP content type. + * @return The content type encoding (upcased). */ static String getContentTypeEncoding(final String httpContentType) { String encoding = null; @@ -298,8 +297,8 @@ static String getContentTypeEncoding(final String httpContentType) { /** * Gets the MIME type or {@code null} if httpContentType is {@code null}. * - * @param httpContentType the HTTP content type - * @return The mime content type + * @param httpContentType the HTTP content type. + * @return The mime content type. */ static String getContentTypeMime(final String httpContentType) { String mime = null; @@ -315,8 +314,8 @@ static String getContentTypeMime(final String httpContentType) { * Gets the encoding declared in the , {@code null} if none. * * @param inputStream InputStream to create the reader from. - * @param guessedEnc guessed encoding - * @return the encoding declared in the + * @param guessedEnc guessed encoding. + * @return the encoding declared in the . * @throws IOException thrown if there is a problem reading the stream. */ private static String getXmlProlog(final InputStream inputStream, final String guessedEnc) throws IOException { @@ -361,8 +360,8 @@ private static String getXmlProlog(final InputStream inputStream, final String g /** * Tests if the MIME type belongs to the APPLICATION XML family. * - * @param mime The mime type - * @return true if the mime type belongs to the APPLICATION XML family, otherwise false + * @param mime The mime type. + * @return true if the mime type belongs to the APPLICATION XML family, otherwise false. */ static boolean isAppXml(final String mime) { return mime != null && (mime.equals("application/xml") || mime.equals("application/xml-dtd") || mime.equals("application/xml-external-parsed-entity") @@ -372,8 +371,8 @@ static boolean isAppXml(final String mime) { /** * Tests if the MIME type belongs to the TEXT XML family. * - * @param mime The mime type - * @return true if the mime type belongs to the TEXT XML family, otherwise false + * @param mime The mime type. + * @return true if the mime type belongs to the TEXT XML family, otherwise false. */ static boolean isTextXml(final String mime) { return mime != null && (mime.equals("text/xml") || mime.equals("text/xml-external-parsed-entity") || mime.startsWith("text/") && mime.endsWith("+xml")); @@ -485,7 +484,7 @@ public XmlStreamReader(final InputStream inputStream, final boolean lenient) thr * * @param inputStream InputStream to create a Reader from. * @param lenient indicates if the charset encoding detection should be relaxed. - * @param defaultEncoding The default encoding + * @param defaultEncoding The default encoding. * @throws NullPointerException if the input stream is {@code null}. * @throws IOException thrown if there is a problem reading the stream. * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specification. @@ -498,7 +497,7 @@ public XmlStreamReader(final InputStream inputStream, final boolean lenient, fin final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(Objects.requireNonNull(inputStream, "inputStream"), IOUtils.DEFAULT_BUFFER_SIZE), false, BOMS); final BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES); - this.encoding = processHttpStream(bom, pis, lenient); + this.encoding = toEncoding(bom, pis, lenient); this.reader = new InputStreamReader(pis, encoding); } @@ -589,7 +588,7 @@ public XmlStreamReader(final InputStream inputStream, final String httpContentTy * @param inputStream InputStream to create the reader from. * @param httpContentType content-type header to use for the resolution of the charset encoding. * @param lenient indicates if the charset encoding detection should be relaxed. - * @param defaultEncoding The default encoding + * @param defaultEncoding The default encoding. * @throws NullPointerException if the input stream is {@code null}. * @throws IOException thrown if there is a problem reading the file. * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specification. @@ -603,7 +602,7 @@ public XmlStreamReader(final InputStream inputStream, final String httpContentTy final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(Objects.requireNonNull(inputStream, "inputStream"), IOUtils.DEFAULT_BUFFER_SIZE), false, BOMS); final BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES); - this.encoding = processHttpStream(bom, pis, lenient, httpContentType); + this.encoding = toEncoding(bom, pis, lenient, httpContentType); this.reader = new InputStreamReader(pis, encoding); } @@ -662,7 +661,7 @@ public XmlStreamReader(final URL url) throws IOException { *

    * * @param urlConnection URLConnection to create a Reader from. - * @param defaultEncoding The default encoding + * @param defaultEncoding The default encoding. * @throws NullPointerException if the input is {@code null}. * @throws IOException thrown if there is a problem reading the stream of the URLConnection. */ @@ -687,51 +686,168 @@ public XmlStreamReader(final URLConnection urlConnection, final String defaultEn .get(); // @formatter:on if (urlConnection instanceof HttpURLConnection || contentType != null) { - this.encoding = processHttpStream(bomInput, piInput, lenient, contentType); + this.encoding = toEncoding(bomInput, piInput, lenient, contentType); } else { - this.encoding = processHttpStream(bomInput, piInput, lenient); + this.encoding = toEncoding(bomInput, piInput, lenient); } this.reader = new InputStreamReader(piInput, encoding); } /** - * Calculates the HTTP encoding. - * @param bomEnc BOM encoding - * @param xmlGuessEnc XML Guess encoding - * @param xmlEnc XML encoding + * Closes the XmlStreamReader stream. + * + * @throws IOException thrown if there was a problem closing the stream. + */ + @Override + public void close() throws IOException { + reader.close(); + } + + /** + * Gets the default encoding to use if none is set in HTTP content-type, XML prolog and the rules based on content-type are not adequate. + *

    + * If it is {@code null} the content-type based rules are used. + *

    + * + * @return the default encoding to use. + */ + public String getDefaultEncoding() { + return defaultEncoding; + } + + /** + * Gets the charset encoding of the XmlStreamReader. + * + * @return charset encoding. + */ + public String getEncoding() { + return encoding; + } + + /** + * Reads the underlying reader's {@code read(char[], int, int)} method. + * + * @param buf the buffer to read the characters into. + * @param offset The start offset. + * @param len The number of bytes to read. + * @return the number of characters read or -1 if the end of stream. + * @throws IOException if an I/O error occurs. + */ + @Override + public int read(final char[] buf, final int offset, final int len) throws IOException { + return reader.read(buf, offset, len); + } + + /** + * Process the raw stream. + * + * @param bomInput BOMInputStream to detect byte order marks. + * @param piInput BOMInputStream to guess XML encoding. + * @param lenient indicates if the charset encoding detection should be relaxed. + * @return the encoding to be used. + * @throws IOException thrown if there is a problem reading the stream. + */ + private String toEncoding(final BOMInputStream bomInput, final BOMInputStream piInput, final boolean lenient) throws IOException { + final String bomEnc = bomInput.getBOMCharsetName(); + final String xmlGuessEnc = piInput.getBOMCharsetName(); + final String xmlEnc = getXmlProlog(piInput, xmlGuessEnc); + try { + return toRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); + } catch (final XmlStreamReaderException ex) { + if (lenient) { + return toEncodingLenient(null, ex); + } + throw ex; + } + } + + /** + * Processes an HTTP stream. + * + * @param bomInput BOMInputStream to detect byte order marks. + * @param piInput BOMInputStream to guess XML encoding. * @param lenient indicates if the charset encoding detection should be relaxed. - * @param httpContentType The HTTP content type - * @return the HTTP encoding + * @param httpContentType The HTTP content type. + * @return the encoding to be used. * @throws IOException thrown if there is a problem reading the stream. */ - String calculateHttpEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc, final boolean lenient, final String httpContentType) + private String toEncoding(final BOMInputStream bomInput, final BOMInputStream piInput, final boolean lenient, final String httpContentType) throws IOException { + final String bomEnc = bomInput.getBOMCharsetName(); + final String xmlGuessEnc = piInput.getBOMCharsetName(); + final String xmlEnc = getXmlProlog(piInput, xmlGuessEnc); + try { + return toHttpEncoding(bomEnc, xmlGuessEnc, xmlEnc, lenient, httpContentType); + } catch (final XmlStreamReaderException ex) { + if (lenient) { + return toEncodingLenient(httpContentType, ex); + } + throw ex; + } + } + /** + * Detects the encoding in lenient mode. + * + * @param httpContentType content-type header to use for the resolution of the charset encoding. + * @param ex The thrown exception. + * @return the encoding. + * @throws IOException thrown if there is a problem reading the stream. + */ + private String toEncodingLenient(String httpContentType, XmlStreamReaderException ex) throws IOException { + if (httpContentType != null && httpContentType.startsWith("text/html")) { + httpContentType = httpContentType.substring("text/html".length()); + httpContentType = "text/xml" + httpContentType; + try { + return toHttpEncoding(ex.getBomEncoding(), ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true, httpContentType); + } catch (final XmlStreamReaderException ex2) { + ex = ex2; + } + } + String encoding = ex.getXmlEncoding(); + if (encoding == null) { + encoding = ex.getContentTypeEncoding(); + } + if (encoding == null) { + encoding = defaultEncoding == null ? UTF_8 : defaultEncoding; + } + return encoding; + } + + /** + * Calculates the HTTP encoding. + * + * @param bomEnc BOM encoding. + * @param xmlGuessEnc XML Guess encoding. + * @param xmlEnc XML encoding. + * @param lenient indicates if the charset encoding detection should be relaxed. + * @param httpContentType The HTTP content type. + * @return the HTTP encoding. + * @throws IOException thrown if there is a problem reading the stream. + */ + String toHttpEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc, final boolean lenient, final String httpContentType) + throws IOException { // Lenient and has XML encoding if (lenient && xmlEnc != null) { return xmlEnc; } - // Determine mime/encoding content types from HTTP Content Type final String cTMime = getContentTypeMime(httpContentType); final String cTEnc = getContentTypeEncoding(httpContentType); final boolean appXml = isAppXml(cTMime); final boolean textXml = isTextXml(cTMime); - // Mime type NOT "application/xml" or "text/xml" if (!appXml && !textXml) { final String msg = MessageFormat.format(HTTP_EX_3, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); } - // No content type encoding if (cTEnc == null) { if (appXml) { - return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); + return toRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); } return defaultEncoding == null ? US_ASCII : defaultEncoding; } - // UTF-16BE or UTF-16LE content type encoding if (cTEnc.equals(UTF_16BE) || cTEnc.equals(UTF_16LE)) { if (bomEnc != null) { @@ -740,7 +856,6 @@ String calculateHttpEncoding(final String bomEnc, final String xmlGuessEnc, fina } return cTEnc; } - // UTF-16 content type encoding if (cTEnc.equals(UTF_16)) { if (bomEnc != null && bomEnc.startsWith(UTF_16)) { @@ -749,7 +864,6 @@ String calculateHttpEncoding(final String bomEnc, final String xmlGuessEnc, fina final String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); } - // UTF-32BE or UTF-132E content type encoding if (cTEnc.equals(UTF_32BE) || cTEnc.equals(UTF_32LE)) { if (bomEnc != null) { @@ -758,7 +872,6 @@ String calculateHttpEncoding(final String bomEnc, final String xmlGuessEnc, fina } return cTEnc; } - // UTF-32 content type encoding if (cTEnc.equals(UTF_32)) { if (bomEnc != null && bomEnc.startsWith(UTF_32)) { @@ -767,20 +880,19 @@ String calculateHttpEncoding(final String bomEnc, final String xmlGuessEnc, fina final String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc); } - return cTEnc; } /** * Calculate the raw encoding. * - * @param bomEnc BOM encoding - * @param xmlGuessEnc XML Guess encoding - * @param xmlEnc XML encoding - * @return the raw encoding + * @param bomEnc BOM encoding. + * @param xmlGuessEnc XML Guess encoding. + * @param xmlEnc XML encoding. + * @return the raw encoding. * @throws IOException thrown if there is a problem reading the stream. */ - String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc) throws IOException { + String toRawEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc) throws IOException { // BOM is Null if (bomEnc == null) { @@ -795,11 +907,7 @@ String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, final // BOM is UTF-8 if (bomEnc.equals(UTF_8)) { - if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8)) { - final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc); - throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); - } - if (xmlEnc != null && !xmlEnc.equals(UTF_8)) { + if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8) || xmlEnc != null && !xmlEnc.equals(UTF_8)) { final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc); throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); } @@ -837,125 +945,4 @@ String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, final throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc); } - /** - * Closes the XmlStreamReader stream. - * - * @throws IOException thrown if there was a problem closing the stream. - */ - @Override - public void close() throws IOException { - reader.close(); - } - - /** - * Does lenient detection. - * - * @param httpContentType content-type header to use for the resolution of the charset encoding. - * @param ex The thrown exception - * @return the encoding - * @throws IOException thrown if there is a problem reading the stream. - */ - private String doLenientDetection(String httpContentType, XmlStreamReaderException ex) throws IOException { - if (httpContentType != null && httpContentType.startsWith("text/html")) { - httpContentType = httpContentType.substring("text/html".length()); - httpContentType = "text/xml" + httpContentType; - try { - return calculateHttpEncoding(ex.getBomEncoding(), ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true, httpContentType); - } catch (final XmlStreamReaderException ex2) { - ex = ex2; - } - } - String encoding = ex.getXmlEncoding(); - if (encoding == null) { - encoding = ex.getContentTypeEncoding(); - } - if (encoding == null) { - encoding = defaultEncoding == null ? UTF_8 : defaultEncoding; - } - return encoding; - } - - /** - * Gets the default encoding to use if none is set in HTTP content-type, XML prolog and the rules based on content-type are not adequate. - *

    - * If it is {@code null} the content-type based rules are used. - *

    - * - * @return the default encoding to use. - */ - public String getDefaultEncoding() { - return defaultEncoding; - } - - /** - * Gets the charset encoding of the XmlStreamReader. - * - * @return charset encoding. - */ - public String getEncoding() { - return encoding; - } - - /** - * Process the raw stream. - * - * @param bomInput BOMInputStream to detect byte order marks - * @param piInput BOMInputStream to guess XML encoding - * @param lenient indicates if the charset encoding detection should be relaxed. - * @return the encoding to be used - * @throws IOException thrown if there is a problem reading the stream. - */ - private String processHttpStream(final BOMInputStream bomInput, final BOMInputStream piInput, final boolean lenient) throws IOException { - final String bomEnc = bomInput.getBOMCharsetName(); - final String xmlGuessEnc = piInput.getBOMCharsetName(); - final String xmlEnc = getXmlProlog(piInput, xmlGuessEnc); - try { - return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); - } catch (final XmlStreamReaderException ex) { - if (lenient) { - return doLenientDetection(null, ex); - } - throw ex; - } - } - - /** - * Processes an HTTP stream. - * - * @param bomInput BOMInputStream to detect byte order marks - * @param piInput BOMInputStream to guess XML encoding - * @param lenient indicates if the charset encoding detection should be relaxed. - * @param httpContentType The HTTP content type - * @return the encoding to be used - * @throws IOException thrown if there is a problem reading the stream. - */ - private String processHttpStream(final BOMInputStream bomInput, final BOMInputStream piInput, final boolean lenient, final String httpContentType) - throws IOException { - final String bomEnc = bomInput.getBOMCharsetName(); - final String xmlGuessEnc = piInput.getBOMCharsetName(); - final String xmlEnc = getXmlProlog(piInput, xmlGuessEnc); - try { - return calculateHttpEncoding(bomEnc, xmlGuessEnc, xmlEnc, lenient, httpContentType); - } catch (final XmlStreamReaderException ex) { - if (lenient) { - return doLenientDetection(httpContentType, ex); - } - throw ex; - } - } - - /** - * Reads the underlying reader's {@code read(char[], int, int)} method. - * - * @param buf the buffer to read the characters into - * @param offset The start offset - * @param len The number of bytes to read - * @return the number of characters read or -1 if the end of stream - * @throws IOException if an I/O error occurs. - */ - @Override - public int read(final char[] buf, final int offset, final int len) throws IOException { - return reader.read(buf, offset, len); - } - } diff --git a/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java b/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java index b92163478d0..cea865ae066 100644 --- a/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java +++ b/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java @@ -104,8 +104,8 @@ protected void fillBuffer() throws IOException { /** * Fills the buffer from the input stream until the given number of bytes have been added to the buffer. * - * @param count number of byte to fill into the buffer - * @return true if the buffer has bytes + * @param count number of byte to fill into the buffer. + * @return true if the buffer has bytes. * @throws IOException in case of an error while reading from the input stream. */ protected boolean haveBytes(final int count) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/buffer/CircularByteBuffer.java b/src/main/java/org/apache/commons/io/input/buffer/CircularByteBuffer.java index 43c8e71bd4a..64780b7c709 100644 --- a/src/main/java/org/apache/commons/io/input/buffer/CircularByteBuffer.java +++ b/src/main/java/org/apache/commons/io/input/buffer/CircularByteBuffer.java @@ -46,7 +46,7 @@ public CircularByteBuffer() { /** * Constructs a new instance with the given buffer size. * - * @param size the size of buffer to create + * @param size the size of buffer to create. */ public CircularByteBuffer(final int size) { buffer = IOUtils.byteArray(size); @@ -79,9 +79,9 @@ public void add(final byte value) { * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., * {@code offset+length-1} of byte array {@code targetBuffer}. * - * @param targetBuffer the buffer to copy - * @param offset start offset - * @param length length to copy + * @param targetBuffer the buffer to copy. + * @param offset start offset. + * @param length length to copy. * @throws IllegalStateException The buffer doesn't have sufficient space. Use * {@link #getSpace()} to prevent this exception. * @throws IllegalArgumentException Either of {@code offset}, or {@code length} is negative. @@ -119,7 +119,7 @@ public void clear() { /** * Gets the number of bytes, that are currently present in the buffer. * - * @return the number of bytes + * @return the number of bytes. */ public int getCurrentNumberOfBytes() { return currentNumberOfBytes; @@ -128,7 +128,7 @@ public int getCurrentNumberOfBytes() { /** * Gets the number of bytes, that can currently be added to the buffer. * - * @return the number of bytes that can be added + * @return the number of bytes that can be added. */ public int getSpace() { return buffer.length - currentNumberOfBytes; @@ -158,7 +158,7 @@ public boolean hasSpace() { /** * Tests whether there is currently room for the given number of bytes in the buffer. * - * @param count the byte count + * @param count the byte count. * @return true whether there is currently room for the given number of bytes in the buffer. * @see #hasSpace() * @see #getSpace() @@ -173,9 +173,9 @@ public boolean hasSpace(final int count) { * removed from the buffer. If the result is true, then the following invocations * of {@link #read()} are guaranteed to return exactly those bytes. * - * @param sourceBuffer the buffer to compare against - * @param offset start offset - * @param length length to compare + * @param sourceBuffer the buffer to compare against. + * @param offset start offset. + * @param length length to compare. * @return True, if the next invocations of {@link #read()} will return the * bytes at offsets {@code sourceBuffer}+0, {@code offset}+1, ..., * {@code offset}+{@code length}-1 of byte array {@code sourceBuffer}. diff --git a/src/main/java/org/apache/commons/io/input/buffer/PeekableInputStream.java b/src/main/java/org/apache/commons/io/input/buffer/PeekableInputStream.java index ec96054fc43..8bb0f7c7158 100644 --- a/src/main/java/org/apache/commons/io/input/buffer/PeekableInputStream.java +++ b/src/main/java/org/apache/commons/io/input/buffer/PeekableInputStream.java @@ -53,8 +53,8 @@ public PeekableInputStream(final InputStream inputStream, final int bufferSize) * Returns whether the next bytes in the buffer are as given by {@code sourceBuffer}. This is equivalent to * {@link #peek(byte[], int, int)} with {@code offset} == 0, and {@code length} == {@code sourceBuffer.length} * - * @param sourceBuffer the buffer to compare against - * @return true if the next bytes are as given + * @param sourceBuffer the buffer to compare against. + * @return true if the next bytes are as given. * @throws IOException Refilling the buffer failed. */ public boolean peek(final byte[] sourceBuffer) throws IOException { @@ -66,11 +66,11 @@ public boolean peek(final byte[] sourceBuffer) throws IOException { * Returns whether the next bytes in the buffer are as given by {@code sourceBuffer}, {code offset}, and * {@code length}. * - * @param sourceBuffer the buffer to compare against - * @param offset the start offset - * @param length the length to compare - * @return true if the next bytes in the buffer are as given - * @throws IOException if there is a problem calling fillBuffer() + * @param sourceBuffer the buffer to compare against. + * @param offset the start offset. + * @param length the length to compare. + * @return true if the next bytes in the buffer are as given. + * @throws IOException if there is a problem calling fillBuffer(). */ public boolean peek(final byte[] sourceBuffer, final int offset, final int length) throws IOException { Objects.requireNonNull(sourceBuffer, "sourceBuffer"); diff --git a/src/main/java/org/apache/commons/io/monitor/FileAlterationListener.java b/src/main/java/org/apache/commons/io/monitor/FileAlterationListener.java index ce64c8d4ed4..b7bf2a0cdb4 100644 --- a/src/main/java/org/apache/commons/io/monitor/FileAlterationListener.java +++ b/src/main/java/org/apache/commons/io/monitor/FileAlterationListener.java @@ -31,56 +31,56 @@ public interface FileAlterationListener { /** * Directory changed Event. * - * @param directory The directory changed + * @param directory The directory changed. */ void onDirectoryChange(File directory); /** * Directory created Event. * - * @param directory The directory created + * @param directory The directory created. */ void onDirectoryCreate(File directory); /** * Directory deleted Event. * - * @param directory The directory deleted + * @param directory The directory deleted. */ void onDirectoryDelete(File directory); /** * File changed Event. * - * @param file The file changed + * @param file The file changed. */ void onFileChange(File file); /** * File created Event. * - * @param file The file created + * @param file The file created. */ void onFileCreate(File file); /** * File deleted Event. * - * @param file The file deleted + * @param file The file deleted. */ void onFileDelete(File file); /** * File system observer started checking event. * - * @param observer The file system observer + * @param observer The file system observer. */ void onStart(FileAlterationObserver observer); /** * File system observer finished checking event. * - * @param observer The file system observer + * @param observer The file system observer. */ void onStop(FileAlterationObserver observer); } diff --git a/src/main/java/org/apache/commons/io/monitor/FileAlterationListenerAdaptor.java b/src/main/java/org/apache/commons/io/monitor/FileAlterationListenerAdaptor.java index 0b54bbd6723..c676470fa31 100644 --- a/src/main/java/org/apache/commons/io/monitor/FileAlterationListenerAdaptor.java +++ b/src/main/java/org/apache/commons/io/monitor/FileAlterationListenerAdaptor.java @@ -36,7 +36,7 @@ public FileAlterationListenerAdaptor() { /** * Directory changed Event. * - * @param directory The directory changed (ignored) + * @param directory The directory changed (ignored). */ @Override public void onDirectoryChange(final File directory) { @@ -46,7 +46,7 @@ public void onDirectoryChange(final File directory) { /** * Directory created Event. * - * @param directory The directory created (ignored) + * @param directory The directory created (ignored). */ @Override public void onDirectoryCreate(final File directory) { @@ -56,7 +56,7 @@ public void onDirectoryCreate(final File directory) { /** * Directory deleted Event. * - * @param directory The directory deleted (ignored) + * @param directory The directory deleted (ignored). */ @Override public void onDirectoryDelete(final File directory) { @@ -66,7 +66,7 @@ public void onDirectoryDelete(final File directory) { /** * File changed Event. * - * @param file The file changed (ignored) + * @param file The file changed (ignored). */ @Override public void onFileChange(final File file) { @@ -76,7 +76,7 @@ public void onFileChange(final File file) { /** * File created Event. * - * @param file The file created (ignored) + * @param file The file created (ignored). */ @Override public void onFileCreate(final File file) { @@ -86,7 +86,7 @@ public void onFileCreate(final File file) { /** * File deleted Event. * - * @param file The file deleted (ignored) + * @param file The file deleted (ignored). */ @Override public void onFileDelete(final File file) { @@ -96,7 +96,7 @@ public void onFileDelete(final File file) { /** * File system observer started checking event. * - * @param observer The file system observer (ignored) + * @param observer The file system observer (ignored). */ @Override public void onStart(final FileAlterationObserver observer) { @@ -106,7 +106,7 @@ public void onStart(final FileAlterationObserver observer) { /** * File system observer finished checking event. * - * @param observer The file system observer (ignored) + * @param observer The file system observer (ignored). */ @Override public void onStop(final FileAlterationObserver observer) { diff --git a/src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java b/src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java index 394e7aca661..ab3a65b9416 100644 --- a/src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java +++ b/src/main/java/org/apache/commons/io/monitor/FileAlterationMonitor.java @@ -98,7 +98,7 @@ public FileAlterationMonitor(final long interval, final FileAlterationObserver.. /** * Adds a file system observer to this monitor. * - * @param observer The file system observer to add + * @param observer The file system observer to add. */ public void addObserver(final FileAlterationObserver observer) { if (observer != null) { @@ -109,7 +109,7 @@ public void addObserver(final FileAlterationObserver observer) { /** * Returns the interval. * - * @return the interval + * @return the interval. */ public long getInterval() { return intervalMillis; @@ -128,7 +128,7 @@ public Iterable getObservers() { /** * Removes a file system observer from this monitor. * - * @param observer The file system observer to remove + * @param observer The file system observer to remove. */ public void removeObserver(final FileAlterationObserver observer) { if (observer != null) { @@ -148,8 +148,8 @@ public void run() { } try { ThreadUtils.sleep(Duration.ofMillis(intervalMillis)); - } catch (final InterruptedException ignored) { - // ignore + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); } } } @@ -157,7 +157,7 @@ public void run() { /** * Sets the thread factory. * - * @param threadFactory the thread factory + * @param threadFactory the thread factory. */ public synchronized void setThreadFactory(final ThreadFactory threadFactory) { this.threadFactory = threadFactory; @@ -166,7 +166,7 @@ public synchronized void setThreadFactory(final ThreadFactory threadFactory) { /** * Starts monitoring. * - * @throws Exception if an error occurs initializing the observer + * @throws Exception if an error occurs initializing the observer. */ public synchronized void start() throws Exception { if (running) { @@ -179,7 +179,7 @@ public synchronized void start() throws Exception { if (threadFactory != null) { thread = threadFactory.newThread(this); } else { - thread = new Thread(this); + thread = new Thread(this, "commons-io-FileAlterationMonitor"); } thread.start(); } @@ -187,7 +187,7 @@ public synchronized void start() throws Exception { /** * Stops monitoring. * - * @throws Exception if an error occurs initializing the observer + * @throws Exception if an error occurs initializing the observer. */ public synchronized void stop() throws Exception { stop(intervalMillis); @@ -198,7 +198,7 @@ public synchronized void stop() throws Exception { * * @param stopInterval the amount of time in milliseconds to wait for the thread to finish. * A value of zero will wait until the thread is finished (see {@link Thread#join(long)}). - * @throws Exception if an error occurs initializing the observer + * @throws Exception if an error occurs initializing the observer. * @since 2.1 */ public synchronized void stop(final long stopInterval) throws Exception { diff --git a/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java b/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java index 277d80cb71e..282f1652d95 100644 --- a/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java +++ b/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java @@ -501,7 +501,7 @@ public FileFilter getFileFilter() { /** * Returns the set of registered file system listeners. * - * @return The file system listeners + * @return The file system listeners. */ public Iterable getListeners() { return new ArrayList<>(listeners); @@ -533,7 +533,7 @@ private FileEntry[] listFileEntries(final File file, final FileEntry entry) { * Lists the contents of a directory. * * @param directory The directory to list. - * @return the directory contents or a zero length array if the empty or the file is not a directory + * @return the directory contents or a zero length array if the empty or the file is not a directory. */ private File[] listFiles(final File directory) { return directory.isDirectory() ? sort(directory.listFiles(fileFilter)) : FileUtils.EMPTY_FILE_ARRAY; diff --git a/src/main/java/org/apache/commons/io/monitor/FileEntry.java b/src/main/java/org/apache/commons/io/monitor/FileEntry.java index d1e07fb885c..fac3317958b 100644 --- a/src/main/java/org/apache/commons/io/monitor/FileEntry.java +++ b/src/main/java/org/apache/commons/io/monitor/FileEntry.java @@ -48,6 +48,7 @@ *

    * Serialization is deprecated and will be removed in 3.0. *

    + * * @see FileAlterationObserver * @since 2.0 */ @@ -84,7 +85,7 @@ public class FileEntry implements Serializable { /** * Constructs a new monitor for a specified {@link File}. * - * @param file The file being monitored + * @param file The file being monitored. */ public FileEntry(final File file) { this(null, file); @@ -107,7 +108,7 @@ public FileEntry(final FileEntry parent, final File file) { * * @return This directory's files or an empty * array if the file is not a directory or the - * directory is empty + * directory is empty. */ public FileEntry[] getChildren() { return children != null ? children : EMPTY_FILE_ENTRY_ARRAY; @@ -116,7 +117,7 @@ public FileEntry[] getChildren() { /** * Gets the file being monitored. * - * @return the file being monitored + * @return the file being monitored. */ public File getFile() { return file; @@ -145,7 +146,7 @@ public FileTime getLastModifiedFileTime() { /** * Gets the length. * - * @return the length + * @return the length. */ public long getLength() { return length; @@ -154,7 +155,7 @@ public long getLength() { /** * Gets the level * - * @return the level + * @return the level. */ public int getLevel() { return parent == null ? 0 : parent.getLevel() + 1; @@ -163,7 +164,7 @@ public int getLevel() { /** * Gets the file name. * - * @return the file name + * @return the file name. */ public String getName() { return name; @@ -172,7 +173,7 @@ public String getName() { /** * Gets the parent entry. * - * @return the parent entry + * @return the parent entry. */ public FileEntry getParent() { return parent; @@ -181,7 +182,7 @@ public FileEntry getParent() { /** * Tests whether the file is a directory or not. * - * @return whether the file is a directory or not + * @return whether the file is a directory or not. */ public boolean isDirectory() { return directory; @@ -191,7 +192,7 @@ public boolean isDirectory() { * Tests whether the file existed the last time it * was checked. * - * @return whether the file existed + * @return whether the file existed. */ public boolean isExists() { return exists; @@ -204,8 +205,8 @@ public boolean isExists() { * a new instance of the appropriate type. *

    * - * @param file The child file - * @return a new child instance + * @param file The child file. + * @return a new child instance. */ public FileEntry newChildInstance(final File file) { return new FileEntry(this, file); @@ -224,8 +225,8 @@ public FileEntry newChildInstance(final File file) { * and {@code length} properties are compared for changes *

    * - * @param file the file instance to compare to - * @return {@code true} if the file has changed, otherwise {@code false} + * @param file the file instance to compare to. + * @return {@code true} if the file has changed, otherwise {@code false}. */ public boolean refresh(final File file) { // cache original values @@ -253,7 +254,7 @@ public boolean refresh(final File file) { /** * Sets the directory's files. * - * @param children This directory's files, may be null + * @param children This directory's files, may be null. */ public void setChildren(final FileEntry... children) { this.children = children; @@ -262,7 +263,7 @@ public void setChildren(final FileEntry... children) { /** * Sets whether the file is a directory or not. * - * @param directory whether the file is a directory or not + * @param directory whether the file is a directory or not. */ public void setDirectory(final boolean directory) { this.directory = directory; @@ -272,7 +273,7 @@ public void setDirectory(final boolean directory) { * Sets whether the file existed the last time it * was checked. * - * @param exists whether the file exists or not + * @param exists whether the file exists or not. */ public void setExists(final boolean exists) { this.exists = exists; @@ -305,7 +306,7 @@ void setLastModified(final SerializableFileTime lastModified) { /** * Sets the length. * - * @param length the length + * @param length the length. */ public void setLength(final long length) { this.length = length; @@ -314,7 +315,7 @@ public void setLength(final long length) { /** * Sets the file name. * - * @param name the file name + * @param name the file name. */ public void setName(final String name) { this.name = name; diff --git a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java index 6b095474fb2..90b65d28801 100644 --- a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java @@ -56,7 +56,7 @@ * ignored. *

    * - * @param The AbstractByteArrayOutputStream subclass + * @param The AbstractByteArrayOutputStream subclass. * @since 2.7 */ public abstract class AbstractByteArrayOutputStream> extends OutputStream { @@ -110,7 +110,7 @@ public AbstractByteArrayOutputStream() { /** * Returns this instance typed to {@code T}. * - * @return {@code this} instance + * @return {@code this} instance. */ @SuppressWarnings("unchecked") protected T asThis() { @@ -232,7 +232,7 @@ protected byte[] toByteArrayImpl() { /** * Gets the current contents of this byte stream as an Input Stream. The * returned stream is backed by buffers of {@code this} stream, - * avoiding memory allocation and copy, thus saving space and time.
    + * avoiding memory allocation and copy, thus saving space and time. * * @return the current contents of this output stream. * @see java.io.ByteArrayOutputStream#toByteArray() @@ -244,9 +244,9 @@ protected byte[] toByteArrayImpl() { /** * Gets the current contents of this byte stream as an Input Stream. The * returned stream is backed by buffers of {@code this} stream, - * avoiding memory allocation and copy, thus saving space and time.
    + * avoiding memory allocation and copy, thus saving space and time. * - * @param the type of the InputStream which makes up + * @param the type of the InputStream which makes up * the {@link SequenceInputStream}. * @param isConstructor A constructor for an InputStream which makes * up the {@link SequenceInputStream}. @@ -257,12 +257,12 @@ protected byte[] toByteArrayImpl() { * @since 2.7 */ @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site. - protected InputStream toInputStream(final InputStreamConstructor isConstructor) { + protected InputStream toInputStream(final InputStreamConstructor isConstructor) { int remaining = count; if (remaining == 0) { return ClosedInputStream.INSTANCE; } - final List list = new ArrayList<>(buffers.size()); + final List list = new ArrayList<>(buffers.size()); for (final byte[] buf : buffers) { final int c = Math.min(buf.length, remaining); list.add(isConstructor.construct(buf, 0, c)); @@ -276,7 +276,7 @@ protected InputStream toInputStream(final InputStreamCon } /** - * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}. + * Gets the current contents of this byte stream as a string using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. * * @return the contents of the byte array as a String. * @see java.io.ByteArrayOutputStream#toString() @@ -349,7 +349,7 @@ public T write(final CharSequence data, final Charset charset) { * this stream. * * @param in the input stream to read from. - * @return total number of bytes read from the input stream (and written to this stream) + * @return total number of bytes read from the input stream (and written to this stream). * @throws IOException if an I/O error occurs while reading the input stream. * @since 1.4 */ diff --git a/src/main/java/org/apache/commons/io/output/AppendableOutputStream.java b/src/main/java/org/apache/commons/io/output/AppendableOutputStream.java index e8320d99d64..ed7727969b9 100644 --- a/src/main/java/org/apache/commons/io/output/AppendableOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/AppendableOutputStream.java @@ -39,7 +39,7 @@ public class AppendableOutputStream extends OutputStream /** * Constructs a new instance with the specified appendable. * - * @param appendable the appendable to write to + * @param appendable the appendable to write to. */ public AppendableOutputStream(final T appendable) { this.appendable = appendable; @@ -48,7 +48,7 @@ public AppendableOutputStream(final T appendable) { /** * Gets the target appendable. * - * @return the target appendable + * @return the target appendable. */ public T getAppendable() { return appendable; @@ -57,7 +57,7 @@ public T getAppendable() { /** * Writes a character to the underlying appendable. * - * @param b the character to write + * @param b the character to write. * @throws IOException If an I/O error occurs. */ @Override diff --git a/src/main/java/org/apache/commons/io/output/AppendableWriter.java b/src/main/java/org/apache/commons/io/output/AppendableWriter.java index b1ace0b908c..4ac262ccea0 100644 --- a/src/main/java/org/apache/commons/io/output/AppendableWriter.java +++ b/src/main/java/org/apache/commons/io/output/AppendableWriter.java @@ -75,10 +75,10 @@ public Writer append(final CharSequence csq) throws IOException { /** * Appends a subsequence of the specified character sequence to the underlying appendable. * - * @param csq the character sequence from which a subsequence will be appended - * @param start the index of the first character in the subsequence - * @param end the index of the character following the last character in the subsequence - * @return this writer + * @param csq the character sequence from which a subsequence will be appended. + * @param start the index of the first character in the subsequence. + * @param end the index of the character following the last character in the subsequence. + * @return this writer. * @throws IndexOutOfBoundsException If {@code start} or {@code end} are negative, {@code start} is greater than * {@code end}, or {@code end} is greater than {@code csq.length()}. * @throws IOException If an I/O error occurs. diff --git a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java index 48fb793669b..dee60bd69b9 100644 --- a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java @@ -40,7 +40,9 @@ public class ByteArrayOutputStream extends AbstractByteArrayOutputStreamIt has network timeout associated. * *

    - * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -65,7 +67,9 @@ public static InputStream toBufferedInputStream(final InputStream input) *
  • It has network timeout associated.
  • * *

    - * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -95,8 +99,8 @@ public ByteArrayOutputStream() { * Constructs a new byte array output stream, with a buffer capacity of * the specified size, in bytes. * - * @param size the initial size - * @throws IllegalArgumentException if size is negative + * @param size the initial size. + * @throws IllegalArgumentException if size is negative. */ public ByteArrayOutputStream(final int size) { if (size < 0) { diff --git a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java index 2588264e172..b47bd3d880a 100644 --- a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java @@ -37,7 +37,7 @@ public class ChunkedOutputStream extends FilterOutputStream { // @formatter:off /** - * Builds a new {@link UnsynchronizedByteArrayOutputStream}. + * Builds a new {@link ChunkedOutputStream}. * *

    * Using File IO: @@ -143,7 +143,7 @@ public ChunkedOutputStream(final OutputStream stream) { /** * Constructs a new stream that uses the specified chunk size. * - * @param stream the stream to wrap + * @param stream the stream to wrap. * @param chunkSize the chunk size to use; must be a positive number. * @throws IllegalArgumentException if the chunk size is <= 0. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. diff --git a/src/main/java/org/apache/commons/io/output/CloseShieldOutputStream.java b/src/main/java/org/apache/commons/io/output/CloseShieldOutputStream.java index cefa71ad41f..876f3e5b7dc 100644 --- a/src/main/java/org/apache/commons/io/output/CloseShieldOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/CloseShieldOutputStream.java @@ -34,7 +34,7 @@ public class CloseShieldOutputStream extends ProxyOutputStream { * Constructs a proxy that shields the given output stream from being closed. * * @param outputStream the output stream to wrap. - * @return the created proxy + * @return the created proxy. * @since 2.9.0 */ public static CloseShieldOutputStream wrap(final OutputStream outputStream) { diff --git a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java index c7216ce6f90..664df91028d 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java @@ -58,7 +58,7 @@ public ClosedOutputStream() { /** * Throws an {@link IOException} to indicate that the stream is closed. * - * @throws IOException always thrown + * @throws IOException always thrown. */ @Override public void flush() throws IOException { diff --git a/src/main/java/org/apache/commons/io/output/ClosedWriter.java b/src/main/java/org/apache/commons/io/output/ClosedWriter.java index c4e1d93b93c..446b31b7950 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedWriter.java +++ b/src/main/java/org/apache/commons/io/output/ClosedWriter.java @@ -38,6 +38,7 @@ public class ClosedWriter extends Writer { * @since 2.12.0 */ public static final ClosedWriter INSTANCE = new ClosedWriter(); + /** * The singleton instance. * @@ -61,7 +62,7 @@ public void close() throws IOException { /** * Throws an {@link IOException} to indicate that the stream is closed. * - * @throws IOException always thrown + * @throws IOException always thrown. */ @Override public void flush() throws IOException { diff --git a/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java b/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java index 5c8672d5bd6..fab73c01eeb 100644 --- a/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java @@ -451,8 +451,10 @@ protected void thresholdReached() throws IOException { /** * Converts the current contents of this byte stream to an {@link InputStream}. If the data for this output stream has been retained in memory, the returned - * stream is backed by buffers of {@code this} stream, avoiding memory allocation and copy, thus saving space and time.
    + * stream is backed by buffers of {@code this} stream, avoiding memory allocation and copy, thus saving space and time. + *

    * Otherwise, the returned stream will be one that is created from the data that has been committed to disk. + *

    * * @return the current contents of this output stream. * @throws IOException if this stream is not yet closed or an error occurs. diff --git a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java index 42d378cc0a5..4b6ebe1ba3e 100644 --- a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java +++ b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java @@ -188,11 +188,7 @@ private static OutputStreamWriter initWriter(final File file, final Object encod } return new OutputStreamWriter(outputStream, (String) encoding); } catch (final IOException | RuntimeException ex) { - try { - IOUtils.close(outputStream); - } catch (final IOException e) { - ex.addSuppressed(e); - } + IOUtils.closeQuietlySuppress(outputStream, ex); if (!fileExistedAlready) { FileUtils.deleteQuietly(file); } diff --git a/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java b/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java new file mode 100644 index 00000000000..772edf190bf --- /dev/null +++ b/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.commons.io.output; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.io.build.AbstractStreamBuilder; + +/** + * Re-implements {@link FilterOutputStream#flush()} to do nothing. + * + * @since 2.22.0 + */ +public final class FlushShieldOutputStream extends ProxyOutputStream { + + // @formatter:off + /** + * Builds a new {@link FlushShieldOutputStream}. + * + *

    + * Using File IO: + *

    + *
    {@code
    +     * FlushShieldOutputStream s = FlushShieldOutputStream.builder()
    +     *   .setPath("over/there.out")
    +     *   .setBufferSize(8192)
    +     *   .get();
    +     * }
    +     * 
    + *

    + * Using NIO Path: + *

    + *
    {@code
    +     * FlushShieldOutputStream s = FlushShieldOutputStream.builder()
    +     *   .setPath("over/there.out")
    +     *   .setBufferSize(8192)
    +     *   .get();
    +     * }
    +     * 
    + * + * @see #get() + */ + // @formatter:on + public static class Builder extends AbstractStreamBuilder { + + /** + * Constructs a new builder of {@link FlushShieldOutputStream}. + */ + public Builder() { + // empty + } + + /** + * Builds a new {@link FlushShieldOutputStream}. + * + * @return a new instance. + * @throws IllegalStateException if the {@code origin} is {@code null}. + * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}. + * @throws IOException if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}. + * @see #getOutputStream() + * @see #getBufferSize() + * @see #getUnchecked() + */ + @Override + public FlushShieldOutputStream get() throws IOException { + return new FlushShieldOutputStream(this); + } + + } + + /** + * Constructs a new builder of {@link FlushShieldOutputStream}. + * + * @return a new builder of {@link FlushShieldOutputStream}. + */ + public static Builder builder() { + return new Builder(); + } + + @SuppressWarnings("resource") // caller closes. + private FlushShieldOutputStream(final Builder builder) throws IOException { + super(builder.getOutputStream()); + } + + /** + * Constructs a {@code FlushShieldOutputStream} filter for the specified underlying output stream. + * + * @param out the underlying output stream to be assigned to the field {@code this.out} for later use, or {@code null} if this instance is to be created + * without an underlying stream. + */ + public FlushShieldOutputStream(final OutputStream out) { + super(out); + } + + @Override + public void flush() throws IOException { + // shield: do nothing. + } +} diff --git a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java index f15048e5c0e..1ee9391af8f 100644 --- a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java +++ b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java @@ -204,7 +204,7 @@ public LockableFileWriter(final File file, final boolean append) throws IOExcept /** * Constructs a LockableFileWriter. *

    - * The new instance uses the virtual machine's {@link Charset#defaultCharset() default charset}. + * The new instance uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}. *

    * * @param file the file to write to, not null. diff --git a/src/main/java/org/apache/commons/io/output/NullWriter.java b/src/main/java/org/apache/commons/io/output/NullWriter.java index 635ffb044a6..e43b6427c75 100644 --- a/src/main/java/org/apache/commons/io/output/NullWriter.java +++ b/src/main/java/org/apache/commons/io/output/NullWriter.java @@ -69,7 +69,7 @@ public Writer append(final char c) { * Does nothing, like writing to {@code /dev/null}. * * @param csq The character sequence to write. - * @return this writer + * @return this writer. * @since 2.0 */ @Override diff --git a/src/main/java/org/apache/commons/io/output/ProxyOutputStream.java b/src/main/java/org/apache/commons/io/output/ProxyOutputStream.java index 090f2d059a7..06f91a21ab5 100644 --- a/src/main/java/org/apache/commons/io/output/ProxyOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ProxyOutputStream.java @@ -25,8 +25,7 @@ import org.apache.commons.io.build.AbstractStreamBuilder; /** - * A Proxy stream which acts as expected, that is it passes the method calls on to the proxied stream and doesn't change which methods are being called. It is - * an alternative base class to FilterOutputStream to increase reusability. + * An output stream proxy which delegates to the wrapped output stream. *

    * See the protected methods for ways in which a subclass can easily decorate a stream with custom pre-, post- or error processing functionality. *

    @@ -162,6 +161,9 @@ protected void handleIOException(final IOException e) throws IOException { /** * Sets the underlying output stream. + *

    + * Use with caution. + *

    * * @param out the underlying output stream. * @return {@code this} instance. @@ -175,12 +177,13 @@ public ProxyOutputStream setReference(final OutputStream out) { /** * Unwraps this instance by returning the underlying {@link OutputStream}. *

    - * Use with caution; useful to query the underlying {@link OutputStream}. + * Use with caution. *

    * * @return the underlying {@link OutputStream}. + * @since 2.22.0 */ - OutputStream unwrap() { + public OutputStream unwrap() { return out; } diff --git a/src/main/java/org/apache/commons/io/output/ProxyWriter.java b/src/main/java/org/apache/commons/io/output/ProxyWriter.java index ac88f4c6943..1cc4b1cb104 100644 --- a/src/main/java/org/apache/commons/io/output/ProxyWriter.java +++ b/src/main/java/org/apache/commons/io/output/ProxyWriter.java @@ -23,10 +23,11 @@ import org.apache.commons.io.IOUtils; /** - * A Proxy stream which acts as expected, that is it passes the method calls on to the proxied stream and doesn't - * change which methods are being called. It is an alternative base class to FilterWriter to increase reusability, - * because FilterWriter changes the methods being called, such as {@code write(char[]) to write(char[], int, int)} - * and {@code write(String) to write(String, int, int)}. + * A writer proxy which delegates to the wrapped writer. + *

    + * It is an alternative base class to FilterWriter to increase reusability, because FilterWriter changes the methods being called, such as + * {@code write(char[]) to write(char[], int, int)} and {@code write(String) to write(String, int, int)}. + *

    */ public class ProxyWriter extends FilterWriter { @@ -51,8 +52,8 @@ public ProxyWriter(final Writer delegate) { * The default implementation does nothing. *

    * - * @param n number of chars written - * @throws IOException if the post-processing fails + * @param n number of chars written. + * @throws IOException if the post-processing fails. * @since 2.0 */ @SuppressWarnings("unused") // Possibly thrown from subclasses. @@ -144,6 +145,7 @@ protected void beforeWrite(final int n) throws IOException { /** * Invokes the delegate's {@code close()} method. + * * @throws IOException if an I/O error occurs. */ @Override @@ -153,6 +155,7 @@ public void close() throws IOException { /** * Invokes the delegate's {@code flush()} method. + * * @throws IOException if an I/O error occurs. */ @Override @@ -179,6 +182,34 @@ protected void handleIOException(final IOException e) throws IOException { throw e; } + /** + * Sets the underlying writer. + *

    + * Use with caution. + *

    + * + * @param out the underlying output writer. + * @return {@code this} instance. + * @since 2.22.0 + */ + public ProxyWriter setReference(final Writer out) { + this.out = out; + return this; + } + + /** + * Unwraps this instance by returning the underlying {@link Writer}. + *

    + * Use with caution. + *

    + * + * @return the underlying {@link Writer}. + * @since 2.22.0 + */ + public Writer unwrap() { + return out; + } + /** * Invokes the delegate's {@code write(char[])} method. * diff --git a/src/main/java/org/apache/commons/io/output/TeeOutputStream.java b/src/main/java/org/apache/commons/io/output/TeeOutputStream.java index 72a1eb9e4c6..59f70ffaafd 100644 --- a/src/main/java/org/apache/commons/io/output/TeeOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/TeeOutputStream.java @@ -35,8 +35,8 @@ public class TeeOutputStream extends ProxyOutputStream { /** * Constructs a TeeOutputStream. * - * @param out the main OutputStream - * @param branch the second OutputStream + * @param out the main OutputStream. + * @param branch the second OutputStream. */ public TeeOutputStream(final OutputStream out, final OutputStream branch) { super(out); diff --git a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java index 742572b2ebc..12ecc53ee85 100644 --- a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java @@ -111,7 +111,12 @@ public ThresholdingOutputStream(final int threshold, final IOConsumer threshold) { thresholdExceeded = true; - thresholdReached(); + try { + thresholdReached(); + } catch (final IOException | RuntimeException e) { + thresholdExceeded = false; + throw e; + } } } @@ -192,7 +197,7 @@ public int getThreshold() { * @return {@code true} if the threshold has been reached; {@code false} otherwise. */ public boolean isThresholdExceeded() { - return written > threshold; + return thresholdExceeded; } /** diff --git a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java index a47a3b2aa2a..0e361b8420a 100644 --- a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java @@ -113,7 +113,9 @@ public static Builder builder() { *
  • It has network timeout associated.
  • * *

    - * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -136,7 +138,9 @@ public static InputStream toBufferedInputStream(final InputStream input) throws *
  • It has network timeout associated.
  • * *

    - * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[].
    + * It can be used in favor of {@link #toByteArray()}, since it avoids unnecessary allocation and copy of byte[]. + *

    + *

    * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}. *

    * @@ -157,6 +161,7 @@ public static InputStream toBufferedInputStream(final InputStream input, final i * Constructs a new byte array output stream. The buffer capacity is initially. * * {@value AbstractByteArrayOutputStream#DEFAULT_SIZE} bytes, though its size increases if necessary. + * * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated diff --git a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java index d17318f79b6..5f3274790b8 100644 --- a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java @@ -143,14 +143,14 @@ public WriterOutputStream get() throws IOException { @Override public Builder setCharset(final Charset charset) { super.setCharset(charset); - this.charsetDecoder = getCharset().newDecoder(); + this.charsetDecoder = newDecoder(getCharset()); return this; } @Override public Builder setCharset(final String charset) { super.setCharset(charset); - this.charsetDecoder = getCharset().newDecoder(); + this.charsetDecoder = newDecoder(getCharset()); return this; } @@ -198,18 +198,18 @@ public static Builder builder() { /** * Checks if the JDK in use properly supports the given charset. * - * @param charset the charset to check the support for + * @param charset the charset to check the support for. */ private static void checkIbmJdkWithBrokenUTF16(final Charset charset) { if (!StandardCharsets.UTF_16.name().equals(charset.name())) { return; } - final String TEST_STRING_2 = "v\u00e9s"; - final byte[] bytes = TEST_STRING_2.getBytes(charset); + final String testString = "v\u00e9s"; + final byte[] bytes = testString.getBytes(charset); final CharsetDecoder charsetDecoder2 = charset.newDecoder(); final ByteBuffer bb2 = ByteBuffer.allocate(16); - final CharBuffer cb2 = CharBuffer.allocate(TEST_STRING_2.length()); + final CharBuffer cb2 = CharBuffer.allocate(testString.length()); final int len = bytes.length; for (int i = 0; i < len; i++) { bb2.put(bytes[i]); @@ -223,14 +223,24 @@ private static void checkIbmJdkWithBrokenUTF16(final Charset charset) { bb2.compact(); } cb2.rewind(); - if (!TEST_STRING_2.equals(cb2.toString())) { + if (!testString.equals(cb2.toString())) { throw new UnsupportedOperationException("UTF-16 requested when running on an IBM JDK with broken UTF-16 support. " + "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream"); } } + private static CharsetDecoder newDecoder(final Charset charset) { + // @formatter:off + return Charsets.toCharset(charset).newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .replaceWith("?"); + // @formatter:on + } + private final Writer writer; + private final CharsetDecoder decoder; private final boolean writeImmediately; @@ -251,9 +261,9 @@ private WriterOutputStream(final Builder builder) throws IOException { } /** - * Constructs a new {@link WriterOutputStream} that uses the virtual machine's {@link Charset#defaultCharset() default charset} and with a default output - * buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()} is - * called. + * Constructs a new {@link WriterOutputStream} that uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} and with a default + * output buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or + * {@link #close()} is called. * * @param writer the target {@link Writer}. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. @@ -289,15 +299,7 @@ public WriterOutputStream(final Writer writer, final Charset charset) { */ @Deprecated public WriterOutputStream(final Writer writer, final Charset charset, final int bufferSize, final boolean writeImmediately) { - // @formatter:off - this(writer, - Charsets.toCharset(charset).newDecoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE) - .replaceWith("?"), - bufferSize, - writeImmediately); - // @formatter:on + this(writer, newDecoder(charset), bufferSize, writeImmediately); } /** diff --git a/src/main/java/org/apache/commons/io/serialization/ClassNameMatcher.java b/src/main/java/org/apache/commons/io/serialization/ClassNameMatcher.java index 47103a4293d..0682c66b802 100644 --- a/src/main/java/org/apache/commons/io/serialization/ClassNameMatcher.java +++ b/src/main/java/org/apache/commons/io/serialization/ClassNameMatcher.java @@ -27,8 +27,8 @@ public interface ClassNameMatcher { /** * Returns {@code true} if the supplied class name matches this object's condition. * - * @param className fully qualified class name - * @return {@code true} if the class name matches this object's condition + * @param className fully qualified class name. + * @return {@code true} if the class name matches this object's condition. */ boolean matches(String className); } \ No newline at end of file diff --git a/src/main/java/org/apache/commons/io/serialization/FullClassNameMatcher.java b/src/main/java/org/apache/commons/io/serialization/FullClassNameMatcher.java index 14f725f5975..ae0268b4a98 100644 --- a/src/main/java/org/apache/commons/io/serialization/FullClassNameMatcher.java +++ b/src/main/java/org/apache/commons/io/serialization/FullClassNameMatcher.java @@ -19,7 +19,6 @@ package org.apache.commons.io.serialization; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -36,10 +35,10 @@ final class FullClassNameMatcher implements ClassNameMatcher { /** * Constructs an object based on the specified class names. * - * @param classes a list of class names + * @param classes a list of class names. */ FullClassNameMatcher(final String... classes) { - classesSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(classes))); + classesSet = new HashSet<>(Arrays.asList(classes)); } @Override diff --git a/src/main/java/org/apache/commons/io/serialization/ObjectStreamClassPredicate.java b/src/main/java/org/apache/commons/io/serialization/ObjectStreamClassPredicate.java index 5a3375cd72e..ffdf1a73df9 100644 --- a/src/main/java/org/apache/commons/io/serialization/ObjectStreamClassPredicate.java +++ b/src/main/java/org/apache/commons/io/serialization/ObjectStreamClassPredicate.java @@ -55,8 +55,8 @@ public ObjectStreamClassPredicate() { * The reject list takes precedence over the accept list. *

    * - * @param classes Classes to accept - * @return this object + * @param classes Classes to accept. + * @return this object. */ public ObjectStreamClassPredicate accept(final Class... classes) { Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add); @@ -112,7 +112,7 @@ public ObjectStreamClassPredicate accept(final String... patterns) { * The reject list takes precedence over the accept list. *

    * - * @param classes Classes to reject + * @param classes Classes to reject. * @return {@code this} instance. */ public ObjectStreamClassPredicate reject(final Class... classes) { @@ -126,7 +126,7 @@ public ObjectStreamClassPredicate reject(final Class... classes) { * The reject list takes precedence over the accept list. *

    * - * @param m the matcher to use + * @param m the matcher to use. * @return {@code this} instance. */ public ObjectStreamClassPredicate reject(final ClassNameMatcher m) { @@ -140,7 +140,7 @@ public ObjectStreamClassPredicate reject(final ClassNameMatcher m) { * The reject list takes precedence over the accept list. *

    * - * @param pattern standard Java regexp + * @param pattern standard Java regexp. * @return {@code this} instance. */ public ObjectStreamClassPredicate reject(final Pattern pattern) { diff --git a/src/main/java/org/apache/commons/io/serialization/RegexpClassNameMatcher.java b/src/main/java/org/apache/commons/io/serialization/RegexpClassNameMatcher.java index 7d204dfa4f7..adb76fdc511 100644 --- a/src/main/java/org/apache/commons/io/serialization/RegexpClassNameMatcher.java +++ b/src/main/java/org/apache/commons/io/serialization/RegexpClassNameMatcher.java @@ -34,8 +34,8 @@ final class RegexpClassNameMatcher implements ClassNameMatcher { /** * Constructs an object based on the specified pattern. * - * @param pattern a pattern for evaluating acceptable class names - * @throws NullPointerException if {@code pattern} is null + * @param pattern a pattern for evaluating acceptable class names. + * @throws NullPointerException if {@code pattern} is null. */ RegexpClassNameMatcher(final Pattern pattern) { this.pattern = Objects.requireNonNull(pattern, "pattern"); @@ -44,7 +44,7 @@ final class RegexpClassNameMatcher implements ClassNameMatcher { /** * Constructs an object based on the specified regular expression. * - * @param regex a regular expression for evaluating acceptable class names + * @param regex a regular expression for evaluating acceptable class names. */ RegexpClassNameMatcher(final String regex) { this(Pattern.compile(regex)); diff --git a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java index 6141cc59b1c..495d02677b6 100644 --- a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java +++ b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java @@ -26,6 +26,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.build.AbstractStreamBuilder; +import org.apache.commons.io.input.BoundedInputStream; /** * An {@link ObjectInputStream} that's restricted to deserialize a limited set of classes. @@ -33,7 +34,7 @@ *

    * Various accept/reject methods allow for specifying which classes can be deserialized. *

    - *

    Reading safely

    + *

    Deserlizing safely

    *

    * Here is the only way to safely read a HashMap of String keys and Integer values: *

    @@ -50,7 +51,7 @@ * oos.flush(); * byteArray = baos.toByteArray(); * } - * // Reading + * // Deserializing * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); * ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() * .accept(HashMap.class, Number.class, Integer.class) @@ -74,9 +75,47 @@ * } * } *

    - * Design inspired by a IBM DeveloperWorks Article. + * This design was inspired by a IBM DeveloperWorks Article. *

    - * + *

    Deserlizing with a size boundary

    + *

    + * You can further guard your application againt untrusted input by limiting how much data to process using a {@link BoundedInputStream}. + * For example: + *

    + *
    {@code
    + * // Deserializing with a size limit successfully
    + * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
    + *         ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
    + *             .accept(HashMap.class, Number.class, Integer.class)
    + *             .setInputStream(BoundedInputStream.builder()
    + *                 .setMaxCount(10_000)
    + *                 .setOnMaxCount((max, count) -> {
    + *                     throw new IllegalArgumentException("Input exceeds limit.");
    + *                 })
    + *                 .setInputStream(bais)
    + *                 .get())
    + *             .get()) {
    + *     // String.class is automatically accepted
    + *     final HashMap map2 = (HashMap) vois.readObject();
    + *     assertEquals(map1, map2);
    + * }
    + * // Deserializing with a size limit reaching the limit
    + * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
    + *         ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
    + *             .accept(HashMap.class, Number.class, Integer.class)
    + *             .setInputStream(BoundedInputStream.builder()
    + *                 .setMaxCount(10)
    + *                 .setOnMaxCount((max, count) -> {
    + *                     throw new IllegalArgumentException("Input exceeds limit.");
    + *                 })
    + *                 .setInputStream(bais)
    + *                 .get())
    + *             .get()) {
    + *     // String.class is automatically accepted
    + *     final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> vois.readObject());
    + *     assertEquals("Input exceeds limit.", e.getMessage());
    + * }
    + * }
    * @since 2.5 */ public class ValidatingObjectInputStream extends ObjectInputStream { @@ -119,8 +158,8 @@ public Builder() { /** * Accepts the specified classes for deserialization, unless they are otherwise rejected. * - * @param classes Classes to accept - * @return this object + * @param classes Classes to accept. + * @return this object. * @since 2.18.0 */ public Builder accept(final Class... classes) { @@ -204,7 +243,7 @@ public ObjectStreamClassPredicate getPredicate() { /** * Rejects the specified classes for deserialization, even if they are otherwise accepted. * - * @param classes Classes to reject + * @param classes Classes to reject. * @return {@code this} instance. * @since 2.18.0 */ @@ -216,7 +255,7 @@ public Builder reject(final Class... classes) { /** * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted. * - * @param matcher the matcher to use + * @param matcher the matcher to use. * @return {@code this} instance. * @since 2.18.0 */ @@ -228,7 +267,7 @@ public Builder reject(final ClassNameMatcher matcher) { /** * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted. * - * @param pattern standard Java regexp + * @param pattern standard Java regexp. * @return {@code this} instance. * @since 2.18.0 */ @@ -285,8 +324,8 @@ private ValidatingObjectInputStream(final Builder builder) throws IOException { * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be * deserialized, as by default no classes are accepted. * - * @param input an input stream - * @throws IOException if an I/O error occurs while reading stream header + * @param input an input stream. + * @throws IOException if an I/O error occurs while reading stream header. * @deprecated Use {@link #builder()}. */ @Deprecated @@ -313,7 +352,7 @@ private ValidatingObjectInputStream(final InputStream input, final ObjectStreamC * The reject list takes precedence over the accept list. *

    * - * @param classes Classes to accept + * @param classes Classes to accept. * @return {@code this} instance. */ public ValidatingObjectInputStream accept(final Class... classes) { diff --git a/src/main/java/org/apache/commons/io/serialization/package-info.java b/src/main/java/org/apache/commons/io/serialization/package-info.java index 2e42a361fd2..b8a677a8a13 100644 --- a/src/main/java/org/apache/commons/io/serialization/package-info.java +++ b/src/main/java/org/apache/commons/io/serialization/package-info.java @@ -17,5 +17,7 @@ /** * Provides a framework for controlling the deserialization of classes. + * + * You can deserlialize an object tree safely using a {@link org.apache.commons.io.serialization.ValidatingObjectInputStream}. */ package org.apache.commons.io.serialization; diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 7fa4bd1f700..7de36f6ede9 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -17,16 +17,18 @@ --> -

    -The Apache Commons IO component contains utility classes, -filters, streams, readers and writers. -

    -

    -These classes aim to add to the standard JDK IO classes. -The utilities provide convenience wrappers around the JDK, simplifying -various operations into pre-tested units of code. -The filters and streams provide useful implementations that perhaps should -be in the JDK itself. -

    + Apache Commons IO + +

    + leafIntroduction +

    +

    Apache Commons IO is a library of utilities to assist with developing IO functionality as the package list below describes.

    +

    + leafRequirements +

    +
      +
    • Java 8 or above.
    • +
    • If using OSGi, R7 or above.
    • +
    diff --git a/src/media/commons-logo-component-100.xcf b/src/media/commons-logo-component-100.xcf new file mode 100644 index 00000000000..d767311a12d Binary files /dev/null and b/src/media/commons-logo-component-100.xcf differ diff --git a/src/media/commons-logo-component.xcf b/src/media/commons-logo-component.xcf new file mode 100644 index 00000000000..a7dcdd43c1e Binary files /dev/null and b/src/media/commons-logo-component.xcf differ diff --git a/src/media/logo-large.xcf b/src/media/logo-large.xcf deleted file mode 100644 index caa37f4d095..00000000000 Binary files a/src/media/logo-large.xcf and /dev/null differ diff --git a/src/media/logo.png b/src/media/logo.png index 396ba49f82f..02a758f0ed8 100644 Binary files a/src/media/logo.png and b/src/media/logo.png differ diff --git a/src/media/logo.xcf b/src/media/logo.xcf deleted file mode 100644 index 72619d28ccf..00000000000 Binary files a/src/media/logo.xcf and /dev/null differ diff --git a/src/site/resources/images/leaf.svg b/src/site/resources/images/leaf.svg new file mode 100644 index 00000000000..71de588c648 --- /dev/null +++ b/src/site/resources/images/leaf.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/site/resources/images/logo.png b/src/site/resources/images/logo.png index 396ba49f82f..02a758f0ed8 100644 Binary files a/src/site/resources/images/logo.png and b/src/site/resources/images/logo.png differ diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml index be5de6b1a1d..bef84849f24 100644 --- a/src/site/xdoc/building.xml +++ b/src/site/xdoc/building.xml @@ -25,7 +25,7 @@ limitations under the License.

    - Commons IO uses Maven its build system. + Commons IO uses Maven its build system.

    Commons IO requires a minimum of JDK 8 to build. @@ -46,7 +46,7 @@ limitations under the License.

    - The following Maven commands can be used to build io: + The following Maven commands can be used to build io:

    • mvn - runs the default Maven goal which performs all build checks
    • diff --git a/src/site/xdoc/description.xml b/src/site/xdoc/description.xml index ac5ab0a77ed..230a43a8410 100644 --- a/src/site/xdoc/description.xml +++ b/src/site/xdoc/description.xml @@ -157,7 +157,7 @@ limitations under the License.

    For more information, see - http://www.cs.umass.edu/~verts/cs32/endian.html + https://www.cs.umass.edu/~verts/cs32/endian.html

    diff --git a/src/site/xdoc/download_io.xml b/src/site/xdoc/download_io.xml index 567d2653265..3f782a85373 100644 --- a/src/site/xdoc/download_io.xml +++ b/src/site/xdoc/download_io.xml @@ -115,32 +115,32 @@ limitations under the License.

    -
    +
    - - - + + + - - - + + +
    commons-io-2.20.0-bin.tar.gzsha512pgpcommons-io-2.22.0-bin.tar.gzsha512pgp
    commons-io-2.20.0-bin.zipsha512pgpcommons-io-2.22.0-bin.zipsha512pgp
    - - - + + + - - - + + +
    commons-io-2.20.0-src.tar.gzsha512pgpcommons-io-2.22.0-src.tar.gzsha512pgp
    commons-io-2.20.0-src.zipsha512pgpcommons-io-2.22.0-src.zipsha512pgp
    diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 54022e401ae..403c225db03 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -28,7 +28,7 @@ limitations under the License. Apache Commons IO is a library of utilities to assist with developing IO functionality.

    - Main areas include: + The Commons IO packages include:

    • diff --git a/src/site/xdoc/security.xml b/src/site/xdoc/security.xml index 90e0d19e87b..1370cf03aac 100644 --- a/src/site/xdoc/security.xml +++ b/src/site/xdoc/security.xml @@ -48,5 +48,10 @@
    +
    +

    + For information about safe deserialization, please see Safe Deserialization. +

    +
    diff --git a/src/test/java/org/apache/commons/io/BuffersTest.java b/src/test/java/org/apache/commons/io/BuffersTest.java new file mode 100644 index 00000000000..0f7c2855c5f --- /dev/null +++ b/src/test/java/org/apache/commons/io/BuffersTest.java @@ -0,0 +1,865 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link Buffers}. + */ +public class BuffersTest { + + private static final int CAPACITY = 8; + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link ByteBuffer}. + */ + @Test + void testClearBufferDispatchByteBuffer() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link CharBuffer}. + */ + @Test + void testClearBufferDispatchCharBuffer() { + final CharBuffer buffer = CharBuffer.allocate(CAPACITY); + buffer.put(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link DoubleBuffer}. + */ + @Test + void testClearBufferDispatchDoubleBuffer() { + final DoubleBuffer buffer = DoubleBuffer.allocate(CAPACITY); + buffer.put(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link FloatBuffer}. + */ + @Test + void testClearBufferDispatchFloatBuffer() { + final FloatBuffer buffer = FloatBuffer.allocate(CAPACITY); + buffer.put(new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0f, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with an {@link IntBuffer}. + */ + @Test + void testClearBufferDispatchIntBuffer() { + final IntBuffer buffer = IntBuffer.allocate(CAPACITY); + buffer.put(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link LongBuffer}. + */ + @Test + void testClearBufferDispatchLongBuffer() { + final LongBuffer buffer = LongBuffer.allocate(CAPACITY); + buffer.put(new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0L, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with a {@link ShortBuffer}. + */ + @Test + void testClearBufferDispatchShortBuffer() { + final ShortBuffer buffer = ShortBuffer.allocate(CAPACITY); + buffer.put(new short[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final Buffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, buffer.position()); + assertEquals(CAPACITY, buffer.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((short) 0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(Buffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearBufferNull() { + assertNull(Buffers.clear((Buffer) null)); + } + + /** + * Tests {@link Buffers#clear(ByteBuffer)} with an array-backed buffer. + */ + @Test + void testClearByteBufferArrayBacked() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final ByteBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(ByteBuffer)} with a direct buffer. + */ + @Test + void testClearByteBufferDirect() { + final ByteBuffer buffer = ByteBuffer.allocateDirect(CAPACITY); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((byte) (i + 1)); + } + final ByteBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(ByteBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearByteBufferNull() { + assertNull(Buffers.clear((ByteBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(ByteBuffer)} returns the same buffer instance. + */ + @Test + void testClearByteBufferReturnsSameInstance() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clear(CharBuffer)} with an array-backed buffer. + */ + @Test + void testClearCharBufferArrayBacked() { + final CharBuffer buffer = CharBuffer.allocate(CAPACITY); + buffer.put(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }); + final CharBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(CharBuffer)} with a direct (view) buffer. + */ + @Test + void testClearCharBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Character.BYTES); + final CharBuffer buffer = byteBuffer.asCharBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((char) (i + 1)); + } + final CharBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(CharBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearCharBufferNull() { + assertNull(Buffers.clear((CharBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(CharBuffer)} returns the same buffer instance. + */ + @Test + void testClearCharBufferReturnsSameInstance() { + final CharBuffer buffer = CharBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clearDirect(Buffer)} with a direct {@link ByteBuffer}. + */ + @Test + void testClearDirectWithDirectByteBuffer() { + final ByteBuffer buffer = ByteBuffer.allocateDirect(CAPACITY); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((byte) (i + 1)); + } + final Buffer result = Buffers.clearDirect(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clearDirect(Buffer)} with a direct {@link IntBuffer}. + */ + @Test + void testClearDirectWithDirectIntBuffer() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Integer.BYTES); + final IntBuffer buffer = byteBuffer.asIntBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put(i + 1); + } + final Buffer result = Buffers.clearDirect(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clearDirect(Buffer)} with a non-direct {@link ByteBuffer}. + */ + @Test + void testClearDirectWithNonDirectByteBuffer() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((byte) (i + 1)); + } + buffer.flip(); + final Buffer result = Buffers.clearDirect(buffer); + assertSame(buffer, result); + // Position and limit should be unchanged (still flipped state) + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + // Content is unchanged + for (int i = 0; i < CAPACITY; i++) { + assertEquals((byte) (i + 1), buffer.get(i)); + } + } + + /** + * Tests {@link Buffers#clearDirect(Buffer)} with {@code null}. + */ + @Test + void testClearDirectWithNull() { + assertNull(Buffers.clearDirect(null)); + } + + /** + * Tests {@link Buffers#clear(DoubleBuffer)} with an array-backed buffer. + */ + @Test + void testClearDoubleBufferArrayBacked() { + final DoubleBuffer buffer = DoubleBuffer.allocate(CAPACITY); + buffer.put(new double[] { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 }); + final DoubleBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(DoubleBuffer)} with a direct (view) buffer . + */ + @Test + void testClearDoubleBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Double.BYTES); + final DoubleBuffer buffer = byteBuffer.asDoubleBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put(i + 1.0); + } + final DoubleBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(DoubleBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearDoubleBufferNull() { + assertNull(Buffers.clear((DoubleBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(DoubleBuffer)} returns the same buffer instance. + */ + @Test + void testClearDoubleBufferReturnsSameInstance() { + final DoubleBuffer buffer = DoubleBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clear(FloatBuffer)} with an array-backed buffer. + */ + @Test + void testClearFloatBufferArrayBacked() { + final FloatBuffer buffer = FloatBuffer.allocate(CAPACITY); + buffer.put(new float[] { 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f }); + final FloatBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0f, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(FloatBuffer)} with a direct (view) buffer. + */ + @Test + void testClearFloatBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Float.BYTES); + final FloatBuffer buffer = byteBuffer.asFloatBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put(i + 1.0f); + } + final FloatBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0f, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(FloatBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearFloatBufferNull() { + assertNull(Buffers.clear((FloatBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(FloatBuffer)} returns the same buffer instance. + */ + @Test + void testClearFloatBufferReturnsSameInstance() { + final FloatBuffer buffer = FloatBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clear(IntBuffer)} with an array-backed buffer. + */ + @Test + void testClearIntBufferArrayBacked() { + final IntBuffer buffer = IntBuffer.allocate(CAPACITY); + buffer.put(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final IntBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(IntBuffer)} with a direct (view) buffer. + */ + @Test + void testClearIntBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Integer.BYTES); + final IntBuffer buffer = byteBuffer.asIntBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put(i + 1); + } + final IntBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(IntBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearIntBufferNull() { + assertNull(Buffers.clear((IntBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(IntBuffer)} returns the same buffer instance. + */ + @Test + void testClearIntBufferReturnsSameInstance() { + final IntBuffer buffer = IntBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clear(LongBuffer)} with an array-backed buffer. + */ + @Test + void testClearLongBufferArrayBacked() { + final LongBuffer buffer = LongBuffer.allocate(CAPACITY); + buffer.put(new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L }); + final LongBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0L, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(LongBuffer)} with a direct (view) buffer. + */ + @Test + void testClearLongBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Long.BYTES); + final LongBuffer buffer = byteBuffer.asLongBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put(i + 1L); + } + final LongBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0L, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(LongBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearLongBufferNull() { + assertNull(Buffers.clear((LongBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(LongBuffer)} returns the same buffer instance. + */ + @Test + void testClearLongBufferReturnsSameInstance() { + final LongBuffer buffer = LongBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clear(ShortBuffer)} with an array-backed buffer. + */ + @Test + void testClearShortBufferArrayBacked() { + final ShortBuffer buffer = ShortBuffer.allocate(CAPACITY); + buffer.put(new short[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final ShortBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((short) 0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(ShortBuffer)} with a direct (view) buffer. + */ + @Test + void testClearShortBufferDirect() { + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(CAPACITY * Short.BYTES); + final ShortBuffer buffer = byteBuffer.asShortBuffer(); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((short) (i + 1)); + } + final ShortBuffer result = Buffers.clear(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((short) 0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clear(ShortBuffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearShortBufferNull() { + assertNull(Buffers.clear((ShortBuffer) null)); + } + + /** + * Tests {@link Buffers#clear(ShortBuffer)} returns the same buffer instance. + */ + @Test + void testClearShortBufferReturnsSameInstance() { + final ShortBuffer buffer = ShortBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clear(buffer)); + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link ByteBuffer} leaves it unchanged. + */ + @Test + void testClearWritableByteBufferReadOnly() { + final ByteBuffer writable = ByteBuffer.allocate(CAPACITY); + writable.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + writable.flip(); + final ByteBuffer readOnly = writable.asReadOnlyBuffer(); + final ByteBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + // Content and position should be unchanged + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((byte) (i + 1), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link ByteBuffer} zeros all bytes and resets position. + */ + @Test + void testClearWritableByteBufferWritable() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + buffer.put(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final ByteBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((byte) 0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link CharBuffer} leaves it unchanged. + */ + @Test + void testClearWritableCharBufferReadOnly() { + final CharBuffer writable = CharBuffer.allocate(CAPACITY); + writable.put(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }); + writable.flip(); + final CharBuffer readOnly = writable.asReadOnlyBuffer(); + final CharBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((char) ('a' + i), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link CharBuffer} zeros all chars. + */ + @Test + void testClearWritableCharBufferWritable() { + final CharBuffer buffer = CharBuffer.allocate(CAPACITY); + buffer.put(new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }); + final CharBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((char) 0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a direct writable {@link ByteBuffer} zeros all bytes. + */ + @Test + void testClearWritableDirectByteBuffer() { + final ByteBuffer buffer = ByteBuffer.allocateDirect(CAPACITY); + for (int i = 0; i < CAPACITY; i++) { + buffer.put((byte) (i + 1)); + } + final ByteBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((byte) 0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link DoubleBuffer} leaves it unchanged. + */ + @Test + void testClearWritableDoubleBufferReadOnly() { + final DoubleBuffer writable = DoubleBuffer.allocate(CAPACITY); + writable.put(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 }); + writable.flip(); + final DoubleBuffer readOnly = writable.asReadOnlyBuffer(); + final DoubleBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((double) (i + 1), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link DoubleBuffer} zeros all doubles. + */ + @Test + void testClearWritableDoubleBufferWritable() { + final DoubleBuffer buffer = DoubleBuffer.allocate(CAPACITY); + buffer.put(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 }); + final DoubleBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link FloatBuffer} leaves it unchanged. + */ + @Test + void testClearWritableFloatBufferReadOnly() { + final FloatBuffer writable = FloatBuffer.allocate(CAPACITY); + writable.put(new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }); + writable.flip(); + final FloatBuffer readOnly = writable.asReadOnlyBuffer(); + final FloatBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((float) (i + 1), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link FloatBuffer} zeros all floats. + */ + @Test + void testClearWritableFloatBufferWritable() { + final FloatBuffer buffer = FloatBuffer.allocate(CAPACITY); + buffer.put(new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }); + final FloatBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0.0f, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link IntBuffer} leaves it unchanged. + */ + @Test + void testClearWritableIntBufferReadOnly() { + final IntBuffer writable = IntBuffer.allocate(CAPACITY); + writable.put(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + writable.flip(); + final IntBuffer readOnly = writable.asReadOnlyBuffer(); + final IntBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(i + 1, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link IntBuffer} zeros all ints. + */ + @Test + void testClearWritableIntBufferWritable() { + final IntBuffer buffer = IntBuffer.allocate(CAPACITY); + buffer.put(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final IntBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link LongBuffer} leaves it unchanged. + */ + @Test + void testClearWritableLongBufferReadOnly() { + final LongBuffer writable = LongBuffer.allocate(CAPACITY); + writable.put(new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L }); + writable.flip(); + final LongBuffer readOnly = writable.asReadOnlyBuffer(); + final LongBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((long) (i + 1), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link LongBuffer} zeros all longs. + */ + @Test + void testClearWritableLongBufferWritable() { + final LongBuffer buffer = LongBuffer.allocate(CAPACITY); + buffer.put(new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L }); + final LongBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals(0L, result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with {@code null} returns {@code null}. + */ + @Test + void testClearWritableNull() { + assertNull(Buffers.clearWritable(null)); + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} returns the same buffer instance for a writable buffer. + */ + @Test + void testClearWritableReturnsSameInstance() { + final ByteBuffer buffer = ByteBuffer.allocate(CAPACITY); + assertSame(buffer, Buffers.clearWritable(buffer)); + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a read-only {@link ShortBuffer} leaves it unchanged. + */ + @Test + void testClearWritableShortBufferReadOnly() { + final ShortBuffer writable = ShortBuffer.allocate(CAPACITY); + writable.put(new short[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + writable.flip(); + final ShortBuffer readOnly = writable.asReadOnlyBuffer(); + final ShortBuffer result = Buffers.clearWritable(readOnly); + assertSame(readOnly, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((short) (i + 1), result.get(i)); + } + } + + /** + * Tests {@link Buffers#clearWritable(Buffer)} with a writable {@link ShortBuffer} zeros all shorts. + */ + @Test + void testClearWritableShortBufferWritable() { + final ShortBuffer buffer = ShortBuffer.allocate(CAPACITY); + buffer.put(new short[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + final ShortBuffer result = Buffers.clearWritable(buffer); + assertSame(buffer, result); + assertEquals(0, result.position()); + assertEquals(CAPACITY, result.limit()); + for (int i = 0; i < CAPACITY; i++) { + assertEquals((short) 0, result.get(i)); + } + } + +} diff --git a/src/test/java/org/apache/commons/io/ByteBuffersTest.java b/src/test/java/org/apache/commons/io/ByteBuffersTest.java new file mode 100644 index 00000000000..fa001a15e2c --- /dev/null +++ b/src/test/java/org/apache/commons/io/ByteBuffersTest.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ByteBuffers}. + */ +public class ByteBuffersTest { + + /** + * Tests {@link ByteBuffers#littleEndian(byte[])} with a non-empty array. + */ + @Test + void testLittleEndianByteArray() { + final byte[] array = { 1, 2, 3, 4 }; + final ByteBuffer buffer = ByteBuffers.littleEndian(array); + assertNotNull(buffer); + assertEquals(ByteOrder.LITTLE_ENDIAN, buffer.order()); + assertEquals(0, buffer.position()); + assertEquals(array.length, buffer.limit()); + assertEquals(array.length, buffer.capacity()); + assertArrayEquals(array, buffer.array()); + assertEquals(0, buffer.arrayOffset()); + } + + /** + * Tests {@link ByteBuffers#littleEndian(byte[])} with an empty array. + */ + @Test + void testLittleEndianByteArrayEmpty() { + final byte[] array = {}; + final ByteBuffer buffer = ByteBuffers.littleEndian(array); + assertNotNull(buffer); + assertEquals(ByteOrder.LITTLE_ENDIAN, buffer.order()); + assertEquals(0, buffer.capacity()); + assertArrayEquals(array, buffer.array()); + } + + /** + * Tests that {@link ByteBuffers#littleEndian(byte[])} wraps the given array (same backing array). + */ + @Test + void testLittleEndianByteArrayIsSameBackingArray() { + final byte[] array = { 10, 20, 30 }; + final ByteBuffer buffer = ByteBuffers.littleEndian(array); + assertSame(array, buffer.array()); + } + + /** + * Tests that a multi-byte value written to a little-endian buffer from a byte array has correct byte order. + */ + @Test + void testLittleEndianByteArrayMultibyteValue() { + final byte[] array = new byte[4]; + final ByteBuffer buffer = ByteBuffers.littleEndian(array); + buffer.putInt(0x01020304); + // In little-endian, least significant byte is first + assertEquals((byte) 0x04, array[0]); + assertEquals((byte) 0x03, array[1]); + assertEquals((byte) 0x02, array[2]); + assertEquals((byte) 0x01, array[3]); + } + + /** + * Tests {@link ByteBuffers#littleEndian(ByteBuffer)} sets order to little-endian and returns the same buffer. + */ + @Test + void testLittleEndianByteBuffer() { + final ByteBuffer buffer = ByteBuffer.allocate(8); + // Default order is BIG_ENDIAN + assertEquals(ByteOrder.BIG_ENDIAN, buffer.order()); + final ByteBuffer result = ByteBuffers.littleEndian(buffer); + assertSame(buffer, result); + assertEquals(ByteOrder.LITTLE_ENDIAN, result.order()); + } + + /** + * Tests {@link ByteBuffers#littleEndian(ByteBuffer)} when the buffer is already little-endian. + */ + @Test + void testLittleEndianByteBufferAlreadyLittleEndian() { + final ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + final ByteBuffer result = ByteBuffers.littleEndian(buffer); + assertSame(buffer, result); + assertEquals(ByteOrder.LITTLE_ENDIAN, result.order()); + } + + /** + * Tests {@link ByteBuffers#littleEndian(int)} with a positive capacity. + */ + @Test + void testLittleEndianInt() { + final int capacity = 16; + final ByteBuffer buffer = ByteBuffers.littleEndian(capacity); + assertNotNull(buffer); + assertEquals(ByteOrder.LITTLE_ENDIAN, buffer.order()); + assertEquals(capacity, buffer.capacity()); + assertEquals(0, buffer.position()); + assertEquals(capacity, buffer.limit()); + assertEquals(0, buffer.arrayOffset()); + // Each element should be initialized to zero + for (int i = 0; i < capacity; i++) { + assertEquals(0, buffer.get(i)); + } + } + + /** + * Tests that a multi-byte value written to a little-endian buffer has correct byte order. + */ + @Test + void testLittleEndianIntMultibyteValue() { + final ByteBuffer buffer = ByteBuffers.littleEndian(4); + buffer.putInt(0x01020304); + buffer.flip(); + // In little-endian, least significant byte is first + assertEquals((byte) 0x04, buffer.get(0)); + assertEquals((byte) 0x03, buffer.get(1)); + assertEquals((byte) 0x02, buffer.get(2)); + assertEquals((byte) 0x01, buffer.get(3)); + } + + /** + * Tests {@link ByteBuffers#littleEndian(int)} with a negative capacity throws {@link IllegalArgumentException}. + */ + @Test + void testLittleEndianIntNegativeCapacity() { + assertThrows(IllegalArgumentException.class, () -> ByteBuffers.littleEndian(-1)); + } + + /** + * Tests {@link ByteBuffers#littleEndian(int)} with zero capacity. + */ + @Test + void testLittleEndianIntZeroCapacity() { + final ByteBuffer buffer = ByteBuffers.littleEndian(0); + assertNotNull(buffer); + assertEquals(ByteOrder.LITTLE_ENDIAN, buffer.order()); + assertEquals(0, buffer.capacity()); + } + +} diff --git a/src/test/java/org/apache/commons/io/CharsetsTest.java b/src/test/java/org/apache/commons/io/CharsetsTest.java index 7c718c148bf..0b48c68404b 100644 --- a/src/test/java/org/apache/commons/io/CharsetsTest.java +++ b/src/test/java/org/apache/commons/io/CharsetsTest.java @@ -46,6 +46,7 @@ public class CharsetsTest { * For parameterized tests. */ public static final String AVAIL_CHARSETS = "org.apache.commons.io.CharsetsTest#availableCharsetsKeySet"; + /** * For parameterized tests. */ diff --git a/src/test/java/org/apache/commons/io/DirectoryWalkerTest.java b/src/test/java/org/apache/commons/io/DirectoryWalkerTest.java index 57316a32677..deef523cd18 100644 --- a/src/test/java/org/apache/commons/io/DirectoryWalkerTest.java +++ b/src/test/java/org/apache/commons/io/DirectoryWalkerTest.java @@ -85,6 +85,7 @@ protected void handleFile(final File file, final int depth, final Collection { private final String cancelFileName; private final boolean suppressCancel; - private boolean cancelled; + private boolean canceled; public List results; TestMultiThreadCancelWalker(final String cancelFileName, final boolean suppressCancel) { @@ -199,9 +203,9 @@ protected void handleCancelled(final File startDirectory, final Collection @Override protected void handleDirectoryEnd(final File directory, final int depth, final Collection results) throws IOException { results.add(directory); - assertFalse(cancelled); + assertFalse(canceled); if (cancelFileName.equals(directory.getName())) { - cancelled = true; + canceled = true; } } @@ -209,16 +213,16 @@ protected void handleDirectoryEnd(final File directory, final int depth, final C @Override protected void handleFile(final File file, final int depth, final Collection results) throws IOException { results.add(file); - assertFalse(cancelled); + assertFalse(canceled); if (cancelFileName.equals(file.getName())) { - cancelled = true; + canceled = true; } } /** Handles Cancelled. */ @Override protected boolean handleIsCancelled(final File file, final int depth, final Collection results) throws IOException { - return cancelled; + return canceled; } } // Directories diff --git a/src/test/java/org/apache/commons/io/FileCleaningTrackerTest.java b/src/test/java/org/apache/commons/io/FileCleaningTrackerTest.java index 33771fe4554..85bfea1aa00 100644 --- a/src/test/java/org/apache/commons/io/FileCleaningTrackerTest.java +++ b/src/test/java/org/apache/commons/io/FileCleaningTrackerTest.java @@ -54,6 +54,11 @@ RandomAccessFile createRandomAccessFile() throws FileNotFoundException { return RandomAccessFileMode.READ_WRITE.create(testFile); } + private void gcFinalize() { + System.gc(); + System.runFinalization(); + } + protected FileCleaningTracker newInstance() { return new FileCleaningTracker(); } @@ -90,32 +95,30 @@ private String showFailures() { @AfterEach public void tearDown() { - // reset file cleaner class, so as not to break other tests - /** - * The following block of code can possibly be removed when the deprecated {@link FileCleaner} is gone. The - * question is, whether we want to support reuse of {@link FileCleaningTracker} instances, which we should, IMO, - * not. + * The following block of code can possibly be removed when the deprecated {@link FileCleaner} is gone. The question is, whether we want to support + * reuse of {@link FileCleaningTracker} instances, which we should, IMO, not. */ { if (fileCleaningTracker != null) { - fileCleaningTracker.q = new ReferenceQueue<>(); + if (fileCleaningTracker.reaper != null) { + fileCleaningTracker.reaper.interrupt(); + } + fileCleaningTracker.refQueue = new ReferenceQueue<>(); fileCleaningTracker.trackers.clear(); fileCleaningTracker.deleteFailures.clear(); fileCleaningTracker.exitWhenFinished = false; fileCleaningTracker.reaper = null; } } - fileCleaningTracker = null; } @Test void testFileCleanerDirectory_ForceStrategy_FileSource() throws Exception { if (!testFile.getParentFile().exists()) { - throw new IOException("Cannot create file " + testFile - + " as the parent directory does not exist"); + throw new IOException("Cannot create file " + testFile + " as the parent directory does not exist"); } try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(testFile.toPath()))) { @@ -131,7 +134,7 @@ void testFileCleanerDirectory_ForceStrategy_FileSource() throws Exception { obj = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); pauseForDeleteToComplete(testFile.getParentFile()); assertEquals(0, fileCleaningTracker.getTrackCount()); @@ -142,8 +145,7 @@ void testFileCleanerDirectory_ForceStrategy_FileSource() throws Exception { @Test void testFileCleanerDirectory_ForceStrategy_PathSource() throws Exception { if (!Files.exists(testPath.getParent())) { - throw new IOException("Cannot create file " + testPath - + " as the parent directory does not exist"); + throw new IOException("Cannot create file " + testPath + " as the parent directory does not exist"); } try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(testPath))) { @@ -159,7 +161,7 @@ void testFileCleanerDirectory_ForceStrategy_PathSource() throws Exception { obj = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); pauseForDeleteToComplete(testPath.getParent()); assertEquals(0, fileCleaningTracker.getTrackCount()); @@ -180,7 +182,7 @@ void testFileCleanerDirectory_NullStrategy() throws Exception { obj = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); assertEquals(0, fileCleaningTracker.getTrackCount()); assertTrue(testFile.exists()); // not deleted, as dir not empty @@ -200,7 +202,7 @@ void testFileCleanerDirectoryFileSource() throws Exception { obj = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); assertEquals(0, fileCleaningTracker.getTrackCount()); assertTrue(testFile.exists()); // not deleted, as dir not empty @@ -220,7 +222,7 @@ void testFileCleanerDirectoryPathSource() throws Exception { obj = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); assertEquals(0, fileCleaningTracker.getTrackCount()); assertTrue(Files.exists(testPath)); // not deleted, as dir not empty @@ -267,7 +269,7 @@ void testFileCleanerExitWhenFinished1() throws Exception { testFile = null; raf = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); pauseForDeleteToComplete(new File(path)); assertEquals(0, fileCleaningTracker.getTrackCount(), "10-Track Count"); @@ -281,20 +283,20 @@ void testFileCleanerExitWhenFinished2() throws Exception { final String path = testFile.getPath(); assertFalse(testFile.exists()); - RandomAccessFile r = createRandomAccessFile(); + RandomAccessFile raf = createRandomAccessFile(); assertTrue(testFile.exists()); assertEquals(0, fileCleaningTracker.getTrackCount()); - fileCleaningTracker.track(path, r); + fileCleaningTracker.track(path, raf); assertEquals(1, fileCleaningTracker.getTrackCount()); assertFalse(fileCleaningTracker.exitWhenFinished); assertTrue(fileCleaningTracker.reaper.isAlive()); - r.close(); + raf.close(); testFile = null; - r = null; + raf = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); pauseForDeleteToComplete(new File(path)); assertEquals(0, fileCleaningTracker.getTrackCount()); @@ -309,6 +311,7 @@ void testFileCleanerExitWhenFinished2() throws Exception { } assertTrue(fileCleaningTracker.exitWhenFinished); assertFalse(fileCleaningTracker.reaper.isAlive()); + assertFalse(Files.exists(Paths.get(path))); } @Test @@ -318,7 +321,7 @@ void testFileCleanerExitWhenFinishedFirst() throws Exception { assertTrue(fileCleaningTracker.exitWhenFinished); assertNull(fileCleaningTracker.reaper); - waitUntilTrackCount(); + waitUntilTrackCount0(); assertEquals(0, fileCleaningTracker.getTrackCount()); assertTrue(fileCleaningTracker.exitWhenFinished); @@ -341,12 +344,13 @@ void testFileCleanerFile() throws Exception { testFile = null; raf = null; - waitUntilTrackCount(); + waitUntilTrackCount0(); pauseForDeleteToComplete(new File(path)); assertEquals(0, fileCleaningTracker.getTrackCount()); assertFalse(new File(path).exists(), showFailures()); } + @Test void testFileCleanerNull() { assertThrows(NullPointerException.class, () -> fileCleaningTracker.track((File) null, new Object())); @@ -355,7 +359,8 @@ void testFileCleanerNull() { assertThrows(NullPointerException.class, () -> fileCleaningTracker.track((String) null, new Object(), FileDeleteStrategy.NORMAL)); } - private void waitUntilTrackCount() throws Exception { + + private void waitUntilTrackCount0() throws Exception { System.gc(); TestUtils.sleep(500); int count = 0; @@ -365,18 +370,17 @@ private void waitUntilTrackCount() throws Exception { long i = 0; while (fileCleaningTracker.getTrackCount() != 0) { list.add( - "A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String " - + i++); + "A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String " + + i++); } } catch (final Throwable ignored) { } list = null; - System.gc(); + gcFinalize(); TestUtils.sleep(1000); } if (fileCleaningTracker.getTrackCount() != 0) { throw new IllegalStateException("Your JVM is not releasing References, try running the test with less memory (-Xmx)"); } - } -} + } diff --git a/src/test/java/org/apache/commons/io/FileDeleteStrategyTest.java b/src/test/java/org/apache/commons/io/FileDeleteStrategyTest.java index ffdd2f559d4..1371afda7da 100644 --- a/src/test/java/org/apache/commons/io/FileDeleteStrategyTest.java +++ b/src/test/java/org/apache/commons/io/FileDeleteStrategyTest.java @@ -29,6 +29,7 @@ import org.apache.commons.io.test.TestUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; + /** * Tests {@link FileDeleteStrategy}. */ diff --git a/src/test/java/org/apache/commons/io/FileSystemUtilsTest.java b/src/test/java/org/apache/commons/io/FileSystemUtilsTest.java index e45f7a332cd..dabaf100046 100644 --- a/src/test/java/org/apache/commons/io/FileSystemUtilsTest.java +++ b/src/test/java/org/apache/commons/io/FileSystemUtilsTest.java @@ -29,6 +29,8 @@ @SuppressWarnings("deprecation") // testing deprecated class class FileSystemUtilsTest { + private static final String NON_EMPTY_DIR = "src/test/resources/dir-equals-tests"; + static char[] getIllegalFileNameChars() { return FileSystem.getCurrent().getIllegalFileNameChars(); } @@ -50,7 +52,7 @@ void testGetFreeSpace_String() throws Exception { assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpace("this directory does not exist, at all.")); // "" means current dir. assertTrue(FileSystemUtils.freeSpace("") > 0); - assertTrue(FileSystemUtils.freeSpace("target") > 0); + assertTrue(FileSystemUtils.freeSpace(NON_EMPTY_DIR) > 0); // files worked as well in previous versions. assertTrue(FileSystemUtils.freeSpace("pom.xml") > 0); } @@ -71,7 +73,7 @@ void testGetFreeSpaceKb_String() throws Exception { assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpaceKb("this directory does not exist, at all.")); // "" means current dir. assertTrue(FileSystemUtils.freeSpaceKb("") > 0); - assertTrue(FileSystemUtils.freeSpaceKb("target") > 0); + assertTrue(FileSystemUtils.freeSpaceKb(NON_EMPTY_DIR) > 0); // files worked as well in previous versions. assertTrue(FileSystemUtils.freeSpaceKb("pom.xml") > 0); } @@ -82,7 +84,7 @@ void testGetFreeSpaceKb_String_long() throws Exception { assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpaceKb("this directory does not exist, at all.", 0)); // "" means current dir. assertTrue(FileSystemUtils.freeSpaceKb("", 0) > 0); - assertTrue(FileSystemUtils.freeSpaceKb("target", 0) > 0); + assertTrue(FileSystemUtils.freeSpaceKb(NON_EMPTY_DIR, 0) > 0); // files worked as well in previous versions. assertTrue(FileSystemUtils.freeSpaceKb("pom.xml", 0) > 0); } diff --git a/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTest.java b/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTest.java index e3c5a8ccf04..66c4c46415a 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTest.java @@ -68,37 +68,31 @@ private boolean chmod(final File file, final int mode, final boolean recurse) th void testCleanDirectoryToForceDelete() throws Exception { final File file = new File(tempDirFile, "restricted"); FileUtils.touch(file); - // 300 = owner: WE. // 500 = owner: RE. // 700 = owner: RWE. assumeTrue(chmod(tempDirFile, 700, false)); - // cleanDirectory calls forceDelete FileUtils.cleanDirectory(tempDirFile); + assertTrue(tempDirFile.exists()); } @Test void testCleanEmpty() throws Exception { assertEquals(0, tempDirFile.list().length); - FileUtils.cleanDirectory(tempDirFile); - + assertTrue(tempDirFile.exists()); assertEquals(0, tempDirFile.list().length); } @Test void testDeletesNested() throws Exception { final File nested = new File(tempDirFile, "nested"); - assertTrue(nested.mkdirs()); - FileUtils.touch(new File(nested, "file")); - assertEquals(1, tempDirFile.list().length); - FileUtils.cleanDirectory(tempDirFile); - + assertTrue(tempDirFile.exists()); assertEquals(0, tempDirFile.list().length); } @@ -106,21 +100,17 @@ void testDeletesNested() throws Exception { void testDeletesRegular() throws Exception { FileUtils.touch(new File(tempDirFile, "regular")); FileUtils.touch(new File(tempDirFile, ".hidden")); - assertEquals(2, tempDirFile.list().length); - FileUtils.cleanDirectory(tempDirFile); - + assertTrue(tempDirFile.exists()); assertEquals(0, tempDirFile.list().length); } @DisabledOnOs(OS.WINDOWS) @Test void testThrowsOnNullList() throws Exception { - // test won't work if we can't restrict permissions on the - // directory, so skip it. + // test won't work if we can't restrict permissions on the directory, so skip it. assumeTrue(chmod(tempDirFile, 0, false)); - try { // cleanDirectory calls forceDelete final IOException e = assertThrows(IOException.class, () -> FileUtils.cleanDirectory(tempDirFile)); @@ -128,6 +118,7 @@ void testThrowsOnNullList() throws Exception { } finally { chmod(tempDirFile, 755, false); } + assertTrue(tempDirFile.exists()); } } diff --git a/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTest.java b/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTest.java index 1be360e7fa1..54348a441f4 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTest.java @@ -82,6 +82,7 @@ void testCleanDirWithASymlinkDir() throws Exception { // assert contents of the real directory were removed including the symlink FileUtils.cleanDirectory(realOuter); + assertTrue(realOuter.exists()); assertEquals(0, realOuter.list().length); // ensure that the contents of the symlink were NOT removed. @@ -121,6 +122,7 @@ void testCleanDirWithParentSymlinks() throws Exception { // assert contents of the real directory were removed including the symlink // should clean the contents of this but not recurse into other links FileUtils.cleanDirectory(symlinkParentDirectory); + assertTrue(symlinkParentDirectory.exists()); assertEquals(0, symlinkParentDirectory.list().length); assertEquals(0, realParent.list().length); @@ -155,6 +157,7 @@ void testCleanDirWithSymlinkFile() throws Exception { // assert contents of the real directory were removed including the symlink FileUtils.cleanDirectory(realOuter); + assertTrue(realOuter.exists()); assertEquals(0, realOuter.list().length); // ensure that the contents of the symlink were NOT removed. @@ -256,6 +259,7 @@ void testStillClearsIfGivenDirectoryIsASymlink() throws Exception { assertTrue(setupSymlink(randomDirectory, symlinkDirectory)); FileUtils.cleanDirectory(symlinkDirectory); + assertTrue(symlinkDirectory.exists()); assertEquals(0, symlinkDirectory.list().length); assertEquals(0, randomDirectory.list().length); } diff --git a/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTest.java b/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTest.java index 28b27b341cd..13bf4a7e795 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTest.java @@ -17,7 +17,7 @@ package org.apache.commons.io; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.File; import java.io.IOException; @@ -38,17 +38,12 @@ */ class FileUtilsCopyDirectoryToDirectoryTest { - private static void assertExceptionTypeAndMessage(final File srcDir, final File destDir, - final Class expectedExceptionType, final String expectedMessage) { - try { - FileUtils.copyDirectoryToDirectory(srcDir, destDir); - } catch (final Exception e) { - final String msg = e.getMessage(); - assertEquals(expectedExceptionType, e.getClass()); - assertEquals(expectedMessage, msg); - return; - } - fail(); + private static void assertExceptionTypeAndMessage(final File srcDir, final File destDir, final Class expectedExceptionType, + final String expectedMessage) { + final Exception e = assertThrows(Exception.class, () -> FileUtils.copyDirectoryToDirectory(srcDir, destDir)); + final String msg = e.getMessage(); + assertEquals(expectedExceptionType, e.getClass()); + assertEquals(expectedMessage, msg); } /** Temporary folder managed by JUnit. */ diff --git a/src/test/java/org/apache/commons/io/FileUtilsCopyToFileTest.java b/src/test/java/org/apache/commons/io/FileUtilsCopyToFileTest.java index f5d0773fd2f..70f97aa23cc 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsCopyToFileTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsCopyToFileTest.java @@ -70,7 +70,7 @@ public void setUp() throws Exception { /** * Tests that {@code copyInputStreamToFile(InputStream, File)} closes the input stream. * - * @throws IOException + * @throws IOException Thrown on a test failure. * @see FileUtils#copyInputStreamToFile(InputStream, File) * @see FileUtils#copyToFile(InputStream, File) */ @@ -85,7 +85,7 @@ void testCopyInputStreamToFile() throws IOException { /** * Tests that {@code copyToFile(InputStream, File)} does not close the input stream. * - * @throws IOException + * @throws IOException Thrown on a test failure. * @see FileUtils#copyToFile(InputStream, File) * @see FileUtils#copyInputStreamToFile(InputStream, File) */ diff --git a/src/test/java/org/apache/commons/io/FileUtilsDirectoryContainsTest.java b/src/test/java/org/apache/commons/io/FileUtilsDirectoryContainsTest.java index b2574587719..4da1d567783 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsDirectoryContainsTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsDirectoryContainsTest.java @@ -127,8 +127,9 @@ void testFileDoesNotExist() throws IOException { } /** - * Test to demonstrate a file which does not exist returns false - * @throws IOException If an I/O error occurs + * Test to demonstrate a file which does not exist returns false. + * + * @throws IOException If an I/O error occurs. */ @Test void testFileDoesNotExistBug() throws IOException { diff --git a/src/test/java/org/apache/commons/io/FileUtilsFileNewerTest.java b/src/test/java/org/apache/commons/io/FileUtilsFileNewerTest.java index 67ee8e2fd2c..6905cecdc33 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsFileNewerTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsFileNewerTest.java @@ -68,7 +68,7 @@ public void setUp() throws Exception { /** * Tests the {@code isFileNewer(File, *)} methods which a "normal" file. * - * @throws IOException + * @throws IOException Thrown on a test failure. * @see FileUtils#isFileNewer(File, long) * @see FileUtils#isFileNewer(File, Date) * @see FileUtils#isFileNewer(File, File) @@ -100,10 +100,10 @@ void testIsFileNewer() throws IOException { *

    * The test is successful if the three comparisons return the specified wanted result. * - * @param description describes the tested situation - * @param file the file of which the last modification date is compared - * @param fileTime the time reference measured in milliseconds since the epoch - * @param wantedResult the expected result + * @param description describes the tested situation. + * @param file the file of which the last modification date is compared. + * @param fileTime the time reference measured in milliseconds since the epoch. + * @param wantedResult the expected result. * @throws IOException if an I/O error occurs. */ protected void testIsFileNewer(final String description, final File file, final FileTime fileTime, final boolean wantedResult) throws IOException { diff --git a/src/test/java/org/apache/commons/io/FileUtilsTest.java b/src/test/java/org/apache/commons/io/FileUtilsTest.java index 4f6efa0f946..81b0eaf2498 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsTest.java @@ -52,6 +52,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.AclFileAttributeView; +import java.nio.file.attribute.DosFileAttributeView; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; @@ -92,18 +93,18 @@ import org.apache.commons.lang3.SystemProperties; import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; /** - * Tests {@link FileUtils}. + * Tests {@link FileUtils} including deprecated methods. */ @SuppressWarnings({"deprecation", "ResultOfMethodCallIgnored"}) // unit tests include tests of many deprecated methods class FileUtilsTest extends AbstractTempDirTest { @@ -169,16 +170,23 @@ List list(final File startDirectory) throws IOException { */ private static final ListDirectoryWalker LIST_WALKER = new ListDirectoryWalker(); + private static void setDosReadOnly(final Path p, final boolean readOnly) throws IOException { + if (Files.getFileStore(p).supportsFileAttributeView(DosFileAttributeView.class)) { + Files.setAttribute(p, "dos:readonly", readOnly, LinkOption.NOFOLLOW_LINKS); + } + } + private File testFile1; + private File testFile2; + private long testFile1Size; + private long testFile2Size; private void assertContentMatchesAfterCopyURLToFileFor(final String resourceName, final File destination) throws IOException { FileUtils.copyURLToFile(getClass().getResource(resourceName), destination); - - try (InputStream fis = Files.newInputStream(destination.toPath()); - InputStream expected = getClass().getResourceAsStream(resourceName)) { + try (InputStream fis = Files.newInputStream(destination.toPath()); InputStream expected = getClass().getResourceAsStream(resourceName)) { assertTrue(IOUtils.contentEquals(expected, fis), "Content is not equal."); } } @@ -205,9 +213,11 @@ private Path createCircularOsSymbolicLink(final String linkName, final String ta /** * May throw java.nio.file.FileSystemException: C:\Users\...\FileUtilsTestCase\cycle: A required privilege is not held - * by the client. On Windows, you are fine if you run a terminal with admin karma. + * by the client. On Windows, you are fine if you run a terminal with administrator permissions. + * + * @return The link path. */ - private void createCircularSymbolicLink(final File file) throws IOException { + private Path createCircularSymbolicLink(final File file) throws IOException { assertTrue(file.exists()); final String linkName = file + "/cycle"; final String targetName = file + "/.."; @@ -218,13 +228,14 @@ private void createCircularSymbolicLink(final File file) throws IOException { assertTrue(Files.exists(targetPath)); try { // May throw java.nio.file.FileSystemException: C:\Users\...\FileUtilsTestCase\cycle: A required privilege is not held by the client. - // On Windows, you are fine if you run a terminal with admin karma. + // On Windows, you are fine if you run a terminal with administrator permissions. Files.createSymbolicLink(linkPath, targetPath); } catch (final UnsupportedOperationException e) { createCircularOsSymbolicLink(linkName, targetName); } // Sanity check: assertTrue(Files.isSymbolicLink(linkPath), () -> "Expected a symbolic link here: " + linkName); + return linkPath; } private void createFilesForTestCopyDirectory(final File grandParentDir, final File parentDir, final File childDir) throws IOException { @@ -616,9 +627,8 @@ void testContentEquals() throws Exception { assertTrue(FileUtils.contentEquals(objFile1, objFile1)); assertTrue(FileUtils.contentEquals(objFile1b, objFile1b)); assertTrue(FileUtils.contentEquals(objFile2, objFile2)); - // Equal files - file.createNewFile(); - file2.createNewFile(); + assertCreateNewFile(file); + assertCreateNewFile(file2); assertTrue(FileUtils.contentEquals(file, file)); assertTrue(FileUtils.contentEquals(file, file2)); } @@ -659,17 +669,17 @@ void testContentEqualsIgnoreEOL() throws Exception { assertFalse(FileUtils.contentEqualsIgnoreEOL(tfile1, tfile3, null)); assertFalse(FileUtils.contentEqualsIgnoreEOL(tfile2, tfile3, null)); - final URL urlCR = getClass().getResource("FileUtilsTestDataCR.dat"); + final URL urlCR = getClass().getResource("FileUtilsTestDataCR.bin"); assertNotNull(urlCR); final File cr = new File(urlCR.toURI()); assertTrue(cr.exists()); - final URL urlCRLF = getClass().getResource("FileUtilsTestDataCRLF.dat"); + final URL urlCRLF = getClass().getResource("FileUtilsTestDataCRLF.bin"); assertNotNull(urlCRLF); final File crlf = new File(urlCRLF.toURI()); assertTrue(crlf.exists()); - final URL urlLF = getClass().getResource("FileUtilsTestDataLF.dat"); + final URL urlLF = getClass().getResource("FileUtilsTestDataLF.bin"); assertNotNull(urlLF); final File lf = new File(urlLF.toURI()); assertTrue(lf.exists()); @@ -691,9 +701,8 @@ void testContentEqualsIgnoreEOL() throws Exception { assertFalse(FileUtils.contentEquals(cr, lf)); assertFalse(FileUtils.contentEquals(crlf, lf)); - // Equal files - file1.createNewFile(); - file2.createNewFile(); + assertCreateNewFile(file1); + assertCreateNewFile(file2); assertTrue(FileUtils.contentEqualsIgnoreEOL(file1, file1, null)); assertTrue(FileUtils.contentEqualsIgnoreEOL(file1, file2, null)); } @@ -709,7 +718,7 @@ void testContentEqualsIgnoreEOL() throws Exception { void testCopyDir_SymbolicLink() throws Exception { // Make a directory final File realDirectory = new File(tempDirFile, "real_directory"); - realDirectory.mkdir(); + assertMkdir(true, realDirectory); final File content = new File(realDirectory, "hello.txt"); FileUtils.writeStringToFile(content, "HELLO WORLD", "UTF8"); @@ -737,11 +746,11 @@ void testCopyDir_SymbolicLink() throws Exception { void testCopyDir_SymbolicLinkCycle() throws Exception { // Make a directory final File topDirectory = new File(tempDirFile, "topDirectory"); - topDirectory.mkdir(); + assertMkdir(true, topDirectory); final File content = new File(topDirectory, "hello.txt"); FileUtils.writeStringToFile(content, "HELLO WORLD", "UTF8"); final File childDirectory = new File(topDirectory, "child_directory"); - childDirectory.mkdir(); + assertMkdir(true, childDirectory); // Make a symlink to the top directory final Path linkPath = childDirectory.toPath().resolve("link_to_top"); @@ -771,7 +780,7 @@ void testCopyDir_SymbolicLinkCycle() throws Exception { void testCopyDirectory_brokenSymbolicLink() throws IOException { // Make a file final File sourceDirectory = new File(tempDirFile, "source_directory"); - sourceDirectory.mkdir(); + assertMkdir(true, sourceDirectory); final File targetFile = new File(sourceDirectory, "hello.txt"); FileUtils.writeStringToFile(targetFile, "HELLO WORLD", "UTF8"); @@ -806,7 +815,7 @@ void testCopyDirectory_brokenSymbolicLink() throws IOException { void testCopyDirectory_SymbolicLink() throws IOException { // Make a file final File sourceDirectory = new File(tempDirFile, "source_directory"); - sourceDirectory.mkdir(); + assertMkdir(true, sourceDirectory); final File targetFile = new File(sourceDirectory, "hello.txt"); FileUtils.writeStringToFile(targetFile, "HELLO WORLD", "UTF8"); @@ -841,7 +850,7 @@ void testCopyDirectory_SymbolicLinkExternalFile() throws Exception { // Make a directory final File realDirectory = new File(tempDirFile, "real_directory"); - realDirectory.mkdir(); + assertMkdir(true, realDirectory); // Make a symlink to the file final Path linkPath = realDirectory.toPath().resolve("link_to_file"); @@ -905,8 +914,8 @@ void testCopyDirectoryPreserveDates() throws Exception { final File sourceFile = new File(sourceDirectory, "hello.txt"); // Prepare source data - source.mkdirs(); - sourceDirectory.mkdir(); + assertMkdir(true, source); + assertMkdir(true, sourceDirectory); FileUtils.writeStringToFile(sourceFile, "HELLO WORLD", "UTF8"); // Set dates in reverse order to avoid overwriting previous values // Also, use full seconds (arguments are in ms) close to today @@ -915,7 +924,7 @@ void testCopyDirectoryPreserveDates() throws Exception { assertTrue(setLastModifiedMillis(sourceDirectory, DATE2)); assertTrue(setLastModifiedMillis(source, DATE1)); - final File target = new File(tempDirFile, "target"); + final File target = new File(tempDirFile, "dest"); final File targetDirectory = new File(target, "directory"); final File targetFile = new File(targetDirectory, "hello.txt"); @@ -1000,7 +1009,7 @@ void testCopyDirectoryToDirectory_NonExistingDest() throws Exception { } final File srcDir = tempDirFile; final File subDir = new File(srcDir, "sub"); - subDir.mkdir(); + assertMkdir(true, subDir); final File subFile = new File(subDir, "A.txt"); FileUtils.writeStringToFile(subFile, "HELLO WORLD", "UTF8"); final File destDir = new File(FileUtils.getTempDirectoryPath(), "tmp-FileUtilsTestCase"); @@ -1036,7 +1045,7 @@ void testCopyDirectoryToExistingDest() throws Exception { } final File srcDir = tempDirFile; final File subDir = new File(srcDir, "sub"); - subDir.mkdir(); + assertMkdir(true, subDir); final File subFile = new File(subDir, "A.txt"); FileUtils.writeStringToFile(subFile, "HELLO WORLD", "UTF8"); final File destDir = new File(SystemProperties.getJavaIoTmpdir(), "tmp-FileUtilsTestCase"); @@ -1094,7 +1103,7 @@ void testCopyDirectoryToNonExistingDest() throws Exception { } final File srcDir = tempDirFile; final File subDir = new File(srcDir, "sub"); - subDir.mkdir(); + assertMkdir(true, subDir); final File subFile = new File(subDir, "A.txt"); FileUtils.writeStringToFile(subFile, "HELLO WORLD", "UTF8"); final File destDir = new File(FileUtils.getTempDirectoryPath(), "tmp-FileUtilsTestCase"); @@ -1142,7 +1151,7 @@ void testCopyDirectoryWithPotentialFalsePartialMatch() throws IOException { void testCopyFile_SymbolicLink() throws Exception { // Make a file final File sourceDirectory = new File(tempDirFile, "source_directory"); - sourceDirectory.mkdir(); + assertMkdir(true, sourceDirectory); final File targetFile = new File(sourceDirectory, "hello.txt"); FileUtils.writeStringToFile(targetFile, "HELLO WORLD", "UTF8"); @@ -1528,6 +1537,7 @@ void testDeleteDirectorySymbolicLinkAbsent() throws IOException { void testDeleteDirectorySymbolicLinkAbsentDeepTarget() throws IOException { final ImmutablePair pair = createTempSymbolicLinkedRelativeDir(); final Path symLinkedDir = pair.getLeft(); + assertNotNull(symLinkedDir); final Path targetDir = pair.getRight(); // more setup final Path targetDir2 = targetDir.resolve("subdir2"); @@ -1640,7 +1650,7 @@ void testFileUtils() throws Exception { @Test void testForceDeleteAFile1() throws Exception { final File destination = new File(tempDirFile, "copy1.txt"); - destination.createNewFile(); + assertCreateNewFile(destination); assertTrue(destination.exists(), "Copy1.txt doesn't exist to delete"); FileUtils.forceDelete(destination); assertFalse(destination.exists(), "Check No Exist"); @@ -1649,7 +1659,7 @@ void testForceDeleteAFile1() throws Exception { @Test void testForceDeleteAFile2() throws Exception { final File destination = new File(tempDirFile, "copy2.txt"); - destination.createNewFile(); + assertCreateNewFile(destination); assertTrue(destination.exists(), "Copy2.txt doesn't exist to delete"); FileUtils.forceDelete(destination); assertFalse(destination.exists(), "Check No Exist"); @@ -1664,7 +1674,7 @@ void testForceDeleteAFileDoesNotExist() { } @Test - public void testForceDeleteBrokenSymlink() throws Exception { + void testForceDeleteBrokenSymlink() throws Exception { final ImmutablePair pair = createTempSymbolicLinkedRelativeDir(); final Path symlinkedDir = pair.getLeft(); final Path targetDir = pair.getRight(); @@ -1735,26 +1745,30 @@ void testForceDeleteReadOnlyDirectory() throws Exception { void testForceDeleteReadOnlyFile() throws Exception { try (TempFile destination = TempFile.create("test-", ".txt")) { final File file = destination.toFile(); - assertTrue(file.setReadOnly()); - assertTrue(file.canRead()); - assertFalse(file.canWrite()); - // sanity check that File.delete() deletes read-only files. - assertTrue(file.delete()); + assertTrue(file.setReadOnly(), "Setting file read-only successful"); + assertTrue(file.canRead(), "File must be readable"); + assertFalse(file.canWrite(), "File must not be writable"); + assertTrue(file.exists(), "File doesn't exist to delete"); + // Since JDK 25 on Windows, File.delete() refuses to remove files + // with the DOS readonly bit set (JDK-8355954). + // We clear the bit here for consistency across JDK versions. + setDosReadOnly(file.toPath(), false); + assertTrue(file.delete(), "File.delete() must delete read-only file"); } try (TempFile destination = TempFile.create("test-", ".txt")) { final File file = destination.toFile(); // real test - assertTrue(file.setReadOnly()); - assertTrue(file.canRead()); - assertFalse(file.canWrite()); + assertTrue(file.setReadOnly(), "Setting file read-only successful"); + assertTrue(file.canRead(), "File must be readable"); + assertFalse(file.canWrite(), "File must not be writable"); assertTrue(file.exists(), "File doesn't exist to delete"); FileUtils.forceDelete(file); - assertFalse(file.exists(), "Check deletion"); + assertFalse(file.exists(), "FileUtils.forceDelete() must delete read-only file"); } } @Test - public void testForceDeleteSymlink() throws Exception { + void testForceDeleteSymlink() throws Exception { final ImmutablePair pair = createTempSymbolicLinkedRelativeDir(); final Path symlinkedDir = pair.getLeft(); final Path targetDir = pair.getRight(); @@ -1823,23 +1837,27 @@ void testForceDeleteUnwritableDirectory() throws Exception { void testForceDeleteUnwritableFile() throws Exception { try (TempFile destination = TempFile.create("test-", ".txt")) { final File file = destination.toFile(); - assertTrue(file.canWrite()); - assertTrue(file.setWritable(false)); - assertFalse(file.canWrite()); - assertTrue(file.canRead()); - // sanity check that File.delete() deletes unwritable files. - assertTrue(file.delete()); + assertTrue(file.canWrite(), "File must be writable"); + assertTrue(file.setWritable(false), "Setting file unwritable successful"); + assertFalse(file.canWrite(), "File must not be writable"); + assertTrue(file.canRead(), "File must be readable"); + assertTrue(file.exists(), "File must exist to delete"); + // Since JDK 25 on Windows, File.delete() refuses to remove files + // with the DOS readonly bit set (JDK-8355954). + // We clear the bit here for consistency across JDK versions. + setDosReadOnly(file.toPath(), false); + assertTrue(file.delete(), "File.delete() must delete unwritable file"); } try (TempFile destination = TempFile.create("test-", ".txt")) { final File file = destination.toFile(); // real test - assertTrue(file.canWrite()); - assertTrue(file.setWritable(false)); - assertFalse(file.canWrite()); - assertTrue(file.canRead()); - assertTrue(file.exists(), "File doesn't exist to delete"); + assertTrue(file.canWrite(), "File must be writable"); + assertTrue(file.setWritable(false), "Setting file unwritable successful"); + assertFalse(file.canWrite(), "File must not be writable"); + assertTrue(file.canRead(), "File must be readable"); + assertTrue(file.exists(), "File must exist to delete"); FileUtils.forceDelete(file); - assertFalse(file.exists(), "Check deletion"); + assertFalse(file.exists(), "FileUtils.forceDelete() must delete unwritable file"); } } @@ -1850,13 +1868,13 @@ void testForceMkdir() throws Exception { // Creates test file final File testFile = new File(tempDirFile, getName()); - testFile.createNewFile(); + assertCreateNewFile(testFile); assertTrue(testFile.exists(), "Test file does not exist."); // Tests with existing file assertThrows(IOException.class, () -> FileUtils.forceMkdir(testFile)); - testFile.delete(); + assertDelete(true, testFile); // Tests with non-existent directory FileUtils.forceMkdir(testFile); @@ -1871,7 +1889,7 @@ void testForceMkdirParent() throws Exception { // Tests with existing directory assertTrue(tempDirFile.exists()); final File testParentDir = new File(tempDirFile, "testForceMkdirParent"); - testParentDir.delete(); + assertDelete(false, testParentDir); assertFalse(testParentDir.exists()); final File testFile = new File(testParentDir, "test.txt"); assertFalse(testParentDir.exists()); @@ -1934,8 +1952,8 @@ void testGetUserDirectoryPath() { } @Test - void testIO276() throws Exception { - final File dir = new File("target", "IO276"); + void testIO276(@TempDir final File dest) throws Exception { + final File dir = new File(dest, "IO276"); Files.deleteIfExists(dir.toPath()); assertTrue(dir.mkdirs(), dir + " should not be present"); final File file = new File(dir, "IO276.txt"); @@ -1996,9 +2014,9 @@ void testIsDirectory() throws IOException { void testIsEmptyDirectory() throws IOException { try (TempDirectory tempDir = TempDirectory.create(getClass().getCanonicalName())) { final File tempDirAsFile = tempDir.toFile(); - Assertions.assertTrue(FileUtils.isEmptyDirectory(tempDirAsFile)); + assertTrue(FileUtils.isEmptyDirectory(tempDirAsFile)); } - Assertions.assertFalse(FileUtils.isEmptyDirectory(DIR_SIZE_1.toFile())); + assertFalse(FileUtils.isEmptyDirectory(DIR_SIZE_1.toFile())); } @ParameterizedTest @@ -2158,9 +2176,9 @@ void testIterateFiles() throws Exception { final File subDir = new File(srcDir, "list_test"); final File subSubDir = new File(subDir, "subSubDir"); final File notSubSubDir = new File(subDir, "notSubSubDir"); - assertTrue(subDir.mkdir()); - assertTrue(subSubDir.mkdir()); - assertTrue(notSubSubDir.mkdir()); + assertMkdir(true, subDir); + assertMkdir(true, subSubDir); + assertMkdir(true, notSubSubDir); Iterator iterator = null; try { // Need list to be appendable @@ -2206,9 +2224,9 @@ void testIterateFiles() throws Exception { assertEquals(expectedFileNames, actualFileNames); } finally { consumeRemaining(iterator); - notSubSubDir.delete(); - subSubDir.delete(); - subDir.delete(); + assertDelete(false, notSubSubDir); + assertDelete(false, subSubDir); + assertDelete(false, subDir); } } @@ -2225,10 +2243,10 @@ void testIterateFilesAndDirs() throws IOException { final File subDir2 = new File(subDir1, "subdir2"); final File subDir3 = new File(subDir2, "subdir3"); final File subDir4 = new File(subDir2, "subdir4"); - assertTrue(subDir1.mkdir()); - assertTrue(subDir2.mkdir()); - assertTrue(subDir3.mkdir()); - assertTrue(subDir4.mkdir()); + assertMkdir(true, subDir1); + assertMkdir(true, subDir2); + assertMkdir(true, subDir3); + assertMkdir(true, subDir4); final File someFile = new File(subDir2, "a.txt"); final WildcardFileFilter fileFilterAllFiles = WildcardFileFilter.builder().setWildcards("*.*").get(); final WildcardFileFilter fileFilterAllDirs = WildcardFileFilter.builder().setWildcards("*").get(); @@ -2257,7 +2275,7 @@ void testIterateFilesAndDirs() throws IOException { @Test void testIterateFilesOnlyNoDirs() throws IOException { final File directory = tempDirFile; - assertTrue(new File(directory, "TEST").mkdir()); + assertMkdir(true, new File(directory, "TEST")); assertTrue(new File(directory, "test.txt").createNewFile()); final IOFileFilter filter = WildcardFileFilter.builder().setWildcards("*").setIoCase(IOCase.INSENSITIVE).get(); @@ -2269,8 +2287,8 @@ void testListFiles() throws Exception { final File srcDir = tempDirFile; final File subDir = new File(srcDir, "list_test"); final File subDir2 = new File(subDir, "subdir"); - subDir.mkdir(); - subDir2.mkdir(); + assertMkdir(true, subDir); + assertMkdir(true, subDir2); final String[] expectedFileNames = { "a.txt", "b.txt", "c.txt", "d.txt", "e.txt", "f.txt" }; final int[] fileSizes = { 123, 234, 345, 456, 678, 789 }; for (int i = 0; i < expectedFileNames.length; ++i) { @@ -2306,7 +2324,7 @@ void testListFiles() throws Exception { @Test void testListFilesOnlyNoDirs() throws IOException { final File directory = tempDirFile; - assertTrue(new File(directory, "TEST").mkdir()); + assertMkdir(true, new File(directory, "TEST")); assertTrue(new File(directory, "test.txt").createNewFile()); final IOFileFilter filter = WildcardFileFilter.builder().setWildcards("*").setIoCase(IOCase.INSENSITIVE).get(); @@ -2320,8 +2338,8 @@ void testListFilesWithDirs() throws IOException { final File srcDir = tempDirFile; final File subDir1 = new File(srcDir, "subdir"); final File subDir2 = new File(subDir1, "subdir2"); - subDir1.mkdir(); - subDir2.mkdir(); + assertMkdir(true, subDir1); + assertMkdir(true, subDir2); final File someFile = new File(subDir2, "a.txt"); if (!someFile.getParentFile().exists()) { fail("Cannot create file " + someFile + " as the parent directory does not exist"); @@ -2330,7 +2348,7 @@ void testListFilesWithDirs() throws IOException { TestUtils.generateTestData(output, 100); } final File subDir3 = new File(subDir2, "subdir3"); - subDir3.mkdir(); + assertMkdir(true, subDir3); // @formatter:off final Collection files = FileUtils.listFilesAndDirs(subDir1, WildcardFileFilter.builder().setWildcards("*.*").get(), @@ -2397,8 +2415,8 @@ void testMoveDirectory_Errors() throws Exception { assertThrows(IllegalArgumentException.class, () -> FileUtils.moveDirectory(testFile, new File("foo"))); final File testSrcFile = new File(tempDirFile, "testMoveDirectorySource"); final File testDestFile = new File(tempDirFile, "testMoveDirectoryDest"); - testSrcFile.mkdir(); - testDestFile.mkdir(); + assertMkdir(true, testSrcFile); + assertMkdir(true, testDestFile); assertThrows(FileExistsException.class, () -> FileUtils.moveDirectory(testSrcFile, testDestFile), "Expected FileExistsException when dest already exists"); @@ -2917,11 +2935,10 @@ void testSizeOf() throws Exception { assertThrows(NullPointerException.class, () -> FileUtils.sizeOf(null)); // Non-existent file assertThrows(IllegalArgumentException.class, () -> FileUtils.sizeOf(file)); - // Creates file - file.createNewFile(); + assertCreateNewFile(file); // New file assertEquals(0, FileUtils.sizeOf(file)); - file.delete(); + assertDelete(true, file); // Existing file assertEquals(testFile1Size, FileUtils.sizeOf(testFile1), "Unexpected files size"); // Existing directory @@ -2935,11 +2952,10 @@ void testSizeOfAsBigInteger() throws Exception { assertThrows(NullPointerException.class, () -> FileUtils.sizeOfAsBigInteger(null)); // Non-existent file assertThrows(IllegalArgumentException.class, () -> FileUtils.sizeOfAsBigInteger(file)); - // Creates file - file.createNewFile(); + assertCreateNewFile(file); // New file assertEquals(BigInteger.ZERO, FileUtils.sizeOfAsBigInteger(file)); - file.delete(); + assertDelete(true, file); // Existing file assertEquals(BigInteger.valueOf(testFile1Size), FileUtils.sizeOfAsBigInteger(testFile1), "Unexpected files size"); // Existing directory @@ -2961,15 +2977,19 @@ void testSizeOfDirectory() throws Exception { // Non-existent file assertThrows(IllegalArgumentException.class, () -> FileUtils.sizeOfAsBigInteger(file)); // Creates file - file.createNewFile(); + assertCreateNewFile(file); // Existing file assertThrows(IllegalArgumentException.class, () -> FileUtils.sizeOfDirectory(file)); - // Existing directory - file.delete(); - file.mkdir(); + assertDelete(true, file); + assertMkdir(true, file); // Create a cyclic symlink - createCircularSymbolicLink(file); - assertEquals(TEST_DIRECTORY_SIZE, FileUtils.sizeOfDirectory(file), "Unexpected directory size"); + final Path linkPath = createCircularSymbolicLink(file); + try { + // Different result value depending on the Java version and operating system, but should not throw an exception or loop infinitely. + FileUtils.sizeOfDirectory(file); + } finally { + Files.deleteIfExists(linkPath); + } } /** @@ -2986,29 +3006,32 @@ void testSizeOfDirectoryAsBigInteger() throws Exception { assertThrows(NullPointerException.class, () -> FileUtils.sizeOfDirectoryAsBigInteger(null)); // Non-existent file assertThrows(UncheckedIOException.class, () -> FileUtils.sizeOfDirectoryAsBigInteger(file)); - // Creates file - file.createNewFile(); + assertCreateNewFile(file); // Existing file assertThrows(IllegalArgumentException.class, () -> FileUtils.sizeOfDirectoryAsBigInteger(file)); - // Existing directory - file.delete(); - file.mkdir(); - createCircularSymbolicLink(file); - assertEquals(TEST_DIRECTORY_SIZE_BI, FileUtils.sizeOfDirectoryAsBigInteger(file), "Unexpected directory size"); - // Existing directory which size is greater than zero - file.delete(); - file.mkdir(); - final File nonEmptyFile = new File(file, "non-emptyFile" + System.nanoTime()); - assertTrue(nonEmptyFile.getParentFile().exists(), () -> "Cannot create file " + nonEmptyFile + " as the parent directory does not exist"); - final OutputStream output = new BufferedOutputStream(Files.newOutputStream(nonEmptyFile.toPath())); + assertDelete(true, file); + assertMkdir(true, file); + final Path linkPath = createCircularSymbolicLink(file); try { - TestUtils.generateTestData(output, TEST_DIRECTORY_SIZE_GT_ZERO_BI.longValue()); + // Different result value depending on the Java version and operating system, but should not throw an exception or loop infinitely. + FileUtils.sizeOfDirectoryAsBigInteger(file); + assertDelete(false, file); + assertMkdir(false, file); + final File nonEmptyFile = new File(file, "non-emptyFile" + System.nanoTime()); + assertTrue(nonEmptyFile.getParentFile().exists(), () -> "Cannot create file " + nonEmptyFile + " as the parent directory does not exist"); + final OutputStream output = new BufferedOutputStream(Files.newOutputStream(nonEmptyFile.toPath())); + try { + TestUtils.generateTestData(output, TEST_DIRECTORY_SIZE_GT_ZERO_BI.longValue()); + } finally { + IOUtils.closeQuietly(output); + } + // Different result value depending on the Java version and operating system, but should not throw an exception or loop infinitely. + FileUtils.sizeOfDirectoryAsBigInteger(file); + assertDelete(true, nonEmptyFile); + assertDelete(false, file); } finally { - IOUtils.closeQuietly(output); + Files.deleteIfExists(linkPath); } - assertEquals(TEST_DIRECTORY_SIZE_GT_ZERO_BI, FileUtils.sizeOfDirectoryAsBigInteger(file), "Unexpected directory size"); - nonEmptyFile.delete(); - file.delete(); } @Test @@ -3109,7 +3132,7 @@ void testTouch() throws IOException { assertThrows(NullPointerException.class, () -> FileUtils.touch(null)); final File file = new File(tempDirFile, "touch.txt"); if (file.exists()) { - file.delete(); + assertDelete(true, file); } assertFalse(file.exists(), "Bad test: test file still exists"); FileUtils.touch(file); @@ -3134,10 +3157,10 @@ void testTouch() throws IOException { @Test void testTouchDirDoesNotExist() throws Exception { - final File file = new File("target/does-not-exist", "touchme.txt"); + final File file = new File(new File(tempDirFile, "dir-touch"), "touchme.txt"); final File parentDir = file.getParentFile(); - file.delete(); - parentDir.delete(); + assertDelete(false, file); + assertDelete(false, parentDir); assertFalse(parentDir.exists()); assertFalse(file.exists()); FileUtils.touch(file); diff --git a/src/test/java/org/apache/commons/io/FileUtilsWaitForTest.java b/src/test/java/org/apache/commons/io/FileUtilsWaitForTest.java index 97170323ecf..dde56e3033c 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsWaitForTest.java +++ b/src/test/java/org/apache/commons/io/FileUtilsWaitForTest.java @@ -48,7 +48,7 @@ void testIO_488() throws InterruptedException { // This will wait (assuming the file is not found) assertFalse(FileUtils.waitFor(NOSUCHFILE, seconds), "Should not find file"); wasInterrupted.set(Thread.currentThread().isInterrupted()); - }); + }, "commons-io-Test-IO-488-Thread"); thread1.start(); Thread.sleep(500); // This should be enough to ensure the waitFor loop has been entered thread1.interrupt(); // Try to interrupt waitFor diff --git a/src/test/java/org/apache/commons/io/FilenameUtilsTest.java b/src/test/java/org/apache/commons/io/FilenameUtilsTest.java index 0441c6ac2e6..9250e810bc6 100644 --- a/src/test/java/org/apache/commons/io/FilenameUtilsTest.java +++ b/src/test/java/org/apache/commons/io/FilenameUtilsTest.java @@ -34,6 +34,7 @@ import org.apache.commons.io.test.TestUtils; import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -349,20 +350,38 @@ void testGetFullPathNoEndSeparator() { */ @Test void testGetFullPathNoEndSeparator_IO_248() { - // Test single separator assertEquals("/", FilenameUtils.getFullPathNoEndSeparator("/")); assertEquals("\\", FilenameUtils.getFullPathNoEndSeparator("\\")); - // Test one level directory assertEquals("/", FilenameUtils.getFullPathNoEndSeparator("/abc")); assertEquals("\\", FilenameUtils.getFullPathNoEndSeparator("\\abc")); - // Test one level directory assertEquals("/abc", FilenameUtils.getFullPathNoEndSeparator("/abc/xyz")); assertEquals("\\abc", FilenameUtils.getFullPathNoEndSeparator("\\abc\\xyz")); } + /** + * Test for https://issues.apache.org/jira/browse/IO-771 + */ + @Test + @Disabled + void testGetFullPathNoEndSeparator_IO_771() { + // IO-771 + // On macOS while in the target folder in jshell: + // new java.io.File("X:\\path\\subfolder").getAbsolutePath() + // ==> "/Users/garygregory/git/commons/commons-lang/target/X:\\path\\subfolder" + assertEquals("/Users/garygregory/git/commons/commons-lang/target/X:\\path", + FilenameUtils.getFullPathNoEndSeparator("/Users/garygregory/git/commons/commons-lang/target/X:\\path\\subfolder")); + assertEquals("X:\\path", FilenameUtils.getFullPathNoEndSeparator("X:\\path\\subfolder")); + assertEquals("/Users/garygregory/git/commons/commons-lang/target/X:\\\\path", + FilenameUtils.getFullPathNoEndSeparator("/Users/garygregory/git/commons/commons-lang/target/X:\\\\path\\\\subfolder")); + assertEquals("/Users/garygregory/git/commons/commons-lang/target/X:\\\\\\path", + FilenameUtils.getFullPathNoEndSeparator("/Users/garygregory/git/commons/commons-lang/target/X:\\\\path\\\\\\subfolder\\\\")); + assertEquals("/Users/garygregory/git/commons/commons-lang/target/X:\\\\\\path", + FilenameUtils.getFullPathNoEndSeparator("/Users/garygregory/git/commons/commons-lang/target/X:\\\\path\\\\\\subfolder")); + } + @Test void testGetName() { assertNull(FilenameUtils.getName(null)); @@ -935,7 +954,7 @@ void testNormalize_with_null_character() { } @Test - void testNormalizeFromJavaDoc() { + void testNormalizeFromJavadoc() { // Examples from Javadoc assertEquals(SEP + "foo" + SEP, FilenameUtils.normalize("/foo//")); assertEquals(SEP + "foo" + SEP, FilenameUtils.normalize(SEP + "foo" + SEP + "." + SEP)); diff --git a/src/test/java/org/apache/commons/io/IOCaseTest.java b/src/test/java/org/apache/commons/io/IOCaseTest.java index 97a990f6369..54073da8096 100644 --- a/src/test/java/org/apache/commons/io/IOCaseTest.java +++ b/src/test/java/org/apache/commons/io/IOCaseTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,6 +30,8 @@ import java.io.ObjectOutputStream; import java.util.Arrays; +import org.apache.commons.io.IOUtils.ScratchBytes; +import org.apache.commons.io.IOUtils.ScratchChars; import org.junit.jupiter.api.Test; /** @@ -296,34 +299,49 @@ void test_getName() { @Test void test_getScratchByteArray() { - final byte[] array = IOUtils.getScratchByteArray(); - assert0(array); - Arrays.fill(array, (byte) 1); - assert0(IOUtils.getScratchCharArray()); - } - - @Test - void test_getScratchByteArrayWriteOnly() { - final byte[] array = IOUtils.getScratchByteArrayWriteOnly(); - assert0(array); - Arrays.fill(array, (byte) 1); - assert0(IOUtils.getScratchCharArray()); + final byte[] array; + try (ScratchBytes scratch = IOUtils.ScratchBytes.get()) { + array = scratch.array(); + assert0(array); + Arrays.fill(array, (byte) 1); + // Get another array, while the first is still in use + // The test doesn't need the try here but that's the pattern. + try (ScratchBytes scratch2 = IOUtils.ScratchBytes.get()) { + assertNotSame(scratch, scratch2); + final byte[] array2 = scratch2.array(); + assert0(array2); + assertNotSame(array, array2); + } + } + // The first array should be reset and reusable + try (ScratchBytes scratch = IOUtils.ScratchBytes.get()) { + final byte[] array3 = scratch.array(); + assert0(array3); + assertSame(array, array3); + } } @Test void test_getScratchCharArray() { - final char[] array = IOUtils.getScratchCharArray(); - assert0(array); - Arrays.fill(array, (char) 1); - assert0(IOUtils.getScratchCharArray()); - } - - @Test - void test_getScratchCharArrayWriteOnly() { - final char[] array = IOUtils.getScratchCharArrayWriteOnly(); - assert0(array); - Arrays.fill(array, (char) 1); - assert0(IOUtils.getScratchCharArray()); + final char[] array; + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + array = scratch.array(); + assert0(array); + Arrays.fill(array, (char) 1); + // Get another array, while the first is still in use + // The test doesn't need the try here but that's the pattern. + try (ScratchChars scratch2 = IOUtils.ScratchChars.get()) { + final char[] array2 = scratch2.array(); + assert0(array2); + assertNotSame(array, array2); + } + } + // The first array should be reset and reusable + try (ScratchChars scratch = IOUtils.ScratchChars.get()) { + final char[] array3 = scratch.array(); + assert0(array3); + assertSame(array, array3); + } } @Test diff --git a/src/test/java/org/apache/commons/io/IOUtilsConcurrentTest.java b/src/test/java/org/apache/commons/io/IOUtilsConcurrentTest.java new file mode 100644 index 00000000000..4aed328a0df --- /dev/null +++ b/src/test/java/org/apache/commons/io/IOUtilsConcurrentTest.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.io; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +import org.apache.commons.io.function.IOConsumer; +import org.apache.commons.io.input.ChecksumInputStream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link IOUtils} methods in a concurrent environment. + */ +class IOUtilsConcurrentTest { + + private static class ChecksumReader extends Reader { + private final CRC32 checksum; + private final long expectedChecksumValue; + private final Reader reader; + + ChecksumReader(final Reader reader, final long expectedChecksumValue) { + this.reader = reader; + this.checksum = new CRC32(); + this.expectedChecksumValue = expectedChecksumValue; + } + + @Override + public void close() throws IOException { + reader.close(); + } + + public long getValue() { + return checksum.getValue(); + } + + @Override + public int read() throws IOException { + return super.read(); + } + + @Override + public int read(final char[] cbuf, final int off, final int len) throws IOException { + final int n = reader.read(cbuf, off, len); + if (n > 0) { + final byte[] bytes = new String(cbuf, off, n).getBytes(Charset.defaultCharset()); + checksum.update(bytes, 0, bytes.length); + } + if (n == -1) { + final long actual = checksum.getValue(); + if (actual != expectedChecksumValue) { + throw new IOException("Checksum mismatch: expected " + expectedChecksumValue + " but got " + actual); + } + } + return n; + } + } + + /** + * Test data for InputStream tests. + */ + private static final byte[][] BYTE_DATA; + + /** + * Checksum values for {@link #BYTE_DATA}. + */ + private static final long[] BYTE_DATA_CHECKSUM; + + /** + * Number of runs per thread (to increase the chance of collisions). + */ + private static final int RUNS_PER_THREAD = 16; + + /** + * Size of test data. + */ + private static final int SIZE = IOUtils.DEFAULT_BUFFER_SIZE; + + /** + * Test data for Reader tests. + */ + private static final String[] STRING_DATA; + + /** + * Checksum values for {@link #STRING_DATA}. + */ + private static final long[] STRING_DATA_CHECKSUM; + + /** + * Number of threads to use. + */ + private static final int THREAD_COUNT = 16; + + /** + * Number of data variants (to increase the chance of collisions). + */ + private static final int VARIANTS = 16; + + static { + final Checksum checksum = new CRC32(); + // Byte data + BYTE_DATA = new byte[VARIANTS][]; + BYTE_DATA_CHECKSUM = new long[VARIANTS]; + for (int variant = 0; variant < VARIANTS; variant++) { + final byte[] data = new byte[SIZE]; + for (int i = 0; i < SIZE; i++) { + data[i] = (byte) ((i + variant) % 256); + } + BYTE_DATA[variant] = data; + checksum.reset(); + checksum.update(data, 0 , data.length); + BYTE_DATA_CHECKSUM[variant] = checksum.getValue(); + } + // Char data + final char[] cdata = new char[SIZE]; + STRING_DATA = new String[VARIANTS]; + STRING_DATA_CHECKSUM = new long[VARIANTS]; + for (int variant = 0; variant < VARIANTS; variant++) { + for (int i = 0; i < SIZE; i++) { + cdata[i] = (char) ((i + variant) % Character.MAX_VALUE); + } + STRING_DATA[variant] = new String(cdata); + checksum.reset(); + final byte[] bytes = STRING_DATA[variant].getBytes(Charset.defaultCharset()); + checksum.update(bytes, 0, bytes.length); + STRING_DATA_CHECKSUM[variant] = checksum.getValue(); + } + } + + static Stream> testConcurrentInputStreamTasks() { + return Stream.of( + IOUtils::consume, + in -> IOUtils.skip(in, Long.MAX_VALUE), + in -> IOUtils.skipFully(in, SIZE), + IOUtils::toByteArray, + in -> IOUtils.toByteArray(in, SIZE), + in -> IOUtils.toByteArray(in, SIZE, 512) + ); + } + + static Stream> testConcurrentReaderTasks() { + return Stream.of( + IOUtils::consume, + reader -> IOUtils.skip(reader, Long.MAX_VALUE), + reader -> IOUtils.skipFully(reader, SIZE), + reader -> IOUtils.toByteArray(reader, Charset.defaultCharset()) + ); + } + + @ParameterizedTest + @MethodSource + void testConcurrentInputStreamTasks(final IOConsumer consumer) throws InterruptedException { + final ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); + try { + final List> futures = IntStream.range(0, THREAD_COUNT * RUNS_PER_THREAD) + .>mapToObj(i -> threadPool.submit(() -> { + try (InputStream in = ChecksumInputStream + .builder() + .setByteArray(BYTE_DATA[i % VARIANTS]) + .setChecksum(new CRC32()) + .setExpectedChecksumValue(BYTE_DATA_CHECKSUM[i % VARIANTS]) + .get()) { + consumer.accept(in); + } + return null; + })).collect(Collectors.toList()); + futures.forEach(f -> assertDoesNotThrow(() -> f.get())); + } finally { + threadPool.shutdownNow(); + } + } + + @ParameterizedTest + @MethodSource + void testConcurrentReaderTasks(final IOConsumer consumer) throws InterruptedException { + final ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); + try { + final List> futures = IntStream.range(0, THREAD_COUNT * RUNS_PER_THREAD) + .>mapToObj(i -> threadPool.submit(() -> { + try (Reader reader = new ChecksumReader(new StringReader(STRING_DATA[i % VARIANTS]), STRING_DATA_CHECKSUM[i % VARIANTS])) { + consumer.accept(reader); + } + return null; + })).collect(Collectors.toList()); + futures.forEach(f -> assertDoesNotThrow(() -> f.get())); + } finally { + threadPool.shutdownNow(); + } + } +} diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index 02574946bf8..d9d073357b5 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -14,12 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -73,11 +75,14 @@ import java.util.stream.Stream; import org.apache.commons.io.function.IOConsumer; +import org.apache.commons.io.input.BoundedInputStream; import org.apache.commons.io.input.BrokenInputStream; import org.apache.commons.io.input.CharSequenceInputStream; +import org.apache.commons.io.input.ChunkedReader; import org.apache.commons.io.input.CircularInputStream; import org.apache.commons.io.input.NullInputStream; import org.apache.commons.io.input.NullReader; +import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream; import org.apache.commons.io.output.AppendableWriter; import org.apache.commons.io.output.BrokenOutputStream; import org.apache.commons.io.output.CountingOutputStream; @@ -87,6 +92,7 @@ import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.commons.io.test.TestUtils; import org.apache.commons.io.test.ThrowOnCloseReader; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.JavaVersion; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; @@ -138,6 +144,71 @@ public static void beforeAll() { IO.clear(); } + static Stream invalidRead_InputStream_Offset_ArgumentsProvider() { + final InputStream input = new ByteArrayInputStream(new byte[10]); + final byte[] b = new byte[10]; + return Stream.of( + // input is null + Arguments.of(null, b, 0, 1, NullPointerException.class), + // b is null + Arguments.of(input, null, 0, 1, NullPointerException.class), + // off is negative + Arguments.of(input, b, -1, 1, IndexOutOfBoundsException.class), + // len is negative + Arguments.of(input, b, 0, -1, IndexOutOfBoundsException.class), + // off + len is too big + Arguments.of(input, b, 1, 10, IndexOutOfBoundsException.class), + // off + len is too big + Arguments.of(input, b, 10, 1, IndexOutOfBoundsException.class) + ); + } + + static Stream testCheckFromIndexSizeInvalidCases() { + return Stream.of( + Arguments.of(-1, 0, 42), + Arguments.of(0, -1, 42), + Arguments.of(0, 0, -1), + // off + len > arrayLength + Arguments.of(1, 42, 42), + Arguments.of(Integer.MAX_VALUE, 1, Integer.MAX_VALUE) + ); + } + + static Stream testCheckFromIndexSizeValidCases() { + return Stream.of( + // Valid cases + Arguments.of(0, 0, 42), + Arguments.of(0, 1, 42), + Arguments.of(0, 42, 42), + Arguments.of(41, 1, 42), + Arguments.of(42, 0, 42) + ); + } + + static Stream testCheckFromToIndexInvalidCases() { + return Stream.of( + Arguments.of(-1, 0, 42), + Arguments.of(0, -1, 42), + Arguments.of(0, 0, -1), + // from > to + Arguments.of(1, 0, 42), + // to > arrayLength + Arguments.of(0, 43, 42), + Arguments.of(1, 43, 42) + ); + } + + static Stream testCheckFromToIndexValidCases() { + return Stream.of( + // Valid cases + Arguments.of(0, 0, 42), + Arguments.of(0, 1, 42), + Arguments.of(0, 42, 42), + Arguments.of(41, 42, 42), + Arguments.of(42, 42, 42) + ); + } + private static Stream testToByteArray_InputStream_Size_BufferSize_Succeeds() { final byte[] data = new byte[1024]; for (int i = 0; i < 1024; i++) { @@ -158,7 +229,9 @@ static Stream testToByteArray_InputStream_Size_BufferSize_Throws() { Arguments.of(-1, 128, IllegalArgumentException.class), // Invalid buffer size Arguments.of(0, 0, IllegalArgumentException.class), - // Huge size: should not cause OutOfMemoryError + // Truncation with requested size < chunk size + Arguments.of(64, 128, EOFException.class), + // Truncation with requested size > chunk size Arguments.of(Integer.MAX_VALUE, 128, EOFException.class)); } @@ -352,37 +425,9 @@ void testByteArrayWithNegativeSize() { assertThrows(NegativeArraySizeException.class, () -> IOUtils.byteArray(-1)); } - static Stream testCheckFromIndexSizeValidCases() { - return Stream.of( - // Valid cases - Arguments.of(0, 0, 42), - Arguments.of(0, 1, 42), - Arguments.of(0, 42, 42), - Arguments.of(41, 1, 42), - Arguments.of(42, 0, 42) - ); - } - @ParameterizedTest @MethodSource - void testCheckFromIndexSizeValidCases(int off, int len, int arrayLength) { - assertDoesNotThrow(() -> IOUtils.checkFromIndexSize(off, len, arrayLength)); - } - - static Stream testCheckFromIndexSizeInvalidCases() { - return Stream.of( - Arguments.of(-1, 0, 42), - Arguments.of(0, -1, 42), - Arguments.of(0, 0, -1), - // off + len > arrayLength - Arguments.of(1, 42, 42), - Arguments.of(Integer.MAX_VALUE, 1, Integer.MAX_VALUE) - ); - } - - @ParameterizedTest - @MethodSource - void testCheckFromIndexSizeInvalidCases(int off, int len, int arrayLength) { + void testCheckFromIndexSizeInvalidCases(final int off, final int len, final int arrayLength) { final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.checkFromIndexSize(off, len, arrayLength)); assertTrue(ex.getMessage().contains(String.valueOf(off))); assertTrue(ex.getMessage().contains(String.valueOf(len))); @@ -392,7 +437,7 @@ void testCheckFromIndexSizeInvalidCases(int off, int len, int arrayLength) { final IndexOutOfBoundsException jreEx = assertThrows(IndexOutOfBoundsException.class, () -> { try { Objects.class.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class).invoke(null, off, len, arrayLength); - } catch (InvocationTargetException ite) { + } catch (final InvocationTargetException ite) { throw ite.getTargetException(); } }); @@ -400,39 +445,15 @@ void testCheckFromIndexSizeInvalidCases(int off, int len, int arrayLength) { } } - static Stream testCheckFromToIndexValidCases() { - return Stream.of( - // Valid cases - Arguments.of(0, 0, 42), - Arguments.of(0, 1, 42), - Arguments.of(0, 42, 42), - Arguments.of(41, 42, 42), - Arguments.of(42, 42, 42) - ); - } - @ParameterizedTest @MethodSource - void testCheckFromToIndexValidCases(int from, int to, int arrayLength) { - assertDoesNotThrow(() -> IOUtils.checkFromToIndex(from, to, arrayLength)); - } - - static Stream testCheckFromToIndexInvalidCases() { - return Stream.of( - Arguments.of(-1, 0, 42), - Arguments.of(0, -1, 42), - Arguments.of(0, 0, -1), - // from > to - Arguments.of(1, 0, 42), - // to > arrayLength - Arguments.of(0, 43, 42), - Arguments.of(1, 43, 42) - ); + void testCheckFromIndexSizeValidCases(final int off, final int len, final int arrayLength) { + assertDoesNotThrow(() -> IOUtils.checkFromIndexSize(off, len, arrayLength)); } @ParameterizedTest @MethodSource - void testCheckFromToIndexInvalidCases(int from, int to, int arrayLength) { + void testCheckFromToIndexInvalidCases(final int from, final int to, final int arrayLength) { final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.checkFromToIndex(from, to, arrayLength)); assertTrue(ex.getMessage().contains(String.valueOf(from))); assertTrue(ex.getMessage().contains(String.valueOf(to))); @@ -442,7 +463,7 @@ void testCheckFromToIndexInvalidCases(int from, int to, int arrayLength) { final IndexOutOfBoundsException jreEx = assertThrows(IndexOutOfBoundsException.class, () -> { try { Objects.class.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class).invoke(null, from, to, arrayLength); - } catch (InvocationTargetException ite) { + } catch (final InvocationTargetException ite) { throw ite.getTargetException(); } }); @@ -450,6 +471,12 @@ void testCheckFromToIndexInvalidCases(int from, int to, int arrayLength) { } } + @ParameterizedTest + @MethodSource + void testCheckFromToIndexValidCases(final int from, final int to, final int arrayLength) { + assertDoesNotThrow(() -> IOUtils.checkFromToIndex(from, to, arrayLength)); + } + @Test void testClose() { assertDoesNotThrow(() -> IOUtils.close((Closeable) null)); @@ -553,9 +580,11 @@ void testCloseQuietly_CloseableExceptionConsumer() { // RuntimeException subclass assertDoesNotThrow(() -> IOUtils.closeQuietly(new BrokenOutputStream(new UnsupportedOperationException()), consumer)); assertTrue(b.get()); + // in-line + assertDoesNotThrow(() -> IOUtils.closeQuietly(new BrokenOutputStream(new UnsupportedOperationException()), e -> b.set(true))); + assertTrue(b.get()); } - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test void testCloseQuietly_Selector() { Selector selector = null; @@ -565,9 +594,9 @@ void testCloseQuietly_Selector() { } finally { IOUtils.closeQuietly(selector); } + assertFalse(selector.isOpen()); } - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test void testCloseQuietly_SelectorIOException() { final Selector selector = new SelectorAdapter() { @@ -577,7 +606,8 @@ public void close() throws IOException { } }; IOUtils.closeQuietly(selector); - } + assertFalse(selector.isOpen()); +} @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test @@ -586,7 +616,6 @@ void testCloseQuietly_SelectorNull() { IOUtils.closeQuietly(selector); } - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test void testCloseQuietly_SelectorTwice() { Selector selector = null; @@ -597,12 +626,15 @@ void testCloseQuietly_SelectorTwice() { IOUtils.closeQuietly(selector); IOUtils.closeQuietly(selector); } + assertFalse(selector.isOpen()); } @Test - void testCloseQuietly_ServerSocket() { + void testCloseQuietly_ServerSocket() throws IOException { assertDoesNotThrow(() -> IOUtils.closeQuietly((ServerSocket) null)); - assertDoesNotThrow(() -> IOUtils.closeQuietly(new ServerSocket())); + final ServerSocket serverSocket = new ServerSocket(); + IOUtils.closeQuietly(serverSocket); + assertTrue(serverSocket.isClosed()); } @Test @@ -620,7 +652,9 @@ public void close() throws IOException { @Test void testCloseQuietly_Socket() { assertDoesNotThrow(() -> IOUtils.closeQuietly((Socket) null)); - assertDoesNotThrow(() -> IOUtils.closeQuietly(new Socket())); + final Socket socket = new Socket(); + IOUtils.closeQuietly(socket); + assertTrue(socket.isClosed()); } @Test @@ -635,6 +669,18 @@ public synchronized void close() throws IOException { }); } + @SuppressWarnings("resource") + @Test + void testCloseQuietlySuppress_CloseableIOExceptionAddSuppressed() { + final Throwable e = new Exception("test").fillInStackTrace(); + assertEquals(0, e.getSuppressed().length); + assertSame(e, IOUtils.closeQuietlySuppress(new BrokenInputStream(new EOFException("Suppressed").fillInStackTrace()), e)); + assertEquals(1, e.getSuppressed().length); + final Throwable suppressed0 = e.getSuppressed()[0]; + assertInstanceOf(EOFException.class, suppressed0); + assertEquals("Suppressed", suppressed0.getMessage()); + } + @Test void testCloseURLConnection() { assertDoesNotThrow(() -> IOUtils.close((URLConnection) null)); @@ -665,13 +711,10 @@ void testConsumeInputStream() throws Exception { final long size = (long) Integer.MAX_VALUE + (long) 1; final NullInputStream in = new NullInputStream(size); final OutputStream out = NullOutputStream.INSTANCE; - // Test copy() method assertEquals(-1, IOUtils.copy(in, out)); - // reset the input in.init(); - // Test consume() method assertEquals(size, IOUtils.consume(in), "consume()"); } @@ -681,13 +724,10 @@ void testConsumeReader() throws Exception { final long size = (long) Integer.MAX_VALUE + (long) 1; final Reader in = new NullReader(size); final Writer out = NullWriter.INSTANCE; - // Test copy() method assertEquals(-1, IOUtils.copy(in, out)); - // reset the input in.close(); - // Test consume() method assertEquals(size, IOUtils.consume(in), "consume()"); } @@ -750,9 +790,7 @@ void testContentEquals_InputStream_InputStream() throws Exception { @Test void testContentEquals_Reader_Reader() throws Exception { - { - assertTrue(IOUtils.contentEquals((Reader) null, null)); - } + assertTrue(IOUtils.contentEquals((Reader) null, null)); { final StringReader input1 = new StringReader(""); assertFalse(IOUtils.contentEquals(null, input1)); @@ -770,14 +808,23 @@ void testContentEquals_Reader_Reader() throws Exception { assertTrue(IOUtils.contentEquals(input1, input1)); } assertTrue(IOUtils.contentEquals(new StringReader(""), new StringReader(""))); - assertTrue( - IOUtils.contentEquals(new BufferedReader(new StringReader("")), new BufferedReader(new StringReader("")))); + assertTrue(IOUtils.contentEquals(new BufferedReader(new StringReader("")), new BufferedReader(new StringReader("")))); assertTrue(IOUtils.contentEquals(new StringReader("ABC"), new StringReader("ABC"))); assertFalse(IOUtils.contentEquals(new StringReader("ABCD"), new StringReader("ABC"))); assertFalse(IOUtils.contentEquals(new StringReader("ABC"), new StringReader("ABCD"))); assertFalse(IOUtils.contentEquals(new StringReader("apache"), new StringReader("apacha"))); } + @Test + void testContentEquals_Reader_Reader_unevenReads() throws Exception { + // sanity test, same chunk size + assertFalse(IOUtils.contentEquals(new ChunkedReader(new StringReader("apache"), 1000), new ChunkedReader(new StringReader("apacha"), 1000))); + assertTrue(IOUtils.contentEquals(new ChunkedReader(new StringReader("ABC"), 1000), new ChunkedReader(new StringReader("ABC"), 1000))); + // test with uneven chunk sizes + assertTrue(IOUtils.contentEquals(new ChunkedReader(new StringReader("ABC"), 1), new ChunkedReader(new StringReader("ABC"), 2))); + assertFalse(IOUtils.contentEquals(new ChunkedReader(new StringReader("apache"), 1), new ChunkedReader(new StringReader("apacha"), 2))); + } + @Test void testContentEqualsIgnoreEOL() throws Exception { { @@ -911,12 +958,9 @@ void testCopy_ByteArray_OutputStream() throws Exception { // Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid. in = IOUtils.toByteArray(fin); } - try (OutputStream fout = Files.newOutputStream(destination.toPath())) { CopyUtils.copy(in, fout); - fout.flush(); - TestUtils.checkFile(destination, testFile); TestUtils.checkWrite(fout); } @@ -931,7 +975,6 @@ void testCopy_ByteArray_Writer() throws Exception { // Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid. in = IOUtils.toByteArray(fin); } - try (Writer fout = Files.newBufferedWriter(destination.toPath())) { CopyUtils.copy(in, fout); fout.flush(); @@ -949,11 +992,9 @@ void testCopy_String_Writer() throws Exception { // Create our String. Rely on testReaderToString() to make sure this is valid. str = IOUtils.toString(fin); } - try (Writer fout = Files.newBufferedWriter(destination.toPath())) { CopyUtils.copy(str, fout); fout.flush(); - TestUtils.checkFile(destination, testFile); TestUtils.checkWrite(fout); } @@ -968,19 +1009,16 @@ void testCopyLarge_CharExtraLength() throws IOException { // Create streams is = new CharArrayReader(carr); os = new CharArrayWriter(); - // Test our copy method // for extra length, it reads till EOF assertEquals(200, IOUtils.copyLarge(is, os, 0, 2000)); final char[] oarr = os.toCharArray(); - // check that output length is correct assertEquals(200, oarr.length); // check that output data corresponds to input data assertEquals(1, oarr[1]); assertEquals(79, oarr[79]); assertEquals((char) -1, oarr[80]); - } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); @@ -995,18 +1033,15 @@ void testCopyLarge_CharFullLength() throws IOException { // Create streams is = new CharArrayReader(carr); os = new CharArrayWriter(); - // Test our copy method assertEquals(200, IOUtils.copyLarge(is, os, 0, -1)); final char[] oarr = os.toCharArray(); - // check that output length is correct assertEquals(200, oarr.length); // check that output data corresponds to input data assertEquals(1, oarr[1]); assertEquals(79, oarr[79]); assertEquals((char) -1, oarr[80]); - } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); @@ -1021,18 +1056,15 @@ void testCopyLarge_CharNoSkip() throws IOException { // Create streams is = new CharArrayReader(carr); os = new CharArrayWriter(); - // Test our copy method assertEquals(100, IOUtils.copyLarge(is, os, 0, 100)); final char[] oarr = os.toCharArray(); - // check that output length is correct assertEquals(100, oarr.length); // check that output data corresponds to input data assertEquals(1, oarr[1]); assertEquals(79, oarr[79]); assertEquals((char) -1, oarr[80]); - } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); @@ -1047,18 +1079,15 @@ void testCopyLarge_CharSkip() throws IOException { // Create streams is = new CharArrayReader(carr); os = new CharArrayWriter(); - // Test our copy method assertEquals(100, IOUtils.copyLarge(is, os, 10, 100)); final char[] oarr = os.toCharArray(); - // check that output length is correct assertEquals(100, oarr.length); // check that output data corresponds to input data assertEquals(11, oarr[1]); assertEquals(79, oarr[69]); assertEquals((char) -1, oarr[70]); - } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); @@ -1074,15 +1103,12 @@ void testCopyLarge_CharSkipInvalid() { @Test void testCopyLarge_ExtraLength() throws IOException { - try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); - ByteArrayOutputStream os = new ByteArrayOutputStream()) { + try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) { // Create streams - // Test our copy method // for extra length, it reads till EOF assertEquals(200, IOUtils.copyLarge(is, os, 0, 2000)); final byte[] oarr = os.toByteArray(); - // check that output length is correct assertEquals(200, oarr.length); // check that output data corresponds to input data @@ -1094,12 +1120,10 @@ void testCopyLarge_ExtraLength() throws IOException { @Test void testCopyLarge_FullLength() throws IOException { - try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); - ByteArrayOutputStream os = new ByteArrayOutputStream()) { + try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) { // Test our copy method assertEquals(200, IOUtils.copyLarge(is, os, 0, -1)); final byte[] oarr = os.toByteArray(); - // check that output length is correct assertEquals(200, oarr.length); // check that output data corresponds to input data @@ -1111,12 +1135,10 @@ void testCopyLarge_FullLength() throws IOException { @Test void testCopyLarge_NoSkip() throws IOException { - try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); - ByteArrayOutputStream os = new ByteArrayOutputStream()) { + try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) { // Test our copy method assertEquals(100, IOUtils.copyLarge(is, os, 0, 100)); final byte[] oarr = os.toByteArray(); - // check that output length is correct assertEquals(100, oarr.length); // check that output data corresponds to input data @@ -1128,12 +1150,10 @@ void testCopyLarge_NoSkip() throws IOException { @Test void testCopyLarge_Skip() throws IOException { - try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); - ByteArrayOutputStream os = new ByteArrayOutputStream()) { + try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) { // Test our copy method assertEquals(100, IOUtils.copyLarge(is, os, 10, 100)); final byte[] oarr = os.toByteArray(); - // check that output length is correct assertEquals(100, oarr.length); // check that output data corresponds to input data @@ -1160,24 +1180,28 @@ void testCopyLarge_SkipWithInvalidOffset() throws IOException { // Create streams is = new ByteArrayInputStream(iarr); os = new ByteArrayOutputStream(); - // Test our copy method assertEquals(100, IOUtils.copyLarge(is, os, -10, 100)); final byte[] oarr = os.toByteArray(); - // check that output length is correct assertEquals(100, oarr.length); // check that output data corresponds to input data assertEquals(1, oarr[1]); assertEquals(79, oarr[79]); assertEquals(-1, oarr[80]); - } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); } } + @ParameterizedTest + @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider") + void testRead_InputStream_Offset_ArgumentsValidation(final InputStream input, final byte[] b, final int off, final int len, + final Class expected) { + assertThrows(expected, () -> IOUtils.read(input, b, off, len)); + } + @Test void testRead_ReadableByteChannel() throws Exception { final ByteBuffer buffer = ByteBuffer.allocate(FILE_SIZE); @@ -1189,7 +1213,7 @@ void testRead_ReadableByteChannel() throws Exception { assertEquals(0, buffer.remaining()); assertEquals(0, input.read(buffer)); buffer.clear(); - assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer), "Should have failed with EOFException"); + assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer)); } finally { IOUtils.closeQuietly(input, fileInputStream); } @@ -1199,11 +1223,8 @@ void testRead_ReadableByteChannel() throws Exception { void testReadFully_InputStream__ReturnByteArray() throws Exception { final byte[] bytes = "abcd1234".getBytes(StandardCharsets.UTF_8); final ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - final byte[] result = IOUtils.readFully(stream, bytes.length); - IOUtils.closeQuietly(stream); - assertEqualContent(result, bytes); } @@ -1213,11 +1234,11 @@ void testReadFully_InputStream_ByteArray() throws Exception { final byte[] buffer = new byte[size]; final InputStream input = new ByteArrayInputStream(new byte[size]); - assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException"); + assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1)); IOUtils.readFully(input, buffer, 0, 0); IOUtils.readFully(input, buffer, 0, size - 1); - assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 2), "Should have failed with EOFException"); + assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 2)); IOUtils.closeQuietly(input); } @@ -1230,6 +1251,13 @@ void testReadFully_InputStream_Offset() throws Exception { IOUtils.closeQuietly(stream); } + @ParameterizedTest + @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider") + void testReadFully_InputStream_Offset_ArgumentsValidation(final InputStream input, final byte[] b, final int off, final int len, + final Class expected) { + assertThrows(expected, () -> IOUtils.read(input, b, off, len)); + } + @Test void testReadFully_ReadableByteChannel() throws Exception { final ByteBuffer buffer = ByteBuffer.allocate(FILE_SIZE); @@ -1246,7 +1274,7 @@ void testReadFully_ReadableByteChannel() throws Exception { assertEquals(0, input.read(buffer)); IOUtils.readFully(input, buffer); buffer.clear(); - assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer), "Should have failed with EOFxception"); + assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer)); } finally { IOUtils.closeQuietly(input, fileInputStream); } @@ -1260,8 +1288,8 @@ void testReadFully_Reader() throws Exception { IOUtils.readFully(input, buffer, 0, 0); IOUtils.readFully(input, buffer, 0, size - 3); - assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException"); - assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5), "Should have failed with EOFException"); + assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1)); + assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5)); IOUtils.closeQuietly(input); } @@ -1376,16 +1404,16 @@ void testResourceToByteArray_ExistingResourceAtRootPackage_WithClassLoader() thr @Test void testResourceToByteArray_ExistingResourceAtSubPackage() throws Exception { - final long fileSize = TestResources.getFile("FileUtilsTestDataCR.dat").length(); - final byte[] bytes = IOUtils.resourceToByteArray("/org/apache/commons/io/FileUtilsTestDataCR.dat"); + final long fileSize = TestResources.getFile("FileUtilsTestDataCR.bin").length(); + final byte[] bytes = IOUtils.resourceToByteArray("/org/apache/commons/io/FileUtilsTestDataCR.bin"); assertNotNull(bytes); assertEquals(fileSize, bytes.length); } @Test void testResourceToByteArray_ExistingResourceAtSubPackage_WithClassLoader() throws Exception { - final long fileSize = TestResources.getFile("FileUtilsTestDataCR.dat").length(); - final byte[] bytes = IOUtils.resourceToByteArray("org/apache/commons/io/FileUtilsTestDataCR.dat", + final long fileSize = TestResources.getFile("FileUtilsTestDataCR.bin").length(); + final byte[] bytes = IOUtils.resourceToByteArray("org/apache/commons/io/FileUtilsTestDataCR.bin", ClassLoader.getSystemClassLoader()); assertNotNull(bytes); assertEquals(fileSize, bytes.length); @@ -1437,8 +1465,8 @@ void testResourceToString_ExistingResourceAtRootPackage_WithClassLoader() throws @Test void testResourceToString_ExistingResourceAtSubPackage() throws Exception { - final long fileSize = TestResources.getFile("FileUtilsTestDataCR.dat").length(); - final String content = IOUtils.resourceToString("/org/apache/commons/io/FileUtilsTestDataCR.dat", + final long fileSize = TestResources.getFile("FileUtilsTestDataCR.bin").length(); + final String content = IOUtils.resourceToString("/org/apache/commons/io/FileUtilsTestDataCR.bin", StandardCharsets.UTF_8); assertNotNull(content); @@ -1447,8 +1475,8 @@ void testResourceToString_ExistingResourceAtSubPackage() throws Exception { @Test void testResourceToString_ExistingResourceAtSubPackage_WithClassLoader() throws Exception { - final long fileSize = TestResources.getFile("FileUtilsTestDataCR.dat").length(); - final String content = IOUtils.resourceToString("org/apache/commons/io/FileUtilsTestDataCR.dat", + final long fileSize = TestResources.getFile("FileUtilsTestDataCR.bin").length(); + final String content = IOUtils.resourceToString("org/apache/commons/io/FileUtilsTestDataCR.bin", StandardCharsets.UTF_8, ClassLoader.getSystemClassLoader()); assertNotNull(content); @@ -1467,16 +1495,14 @@ void testResourceToString_NonExistingResource_WithClassLoader() { ClassLoader.getSystemClassLoader())); } - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test void testResourceToString_NullCharset() throws Exception { - IOUtils.resourceToString("/org/apache/commons/io//test-file-utf8.bin", null); + assertNotNull(IOUtils.resourceToString("/org/apache/commons/io//test-file-utf8.bin", null)); } - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" @Test void testResourceToString_NullCharset_WithClassLoader() throws Exception { - IOUtils.resourceToString("org/apache/commons/io/test-file-utf8.bin", null, ClassLoader.getSystemClassLoader()); + assertNotNull(IOUtils.resourceToString("org/apache/commons/io/test-file-utf8.bin", null, ClassLoader.getSystemClassLoader())); } @Test @@ -1507,18 +1533,18 @@ void testResourceToURL_ExistingResourceAtRootPackage_WithClassLoader() throws Ex @Test void testResourceToURL_ExistingResourceAtSubPackage() throws Exception { - final URL url = IOUtils.resourceToURL("/org/apache/commons/io/FileUtilsTestDataCR.dat"); + final URL url = IOUtils.resourceToURL("/org/apache/commons/io/FileUtilsTestDataCR.bin"); assertNotNull(url); - assertTrue(url.getFile().endsWith("/org/apache/commons/io/FileUtilsTestDataCR.dat")); + assertTrue(url.getFile().endsWith("/org/apache/commons/io/FileUtilsTestDataCR.bin")); } @Test void testResourceToURL_ExistingResourceAtSubPackage_WithClassLoader() throws Exception { - final URL url = IOUtils.resourceToURL("org/apache/commons/io/FileUtilsTestDataCR.dat", + final URL url = IOUtils.resourceToURL("org/apache/commons/io/FileUtilsTestDataCR.bin", ClassLoader.getSystemClassLoader()); assertNotNull(url); - assertTrue(url.getFile().endsWith("/org/apache/commons/io/FileUtilsTestDataCR.dat")); + assertTrue(url.getFile().endsWith("/org/apache/commons/io/FileUtilsTestDataCR.bin")); } @Test @@ -1595,13 +1621,11 @@ void testSkip_ReadableByteChannel() throws Exception { @Test void testSkipFully_InputStream() throws Exception { final int size = 1027; - try (InputStream input = new ByteArrayInputStream(new byte[size])) { - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1), "Should have failed with IllegalArgumentException"); - + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1)); IOUtils.skipFully(input, 0); IOUtils.skipFully(input, size - 1); - assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2), "Should have failed with IOException"); + assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2)); } } @@ -1610,11 +1634,10 @@ void testSkipFully_InputStream_Buffer_New_bytes() throws Exception { final int size = 1027; final Supplier bas = () -> new byte[size]; try (InputStream input = new ByteArrayInputStream(new byte[size])) { - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas), "Should have failed with IllegalArgumentException"); - + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas)); IOUtils.skipFully(input, 0, bas); IOUtils.skipFully(input, size - 1, bas); - assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas), "Should have failed with IOException"); + assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas)); } } @@ -1624,11 +1647,10 @@ void testSkipFully_InputStream_Buffer_Reuse_bytes() throws Exception { final byte[] ba = new byte[size]; final Supplier bas = () -> ba; try (InputStream input = new ByteArrayInputStream(new byte[size])) { - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas), "Should have failed with IllegalArgumentException"); - + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas)); IOUtils.skipFully(input, 0, bas); IOUtils.skipFully(input, size - 1, bas); - assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas), "Should have failed with IOException"); + assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas)); } } @@ -1637,11 +1659,10 @@ void testSkipFully_InputStream_Buffer_Reuse_ThreadLocal() throws Exception { final int size = 1027; final ThreadLocal tl = ThreadLocal.withInitial(() -> new byte[size]); try (InputStream input = new ByteArrayInputStream(new byte[size])) { - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, tl::get), "Should have failed with IllegalArgumentException"); - + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, tl::get)); IOUtils.skipFully(input, 0, tl::get); IOUtils.skipFully(input, size - 1, tl::get); - assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, tl::get), "Should have failed with IOException"); + assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, tl::get)); } } @@ -1650,10 +1671,10 @@ void testSkipFully_ReadableByteChannel() throws Exception { final FileInputStream fileInputStream = new FileInputStream(testFile); final FileChannel fileChannel = fileInputStream.getChannel(); try { - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(fileChannel, -1), "Should have failed with IllegalArgumentException"); + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(fileChannel, -1)); IOUtils.skipFully(fileChannel, 0); IOUtils.skipFully(fileChannel, FILE_SIZE - 1); - assertThrows(IOException.class, () -> IOUtils.skipFully(fileChannel, 2), "Should have failed with IOException"); + assertThrows(IOException.class, () -> IOUtils.skipFully(fileChannel, 2)); } finally { IOUtils.closeQuietly(fileChannel, fileInputStream); } @@ -1665,8 +1686,8 @@ void testSkipFully_Reader() throws Exception { try (Reader input = new CharArrayReader(new char[size])) { IOUtils.skipFully(input, 0); IOUtils.skipFully(input, size - 3); - assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1), "Should have failed with IllegalArgumentException"); - assertThrows(IOException.class, () -> IOUtils.skipFully(input, 5), "Should have failed with IOException"); + assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1)); + assertThrows(IOException.class, () -> IOUtils.skipFully(input, 5)); } } @@ -1678,7 +1699,6 @@ void testStringToOutputStream() throws Exception { // Create our String. Rely on testReaderToString() to make sure this is valid. str = IOUtils.toString(fin); } - try (OutputStream fout = Files.newOutputStream(destination.toPath())) { CopyUtils.copy(str, fout); // Note: this method *does* flush. It is equivalent to: @@ -1687,7 +1707,6 @@ void testStringToOutputStream() throws Exception { // _out.flush(); // out = fout; // note: we don't flush here; this IOUtils method does it for us - TestUtils.checkFile(destination, testFile); TestUtils.checkWrite(fout); } @@ -1729,6 +1748,22 @@ void testToByteArray_InputStream() throws Exception { } } + @Test + void testToByteArray_InputStream_Empty() throws Exception { + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY))); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, + IOUtils.toByteArray(BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY)).get())); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, + IOUtils.toByteArray(UnsynchronizedByteArrayInputStream.builder().setInputStream(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY)).get())); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, + IOUtils.toByteArray(BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY)).setMaxCount(1).get())); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY))); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, + IOUtils.toByteArray(BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY)).get())); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, + IOUtils.toByteArray(BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(ArrayUtils.EMPTY_BYTE_ARRAY)).setMaxCount(0).get())); + } + @Test @Disabled("Disable by default as it uses too much memory and can cause builds to fail.") void testToByteArray_InputStream_LongerThanIntegerMaxValue() throws Exception { @@ -1739,10 +1774,8 @@ void testToByteArray_InputStream_LongerThanIntegerMaxValue() throws Exception { @Test void testToByteArray_InputStream_NegativeSize() throws Exception { try (InputStream fin = Files.newInputStream(testFilePath)) { - final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, -1), - "Should have failed with IllegalArgumentException"); - assertTrue(exc.getMessage().startsWith("Size must be equal or greater than zero"), - "Exception message does not start with \"Size must be equal or greater than zero\""); + final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, -1)); + assertTrue(exc.getMessage().startsWith("size < 0"), exc.getMessage()); } } @@ -1775,22 +1808,26 @@ void testToByteArray_InputStream_Size_BufferSize_Throws( } } + @Test + void testToByteArray_InputStream_Size_Truncated() throws Exception { + try (InputStream in = new NullInputStream(0)) { + assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1)); + } + } + @Test void testToByteArray_InputStream_SizeIllegal() throws Exception { try (InputStream fin = Files.newInputStream(testFilePath)) { - final IOException exc = assertThrows(IOException.class, () -> IOUtils.toByteArray(fin, testFile.length() + 1), - "Should have failed with IOException"); - assertTrue(exc.getMessage().startsWith("Unexpected read size"), "Exception message does not start with \"Unexpected read size\""); + final IOException exc = assertThrows(IOException.class, () -> IOUtils.toByteArray(fin, testFile.length() + 1)); + assertTrue(exc.getMessage().startsWith("Expected read size"), exc.getMessage()); } } @Test void testToByteArray_InputStream_SizeLong() throws Exception { try (InputStream fin = Files.newInputStream(testFilePath)) { - final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, (long) Integer.MAX_VALUE + 1), - "Should have failed with IllegalArgumentException"); - assertTrue(exc.getMessage().startsWith("Size cannot be greater than Integer max value"), - "Exception message does not start with \"Size cannot be greater than Integer max value\""); + final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, (long) Integer.MAX_VALUE + 1)); + assertTrue(exc.getMessage().startsWith("size > Integer.MAX_VALUE"), exc.getMessage()); } } @@ -1827,7 +1864,6 @@ void testToByteArray_String() throws Exception { try (Reader fin = Files.newBufferedReader(testFilePath)) { // Create our String. Rely on testReaderToString() to make sure this is valid. final String str = IOUtils.toString(fin); - final byte[] out = IOUtils.toByteArray(str); assertEqualContent(str.getBytes(), out); } @@ -1910,7 +1946,7 @@ void testToCharArray_Reader() throws Exception { * Note, this test utilizes on {@link IOUtils#toByteArray(InputStream)} and so relies on * {@link #testToByteArray_InputStream()} to ensure this method functions correctly. * - * @throws Exception on error + * @throws Exception on error. */ @Test void testToInputStream_CharSequence() throws Exception { @@ -1931,7 +1967,7 @@ void testToInputStream_CharSequence() throws Exception { * utilizes on {@link IOUtils#toByteArray(InputStream)} and so relies on * {@link #testToByteArray_InputStream()} to ensure this method functions correctly. * - * @throws Exception on error + * @throws Exception on error. */ @Test void testToInputStream_String() throws Exception { diff --git a/src/test/java/org/apache/commons/io/LineIteratorTest.java b/src/test/java/org/apache/commons/io/LineIteratorTest.java index 1ab177e8e1c..e4f5f88a970 100644 --- a/src/test/java/org/apache/commons/io/LineIteratorTest.java +++ b/src/test/java/org/apache/commons/io/LineIteratorTest.java @@ -63,9 +63,9 @@ private void assertLines(final List lines, final LineIterator iterator) /** * Creates a test file with a specified number of lines. * - * @param file target file - * @param lineCount number of lines to create - * @throws IOException If an I/O error occurs + * @param file target file. + * @param lineCount number of lines to create. + * @throws IOException If an I/O error occurs. */ private List createLinesFile(final File file, final int lineCount) throws IOException { final List lines = createStringLines(lineCount); @@ -76,10 +76,10 @@ private List createLinesFile(final File file, final int lineCount) throw /** * Creates a test file with a specified number of lines. * - * @param file target file - * @param encoding the encoding to use while writing the lines - * @param lineCount number of lines to create - * @throws IOException If an I/O error occurs + * @param file target file. + * @param encoding the encoding to use while writing the lines. + * @param lineCount number of lines to create. + * @throws IOException If an I/O error occurs. */ private List createLinesFile(final File file, final String encoding, final int lineCount) throws IOException { final List lines = createStringLines(lineCount); @@ -90,7 +90,7 @@ private List createLinesFile(final File file, final String encoding, fin /** * Creates String data lines. * - * @param lineCount number of lines to create + * @param lineCount number of lines to create. * @return a new lines list. */ private List createStringLines(final int lineCount) { @@ -104,8 +104,8 @@ private List createStringLines(final int lineCount) { /** * Utility method to create and test a file with a specified number of lines. * - * @param lineCount the lines to create in the test file - * @throws IOException If an I/O error occurs while creating the file + * @param lineCount the lines to create in the test file. + * @throws IOException If an I/O error occurs while creating the file. */ private void doTestFileWithSpecifiedLines(final int lineCount) throws IOException { final String encoding = UTF_8; diff --git a/src/test/java/org/apache/commons/io/ThreadMonitorTest.java b/src/test/java/org/apache/commons/io/ThreadMonitorTest.java index 085f210c087..fe611a7f840 100644 --- a/src/test/java/org/apache/commons/io/ThreadMonitorTest.java +++ b/src/test/java/org/apache/commons/io/ThreadMonitorTest.java @@ -40,6 +40,7 @@ void testCompletedWithoutTimeout() { TestUtils.sleep(1); ThreadMonitor.stop(monitor); } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); fail("Timed Out", e); } } diff --git a/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java b/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java index 36c994c185b..6e3e0f3aed9 100644 --- a/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java +++ b/src/test/java/org/apache/commons/io/build/AbstractStreamBuilderTest.java @@ -17,6 +17,7 @@ package org.apache.commons.io.build; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -24,23 +25,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.FileInputStream; +import java.io.InputStream; import java.io.RandomAccessFile; import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; +import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Objects; import java.util.stream.Stream; import org.apache.commons.io.function.IOConsumer; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RandomUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests {@link AbstractStreamBuilder}. @@ -118,15 +124,70 @@ void testBufferSizeChecker() { } /** - * Tests various ways to obtain a {@link java.io.InputStream}. + * Tests various ways to obtain a byte array. * - * @param configurer Lambda to configure the builder. + * @param configurer configures a builder. + */ + @ParameterizedTest + @MethodSource("fileBasedConfigurers") + void testGetByteArray(final IOConsumer configurer) throws Exception { + final Builder builder = builder(); + configurer.accept(builder); + assertNotNull(builder.getByteArray()); + } + + /** + * Tests various ways to obtain a {@link InputStream}. + * + * @param configurer configures a builder. */ @ParameterizedTest @MethodSource("fileBasedConfigurers") void testGetInputStream(final IOConsumer configurer) throws Exception { final Builder builder = builder(); configurer.accept(builder); - assertNotNull(builder.getInputStream()); + try (InputStream inputStream = builder.getInputStream()) { + assertNotNull(inputStream); + } + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 4 }) + void testSetByteArrayGetByteArray(final int size) throws Exception { + final Builder builder = builder(); + final byte[] randomBytes = RandomUtils.insecure().randomBytes(size); + builder.setByteArray(randomBytes); + assertArrayEquals(randomBytes, builder.getByteArray()); + } + + @Test + void testSetFileGetByteArray() throws Exception { + final Builder builder = builder(); + final Path path = Paths.get(AbstractOriginTest.FILE_NAME_RO); + builder.setFile(path.toFile()); + assertArrayEquals(Files.readAllBytes(path), builder.getByteArray()); + } + + @Test + void testSetOpenOptions() { + final Builder builder = builder(); + assertEquals(0, builder.setOpenOptions().getOpenOptions().length); + assertEquals(0, builder.setOpenOptions((OpenOption[]) null).getOpenOptions().length); + assertEquals(1, builder.setOpenOptions(StandardOpenOption.READ).getOpenOptions().length); + final OpenOption[] options = { StandardOpenOption.READ, StandardOpenOption.WRITE }; + assertArrayEquals(options, builder.setOpenOptions(options).getOpenOptions()); + // Check that the builder makes a defensive copy of the array. + options[0] = null; + options[1] = null; + assertEquals(StandardOpenOption.READ, builder.getOpenOptions()[0]); + assertEquals(StandardOpenOption.WRITE, builder.getOpenOptions()[1]); + } + + @Test + void testSetPathGetByteArray() throws Exception { + final Builder builder = builder(); + final Path path = Paths.get(AbstractOriginTest.FILE_NAME_RO); + builder.setPath(path); + assertArrayEquals(Files.readAllBytes(path), builder.getByteArray()); } } diff --git a/src/test/java/org/apache/commons/io/build/URIOriginTest.java b/src/test/java/org/apache/commons/io/build/URIOriginTest.java index 6fd2043eb12..bce1c1fb3fe 100644 --- a/src/test/java/org/apache/commons/io/build/URIOriginTest.java +++ b/src/test/java/org/apache/commons/io/build/URIOriginTest.java @@ -21,16 +21,22 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.Duration; +import java.util.function.Supplier; import org.apache.commons.io.build.AbstractOrigin.URIOrigin; +import org.apache.commons.io.build.AbstractOrigin.URIOrigin.URIOpenOption; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests {@link URIOrigin}. @@ -41,6 +47,31 @@ */ class URIOriginTest extends AbstractOriginTest { + // @formatter:off + private static final URIOpenOption URI_OPEN_OPTION = URIOpenOption.builder() + .setConnectTimeout(Duration.ofSeconds(60)) + .setReadTimeout(Duration.ofSeconds(60)) + .get(); + // @formatter:on + + static String[] fixtures() { + return new String[] { "http://1.1.1.1", // IP + "http://google.com", // HTTP + "https://apache.org" // HTTPS + }; + } + + private void checkRead(final Channel in, final Supplier message) throws IOException { + if (in instanceof ReadableByteChannel) { + final ReadableByteChannel rbc = (ReadableByteChannel) in; + assertNotEquals(-1, rbc.read(ByteBuffer.allocate(1)), message); + } + } + + private void checkRead(final InputStream in) throws IOException { + assertNotEquals(-1, in.read()); + } + @Override protected URIOrigin newOriginRo() { return new URIOrigin(Paths.get(FILE_NAME_RO).toUri()); @@ -58,23 +89,36 @@ protected void resetOriginRw() throws IOException { Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, StandardOpenOption.CREATE); } + @Test + void testGetChannelFileURI() throws Exception { + final AbstractOrigin.URIOrigin origin = getOriginRo().asThis(); + try (Channel in = origin.getChannel()) { + checkRead(in, origin::toString); + } + } + @ParameterizedTest - @ValueSource(strings = { - "http://apache.com", - "https://apache.com" - }) - void testGetInputStream(final String uri) throws Exception { + @MethodSource("fixtures") + void testGetInputStrea(final String uri) throws Exception { final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri)); - try (InputStream in = origin.getInputStream()) { - assertNotEquals(-1, in.read()); + try (Channel in = origin.getChannel(URI_OPEN_OPTION)) { + checkRead(in, uri::toString); } } + @ParameterizedTest + @MethodSource("fixtures") + void testGetInputStream(final String uri) throws Exception { + final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri)); + try (InputStream in = origin.getInputStream(URI_OPEN_OPTION)) { + checkRead(in); + } + } @Test void testGetInputStreamFileURI() throws Exception { final AbstractOrigin.URIOrigin origin = getOriginRo().asThis(); try (InputStream in = origin.getInputStream()) { - assertNotEquals(-1, in.read()); + checkRead(in); } } } diff --git a/src/test/java/org/apache/commons/io/channels/AbstractSeekableByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/AbstractSeekableByteChannelTest.java index abf96d8f47d..afb2245a49f 100644 --- a/src/test/java/org/apache/commons/io/channels/AbstractSeekableByteChannelTest.java +++ b/src/test/java/org/apache/commons/io/channels/AbstractSeekableByteChannelTest.java @@ -47,7 +47,7 @@ */ abstract class AbstractSeekableByteChannelTest { - private SeekableByteChannel channel; + protected SeekableByteChannel channel; @TempDir protected Path tempDir; @@ -87,6 +87,7 @@ void testCloseMultipleTimes() throws IOException { assertFalse(channel.isOpen()); } + @Test void testConcurrentPositionAndSizeQueries() throws IOException { final byte[] data = "test data".getBytes(); @@ -136,6 +137,20 @@ void testPositionBeyondSize() throws IOException { assertEquals(4, channel.size()); // Size should not change } + @Test + void testPositionBeyondSizeRead() throws IOException { + final ByteBuffer buffer = ByteBuffer.allocate(1); + channel.position(channel.size() + 1); + assertEquals(channel.size() + 1, channel.position()); + assertEquals(-1, channel.read(buffer)); + channel.position(Integer.MAX_VALUE + 1L); + assertEquals(Integer.MAX_VALUE + 1L, channel.position()); + assertEquals(-1, channel.read(buffer)); + assertThrows(IllegalArgumentException.class, () -> channel.position(-1)); + assertThrows(IllegalArgumentException.class, () -> channel.position(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> channel.position(Long.MIN_VALUE)); + } + @ParameterizedTest @CsvSource({ "0, 0", "5, 5", "10, 10", "100, 100" }) void testPositionInBounds(final long newPosition, final long expectedPosition) throws IOException { @@ -149,6 +164,7 @@ void testPositionInBounds(final long newPosition, final long expectedPosition) t assertEquals(expectedPosition, channel.position()); } + @Test void testPositionNegative() { assertThrows(IllegalArgumentException.class, () -> channel.position(-1)); @@ -292,6 +308,18 @@ void testSizeSameOnOverwrite() throws IOException { assertEquals(11, channel.size()); // Size should not change } + @Test + void testTrucateBeyondSizeReadWrite() throws IOException { + final ByteBuffer buffer = ByteBuffer.allocate(1); + channel.truncate(channel.size() + 1); + assertEquals(-1, channel.read(buffer)); + channel.truncate(Integer.MAX_VALUE + 1L); + assertEquals(-1, channel.read(buffer)); + assertThrows(IllegalArgumentException.class, () -> channel.truncate(-1)); + assertThrows(IllegalArgumentException.class, () -> channel.truncate(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> channel.truncate(Long.MIN_VALUE)); + } + @Test void testTruncateNegative() { assertThrows(IllegalArgumentException.class, () -> channel.truncate(-1)); diff --git a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java index 66c8e60b91a..26281d4e08b 100644 --- a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java +++ b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelCompressTest.java @@ -161,20 +161,6 @@ void testShouldThrowExceptionOnWritingToClosedChannel() { assertThrows(ClosedChannelException.class, () -> c.write(ByteBuffer.allocate(1))); } - @Test - void testShouldThrowExceptionWhenSettingIncorrectPosition() { - try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) { - assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MAX_VALUE + 1L)); - } - } - - @Test - void testShouldThrowExceptionWhenTruncatingToIncorrectSize() { - try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) { - assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MAX_VALUE + 1L)); - } - } - @Test void testShouldTruncateContentsProperly() throws ClosedChannelException { try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) { @@ -244,6 +230,43 @@ void testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception { } } + @Test + void testThrowWhenSettingIncorrectPosition() throws IOException { + try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) { + final ByteBuffer buffer = ByteBuffer.allocate(1); + // write + c.write(buffer); + assertEquals(1, c.position()); + // bad pos A + c.position(c.size() + 1); + assertEquals(c.size() + 1, c.position()); + assertEquals(-1, c.read(buffer)); + // bad pos B + c.position(Integer.MAX_VALUE + 1L); + assertEquals(Integer.MAX_VALUE + 1L, c.position()); + assertEquals(-1, c.read(buffer)); + assertThrows(IOException.class, () -> c.write(buffer)); + // negative input is the only illegal input + assertThrows(IllegalArgumentException.class, () -> c.position(-1)); + assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> c.position(Long.MIN_VALUE)); + } + } + + @Test + void testThrowWhenTruncatingToIncorrectSize() throws IOException { + try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) { + final ByteBuffer buffer = ByteBuffer.allocate(1); + c.truncate(c.size() + 1); + assertEquals(-1, c.read(buffer)); + c.truncate(Integer.MAX_VALUE + 1L); + assertEquals(-1, c.read(buffer)); + assertThrows(IllegalArgumentException.class, () -> c.truncate(-1)); + assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> c.truncate(Long.MIN_VALUE)); + } + } + /* * In either case, if the current position is greater than the given size then it is set to that size. */ diff --git a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java index 5b29b3f1be0..05b93fa9a11 100644 --- a/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java +++ b/src/test/java/org/apache/commons/io/channels/ByteArraySeekableByteChannelTest.java @@ -25,12 +25,17 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NonWritableChannelException; import java.nio.channels.SeekableByteChannel; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.IOSupplier; +import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -41,7 +46,9 @@ */ public class ByteArraySeekableByteChannelTest extends AbstractSeekableByteChannelTest { - private static final byte[] testData = "Some data".getBytes(StandardCharsets.UTF_8); + private static final Charset CHARSET = StandardCharsets.UTF_8; + private static final String STRING = "Some data"; + private static final byte[] BYTE_ARRAY = STRING.getBytes(CHARSET); static Stream testConstructor() { return Stream.of( @@ -49,7 +56,7 @@ static Stream testConstructor() { Arguments.of((IOSupplier) () -> new ByteArraySeekableByteChannel(8), EMPTY_BYTE_ARRAY, 8), Arguments.of((IOSupplier) () -> new ByteArraySeekableByteChannel(16), EMPTY_BYTE_ARRAY, 16), Arguments.of((IOSupplier) () -> ByteArraySeekableByteChannel.wrap(EMPTY_BYTE_ARRAY), EMPTY_BYTE_ARRAY, 0), - Arguments.of((IOSupplier) () -> ByteArraySeekableByteChannel.wrap(testData), testData, testData.length)); + Arguments.of((IOSupplier) () -> ByteArraySeekableByteChannel.wrap(BYTE_ARRAY), BYTE_ARRAY, BYTE_ARRAY.length)); } static Stream testShouldResizeWhenWritingMoreDataThanCapacity() { @@ -67,6 +74,102 @@ protected SeekableByteChannel createChannel() throws IOException { return new ByteArraySeekableByteChannel(); } + @Test + void testBuilderDefaultConstructor() throws IOException { + try (ByteArraySeekableByteChannel channel = new ByteArraySeekableByteChannel.Builder().get()) { + assertEquals(0, channel.position()); + assertEquals(0, channel.size()); + assertEquals(0, channel.array().length); + } + } + + @Test + void testBuilderDefaultMethod() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder().get()) { + assertEquals(0, channel.position()); + assertEquals(0, channel.size()); + assertEquals(0, channel.array().length); + } + } + + @Test + void testBuilderReadOnly() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setByteArray(BYTE_ARRAY) + .setOpenOptions(StandardOpenOption.READ) + .get()) { + assertEquals(0, channel.position()); + assertEquals(9, channel.size()); + assertEquals(9, channel.array().length); + assertThrows(NonWritableChannelException.class, () -> channel.write(ByteBuffer.wrap(BYTE_ARRAY))); + assertThrows(NonWritableChannelException.class, () -> channel.truncate(0)); + } + } + + private void testBuilderReadWrite(final ByteArraySeekableByteChannel channel) throws ClosedChannelException, IOException { + assertEquals(0, channel.position()); + assertEquals(9, channel.size()); + assertEquals(9, channel.array().length); + channel.truncate(0); + assertEquals(0, channel.position()); + channel.write(ByteBuffer.wrap(BYTE_ARRAY)); + assertEquals(9, channel.size()); + assertEquals(9, channel.array().length); + } + + @Test + void testBuilderReadWriteExplict() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setByteArray(BYTE_ARRAY) + .setOpenOptions(StandardOpenOption.READ, StandardOpenOption.WRITE) + .get()) { + testBuilderReadWrite(channel); + } + } + + @Test + void testBuilderReadWriteImplicit() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setByteArray(BYTE_ARRAY) + .get()) { + testBuilderReadWrite(channel); + } + } + + @Test + void testBuilderSetByteArray() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setByteArray(BYTE_ARRAY) + .get()) { + assertEquals(0, channel.position()); + assertEquals(9, channel.size()); + assertEquals(9, channel.array().length); + } + } + + @Test + void testBuilderSetByteArrayEmpty() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setByteArray(ArrayUtils.EMPTY_BYTE_ARRAY) + .get()) { + assertEquals(0, channel.position()); + assertEquals(0, channel.size()); + assertEquals(0, channel.array().length); + } + } + + @Test + void testBuilderSetCharSequence() throws IOException { + try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder() + .setCharSequence(STRING) + .setCharset(CHARSET) + .get()) { + assertEquals(0, channel.position()); + assertEquals(9, channel.size()); + assertEquals(9, channel.array().length); + } + } + @ParameterizedTest @MethodSource void testConstructor(final IOSupplier supplier, final byte[] expected, final int capacity) throws IOException { @@ -84,6 +187,22 @@ void testConstructorInvalid() { assertThrows(NullPointerException.class, () -> ByteArraySeekableByteChannel.wrap(null)); } + @Test + void testPositionBeyondSizeReadWrite() throws IOException { + final ByteBuffer buffer = ByteBuffer.allocate(1); + channel.position(channel.size() + 1); + assertEquals(channel.size() + 1, channel.position()); + assertEquals(-1, channel.read(buffer)); + channel.position(Integer.MAX_VALUE + 1L); + assertEquals(Integer.MAX_VALUE + 1L, channel.position()); + assertEquals(-1, channel.read(buffer)); + // ByteArraySeekableByteChannel has a hard boundary at Integer.MAX_VALUE, files don't. + assertThrows(IOException.class, () -> channel.write(buffer)); + assertThrows(IllegalArgumentException.class, () -> channel.position(-1)); + assertThrows(IllegalArgumentException.class, () -> channel.position(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> channel.position(Long.MIN_VALUE)); + } + @ParameterizedTest @MethodSource void testShouldResizeWhenWritingMoreDataThanCapacity(final byte[] data, final int wanted) throws IOException { diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelFilterByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelFilterByteChannelTest.java new file mode 100644 index 00000000000..fb73a11dccc --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelFilterByteChannelTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.ClosedChannelException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; + +/** + * Tests {@link CloseShieldChannel} wrapping a {@link FilterByteChannel}. + */ +class CloseShieldChannelFilterByteChannelTest { + + /** A FilterByteChannel wrapping the mock. */ + private FilterByteChannel filterChannel; + + /** The innermost mock delegate. */ + private ByteChannel mockChannel; + + /** The CloseShieldChannel wrapping the FilterByteChannel. */ + private ByteChannel shield; + + @BeforeEach + void setUp() throws IOException { + mockChannel = mock(ByteChannel.class); + filterChannel = new FilterByteChannel<>(mockChannel); + shield = CloseShieldChannel.wrap(filterChannel); + } + + @Test + void testCloseDoesNotCloseFilterByteChannel() throws IOException { + when(mockChannel.isOpen()).thenReturn(true); + shield.close(); + // The FilterByteChannel (and thus the mock) must not have been closed. + verify(mockChannel, never()).close(); + assertTrue(filterChannel.isOpen(), "FilterByteChannel must still be open"); + } + + @Test + void testCloseIsIdempotent() throws IOException { + shield.close(); + shield.close(); + assertFalse(shield.isOpen()); + verify(mockChannel, never()).close(); + } + + @Test + void testDoubleWrapReturnsSameShield() { + final ByteChannel shield2 = CloseShieldChannel.wrap(shield); + assertSame(shield, shield2, "wrapping an existing shield must return the same proxy"); + } + + @Test + void testFilterByteChannelUsableAfterShieldClose() throws IOException { + when(mockChannel.isOpen()).thenReturn(true); + shield.close(); + // The shield is closed, but the underlying FilterByteChannel must still work. + assertTrue(filterChannel.isOpen()); + final ByteBuffer buf = ByteBuffer.allocate(4); + when(mockChannel.read(buf)).thenReturn(4); + assertEquals(4, filterChannel.read(buf)); + verify(mockChannel).read(buf); + } + + @Test + void testIsOpenDelegatesBeforeShieldClose() throws IOException { + when(mockChannel.isOpen()).thenReturn(true, false); + assertTrue(shield.isOpen(), "reflects open delegate"); + assertFalse(shield.isOpen(), "reflects closed delegate"); + verify(mockChannel, times(2)).isOpen(); + } + + @Test + void testIsOpenReturnsFalseAfterShieldClose() throws IOException { + when(mockChannel.isOpen()).thenReturn(true); + shield.close(); + assertFalse(shield.isOpen(), "shield is closed so isOpen must return false"); + // isOpen must NOT be forwarded to the delegate after the shield is closed. + verify(mockChannel, never()).isOpen(); + } + + @Test + void testReadDelegatesToFilterByteChannel() throws IOException { + final ByteBuffer buf = ByteBuffer.allocate(16); + when(mockChannel.read(buf)).thenReturn(8); + when(mockChannel.isOpen()).thenReturn(true); + assertEquals(8, shield.read(buf)); + verify(mockChannel).read(buf); + } + + @Test + void testReadIOExceptionPropagates() throws IOException { + final ByteBuffer buf = ByteBuffer.allocate(8); + when(mockChannel.isOpen()).thenReturn(true); + when(mockChannel.read(buf)).thenThrow(new IOException("read failure")); + assertThrows(IOException.class, () -> shield.read(buf)); + verify(mockChannel).read(buf); + } + + @Test + void testReadThrowsClosedChannelExceptionAfterShieldClose() throws IOException { + shield.close(); + assertThrows(ClosedChannelException.class, () -> shield.read(ByteBuffer.allocate(8))); + verify(mockChannel, never()).read(ArgumentMatchers.any()); + } + + @Test + void testShieldImplementsByteChannel() { + assertInstanceOf(ByteChannel.class, shield); + } + + @Test + void testWriteDelegatesToFilterByteChannel() throws IOException { + final ByteBuffer buf = ByteBuffer.allocate(16); + when(mockChannel.write(buf)).thenReturn(16); + when(mockChannel.isOpen()).thenReturn(true); + assertEquals(16, shield.write(buf)); + verify(mockChannel).write(buf); + } + + @Test + void testWriteIOExceptionPropagates() throws IOException { + final ByteBuffer buf = ByteBuffer.allocate(8); + when(mockChannel.isOpen()).thenReturn(true); + when(mockChannel.write(buf)).thenThrow(new IOException("write failure")); + assertThrows(IOException.class, () -> shield.write(buf)); + verify(mockChannel).write(buf); + } + + @Test + void testWriteThrowsClosedChannelExceptionAfterShieldClose() throws IOException { + shield.close(); + assertThrows(ClosedChannelException.class, () -> shield.write(ByteBuffer.allocate(8))); + verify(mockChannel, never()).write(ArgumentMatchers.any()); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java index ac7e85b7a89..4676e64c060 100644 --- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java +++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -32,22 +33,27 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import java.nio.channels.AsynchronousByteChannel; +import java.io.IOException; import java.nio.channels.AsynchronousChannel; import java.nio.channels.ByteChannel; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; import java.nio.channels.GatheringByteChannel; import java.nio.channels.InterruptibleChannel; -import java.nio.channels.MulticastChannel; import java.nio.channels.NetworkChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.ScatteringByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.file.Path; +import java.util.List; import java.util.stream.Stream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.ClassUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -56,17 +62,17 @@ */ class CloseShieldChannelTest { - static Stream> testedInterfaces() { + /** + * JRE {@link Channel} interfaces. + */ + static Stream> channelInterfaces() { // @formatter:off return Stream.of( - AsynchronousByteChannel.class, AsynchronousChannel.class, ByteChannel.class, Channel.class, GatheringByteChannel.class, InterruptibleChannel.class, - MulticastChannel.class, - NetworkChannel.class, NetworkChannel.class, ReadableByteChannel.class, ScatteringByteChannel.class, @@ -75,8 +81,15 @@ static Stream> testedInterfaces() { // @formatter:on } + /** + * Gets all interfaces implemented by the class {@link FileChannel}. + */ + static List> fileChannelInterfaces() { + return ClassUtils.getAllInterfaces(FileChannel.class); + } + @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testCloseDoesNotCloseDelegate(final Class channelClass) throws Exception { final Channel channel = mock(channelClass); final Channel shield = CloseShieldChannel.wrap(channel); @@ -85,7 +98,7 @@ void testCloseDoesNotCloseDelegate(final Class channelClass) } @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testCloseIsIdempotent(final Class channelClass) throws Exception { final Channel channel = mock(channelClass); final Channel shield = CloseShieldChannel.wrap(channel); @@ -97,9 +110,9 @@ void testCloseIsIdempotent(final Class channelClass) throws E } @ParameterizedTest - @MethodSource("testedInterfaces") - void testCloseIsShielded(final Class channelClass) throws Exception { - final Channel channel = mock(channelClass); + @MethodSource("channelInterfaces") + void testCloseIsShielded(final Class channelInterface) throws Exception { + final Channel channel = mock(channelInterface); when(channel.isOpen()).thenReturn(true, false, true, false); final Channel shield = CloseShieldChannel.wrap(channel); // Reflects delegate state initially @@ -122,7 +135,7 @@ void testDoesNotDoubleWrap() { } @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testEquals(final Class channelClass) throws Exception { final Channel channel = mock(channelClass); final Channel shield = CloseShieldChannel.wrap(channel); @@ -149,7 +162,7 @@ void testGatheringByteChannelMethods() throws Exception { } @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testHashCode(final Class channelClass) throws Exception { final Channel channel = mock(channelClass); final Channel shield = CloseShieldChannel.wrap(channel); @@ -189,7 +202,7 @@ void testNetworkChannelMethods() throws Exception { } @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testPreservesInterfaces(final Class channelClass) { final Channel channel = mock(channelClass); final Channel shield = CloseShieldChannel.wrap(channel); @@ -256,7 +269,7 @@ void testSeekableByteChannelMethods() throws Exception { } @ParameterizedTest - @MethodSource("testedInterfaces") + @MethodSource("channelInterfaces") void testToString(final Class channelClass) throws Exception { final Channel channel = mock(channelClass); when(channel.toString()).thenReturn("MyChannel"); @@ -266,6 +279,17 @@ void testToString(final Class channelClass) throws Exception assertTrue(shieldString.contains("MyChannel")); } + @Test + void testWrapFileChannel(final @TempDir Path tempDir) throws IOException { + final Path testFile = tempDir.resolve("test.txt"); + FileUtils.touch(testFile.toFile()); + try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) { + fileChannelInterfaces().forEach(iface -> assertInstanceOf(iface, shield)); + // FileChannel is not an interface, so can not be implemented. + assertFalse(shield instanceof FileChannel, "not FileChannel"); + } + } + @Test void testWritableByteChannelMethods() throws Exception { final WritableByteChannel channel = mock(WritableByteChannel.class); diff --git a/src/test/java/org/apache/commons/io/channels/FileChannelsTest.java b/src/test/java/org/apache/commons/io/channels/FileChannelsTest.java index bbfbee9c575..c0727709daf 100644 --- a/src/test/java/org/apache/commons/io/channels/FileChannelsTest.java +++ b/src/test/java/org/apache/commons/io/channels/FileChannelsTest.java @@ -91,7 +91,7 @@ private static FileChannel wrap(final FileChannel fc, final FileChannelType file case STOCK: return fc; case PROXY: - return new FileChannelProxy(fc); + return new FilterFileChannel(fc); case FIXED_READ_SIZE: return new FixedReadSizeFileChannelProxy(fc, readSize); default: diff --git a/src/test/java/org/apache/commons/io/channels/FilterByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterByteChannelTest.java new file mode 100644 index 00000000000..0994b726d14 --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterByteChannelTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterByteChannel}. + */ +class FilterByteChannelTest { + + private FilterByteChannel buildFilterByteChannel(final ByteChannel channel) throws IOException { + return FilterByteChannel.forByteChannel().setChannel(channel).get(); + } + + @Test + void testBuilderRequiresChannel() { + assertThrows(IllegalStateException.class, () -> FilterByteChannel.forByteChannel().get()); + } + + @Test + void testClose() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testImplementsByteChannel() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertInstanceOf(ByteChannel.class, filterChannel); + } + + @Test + void testIsOpenAfterClose() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + assertFalse(filterChannel.isOpen()); + } + + @Test + void testIsOpenDelegatesToChannel() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertTrue(filterChannel.isOpen()); + assertFalse(filterChannel.isOpen()); + verify(channel, times(2)).isOpen(); + } + + @Test + void testReadDelegatesToChannel() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(8); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertEquals(8, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadPropagatesIOException() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenThrow(new IOException("read error")); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertThrows(IOException.class, () -> filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadReturnsMinusOneAtEndOfStream() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(-1); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertEquals(-1, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testUnwrapReturnsWrappedChannel() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertSame(channel, filterChannel.unwrap()); + } + + @Test + void testWriteDelegatesToChannel() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenReturn(16); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertEquals(16, filterChannel.write(buffer)); + verify(channel).write(buffer); + } + + @Test + void testWritePropagatesIOException() throws IOException { + final ByteChannel channel = mock(ByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenThrow(new IOException("write error")); + final FilterByteChannel filterChannel = buildFilterByteChannel(channel); + assertThrows(IOException.class, () -> filterChannel.write(buffer)); + verify(channel).write(buffer); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FilterChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterChannelTest.java new file mode 100644 index 00000000000..68bea2a717f --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterChannelTest.java @@ -0,0 +1,93 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.channels.Channel; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterChannel}. + */ +class FilterChannelTest { + + private FilterChannel buildFilterChannel(final Channel channel) throws IOException { + return FilterChannel.forChannel().setChannel(channel).get(); + } + + @Test + void testBuilderRequiresChannel() { + assertThrows(IllegalStateException.class, () -> FilterChannel.forChannel().get()); + } + + @Test + void testClose() throws IOException { + final Channel channel = mock(Channel.class); + final FilterChannel filterChannel = buildFilterChannel(channel); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testCloseClosesUnderlyingChannel() throws IOException { + final Channel channel = mock(Channel.class); + when(channel.isOpen()).thenReturn(true); + final FilterChannel filterChannel = buildFilterChannel(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testIsOpenAfterClose() throws IOException { + final Channel channel = mock(Channel.class); + // Simulate the channel reporting open=true then open=false after close + when(channel.isOpen()).thenReturn(true).thenReturn(false); + final FilterChannel filterChannel = buildFilterChannel(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + assertFalse(filterChannel.isOpen()); + } + + @Test + void testIsOpenDelegatesToChannel() throws IOException { + final Channel channel = mock(Channel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterChannel filterChannel = buildFilterChannel(channel); + assertTrue(filterChannel.isOpen()); + assertFalse(filterChannel.isOpen()); + verify(channel, org.mockito.Mockito.times(2)).isOpen(); + } + + @Test + void testUnwrapReturnsWrappedChannel() throws IOException { + final Channel channel = mock(Channel.class); + final FilterChannel filterChannel = buildFilterChannel(channel); + assertSame(channel, filterChannel.unwrap()); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FilterFileChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterFileChannelTest.java new file mode 100644 index 00000000000..cfd78ae3f00 --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterFileChannelTest.java @@ -0,0 +1,450 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterFileChannel}. + */ +class FilterFileChannelTest { + + private FilterFileChannel build(final FileChannel channel) { + return new FilterFileChannel(channel); + } + + private FileChannel mockFileChannel() { + return mock(FileChannel.class); + } + + @Test + void testBuilderForNothing() throws IOException { + assertThrows(IllegalStateException.class, () -> FilterFileChannel.forFilterFileChannel().get()); + } + + @Test + void testConstructorRequiresNonNullChannel() { + assertThrows(NullPointerException.class, () -> new FilterFileChannel((FileChannel) null)); + } + + @Test + void testEqualsDelegatesToChannel() { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + // equals() delegates to the underlying channel. The mock returns false by default, + // so filter.equals(anything) should be false. + final Object other = new Object(); + assertEquals(channel.equals(other), filter.equals(other)); + } + + @Test + void testForceDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + filter.force(true); + verify(channel).force(true); + } + + @Test + void testForcePropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + // IOException is checked via implCloseChannel; use force directly + final IOException ex = new IOException("force error"); + org.mockito.Mockito.doThrow(ex).when(channel).force(false); + assertThrows(IOException.class, () -> filter.force(false)); + verify(channel).force(false); + } + + @Test + void testHashCodeDelegatesToChannel() { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + // hashCode() delegates to the underlying channel; both should return the same value. + assertEquals(channel.hashCode(), filter.hashCode()); + } + + @Test + void testImplCloseChannelDelegatesToChannelClose() throws IOException { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + filter.close(); + verify(channel).close(); + } + + @Test + void testImplementsFileChannel() { + final FileChannel channel = mockFileChannel(); + assertInstanceOf(FileChannel.class, build(channel)); + } + + @Test + void testLockDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final FileLock lock = mock(FileLock.class); + when(channel.lock(0L, Long.MAX_VALUE, false)).thenReturn(lock); + final FilterFileChannel filter = build(channel); + assertSame(lock, filter.lock(0L, Long.MAX_VALUE, false)); + verify(channel).lock(0L, Long.MAX_VALUE, false); + } + + @Test + void testLockPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.lock(0L, Long.MAX_VALUE, false)).thenThrow(new IOException("lock error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.lock(0L, Long.MAX_VALUE, false)); + verify(channel).lock(0L, Long.MAX_VALUE, false); + } + + @Test + void testMapDelegatesToChannel() throws IOException { + final Path tmp = Files.createTempFile("FilterFileChannelTest", ".bin"); + try { + Files.write(tmp, new byte[1024]); + try (FileChannel real = FileChannel.open(tmp, StandardOpenOption.READ); FilterFileChannel filter = build(real)) { + final MappedByteBuffer mapped = filter.map(MapMode.READ_ONLY, 0L, 1024L); + assertNotNull(mapped); + assertEquals(1024, mapped.capacity()); + } + } finally { + Files.deleteIfExists(tmp); + } + } + + @Test + void testMapPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.map(MapMode.READ_ONLY, 0L, 1024L)).thenThrow(new IOException("map error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.map(MapMode.READ_ONLY, 0L, 1024L)); + verify(channel).map(MapMode.READ_ONLY, 0L, 1024L); + } + + @Test + void testPositionDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.position()).thenReturn(42L); + final FilterFileChannel filter = build(channel); + assertEquals(42L, filter.position()); + verify(channel).position(); + } + + @Test + void testPositionPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.position()).thenThrow(new IOException("position error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, filter::position); + verify(channel).position(); + } + + @Test + void testReadAtPositionDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer, 10L)).thenReturn(8); + final FilterFileChannel filter = build(channel); + assertEquals(8, filter.read(buffer, 10L)); + verify(channel).read(buffer, 10L); + } + + @Test + void testReadAtPositionPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer, 10L)).thenThrow(new IOException("read error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.read(buffer, 10L)); + verify(channel).read(buffer, 10L); + } + // -- read(ByteBuffer) -- + + @Test + void testReadDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(8); + final FilterFileChannel filter = build(channel); + assertEquals(8, filter.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenThrow(new IOException("read error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadReturnsMinusOneAtEndOfStream() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(-1); + final FilterFileChannel filter = build(channel); + assertEquals(-1, filter.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadScatteringDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer[] buffers = { ByteBuffer.allocate(8), ByteBuffer.allocate(8) }; + when(channel.read(buffers, 0, 2)).thenReturn(16L); + final FilterFileChannel filter = build(channel); + assertEquals(16L, filter.read(buffers, 0, 2)); + verify(channel).read(buffers, 0, 2); + } + + @Test + void testReadScatteringPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer[] buffers = { ByteBuffer.allocate(8), ByteBuffer.allocate(8) }; + when(channel.read(buffers, 0, 2)).thenThrow(new IOException("scatter read error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.read(buffers, 0, 2)); + verify(channel).read(buffers, 0, 2); + } + + @Test + void testSetPositionDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.position(100L)).thenReturn(channel); + final FilterFileChannel filter = build(channel); + assertSame(channel, filter.position(100L)); + verify(channel).position(100L); + } + + @Test + void testSetPositionPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.position(100L)).thenThrow(new IOException("position error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.position(100L)); + verify(channel).position(100L); + } + + @Test + void testSizeDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.size()).thenReturn(2048L); + final FilterFileChannel filter = build(channel); + assertEquals(2048L, filter.size()); + verify(channel).size(); + } + + @Test + void testSizePropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.size()).thenThrow(new IOException("size error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, filter::size); + verify(channel).size(); + } + + @Test + void testToStringDelegatesToChannel() { + final FileChannel channel = mockFileChannel(); + when(channel.toString()).thenReturn("mockChannel"); + final FilterFileChannel filter = build(channel); + assertEquals("mockChannel", filter.toString()); + } + + @Test + void testTransferFromDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ReadableByteChannel src = mock(ReadableByteChannel.class); + when(channel.transferFrom(src, 0L, 512L)).thenReturn(512L); + final FilterFileChannel filter = build(channel); + assertEquals(512L, filter.transferFrom(src, 0L, 512L)); + verify(channel).transferFrom(src, 0L, 512L); + } + + @Test + void testTransferFromPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ReadableByteChannel src = mock(ReadableByteChannel.class); + when(channel.transferFrom(src, 0L, 512L)).thenThrow(new IOException("transferFrom error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.transferFrom(src, 0L, 512L)); + verify(channel).transferFrom(src, 0L, 512L); + } + + @Test + void testTransferToDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final WritableByteChannel target = mock(WritableByteChannel.class); + when(channel.transferTo(0L, 512L, target)).thenReturn(512L); + final FilterFileChannel filter = build(channel); + assertEquals(512L, filter.transferTo(0L, 512L, target)); + verify(channel).transferTo(0L, 512L, target); + } + + @Test + void testTransferToPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final WritableByteChannel target = mock(WritableByteChannel.class); + when(channel.transferTo(0L, 512L, target)).thenThrow(new IOException("transferTo error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.transferTo(0L, 512L, target)); + verify(channel).transferTo(0L, 512L, target); + } + + @Test + void testTruncateDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.truncate(512L)).thenReturn(channel); + final FilterFileChannel filter = build(channel); + assertSame(channel, filter.truncate(512L)); + verify(channel).truncate(512L); + } + + @Test + void testTruncatePropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.truncate(512L)).thenThrow(new IOException("truncate error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.truncate(512L)); + verify(channel).truncate(512L); + } + + @Test + void testTryLockDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final FileLock lock = mock(FileLock.class); + when(channel.tryLock(0L, Long.MAX_VALUE, false)).thenReturn(lock); + final FilterFileChannel filter = build(channel); + assertSame(lock, filter.tryLock(0L, Long.MAX_VALUE, false)); + verify(channel).tryLock(0L, Long.MAX_VALUE, false); + } + + @Test + void testTryLockPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.tryLock(0L, Long.MAX_VALUE, false)).thenThrow(new IOException("tryLock error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.tryLock(0L, Long.MAX_VALUE, false)); + verify(channel).tryLock(0L, Long.MAX_VALUE, false); + } + + @Test + void testTryLockReturnsNullWhenLockNotAcquired() throws IOException { + final FileChannel channel = mockFileChannel(); + when(channel.tryLock(0L, Long.MAX_VALUE, false)).thenReturn(null); + final FilterFileChannel filter = build(channel); + assertEquals(null, filter.tryLock(0L, Long.MAX_VALUE, false)); + verify(channel).tryLock(0L, Long.MAX_VALUE, false); + } + + @Test + void testUnwrapIsNotNull() { + assertNotNull(build(mockFileChannel()).unwrap()); + } + + @Test + void testUnwrapReturnsWrappedChannel() { + final FileChannel channel = mockFileChannel(); + final FilterFileChannel filter = build(channel); + assertSame(channel, filter.unwrap()); + } + + @Test + void testWriteAtPositionDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer, 10L)).thenReturn(16); + final FilterFileChannel filter = build(channel); + assertEquals(16, filter.write(buffer, 10L)); + verify(channel).write(buffer, 10L); + } + + @Test + void testWriteAtPositionPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer, 10L)).thenThrow(new IOException("write error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.write(buffer, 10L)); + verify(channel).write(buffer, 10L); + } + + @Test + void testWriteDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenReturn(16); + final FilterFileChannel filter = build(channel); + assertEquals(16, filter.write(buffer)); + verify(channel).write(buffer); + } + + @Test + void testWriteGatheringDelegatesToChannel() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer[] buffers = { ByteBuffer.allocate(8), ByteBuffer.allocate(8) }; + when(channel.write(buffers, 0, 2)).thenReturn(16L); + final FilterFileChannel filter = build(channel); + assertEquals(16L, filter.write(buffers, 0, 2)); + verify(channel).write(buffers, 0, 2); + } + + @Test + void testWriteGatheringPropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer[] buffers = { ByteBuffer.allocate(8), ByteBuffer.allocate(8) }; + when(channel.write(buffers, 0, 2)).thenThrow(new IOException("gather write error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.write(buffers, 0, 2)); + verify(channel).write(buffers, 0, 2); + } + + @Test + void testWritePropagatesIOException() throws IOException { + final FileChannel channel = mockFileChannel(); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenThrow(new IOException("write error")); + final FilterFileChannel filter = build(channel); + assertThrows(IOException.class, () -> filter.write(buffer)); + verify(channel).write(buffer); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FilterReadableByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterReadableByteChannelTest.java new file mode 100644 index 00000000000..585b5e81da0 --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterReadableByteChannelTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterReadableByteChannel}. + */ +class FilterReadableByteChannelTest { + + private FilterReadableByteChannel build(final ReadableByteChannel channel) throws IOException { + return new FilterReadableByteChannel(channel); + } + + @Test + void testBuilderRequiresChannel() { + assertThrows(IllegalStateException.class, () -> FilterReadableByteChannel.forReadableByteChannel().get()); + } + + @Test + void testClose() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + final FilterReadableByteChannel filterChannel = build(channel); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testImplementsReadableByteChannel() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + assertInstanceOf(ReadableByteChannel.class, build(channel)); + } + + @Test + void testIsOpenAfterClose() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterReadableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + assertFalse(filterChannel.isOpen()); + } + + @Test + void testIsOpenDelegatesToChannel() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterReadableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + assertFalse(filterChannel.isOpen()); + verify(channel, times(2)).isOpen(); + } + + @Test + void testReadDelegatesToChannel() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(8); + final FilterReadableByteChannel filterChannel = build(channel); + assertEquals(8, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadMultipleCalls() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(4, 4, -1); + final FilterReadableByteChannel filterChannel = build(channel); + assertEquals(4, filterChannel.read(buffer)); + assertEquals(4, filterChannel.read(buffer)); + assertEquals(-1, filterChannel.read(buffer)); + verify(channel, times(3)).read(buffer); + } + + @Test + void testReadPropagatesIOException() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenThrow(new IOException("read error")); + final FilterReadableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadReturnsMinusOneAtEndOfStream() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(-1); + final FilterReadableByteChannel filterChannel = build(channel); + assertEquals(-1, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testUnwrapReturnsWrappedChannel() throws IOException { + final ReadableByteChannel channel = mock(ReadableByteChannel.class); + assertSame(channel, build(channel).unwrap()); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FilterSeekableByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterSeekableByteChannelTest.java new file mode 100644 index 00000000000..997eac4dd8c --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterSeekableByteChannelTest.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterSeekableByteChannel}. + */ +class FilterSeekableByteChannelTest { + + private FilterSeekableByteChannel build(final SeekableByteChannel channel) throws IOException { + return new FilterSeekableByteChannel(channel); + } + + @Test + void testBuilderRequiresChannel() { + assertThrows(IllegalStateException.class, () -> FilterSeekableByteChannel.forSeekableByteChannel().get()); + } + + @Test + void testClose() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final FilterSeekableByteChannel filterChannel = build(channel); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testImplementsSeekableByteChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + assertInstanceOf(SeekableByteChannel.class, build(channel)); + } + + @Test + void testIsOpenAfterClose() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterSeekableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + assertFalse(filterChannel.isOpen()); + } + + @Test + void testIsOpenDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterSeekableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + assertFalse(filterChannel.isOpen()); + verify(channel, times(2)).isOpen(); + } + // -- read -- + + @Test + void testPositionDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.position()).thenReturn(42L); + final FilterSeekableByteChannel filterChannel = build(channel); + assertEquals(42L, filterChannel.position()); + verify(channel).position(); + } + + @Test + void testPositionPropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.position()).thenThrow(new IOException("position error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, filterChannel::position); + verify(channel).position(); + } + + @Test + void testReadDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(8); + final FilterSeekableByteChannel filterChannel = build(channel); + assertEquals(8, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + // -- write -- + + @Test + void testReadPropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenThrow(new IOException("read error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.read(buffer)); + verify(channel).read(buffer); + } + + @Test + void testReadReturnsMinusOneAtEndOfStream() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.read(buffer)).thenReturn(-1); + final FilterSeekableByteChannel filterChannel = build(channel); + assertEquals(-1, filterChannel.read(buffer)); + verify(channel).read(buffer); + } + // -- position() -- + + @Test + void testSetPositionDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.position(10L)).thenReturn(channel); + final FilterSeekableByteChannel filterChannel = build(channel); + assertSame(channel, filterChannel.position(10L)); + verify(channel).position(10L); + } + + @Test + void testSetPositionPropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.position(10L)).thenThrow(new IOException("position error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.position(10L)); + verify(channel).position(10L); + } + // -- position(long) -- + + @Test + void testSizeDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.size()).thenReturn(1024L); + final FilterSeekableByteChannel filterChannel = build(channel); + assertEquals(1024L, filterChannel.size()); + verify(channel).size(); + } + + @Test + void testSizePropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.size()).thenThrow(new IOException("size error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, filterChannel::size); + verify(channel).size(); + } + // -- size() -- + + @Test + void testTruncateDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.truncate(512L)).thenReturn(channel); + final FilterSeekableByteChannel filterChannel = build(channel); + assertSame(channel, filterChannel.truncate(512L)); + verify(channel).truncate(512L); + } + + @Test + void testTruncatePropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + when(channel.truncate(512L)).thenThrow(new IOException("truncate error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.truncate(512L)); + verify(channel).truncate(512L); + } + // -- truncate(long) -- + + @Test + void testUnwrapReturnsWrappedChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + assertSame(channel, build(channel).unwrap()); + } + + @Test + void testWriteDelegatesToChannel() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenReturn(16); + final FilterSeekableByteChannel filterChannel = build(channel); + assertEquals(16, filterChannel.write(buffer)); + verify(channel).write(buffer); + } + // -- unwrap -- + + @Test + void testWritePropagatesIOException() throws IOException { + final SeekableByteChannel channel = mock(SeekableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenThrow(new IOException("write error")); + final FilterSeekableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.write(buffer)); + verify(channel).write(buffer); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FilterWritableByteChannelTest.java b/src/test/java/org/apache/commons/io/channels/FilterWritableByteChannelTest.java new file mode 100644 index 00000000000..a9dd60dc9a0 --- /dev/null +++ b/src/test/java/org/apache/commons/io/channels/FilterWritableByteChannelTest.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FilterWritableByteChannel}. + */ +class FilterWritableByteChannelTest { + + private FilterWritableByteChannel build(final WritableByteChannel channel) throws IOException { + return new FilterWritableByteChannel(channel); + } + + @Test + void testBuilderRequiresChannel() { + assertThrows(IllegalStateException.class, () -> FilterWritableByteChannel.forWritableByteChannel().get()); + } + + @Test + void testClose() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + final FilterWritableByteChannel filterChannel = build(channel); + filterChannel.close(); + verify(channel).close(); + } + + @Test + void testImplementsWritableByteChannel() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + assertInstanceOf(WritableByteChannel.class, build(channel)); + } + + @Test + void testIsOpenAfterClose() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterWritableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + filterChannel.close(); + verify(channel).close(); + assertFalse(filterChannel.isOpen()); + } + + @Test + void testIsOpenDelegatesToChannel() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + when(channel.isOpen()).thenReturn(true, false); + final FilterWritableByteChannel filterChannel = build(channel); + assertTrue(filterChannel.isOpen()); + assertFalse(filterChannel.isOpen()); + verify(channel, times(2)).isOpen(); + } + + @Test + void testUnwrapReturnsWrappedChannel() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + assertSame(channel, build(channel).unwrap()); + } + + @Test + void testWriteDelegatesToChannel() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenReturn(16); + final FilterWritableByteChannel filterChannel = build(channel); + assertEquals(16, filterChannel.write(buffer)); + verify(channel).write(buffer); + } + + @Test + void testWriteMultipleCalls() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenReturn(8, 8); + final FilterWritableByteChannel filterChannel = build(channel); + assertEquals(8, filterChannel.write(buffer)); + assertEquals(8, filterChannel.write(buffer)); + verify(channel, times(2)).write(buffer); + } + + @Test + void testWritePropagatesIOException() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + when(channel.write(buffer)).thenThrow(new IOException("write error")); + final FilterWritableByteChannel filterChannel = build(channel); + assertThrows(IOException.class, () -> filterChannel.write(buffer)); + verify(channel).write(buffer); + } + + @Test + void testWriteReturnsZeroOnFullBuffer() throws IOException { + final WritableByteChannel channel = mock(WritableByteChannel.class); + final ByteBuffer buffer = ByteBuffer.allocate(0); + when(channel.write(buffer)).thenReturn(0); + final FilterWritableByteChannel filterChannel = build(channel); + assertEquals(0, filterChannel.write(buffer)); + verify(channel).write(buffer); + } +} diff --git a/src/test/java/org/apache/commons/io/channels/FixedReadSizeFileChannelProxy.java b/src/test/java/org/apache/commons/io/channels/FixedReadSizeFileChannelProxy.java index bfb1b72576b..bd77ed8320f 100644 --- a/src/test/java/org/apache/commons/io/channels/FixedReadSizeFileChannelProxy.java +++ b/src/test/java/org/apache/commons/io/channels/FixedReadSizeFileChannelProxy.java @@ -24,7 +24,7 @@ /** * Always reads the same amount of bytes on each call (or less). */ -class FixedReadSizeFileChannelProxy extends FileChannelProxy { +class FixedReadSizeFileChannelProxy extends FilterFileChannel { final int readSize; diff --git a/src/test/java/org/apache/commons/io/channels/NonBlockingFileChannelProxy.java b/src/test/java/org/apache/commons/io/channels/NonBlockingFileChannelProxy.java index 0af10704d41..d9c389d0619 100644 --- a/src/test/java/org/apache/commons/io/channels/NonBlockingFileChannelProxy.java +++ b/src/test/java/org/apache/commons/io/channels/NonBlockingFileChannelProxy.java @@ -25,7 +25,7 @@ /** * Simulates a non-blocking file channel by returning 0 from reads every other call as allowed by {@link ReadableByteChannel} and {@link FileChannel}. */ -class NonBlockingFileChannelProxy extends FileChannelProxy { +class NonBlockingFileChannelProxy extends FilterFileChannel { boolean toggleRead0; diff --git a/src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java b/src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java index 88d291af1eb..2afe9ca656c 100644 --- a/src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java +++ b/src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java @@ -17,12 +17,16 @@ package org.apache.commons.io.file; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.io.TempDir; @@ -34,6 +38,18 @@ public abstract class AbstractTempDirTest { protected static final String SUB_DIR = "subdir"; protected static final String SYMLINKED_DIR = "symlinked-dir"; + public static void assertCreateNewFile(final File file) throws IOException { + assertTrue(file.createNewFile(), file::toString); + } + + public static void assertDelete(final boolean expected, final File file) { + assertEquals(expected, file.delete(), file::toString); + } + + public static void assertMkdir(final boolean expected, final File subDir) { + assertEquals(expected, subDir.mkdir(), subDir::toString); + } + /** * Creates directory test fixtures in the given directory {@code rootDir}. *

      @@ -67,9 +83,17 @@ protected static Path createTempSymbolicLinkedRelativeDir(final Path rootDir) th */ public Path tempDirPath; + @AfterEach + void afterEachDeleteTempDir() throws IOException { + // For @TempDir, symbolic and other types of links, such as junctions on Windows, are not followed. + if (Files.exists(tempDirPath)) { + PathUtils.deleteDirectory(tempDirPath); + } + } + @BeforeEach public void beforeEachCreateTempDirs() throws IOException { - tempDirPath = Files.createTempDirectory(managedTempDirPath, getClass().getSimpleName()); + tempDirPath = Files.createDirectory(managedTempDirPath.resolve(getClass().getSimpleName())); tempDirFile = tempDirPath.toFile(); } diff --git a/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java b/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java index 4380823d1bf..1bfe338db9d 100644 --- a/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java +++ b/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java @@ -207,6 +207,7 @@ public FileVisitResult visitFile(final Path path, final BasicFileAttributes attr try { ThreadUtils.sleep(Duration.ofMillis(10)); } catch (final InterruptedException ignore) { + Thread.currentThread().interrupt(); // e.printStackTrace(); } return super.visitFile(path, attributes); diff --git a/src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java b/src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java index 340ff2f9f8e..1ac7d98c1c4 100644 --- a/src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java +++ b/src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.file.Files; @@ -28,7 +29,6 @@ import java.nio.file.Paths; import org.apache.commons.io.file.Counters.PathCounters; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -112,7 +112,7 @@ void testCleanFolders1FileSize1Skip(final PathCounters pathCounters) throws IOEx assertCounts(1, 1, 1, visitFileTree); assertSame(visitor, visitFileTree); final Path skippedFile = tempDir.resolve(skipFileName); - Assertions.assertTrue(Files.exists(skippedFile)); + assertTrue(Files.exists(skippedFile)); Files.delete(skippedFile); // assertNotEquals(visitFileTree, CleaningPathVisitor.withLongCounters()); diff --git a/src/test/java/org/apache/commons/io/file/CountersEqualsAndHashCodeTest.java b/src/test/java/org/apache/commons/io/file/CountersEqualsAndHashCodeTest.java index 31c2fa2ae92..98f5c34d938 100644 --- a/src/test/java/org/apache/commons/io/file/CountersEqualsAndHashCodeTest.java +++ b/src/test/java/org/apache/commons/io/file/CountersEqualsAndHashCodeTest.java @@ -17,9 +17,11 @@ package org.apache.commons.io.file; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + import org.apache.commons.io.file.Counters.Counter; import org.apache.commons.io.file.Counters.PathCounters; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class CountersEqualsAndHashCodeTest { @@ -35,51 +37,51 @@ void testBigIntegerHashCode() { } private void testEquals(final Counter counter1, final Counter counter2) { - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); counter1.increment(); - Assertions.assertNotEquals(counter1, counter2); + assertNotEquals(counter1, counter2); counter2.increment(); - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); } private void testEqualsByteCounters(final PathCounters counter1, final PathCounters counter2) { - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); counter1.getByteCounter().increment(); - Assertions.assertNotEquals(counter1, counter2); + assertNotEquals(counter1, counter2); counter2.getByteCounter().increment(); - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); } private void testEqualsDirectoryCounters(final PathCounters counter1, final PathCounters counter2) { - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); counter1.getDirectoryCounter().increment(); - Assertions.assertNotEquals(counter1, counter2); + assertNotEquals(counter1, counter2); counter2.getDirectoryCounter().increment(); - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); } private void testEqualsFileCounters(final PathCounters counter1, final PathCounters counter2) { - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); counter1.getFileCounter().increment(); - Assertions.assertNotEquals(counter1, counter2); + assertNotEquals(counter1, counter2); counter2.getFileCounter().increment(); - Assertions.assertEquals(counter1, counter2); + assertEquals(counter1, counter2); } private void testHashCodeFileCounters(final PathCounters counter1, final PathCounters counter2) { - Assertions.assertEquals(counter1.hashCode(), counter2.hashCode()); + assertEquals(counter1.hashCode(), counter2.hashCode()); counter1.getFileCounter().increment(); - Assertions.assertNotEquals(counter1.hashCode(), counter2.hashCode()); + assertNotEquals(counter1.hashCode(), counter2.hashCode()); counter2.getFileCounter().increment(); - Assertions.assertEquals(counter1.hashCode(), counter2.hashCode()); + assertEquals(counter1.hashCode(), counter2.hashCode()); } private void testHashCodes(final Counter counter1, final Counter counter2) { - Assertions.assertEquals(counter1.hashCode(), counter2.hashCode()); + assertEquals(counter1.hashCode(), counter2.hashCode()); counter1.increment(); - Assertions.assertNotEquals(counter1.hashCode(), counter2.hashCode()); + assertNotEquals(counter1.hashCode(), counter2.hashCode()); counter2.increment(); - Assertions.assertEquals(counter1.hashCode(), counter2.hashCode()); + assertEquals(counter1.hashCode(), counter2.hashCode()); } @Test diff --git a/src/test/java/org/apache/commons/io/file/CountingPathVisitorTest.java b/src/test/java/org/apache/commons/io/file/CountingPathVisitorTest.java index 54a0e9a0a95..381d898ddcd 100644 --- a/src/test/java/org/apache/commons/io/file/CountingPathVisitorTest.java +++ b/src/test/java/org/apache/commons/io/file/CountingPathVisitorTest.java @@ -18,10 +18,10 @@ package org.apache.commons.io.file; import static org.apache.commons.io.file.CounterAssertions.assertCounts; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -31,8 +31,8 @@ class CountingPathVisitorTest extends TestArguments { private void checkZeroCounts(final CountingPathVisitor visitor) { - Assertions.assertEquals(CountingPathVisitor.withLongCounters(), visitor); - Assertions.assertEquals(CountingPathVisitor.withBigIntegerCounters(), visitor); + assertEquals(CountingPathVisitor.withLongCounters(), visitor); + assertEquals(CountingPathVisitor.withBigIntegerCounters(), visitor); } /** diff --git a/src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java b/src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java index db5e3a63a79..f477c66faf9 100644 --- a/src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java +++ b/src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java @@ -30,7 +30,6 @@ import java.nio.file.Paths; import org.apache.commons.io.file.Counters.PathCounters; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -104,7 +103,7 @@ void testDeleteFolders1FileSize1Skip(final PathCounters pathCounters) throws IOE final CountingPathVisitor visitor = new DeletingPathVisitor(pathCounters, skipFileName); assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDirPath)); final Path skippedFile = tempDirPath.resolve(skipFileName); - Assertions.assertTrue(Files.exists(skippedFile)); + assertTrue(Files.exists(skippedFile)); Files.delete(skippedFile); } diff --git a/src/test/java/org/apache/commons/io/file/FilesUncheckTest.java b/src/test/java/org/apache/commons/io/file/FilesUncheckTest.java index 137e5ca2f50..42b77ad7470 100644 --- a/src/test/java/org/apache/commons/io/file/FilesUncheckTest.java +++ b/src/test/java/org/apache/commons/io/file/FilesUncheckTest.java @@ -58,8 +58,10 @@ import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * Tests {@link FilesUncheck}. @@ -72,19 +74,35 @@ class FilesUncheckTest { private static final Path FILE_PATH_A = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); - private static final Path FILE_PATH_EMPTY = Paths.get("src/test/resources/org/apache/commons/io/test-file-empty.bin"); + private static final Path FILE_PATH_EMPTY_SRC = Paths.get("src/test/resources/org/apache/commons/io/test-file-empty.bin"); - private static final Path NEW_DIR_PATH = Paths.get("target/newdir"); + private static Path FILE_PATH_EMPTY; - private static final Path NEW_FILE_PATH = Paths.get("target/file.txt"); + private static Path NEW_DIR_PATH; - private static final Path NEW_FILE_PATH_LINK = Paths.get("target/to_another_file.txt"); + private static Path NEW_FILE_PATH; + + private static Path NEW_FILE_PATH_LINK; private static final String PREFIX = "prefix"; private static final String SUFFIX = "suffix"; - private static final Path TARGET_PATH = Paths.get("target"); + @TempDir + static Path DEST_PATH; + + @BeforeAll + public static void beforeAll() throws IOException { + NEW_DIR_PATH = DEST_PATH.resolve("newdir"); + NEW_FILE_PATH = DEST_PATH.resolve("file.txt"); + NEW_FILE_PATH_LINK = DEST_PATH.resolve("to_another_file.txt"); + // Allowance for Java 25, GitHub CI on Windows, when cloned repository is on a different volume than the Java temporary directory. + // Details: + // On GitHub CI on Windows, the cloned repository is on a different volume than the Java temporary directory. + // This means we cannot use the source file directly as the target of a link. + // Copy the text fixture to the destination directory to make sure it is on the same volume as the Java temporary directory. + FILE_PATH_EMPTY = Files.copy(FILE_PATH_EMPTY_SRC, DEST_PATH.resolve(FILE_PATH_EMPTY_SRC.getFileName())); + } @BeforeEach @AfterEach @@ -111,7 +129,7 @@ void testCopyPathPathCopyOptionArray() { @Test void testCreateDirectories() { - assertEquals(TARGET_PATH, FilesUncheck.createDirectories(TARGET_PATH, EMPTY_FILE_ATTRIBUTES_ARRAY)); + assertEquals(DEST_PATH, FilesUncheck.createDirectories(DEST_PATH, EMPTY_FILE_ATTRIBUTES_ARRAY)); } @Test @@ -126,7 +144,7 @@ void testCreateFile() { @Test void testCreateLink() { - assertEquals(NEW_FILE_PATH_LINK, FilesUncheck.createLink(NEW_FILE_PATH_LINK, FILE_PATH_EMPTY)); + assertEquals(NEW_FILE_PATH_LINK, FilesUncheck.createLink(NEW_FILE_PATH_LINK, FILE_PATH_EMPTY.toAbsolutePath())); } @Test @@ -137,7 +155,7 @@ void testCreateSymbolicLink() { @Test void testCreateTempDirectoryPathStringFileAttributeOfQArray() { - assertEquals(TARGET_PATH, FilesUncheck.createTempDirectory(TARGET_PATH, PREFIX, EMPTY_FILE_ATTRIBUTES_ARRAY).getParent()); + assertEquals(DEST_PATH, FilesUncheck.createTempDirectory(DEST_PATH, PREFIX, EMPTY_FILE_ATTRIBUTES_ARRAY).getParent()); } @Test @@ -147,7 +165,7 @@ void testCreateTempDirectoryStringFileAttributeOfQArray() { @Test void testCreateTempFilePathStringStringFileAttributeOfQArray() { - assertEquals(TARGET_PATH, FilesUncheck.createTempFile(TARGET_PATH, PREFIX, SUFFIX, EMPTY_FILE_ATTRIBUTES_ARRAY).getParent()); + assertEquals(DEST_PATH, FilesUncheck.createTempFile(DEST_PATH, PREFIX, SUFFIX, EMPTY_FILE_ATTRIBUTES_ARRAY).getParent()); } @Test @@ -300,8 +318,8 @@ void testNewByteChannelPathSetOfQextendsOpenOptionFileAttributeOfQArray() { @Test void testNewDirectoryStreamPath() { Uncheck.run(() -> { - try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(TARGET_PATH)) { - directoryStream.forEach(e -> assertEquals(TARGET_PATH, e.getParent())); + try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(DEST_PATH)) { + directoryStream.forEach(e -> assertEquals(DEST_PATH, e.getParent())); } }); } @@ -309,8 +327,8 @@ void testNewDirectoryStreamPath() { @Test void testNewDirectoryStreamPathFilterOfQsuperPath() { Uncheck.run(() -> { - try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(TARGET_PATH, e -> true)) { - directoryStream.forEach(e -> assertEquals(TARGET_PATH, e.getParent())); + try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(DEST_PATH, e -> true)) { + directoryStream.forEach(e -> assertEquals(DEST_PATH, e.getParent())); } }); } @@ -318,8 +336,8 @@ void testNewDirectoryStreamPathFilterOfQsuperPath() { @Test void testNewDirectoryStreamPathString() { Uncheck.run(() -> { - try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(TARGET_PATH, "*.xml")) { - directoryStream.forEach(e -> assertEquals(TARGET_PATH, e.getParent())); + try (DirectoryStream directoryStream = FilesUncheck.newDirectoryStream(DEST_PATH, "*.xml")) { + directoryStream.forEach(e -> assertEquals(DEST_PATH, e.getParent())); } }); } @@ -346,19 +364,22 @@ void testNewOutputStream() { @Test void testProbeContentType() { - // Empty file: - String probeContentType = FilesUncheck.probeContentType(FILE_PATH_EMPTY); - // Empirical: probeContentType is null on Windows - // Empirical: probeContentType is "text/plain" on Ubuntu - // Empirical: probeContentType is ? on macOS - // // BOM file: - probeContentType = FilesUncheck.probeContentType(Paths.get("src/test/resources/org/apache/commons/io/testfileBOM.xml")); + FilesUncheck.probeContentType(Paths.get("src/test/resources/org/apache/commons/io/testfileBOM.xml")); // Empirical: probeContentType is "text/plain" on Windows // Empirical: probeContentType is "application/plain" on Ubuntu // Empirical: probeContentType is ? on macOS } + @Test + void testProbeContentTypeEmpty() { + // Empty file: + FilesUncheck.probeContentType(FILE_PATH_EMPTY); + // Empirical: probeContentType is null on Windows + // Empirical: probeContentType is "text/plain" on Ubuntu + // Empirical: probeContentType is ? on macOS + } + @Test void testReadAllBytes() { assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, FilesUncheck.readAllBytes(FILE_PATH_EMPTY)); @@ -425,24 +446,24 @@ void testSize() { @Test void testWalkFileTreePathFileVisitorOfQsuperPath() { - assertEquals(TARGET_PATH, FilesUncheck.walkFileTree(TARGET_PATH, NoopPathVisitor.INSTANCE)); + assertEquals(DEST_PATH, FilesUncheck.walkFileTree(DEST_PATH, NoopPathVisitor.INSTANCE)); } @Test void testWalkFileTreePathSetOfFileVisitOptionIntFileVisitorOfQsuperPath() { - assertEquals(TARGET_PATH, FilesUncheck.walkFileTree(TARGET_PATH, new HashSet<>(), 1, NoopPathVisitor.INSTANCE)); + assertEquals(DEST_PATH, FilesUncheck.walkFileTree(DEST_PATH, new HashSet<>(), 1, NoopPathVisitor.INSTANCE)); } @Test void testWalkPathFileVisitOptionArray() { - try (Stream stream = FilesUncheck.walk(TARGET_PATH, FileVisitOption.FOLLOW_LINKS)) { + try (Stream stream = FilesUncheck.walk(DEST_PATH, FileVisitOption.FOLLOW_LINKS)) { assertTrue(0 < stream.count()); } } @Test void testWalkPathIntFileVisitOptionArray() { - try (Stream stream = FilesUncheck.walk(TARGET_PATH, 0, FileVisitOption.FOLLOW_LINKS)) { + try (Stream stream = FilesUncheck.walk(DEST_PATH, 0, FileVisitOption.FOLLOW_LINKS)) { assertEquals(1, stream.count()); } } diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsCleanDirectoryTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsCleanDirectoryTest.java index 7fb6def5779..7a514d5fdbe 100644 --- a/src/test/java/org/apache/commons/io/file/PathUtilsCleanDirectoryTest.java +++ b/src/test/java/org/apache/commons/io/file/PathUtilsCleanDirectoryTest.java @@ -18,8 +18,10 @@ package org.apache.commons.io.file; import static org.apache.commons.io.file.CounterAssertions.assertCounts; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -41,6 +43,7 @@ class PathUtilsCleanDirectoryTest { void testCleanDirectory1FileSize0() throws IOException { PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"), tempDir); assertCounts(1, 1, 0, PathUtils.cleanDirectory(tempDir)); + assertTrue(Files.exists(tempDir)); } /** @@ -50,6 +53,7 @@ void testCleanDirectory1FileSize0() throws IOException { void testCleanDirectory1FileSize1() throws IOException { PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir); assertCounts(1, 1, 1, PathUtils.cleanDirectory(tempDir)); + assertTrue(Files.exists(tempDir)); } /** @@ -59,6 +63,7 @@ void testCleanDirectory1FileSize1() throws IOException { void testCleanDirectory2FileSize2() throws IOException { PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"), tempDir); assertCounts(3, 2, 2, PathUtils.cleanDirectory(tempDir)); + assertTrue(Files.exists(tempDir)); } /** @@ -67,5 +72,6 @@ void testCleanDirectory2FileSize2() throws IOException { @Test void testCleanEmptyDirectory() throws IOException { assertCounts(1, 0, 0, PathUtils.cleanDirectory(tempDir)); + assertTrue(Files.exists(tempDir)); } } diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsCopyTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsCopyTest.java new file mode 100644 index 00000000000..0be04f3074a --- /dev/null +++ b/src/test/java/org/apache/commons/io/file/PathUtilsCopyTest.java @@ -0,0 +1,461 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.file; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.CopyOption; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.file.Counters.PathCounters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; + +/** + * Tests {@link PathUtils}. + */ +class PathUtilsCopyTest extends AbstractTempDirTest { + + private static class CopyOptionsArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(final ParameterDeclarations parameters, final ExtensionContext context) { + return Stream.of( + Arguments.of((Object) new CopyOption[0]), + Arguments.of((Object) new CopyOption[] { LinkOption.NOFOLLOW_LINKS }) + ); + } + } + + private static final String TEST_JAR_NAME = "test.jar"; + + private static final String TEST_JAR_PATH = "src/test/resources/org/apache/commons/io/test.jar"; + + private FileSystem openArchive(final Path p, final boolean createNew) throws IOException { + if (createNew) { + final Map env = new HashMap<>(); + env.put("create", "true"); + final URI fileUri = p.toAbsolutePath().toUri(); + final URI uri = URI.create("jar:" + fileUri.toASCIIString()); + return FileSystems.newFileSystem(uri, env, null); + } + return FileSystems.newFileSystem(p, (ClassLoader) null); + } + + @Test + void testCopyDirectoryCyclicSymbolicLink() throws Exception { + // sourceDir = tempDirPath/source + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + // dir1 = tempDirPath/source/dir1 + final Path dir1 = Files.createDirectory(sourceDir.resolve("dir1")); + // dir2 = tempDirPath/source/dir1/dir2 + final Path dir2 = Files.createDirectory(dir1.resolve("dir2")); + // link = tempDirPath/source/dir1/dir2/cyclic-symlink + // target = .. + Files.createSymbolicLink(dir2.resolve("cyclic-symlink"), dir2.relativize(dir1)); + final Path targetDir = tempDirPath.resolve("target"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir)); + final Path copyOfDir2 = targetDir.resolve("dir1").resolve("dir2"); + assertTrue(Files.exists(copyOfDir2)); + assertTrue(Files.isDirectory(copyOfDir2)); + assertTrue(Files.exists(copyOfDir2.resolve("cyclic-symlink"))); + } + + @Test + void testCopyDirectoryFollowsAbsoluteSymbolicLinkToDirectory() throws Exception { + // Given + final Path externalDir = Files.createDirectory(tempDirPath.resolve("external")); + final Path dir1 = Files.createDirectory(externalDir.resolve("dir1")); + final Path file2 = Files.write(dir1.resolve("file2"), PathUtilsTest.BYTE_ARRAY_FIXTURE); + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir3 = Files.createDirectory(sourceDir.resolve("dir3")); + final Path file4 = Files.write(dir3.resolve("file4"), PathUtilsTest.BYTE_ARRAY_FIXTURE); + Files.createSymbolicLink(sourceDir.resolve("symlink1"), dir1.toAbsolutePath()); + Files.createSymbolicLink(sourceDir.resolve("symlink2"), sourceDir.relativize(file2)); + Files.createSymbolicLink(sourceDir.resolve("symlink3"), sourceDir.relativize(dir3)); + Files.createSymbolicLink(dir3.resolve("symlink4"), file4.toAbsolutePath()); + final Path targetDir = tempDirPath.resolve("target"); + // When + final PathCounters pathCounters = PathUtils.copyDirectory(sourceDir, targetDir); + // Then + // 6 * 11 bytes == 66: + // file2 + // file4 + // symlink2 -> file2 + // symlink4 -> file4 + // symlink1 -> dir1 containing file2 + // symlink3 -> dir3 containing file4 + // + // Different result value depending on the Java version and operating system, but should not throw an exception or loop infinitely. + pathCounters.getByteCounter().get(); + assertEquals(2L, pathCounters.getDirectoryCounter().get()); + assertEquals(5L, pathCounters.getFileCounter().get()); + assertTrue(Files.exists(targetDir.resolve("dir3").resolve("file4"))); + assertTrue(Files.exists(targetDir.resolve("dir3").resolve("symlink4"))); + } + + @Test + void testCopyDirectoryForDifferentFilesystemsWithAbsolutePath() throws IOException { + final Path archivePath = Paths.get(TEST_JAR_PATH); + try (FileSystem archive = openArchive(archivePath, false)) { + // relative jar -> absolute dir + Path sourceDir = archive.getPath("dir1"); + PathUtils.copyDirectory(sourceDir, tempDirPath); + assertTrue(Files.exists(tempDirPath.resolve("f1"))); + // absolute jar -> absolute dir + sourceDir = archive.getPath("/next"); + PathUtils.copyDirectory(sourceDir, tempDirPath); + assertTrue(Files.exists(tempDirPath.resolve("dir"))); + } + } + + @Test + void testCopyDirectoryForDifferentFilesystemsWithAbsolutePathReverse() throws IOException { + try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { + // absolute dir -> relative jar + Path targetDir = archive.getPath("target"); + Files.createDirectory(targetDir); + final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2").toAbsolutePath(); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); + // absolute dir -> absolute jar + targetDir = archive.getPath("/"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); + } + } + + @Test + void testCopyDirectoryForDifferentFilesystemsWithRelativePath() throws IOException { + final Path archivePath = Paths.get(TEST_JAR_PATH); + try (FileSystem archive = openArchive(archivePath, false); FileSystem targetArchive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { + final Path targetDir = targetArchive.getPath("targetDir"); + Files.createDirectory(targetDir); + // relative jar -> relative dir + Path sourceDir = archive.getPath("next"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("dir"))); + // absolute jar -> relative dir + sourceDir = archive.getPath("/dir1"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("f1"))); + } + } + + @Test + void testCopyDirectoryForDifferentFilesystemsWithRelativePathReverse() throws IOException { + try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { + // relative dir -> relative jar + Path targetDir = archive.getPath("target"); + Files.createDirectory(targetDir); + final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); + // relative dir -> absolute jar + targetDir = archive.getPath("/"); + PathUtils.copyDirectory(sourceDir, targetDir); + assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); + } + } + + @ParameterizedTest + @ArgumentsSource(CopyOptionsArgumentsProvider.class) + void testCopyDirectoryIgnoresBrokenSymbolicLink(final CopyOption... copyOptions) throws Exception { + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir = Files.createDirectory(sourceDir.resolve("dir")); + Files.createSymbolicLink(dir.resolve("broken-symlink"), dir.relativize(sourceDir.resolve("file"))); + final Path targetDir = tempDirPath.resolve("target"); + PathUtils.copyDirectory(sourceDir, targetDir, copyOptions); + assertTrue(Files.exists(targetDir)); + final Path copyOfDir = targetDir.resolve("dir"); + assertTrue(Files.exists(copyOfDir)); + assertTrue(Files.isDirectory(copyOfDir)); + assertFalse(Files.exists(copyOfDir.resolve("broken-symlink"))); + } + + /** + * Source tree: + *
      +     *
      +     * source/
      +     *   dir/
      +     *     file
      +     *     symlink-to-file
      +     *   symlink-to-dir
      +     * 
      + */ + @Test + void testCopyDirectoryPreservesSymlinks(@TempDir final Path tempDir) throws Exception { + final Path sourceDir = Files.createDirectory(tempDir.resolve("source")); + final Path dir = Files.createDirectory(sourceDir.resolve("dir")); + final Path dirLink = Files.createSymbolicLink(sourceDir.resolve("link-to-dir"), dir); + assertTrue(Files.exists(dirLink)); + final Path file = Files.createFile(dir.resolve("file")); + final Path fileLink = Files.createSymbolicLink(dir.resolve("link-to-file"), file); + assertTrue(Files.exists(fileLink)); + final Path targetDir = tempDir.resolve("target"); + PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + final Path copyOfDir = targetDir.resolve("dir"); + assertTrue(Files.exists(copyOfDir)); + final Path copyOfDirLink = targetDir.resolve("link-to-dir"); + assertTrue(Files.exists(copyOfDirLink)); + assertTrue(Files.isSymbolicLink(copyOfDirLink)); + final Path copyOfFileLink = copyOfDir.resolve("link-to-file"); + assertTrue(Files.exists(copyOfFileLink)); + assertTrue(Files.isSymbolicLink(copyOfFileLink)); + } + + /** + * Illustrates how copy with {@link LinkOption#NOFOLLOW_LINKS} preserves absolute symlinks to directories. + * This simulates to the behavior of Linux {@code cp -r}. + * Given the source directory structure: + *
      {@code
      +     * user@host:/tmp$ tree source/ external/
      +     * source/
      +     * └── dir
      +     *     └── symlink -> /tmp/external
      +     * external/
      +     * }
      + * When doing {@code user@host:/tmp$ cp -r source target}, then the resulting target directory structure is: + *
      {@code
      +     * user@host:/tmp$ tree target/
      +     * target/
      +     * └── dir
      +     *     └── symlink -> /tmp/external
      +     * }
      + */ + @Test + void testCopyDirectoryWithNoFollowLinksPreservesAbsoluteSymbolicLinkToDir() throws Exception { + // Given + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path externalDir = Files.createDirectory(tempDirPath.resolve("external")); + final Path dir = Files.createDirectory(sourceDir.resolve("dir")); + // source/dir/symlink -> /tmp/external + Files.createSymbolicLink(dir.resolve("symlink"), externalDir.toAbsolutePath()); + final Path targetDir = tempDirPath.resolve("target"); + // When + final PathCounters pathCounters = PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + // Then + // assertEquals(0L, pathCounters.getByteCounter().get()); + assertEquals(2L, pathCounters.getDirectoryCounter().get()); + // Verify that symlink with NOFOLLOW_LINKS counts as file + assertEquals(1L, pathCounters.getFileCounter().get()); + final Path copyOfAbsoluteSymlinkToDir = targetDir.resolve("dir").resolve("symlink"); + assertTrue(Files.isSymbolicLink(copyOfAbsoluteSymlinkToDir)); + assertTrue(Files.isDirectory(copyOfAbsoluteSymlinkToDir)); + // Verify that target/dir/symlink resolves to /tmp/external + assertEquals(externalDir.toRealPath(), copyOfAbsoluteSymlinkToDir.toRealPath()); + } + + /** + * Illustrates how copy with {@link LinkOption#NOFOLLOW_LINKS} preserves relative symlinks to files. + * This simulates to the behavior of Linux {@code cp -r}. + * Given the source directory structure: + *
      {@code
      +     * user@host:/tmp$ tree source/
      +     * source/
      +     * ├── dir
      +     * │   └── symlink -> ../file
      +     * └── file
      +     * }
      + * When doing {@code user@host:/tmp$ cp -r source target}, then the resulting target directory structure is: + *
      {@code
      +     * user@host:/tmp$ tree target/
      +     * target/
      +     * ├── dir
      +     * │   └── symlink -> ../file
      +     * └── file
      +     * }
      + */ + @Test + void testCopyDirectoryWithNoFollowLinksPreservesAbsoluteSymbolicLinkToFile() throws Exception { + // Given + final Path externalDir = Files.createDirectory(tempDirPath.resolve("external")); + final Path file = Files.write(externalDir.resolve("file"), PathUtilsTest.BYTE_ARRAY_FIXTURE); + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir = Files.createDirectory(sourceDir.resolve("dir")); + // source/dir/symlink -> /tmp/file + Files.createSymbolicLink(dir.resolve("symlink"), file.toAbsolutePath()); + final Path targetDir = tempDirPath.resolve("target"); + // When + final PathCounters pathCounters = PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + // Then + // assertEquals(0L, pathCounters.getByteCounter().get()); + assertEquals(2L, pathCounters.getDirectoryCounter().get()); + assertEquals(1L, pathCounters.getFileCounter().get()); + final Path copyOfAbsoluteSymlinkToFile = targetDir.resolve("dir").resolve("symlink"); + assertTrue(Files.isSymbolicLink(copyOfAbsoluteSymlinkToFile)); + assertTrue(Files.isRegularFile(copyOfAbsoluteSymlinkToFile)); + // Verify that /tmp/target/dir/symlink resolves to /tmp/source/file + assertEquals(file.toRealPath(), copyOfAbsoluteSymlinkToFile.toRealPath()); + } + + @Test + void testCopyDirectoryWithNoFollowLinksPreservesCyclicSymbolicLink() throws Exception { + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir1 = Files.createDirectory(sourceDir.resolve("dir1")); + final Path dir2 = Files.createDirectory(dir1.resolve("dir2")); + Files.createSymbolicLink(dir2.resolve("cyclic-symlink"), dir2.relativize(dir1)); + final Path targetDir = tempDirPath.resolve("target"); + PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + assertTrue(Files.exists(targetDir)); + final Path copyOfDir1 = targetDir.resolve("dir1"); + final Path copyOfDir2 = copyOfDir1.resolve("dir2"); + assertTrue(Files.exists(copyOfDir2)); + assertTrue(Files.isDirectory(copyOfDir2)); + final Path copyOfCyclicSymlink = copyOfDir2.resolve("cyclic-symlink"); + assertTrue(Files.exists(copyOfCyclicSymlink)); + assertEquals(copyOfDir1.toRealPath(), copyOfCyclicSymlink.toRealPath()); + } + + /** + * Illustrates how copy with {@link LinkOption#NOFOLLOW_LINKS} preserves relative symlinks to directories. + * This simulates to the behavior of Linux {@code cp -r}. + * Given the source directory structure: + *
      {@code
      +     * user@host:/tmp$ tree source/
      +     * source/
      +     * ├── dir1
      +     * │   └── symlink -> ../dir2
      +     * └── dir2
      +     * }
      + * When doing {@code user@host:/tmp$ cp -r source target}, then the resulting target directory structure is: + *
      {@code
      +     * user@host:/tmp$ tree target/
      +     * target/
      +     * ├── dir1
      +     * │   └── symlink -> ../dir2
      +     * └── dir2
      +     * }
      + */ + @Test + void testCopyDirectoryWithNoFollowLinksPreservesRelativeSymbolicLinkToDir() throws Exception { + // Given + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir1 = Files.createDirectory(sourceDir.resolve("dir1")); + final Path dir2 = Files.createDirectory(sourceDir.resolve("dir2")); + // source/dir1/symlink -> ../dir2 + Files.createSymbolicLink(dir1.resolve("symlink"), dir1.relativize(dir2)); + final Path targetDir = tempDirPath.resolve("target"); + // When + final PathCounters pathCounters = PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + // Then + // assertEquals(0L, pathCounters.getByteCounter().get()); + assertEquals(3L, pathCounters.getDirectoryCounter().get()); + // Verify that symlink with NOFOLLOW_LINKS counts as file + assertEquals(1L, pathCounters.getFileCounter().get()); + final Path copyOfDir2 = targetDir.resolve("dir2"); + final Path copyOfRelativeSymlinkToDir2 = targetDir.resolve("dir1").resolve("symlink"); + assertTrue(Files.isSymbolicLink(copyOfRelativeSymlinkToDir2)); + assertTrue(Files.isDirectory(copyOfRelativeSymlinkToDir2)); + // Verify that target/dir1/symlink resolves to /tmp/target/dir2 + assertEquals(copyOfDir2.toRealPath(), copyOfRelativeSymlinkToDir2.toRealPath()); + } + + /** + * Illustrates how copy with {@link LinkOption#NOFOLLOW_LINKS} preserves relative symlinks to files. + * This simulates to the behavior of Linux {@code cp -r}. + * Given the source directory structure: + *
      {@code
      +     * user@host:/tmp$ tree source/
      +     * source/
      +     * ├── dir
      +     * │   └── symlink -> ../file
      +     * └── file
      +     * }
      + * When doing {@code user@host:/tmp$ cp -r source target}, then the resulting target directory structure is: + *
      {@code
      +     * user@host:/tmp$ tree target/
      +     * target/
      +     * ├── dir
      +     * │   └── symlink -> ../file
      +     * └── file
      +     * }
      + */ + @Test + void testCopyDirectoryWithNoFollowLinksPreservesRelativeSymbolicLinkToFile() throws Exception { + // Given + final Path sourceDir = Files.createDirectory(tempDirPath.resolve("source")); + final Path dir = Files.createDirectory(sourceDir.resolve("dir")); + final Path file = Files.write(sourceDir.resolve("file"), PathUtilsTest.BYTE_ARRAY_FIXTURE); + // source/dir/symlink -> ../file + Files.createSymbolicLink(dir.resolve("symlink"), dir.relativize(file)); + final Path targetDir = tempDirPath.resolve("target"); + // When + final PathCounters pathCounters = PathUtils.copyDirectory(sourceDir, targetDir, LinkOption.NOFOLLOW_LINKS); + // Then + // assertEquals(11L, pathCounters.getByteCounter().get()); + assertEquals(2L, pathCounters.getDirectoryCounter().get()); + // Verify that file + symlink with NOFOLLOW_LINKS counts as 2 files + assertEquals(2L, pathCounters.getFileCounter().get()); + final Path copyOfFile = targetDir.resolve("file"); + final Path copyOfRelativeSymlinkToFile = targetDir.resolve("dir").resolve("symlink"); + assertTrue(Files.isSymbolicLink(copyOfRelativeSymlinkToFile)); + assertTrue(Files.isRegularFile(copyOfRelativeSymlinkToFile)); + // Verify that /tmp/target/dir/symlink resolves to /tmp/target/file + assertEquals(copyOfFile.toRealPath(), copyOfRelativeSymlinkToFile.toRealPath()); + } + + @Test + void testCopyFile() throws IOException { + final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); + final Path targetFile = PathUtils.copyFileToDirectory(sourceFile, tempDirPath); + assertTrue(Files.exists(targetFile)); + assertEquals(Files.size(sourceFile), Files.size(targetFile)); + } + + @Test + void testCopyFileTwoFileSystem() throws IOException { + try (FileSystem archive = openArchive(Paths.get(TEST_JAR_PATH), false)) { + final Path sourceFile = archive.getPath("next/dir/test.log"); + final Path targetFile = PathUtils.copyFileToDirectory(sourceFile, tempDirPath); + assertTrue(Files.exists(targetFile)); + assertEquals(Files.size(sourceFile), Files.size(targetFile)); + } + } + + @Test + void testCopyURL() throws IOException { + final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); + final URL url = new URL("file:///" + FilenameUtils.getPath(sourceFile.toAbsolutePath().toString()) + sourceFile.getFileName()); + final Path targetFile = PathUtils.copyFileToDirectory(url, tempDirPath); + assertTrue(Files.exists(targetFile)); + assertEquals(Files.size(sourceFile), Files.size(targetFile)); + } +} diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java index cd65c92d484..4576c77f8ef 100644 --- a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java +++ b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java @@ -32,7 +32,6 @@ import org.apache.commons.io.file.Counters.PathCounters; import org.apache.commons.lang3.SystemUtils; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -95,7 +94,7 @@ private void testDeleteFileEmpty(final PathCounters pathCounts) { */ @Test void testDeleteFileEmptyDirectory() throws IOException { - Assertions.assertThrows(NoSuchFileException.class, () -> testDeleteFileEmpty(PathUtils.deleteFile(tempDirPath))); + assertThrows(NoSuchFileException.class, () -> testDeleteFileEmpty(PathUtils.deleteFile(tempDirPath))); // This will throw if not empty. Files.deleteIfExists(tempDirPath); } diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsIsEmptyTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsIsEmptyTest.java index a69f31e81e1..4829e9332fb 100644 --- a/src/test/java/org/apache/commons/io/file/PathUtilsIsEmptyTest.java +++ b/src/test/java/org/apache/commons/io/file/PathUtilsIsEmptyTest.java @@ -17,11 +17,13 @@ package org.apache.commons.io.file; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -39,25 +41,25 @@ class PathUtilsIsEmptyTest { @Test void testIsEmpty() throws IOException { - Assertions.assertTrue(PathUtils.isEmpty(FILE_SIZE_0)); - Assertions.assertFalse(PathUtils.isEmpty(FILE_SIZE_1)); + assertTrue(PathUtils.isEmpty(FILE_SIZE_0)); + assertFalse(PathUtils.isEmpty(FILE_SIZE_1)); try (TempDirectory tempDir = TempDirectory.create(getClass().getCanonicalName())) { - Assertions.assertTrue(PathUtils.isEmpty(tempDir.get())); + assertTrue(PathUtils.isEmpty(tempDir.get())); } - Assertions.assertFalse(PathUtils.isEmpty(DIR_SIZE_1)); + assertFalse(PathUtils.isEmpty(DIR_SIZE_1)); } @Test void testIsEmptyDirectory() throws IOException { try (TempDirectory tempDir = TempDirectory.create(getClass().getCanonicalName())) { - Assertions.assertTrue(PathUtils.isEmptyDirectory(tempDir.get())); + assertTrue(PathUtils.isEmptyDirectory(tempDir.get())); } - Assertions.assertFalse(PathUtils.isEmptyDirectory(DIR_SIZE_1)); + assertFalse(PathUtils.isEmptyDirectory(DIR_SIZE_1)); } @Test void testisEmptyFile() throws IOException { - Assertions.assertTrue(PathUtils.isEmptyFile(FILE_SIZE_0)); - Assertions.assertFalse(PathUtils.isEmptyFile(FILE_SIZE_1)); + assertTrue(PathUtils.isEmptyFile(FILE_SIZE_0)); + assertFalse(PathUtils.isEmptyFile(FILE_SIZE_1)); } } diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java index a533f5280ad..5d7e270b351 100644 --- a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java +++ b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java @@ -30,12 +30,9 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -45,12 +42,9 @@ import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributes; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.test.TestUtils; import org.apache.commons.lang3.ArrayUtils; @@ -66,11 +60,7 @@ class PathUtilsTest extends AbstractTempDirTest { private static final String STRING_FIXTURE = "Hello World"; - private static final byte[] BYTE_ARRAY_FIXTURE = STRING_FIXTURE.getBytes(StandardCharsets.UTF_8); - - private static final String TEST_JAR_NAME = "test.jar"; - - private static final String TEST_JAR_PATH = "src/test/resources/org/apache/commons/io/test.jar"; + static final byte[] BYTE_ARRAY_FIXTURE = STRING_FIXTURE.getBytes(StandardCharsets.UTF_8); private static final String PATH_FIXTURE = "NOTICE.txt"; @@ -86,117 +76,10 @@ private Path getNonExistentPath() { return Paths.get("/does not exist/for/certain"); } - private FileSystem openArchive(final Path p, final boolean createNew) throws IOException { - if (createNew) { - final Map env = new HashMap<>(); - env.put("create", "true"); - final URI fileUri = p.toAbsolutePath().toUri(); - final URI uri = URI.create("jar:" + fileUri.toASCIIString()); - return FileSystems.newFileSystem(uri, env, null); - } - return FileSystems.newFileSystem(p, (ClassLoader) null); - } - private void setLastModifiedMillis(final Path file, final long millis) throws IOException { Files.setLastModifiedTime(file, FileTime.fromMillis(millis)); } - @Test - void testCopyDirectoryForDifferentFilesystemsWithAbsolutePath() throws IOException { - final Path archivePath = Paths.get(TEST_JAR_PATH); - try (FileSystem archive = openArchive(archivePath, false)) { - // relative jar -> absolute dir - Path sourceDir = archive.getPath("dir1"); - PathUtils.copyDirectory(sourceDir, tempDirPath); - assertTrue(Files.exists(tempDirPath.resolve("f1"))); - - // absolute jar -> absolute dir - sourceDir = archive.getPath("/next"); - PathUtils.copyDirectory(sourceDir, tempDirPath); - assertTrue(Files.exists(tempDirPath.resolve("dir"))); - } - } - - @Test - void testCopyDirectoryForDifferentFilesystemsWithAbsolutePathReverse() throws IOException { - try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { - // absolute dir -> relative jar - Path targetDir = archive.getPath("target"); - Files.createDirectory(targetDir); - final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2").toAbsolutePath(); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); - - // absolute dir -> absolute jar - targetDir = archive.getPath("/"); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); - } - } - - @Test - void testCopyDirectoryForDifferentFilesystemsWithRelativePath() throws IOException { - final Path archivePath = Paths.get(TEST_JAR_PATH); - try (FileSystem archive = openArchive(archivePath, false); - FileSystem targetArchive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { - final Path targetDir = targetArchive.getPath("targetDir"); - Files.createDirectory(targetDir); - // relative jar -> relative dir - Path sourceDir = archive.getPath("next"); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("dir"))); - - // absolute jar -> relative dir - sourceDir = archive.getPath("/dir1"); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("f1"))); - } - } - - @Test - void testCopyDirectoryForDifferentFilesystemsWithRelativePathReverse() throws IOException { - try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { - // relative dir -> relative jar - Path targetDir = archive.getPath("target"); - Files.createDirectory(targetDir); - final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); - - // relative dir -> absolute jar - targetDir = archive.getPath("/"); - PathUtils.copyDirectory(sourceDir, targetDir); - assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); - } - } - - @Test - void testCopyFile() throws IOException { - final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); - final Path targetFile = PathUtils.copyFileToDirectory(sourceFile, tempDirPath); - assertTrue(Files.exists(targetFile)); - assertEquals(Files.size(sourceFile), Files.size(targetFile)); - } - - @Test - void testCopyFileTwoFileSystem() throws IOException { - try (FileSystem archive = openArchive(Paths.get(TEST_JAR_PATH), false)) { - final Path sourceFile = archive.getPath("next/dir/test.log"); - final Path targetFile = PathUtils.copyFileToDirectory(sourceFile, tempDirPath); - assertTrue(Files.exists(targetFile)); - assertEquals(Files.size(sourceFile), Files.size(targetFile)); - } - } - - @Test - void testCopyURL() throws IOException { - final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); - final URL url = new URL("file:///" + FilenameUtils.getPath(sourceFile.toAbsolutePath().toString()) + sourceFile.getFileName()); - final Path targetFile = PathUtils.copyFileToDirectory(url, tempDirPath); - assertTrue(Files.exists(targetFile)); - assertEquals(Files.size(sourceFile), Files.size(targetFile)); - } - @Test void testCreateDirectoriesAlreadyExists() throws IOException { assertEquals(tempDirPath.getParent(), PathUtils.createParentDirectories(tempDirPath)); diff --git a/src/test/java/org/apache/commons/io/file/TempDirectory.java b/src/test/java/org/apache/commons/io/file/TempDirectory.java index bf858514bec..991a73f6793 100644 --- a/src/test/java/org/apache/commons/io/file/TempDirectory.java +++ b/src/test/java/org/apache/commons/io/file/TempDirectory.java @@ -36,7 +36,7 @@ public class TempDirectory extends DeletablePath { * @param dir See {@link Files#createTempDirectory(String, FileAttribute...)}. * @param prefix See {@link Files#createTempDirectory(String, FileAttribute...)}. * @param attrs See {@link Files#createTempDirectory(String, FileAttribute...)}. - * @return a new instance for a new temporary directory + * @return a new instance for a new temporary directory. * @throws IOException See {@link Files#createTempDirectory(String, FileAttribute...)}. */ public static TempDirectory create(final Path dir, final String prefix, final FileAttribute... attrs) throws IOException { @@ -49,7 +49,7 @@ public static TempDirectory create(final Path dir, final String prefix, final Fi * * @param prefix See {@link Files#createTempDirectory(String, FileAttribute...)}. * @param attrs See {@link Files#createTempDirectory(String, FileAttribute...)}. - * @return a new instance for a new temporary directory + * @return a new instance for a new temporary directory. * @throws IOException See {@link Files#createTempDirectory(String, FileAttribute...)}. */ public static TempDirectory create(final String prefix, final FileAttribute... attrs) throws IOException { diff --git a/src/test/java/org/apache/commons/io/file/TempDirectoryTest.java b/src/test/java/org/apache/commons/io/file/TempDirectoryTest.java index 506afb8d9b7..f1940a17436 100644 --- a/src/test/java/org/apache/commons/io/file/TempDirectoryTest.java +++ b/src/test/java/org/apache/commons/io/file/TempDirectoryTest.java @@ -22,16 +22,20 @@ import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * Tests {@link TempDirectory}. */ class TempDirectoryTest { + @TempDir + Path tempDirPath; + @SuppressWarnings("resource") @Test void testCreatePath() throws IOException { @@ -51,7 +55,7 @@ void testCreatePath() throws IOException { @Test void testCreateString() throws IOException { final TempDirectory ref; - try (TempDirectory tempDir = TempDirectory.create(Paths.get("target"), getClass().getCanonicalName())) { + try (TempDirectory tempDir = TempDirectory.create(tempDirPath, getClass().getCanonicalName())) { ref = tempDir; assertTrue(FileUtils.isEmptyDirectory(tempDir.toFile())); } diff --git a/src/test/java/org/apache/commons/io/file/TempFile.java b/src/test/java/org/apache/commons/io/file/TempFile.java index 56e7b395a4f..7a177607da3 100644 --- a/src/test/java/org/apache/commons/io/file/TempFile.java +++ b/src/test/java/org/apache/commons/io/file/TempFile.java @@ -37,7 +37,7 @@ public class TempFile extends DeletablePath { * @param prefix See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. * @param suffix See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. * @param attrs See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. - * @return a new instance for a new temporary directory + * @return a new instance for a new temporary directory. * @throws IOException See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. */ public static TempFile create(final Path dir, final String prefix, final String suffix, final FileAttribute... attrs) throws IOException { @@ -51,7 +51,7 @@ public static TempFile create(final Path dir, final String prefix, final String * @param prefix See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. * @param suffix See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. * @param attrs See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. - * @return a new instance for a new temporary directory + * @return a new instance for a new temporary directory. * @throws IOException See {@link Files#createTempFile(Path, String, String, FileAttribute...)}. */ public static TempFile create(final String prefix, final String suffix, final FileAttribute... attrs) throws IOException { diff --git a/src/test/java/org/apache/commons/io/file/TempFileTest.java b/src/test/java/org/apache/commons/io/file/TempFileTest.java index 99736e29b8d..e18a5e2a8e7 100644 --- a/src/test/java/org/apache/commons/io/file/TempFileTest.java +++ b/src/test/java/org/apache/commons/io/file/TempFileTest.java @@ -22,20 +22,24 @@ import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * Tests {@link TempFile}. */ class TempFileTest { + @TempDir + Path tempDirPath; + @SuppressWarnings("resource") @Test void testCreatePath() throws IOException { final TempFile ref; - try (TempFile tempDir = TempFile.create(Paths.get("target"), "prefix", ".suffix")) { + try (TempFile tempDir = TempFile.create(tempDirPath, "prefix", ".suffix")) { ref = tempDir; assertTrue(Files.exists(ref.get())); } diff --git a/src/test/java/org/apache/commons/io/filefilter/FileFilterTest.java b/src/test/java/org/apache/commons/io/filefilter/FileFilterTest.java index 6e11b5e770f..36698b48028 100644 --- a/src/test/java/org/apache/commons/io/filefilter/FileFilterTest.java +++ b/src/test/java/org/apache/commons/io/filefilter/FileFilterTest.java @@ -78,7 +78,7 @@ void testAgeFilter() throws Exception { try { TestUtils.sleep(1000); } catch (final InterruptedException ie) { - // ignore + Thread.currentThread().interrupt(); } if (!reference.getParentFile().exists()) { fail("Cannot create file " + reference + " as the parent directory does not exist"); diff --git a/src/test/java/org/apache/commons/io/filefilter/RegexFileFilterTest.java b/src/test/java/org/apache/commons/io/filefilter/RegexFileFilterTest.java index 1734969bd6e..f112b5dad03 100644 --- a/src/test/java/org/apache/commons/io/filefilter/RegexFileFilterTest.java +++ b/src/test/java/org/apache/commons/io/filefilter/RegexFileFilterTest.java @@ -152,7 +152,7 @@ void testRegexEdgeCases() { /** * Tests https://issues.apache.org/jira/browse/IO-733. * - * @throws IOException + * @throws IOException Thrown on a test failure. */ @SuppressWarnings("unchecked") @Test diff --git a/src/test/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilterTest.java b/src/test/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilterTest.java index 4c536df45db..cdd5ea9b252 100644 --- a/src/test/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilterTest.java +++ b/src/test/java/org/apache/commons/io/filefilter/SymbolicLinkFileFilterTest.java @@ -100,7 +100,7 @@ static void tearDown() { * Unit test teardown deletes all four of these files. *

      * - * @throws IOException If it fails to create the temporary files + * @throws IOException If it fails to create the temporary files. */ @BeforeAll static void testSetup() throws IOException { diff --git a/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java b/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java index f199be777b3..e32540afddf 100644 --- a/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java +++ b/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java @@ -45,7 +45,7 @@ private boolean not(final boolean value) throws IOException { /** * Tests {@link IOBiFunction#andThen(IOFunction)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAndThenIOFunction() throws IOException { @@ -59,7 +59,7 @@ void testAndThenIOFunction() throws IOException { /** * Tests {@link IOBiFunction#apply(Object, Object)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testApply() throws IOException { diff --git a/src/test/java/org/apache/commons/io/function/IOConsumerTest.java b/src/test/java/org/apache/commons/io/function/IOConsumerTest.java index 172973b206f..d0aefff5c74 100644 --- a/src/test/java/org/apache/commons/io/function/IOConsumerTest.java +++ b/src/test/java/org/apache/commons/io/function/IOConsumerTest.java @@ -54,6 +54,17 @@ void testAccept() throws IOException { assertEquals("A1", ref.get()); } + @Test + void testAcceptStatic() throws IOException { + IOConsumer.accept(null, null); + IOConsumer.accept((IOConsumer) null, "."); + // + final AtomicReference ref = new AtomicReference<>(); + final IOConsumer consumer = s -> ref.set(s + "1"); + IOConsumer.accept(consumer, "A"); + assertEquals("A1", ref.get()); + } + @Test void testAndThen() throws IOException { final AtomicReference ref = new AtomicReference<>(); diff --git a/src/test/java/org/apache/commons/io/function/IOIterableTest.java b/src/test/java/org/apache/commons/io/function/IOIterableTest.java index 6ff376c2280..a1c93946fad 100644 --- a/src/test/java/org/apache/commons/io/function/IOIterableTest.java +++ b/src/test/java/org/apache/commons/io/function/IOIterableTest.java @@ -91,7 +91,7 @@ void testSpliterator() { } @Test - void testUnrwap() { + void testUnwrap() { assertSame(fixture.list, iterable.unwrap()); assertSame(fixture.unwrap(), iterable.unwrap()); } diff --git a/src/test/java/org/apache/commons/io/function/IOQuadFunctionTest.java b/src/test/java/org/apache/commons/io/function/IOQuadFunctionTest.java index 66520402df8..bb3524389c3 100644 --- a/src/test/java/org/apache/commons/io/function/IOQuadFunctionTest.java +++ b/src/test/java/org/apache/commons/io/function/IOQuadFunctionTest.java @@ -33,7 +33,7 @@ class IOQuadFunctionTest { /** * Tests {@link IOQuadFunction#apply(Object, Object, Object, Object)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAccept() throws IOException { @@ -58,7 +58,7 @@ void testAccept() throws IOException { /** * Tests {@link IOTriFunction#andThen(IOFunction)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAndThenIOFunction() throws IOException { diff --git a/src/test/java/org/apache/commons/io/function/IORunnableTest.java b/src/test/java/org/apache/commons/io/function/IORunnableTest.java index 4fe035d92e8..134ad6027fc 100644 --- a/src/test/java/org/apache/commons/io/function/IORunnableTest.java +++ b/src/test/java/org/apache/commons/io/function/IORunnableTest.java @@ -39,7 +39,7 @@ class IORunnableTest { /** * Tests {@link IORunnable#run()}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAccept() throws IOException { diff --git a/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java b/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java index 0ed956a8556..4575d47e577 100644 --- a/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java +++ b/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java @@ -33,7 +33,7 @@ class IOTriFunctionTest { /** * Tests {@link IOTriFunction#apply(Object, Object, Object)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAccept() throws IOException { @@ -55,7 +55,7 @@ void testAccept() throws IOException { /** * Tests {@link IOTriFunction#andThen(IOFunction)}. * - * @throws IOException thrown on test failure + * @throws IOException thrown on test failure. */ @Test void testAndThenIOFunction() throws IOException { diff --git a/src/test/java/org/apache/commons/io/function/UncheckTest.java b/src/test/java/org/apache/commons/io/function/UncheckTest.java index 2622c6176da..cdcbdaaab17 100644 --- a/src/test/java/org/apache/commons/io/function/UncheckTest.java +++ b/src/test/java/org/apache/commons/io/function/UncheckTest.java @@ -20,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -262,12 +261,8 @@ void testGetAsIntMessage() { assertEquals(1, atomicInt.get()); // exception final IOException expected = new IOException(CAUSE_MESSAGE); - try { - Uncheck.getAsInt(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE); - fail(); - } catch (final UncheckedIOException e) { - assertUncheckedIOException(expected, e); - } + assertUncheckedIOException(expected, + assertThrows(UncheckedIOException.class, () -> Uncheck.getAsInt(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE))); } @Test @@ -291,12 +286,8 @@ void testGetAsLongMessage() { assertEquals(1L, atomicLong.get()); // exception final IOException expected = new IOException(CAUSE_MESSAGE); - try { - Uncheck.getAsLong(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE); - fail(); - } catch (final UncheckedIOException e) { - assertUncheckedIOException(expected, e); - } + assertUncheckedIOException(expected, + assertThrows(UncheckedIOException.class, () -> Uncheck.getAsLong(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE))); } /** @@ -308,12 +299,8 @@ void testGetMessage() { assertEquals('a', Uncheck.get(() -> newInputStream().read()).intValue(), () -> CUSTOM_MESSAGE); // Exception final IOException expected = new IOException(CAUSE_MESSAGE); - try { - Uncheck.get(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE); - fail(); - } catch (final UncheckedIOException e) { - assertUncheckedIOException(expected, e); - } + assertUncheckedIOException(expected, + assertThrows(UncheckedIOException.class, () -> Uncheck.get(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE))); } /** @@ -334,9 +321,9 @@ void testRun() { } /** - * Tests {@link Uncheck#run(IORunnable, Supplier))}. + * Tests {@link Uncheck#run(IORunnable, Supplier)}. * - * @throws IOException + * @throws IOException Thrown on a test failure. */ @Test void testRunMessage() throws IOException { @@ -346,12 +333,8 @@ void testRunMessage() throws IOException { assertEquals('b', Uncheck.get(stream::read).intValue()); final IOException expected = new IOException(CAUSE_MESSAGE); // Exception - try { - Uncheck.run(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE); - fail(); - } catch (final UncheckedIOException e) { - assertUncheckedIOException(expected, e); - } + assertUncheckedIOException(expected, + assertThrows(UncheckedIOException.class, () -> Uncheck.run(() -> new BrokenInputStream(expected).read(), () -> CUSTOM_MESSAGE))); } @Test diff --git a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java index 64ee834731d..ee8297c3b54 100644 --- a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java @@ -55,7 +55,7 @@ class BOMInputStreamTest { /** - * A mock InputStream that expects {@code close()} to be called. + * A mock InputStream that tracks if {@code close()} is called. */ private static final class ExpectCloseInputStream extends InputStream { private boolean closed; @@ -193,7 +193,6 @@ private void readBOMInputStreamTwice(final String resource) throws Exception { assertNotNull(inputStream); try (BOMInputStream bomInputStream = BOMInputStream.builder().setInputStream(inputStream).get()) { bomInputStream.mark(1_000_000); - readFile(bomInputStream); bomInputStream.reset(); readFile(bomInputStream); @@ -215,7 +214,7 @@ void testAfterReadConsumer() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; final AtomicBoolean boolRef = new AtomicBoolean(); // @formatter:off - try (InputStream bounded = BOMInputStream.builder() + try (BOMInputStream bounded = BOMInputStream.builder() .setInputStream(createUtf8Input(data, true)) .setAfterRead(i -> boolRef.set(true)) .get()) { @@ -226,14 +225,12 @@ void testAfterReadConsumer() throws Exception { // Throwing final String message = "test exception message"; // @formatter:off - try (InputStream bounded = BOMInputStream.builder() + assertEquals(message, assertThrowsExactly(CustomIOException.class, () -> BOMInputStream.builder() .setInputStream(createUtf8Input(data, true)) .setAfterRead(i -> { throw new CustomIOException(message); }) - .get()) { - assertEquals(message, assertThrowsExactly(CustomIOException.class, () -> IOUtils.consume(bounded)).getMessage()); - } + .get()).getMessage()); // @formatter:on } @@ -241,8 +238,8 @@ void testAfterReadConsumer() throws Exception { void testAvailableWithBOMAfterClose() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; final InputStream shadow; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { - assertEquals(7, in.available()); + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { + assertEquals(4, in.available()); shadow = in; } assertEquals(0, shadow.available()); @@ -251,16 +248,16 @@ void testAvailableWithBOMAfterClose() throws Exception { @Test void testAvailableWithBOMAfterOpen() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { - assertEquals(7, in.available()); + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { + assertEquals(4, in.available()); } } @Test void testAvailableWithoutBOM() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { - assertEquals(4, in.available()); + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + assertEquals(1, in.available()); } } @@ -274,7 +271,7 @@ void testBuilderGet() { // this is here for coverage void testClose() throws Exception { try (ExpectCloseInputStream del = new ExpectCloseInputStream()) { - try (InputStream in = new BOMInputStream(del)) { + try (BOMInputStream in = new BOMInputStream(del)) { // nothing } del.assertCloseCalled(); @@ -283,7 +280,14 @@ void testClose() throws Exception { @Test void testCloseHandleIOException() throws IOException { - ProxyInputStreamTest.testCloseHandleIOException(BOMInputStream.builder()); + final IOException exception = new IOException(); + ProxyInputStreamTest.testCloseHandleIOException(BOMInputStream.builder().setInputStream(new BrokenInputStream(() -> exception) { + + @Override + public int read() throws IOException { + return 'X'; + } + }).get()); } @Test @@ -298,7 +302,7 @@ void testEmptyBufferWithBOM() throws Exception { @Test void testEmptyBufferWithoutBOM() throws Exception { final byte[] data = {}; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { final byte[] buf = new byte[1024]; assertEquals(-1, in.read(buf)); } @@ -338,7 +342,7 @@ void testGetBOMFirstThenReadInclude() throws Exception { @Test void testLargeBufferWithBOM() throws Exception { final byte[] data = { 'A', 'B', 'C' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { final byte[] buf = new byte[1024]; assertData(data, buf, in.read(buf)); } @@ -347,7 +351,7 @@ void testLargeBufferWithBOM() throws Exception { @Test void testLargeBufferWithoutBOM() throws Exception { final byte[] data = { 'A', 'B', 'C' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { final byte[] buf = new byte[1024]; assertData(data, buf, in.read(buf)); } @@ -356,7 +360,7 @@ void testLargeBufferWithoutBOM() throws Exception { @Test void testLeadingNonBOMBufferedRead() throws Exception { final byte[] data = { (byte) 0xEF, (byte) 0xAB, (byte) 0xCD }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { final byte[] buf = new byte[1024]; assertData(data, buf, in.read(buf)); } @@ -365,7 +369,7 @@ void testLeadingNonBOMBufferedRead() throws Exception { @Test void testLeadingNonBOMSingleRead() throws Exception { final byte[] data = { (byte) 0xEF, (byte) 0xAB, (byte) 0xCD }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { assertEquals(0xEF, in.read()); assertEquals(0xAB, in.read()); assertEquals(0xCD, in.read()); @@ -376,12 +380,10 @@ void testLeadingNonBOMSingleRead() throws Exception { @Test void testMarkResetAfterReadWithBOM() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { assertTrue(in.markSupported()); - in.read(); in.mark(10); - in.read(); in.read(); in.reset(); @@ -392,12 +394,10 @@ void testMarkResetAfterReadWithBOM() throws Exception { @Test void testMarkResetAfterReadWithoutBOM() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { assertTrue(in.markSupported()); - in.read(); in.mark(10); - in.read(); in.read(); in.reset(); @@ -408,11 +408,9 @@ void testMarkResetAfterReadWithoutBOM() throws Exception { @Test void testMarkResetBeforeReadWithBOM() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { assertTrue(in.markSupported()); - in.mark(10); - in.read(); in.read(); in.reset(); @@ -423,11 +421,9 @@ void testMarkResetBeforeReadWithBOM() throws Exception { @Test void testMarkResetBeforeReadWithoutBOM() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; - try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { + try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) { assertTrue(in.markSupported()); - in.mark(10); - in.read(); in.read(); in.reset(); @@ -460,7 +456,7 @@ void testNoBoms() throws Exception { void testReadAfterClose() throws Exception { final byte[] data = { 'A', 'B', 'C', 'D' }; try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) { - assertEquals(7, in.available()); + assertEquals(4, in.available()); in.close(); assertThrows(IOException.class, in::read); } diff --git a/src/test/java/org/apache/commons/io/input/BoundedReaderTest.java b/src/test/java/org/apache/commons/io/input/BoundedReaderTest.java index 62a8659cc0f..3a90851478d 100644 --- a/src/test/java/org/apache/commons/io/input/BoundedReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/BoundedReaderTest.java @@ -49,9 +49,9 @@ class BoundedReaderTest { private static final String STRING_END_EOL = "0\n1\n2\n"; - private final Reader sr = new BufferedReader(new StringReader("01234567890")); + private final Reader bufReader1 = new BufferedReader(new StringReader("01234567890")); - private final Reader shortReader = new BufferedReader(new StringReader("01")); + private final Reader bufReader0 = new BufferedReader(new StringReader("01")); @Test void testCloseTest() throws IOException { @@ -111,7 +111,7 @@ void testLineNumberReaderAndStringReaderLastLineEolYes() { @Test void testMarkReset() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.mark(3); mr.read(); mr.read(); @@ -126,7 +126,7 @@ void testMarkReset() throws IOException { @Test void testMarkResetFromOffset1() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.mark(3); mr.read(); mr.read(); @@ -141,7 +141,7 @@ void testMarkResetFromOffset1() throws IOException { @Test void testMarkResetMarkMore() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.mark(4); mr.read(); mr.read(); @@ -156,7 +156,7 @@ void testMarkResetMarkMore() throws IOException { @Test void testMarkResetWithMarkOutsideBoundedReaderMax() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.mark(4); mr.read(); mr.read(); @@ -167,7 +167,7 @@ void testMarkResetWithMarkOutsideBoundedReaderMax() throws IOException { @Test void testMarkResetWithMarkOutsideBoundedReaderMaxAndInitialOffset() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.read(); mr.mark(3); mr.read(); @@ -179,7 +179,7 @@ void testMarkResetWithMarkOutsideBoundedReaderMaxAndInitialOffset() throws IOExc @Test void testReadBytesEOF() { assertTimeout(TIMEOUT, () -> { - final BoundedReader mr = new BoundedReader(sr, 3); + final BoundedReader mr = new BoundedReader(bufReader1, 3); try (BufferedReader br = new BufferedReader(mr)) { br.readLine(); br.readLine(); @@ -189,7 +189,7 @@ void testReadBytesEOF() { @Test void testReadMulti() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { final char[] cbuf = new char[4]; Arrays.fill(cbuf, 'X'); final int read = mr.read(cbuf, 0, 4); @@ -203,7 +203,7 @@ void testReadMulti() throws IOException { @Test void testReadMultiWithOffset() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { final char[] cbuf = new char[4]; Arrays.fill(cbuf, 'X'); final int read = mr.read(cbuf, 1, 2); @@ -217,7 +217,7 @@ void testReadMultiWithOffset() throws IOException { @Test void testReadTillEnd() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.read(); mr.read(); mr.read(); @@ -227,7 +227,7 @@ void testReadTillEnd() throws IOException { @Test void testShortReader() throws IOException { - try (BoundedReader mr = new BoundedReader(shortReader, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader0, 3)) { mr.read(); mr.read(); assertEquals(-1, mr.read()); @@ -236,7 +236,7 @@ void testShortReader() throws IOException { @Test void testSkipTest() throws IOException { - try (BoundedReader mr = new BoundedReader(sr, 3)) { + try (BoundedReader mr = new BoundedReader(bufReader1, 3)) { mr.skip(2); mr.read(); assertEquals(-1, mr.read()); diff --git a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java index fcd430091be..c03e0fb14a5 100644 --- a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java @@ -14,8 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io.input; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -65,6 +67,22 @@ void testBuilderGet() { assertThrows(IllegalStateException.class, () -> BufferedFileChannelInputStream.builder().get()); } + /** + * Tests that the {@code clean(ByteBuffer)} method only performs buffer cleaning once, even when {@link BufferedFileChannelInputStream#close()} is invoked + * multiple times. The private {@code clean} boolean field is inspected via reflection: it must be {@code false} before any close, {@code true} after the + * first close, and remain {@code true} after a second close, confirming that {@code ByteBufferCleaner.clean()} is never called more than once. + */ + @Test + void testCleanCalledOnlyOnce() throws Exception { + try (BufferedFileChannelInputStream stream = BufferedFileChannelInputStream.builder().setPath(InputPath).get()) { + assertFalse(stream.isClean()); + stream.close(); + assertTrue(stream.isClean()); + stream.close(); + assertTrue(stream.isClean()); + } + } + @Test void testReadAfterClose() throws Exception { for (final InputStream inputStream : inputStreams) { @@ -72,5 +90,4 @@ void testReadAfterClose() throws Exception { assertThrows(IOException.class, inputStream::read); } } - } diff --git a/src/test/java/org/apache/commons/io/input/ChunkedReader.java b/src/test/java/org/apache/commons/io/input/ChunkedReader.java new file mode 100644 index 00000000000..7e6fd27363c --- /dev/null +++ b/src/test/java/org/apache/commons/io/input/ChunkedReader.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.input; + +import java.io.FilterReader; +import java.io.IOException; +import java.io.Reader; + +/** + * Reader that limits the number of characters read in a chunk of the specified size or less. + */ +public class ChunkedReader extends FilterReader { + + private final int chunkSize; + + public ChunkedReader(final Reader reader, final int chunkSize) { + super(reader); + if (chunkSize <= 0) { + throw new IllegalArgumentException("chunkSize must be > 0"); + } + this.chunkSize = chunkSize; + } + + @Override + public void close() throws IOException { + // nothing to do. + } + + @Override + public int read(final char[] cbuf, final int off, final int len) throws IOException { + return super.read(cbuf, off, len > chunkSize ? chunkSize : len); + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java b/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java index 54bb0810911..ceb52ed993d 100644 --- a/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; @@ -79,6 +80,32 @@ void testClose() throws IOException { assertEquals(data[0], byteArrayInputStream.read(), "read()"); } + @Test + void testOnClose() throws Exception { + try (InputStream in = CloseShieldInputStream.builder().setInputStream(byteArrayInputStream).setOnClose(is -> { + assertFalse(closed); + closed = true; + return ClosedInputStream.INSTANCE; + }).get()) { + assertEquals(3, in.available()); + } + assertTrue(closed); + } + + @Test + void testOnCloseException() throws Exception { + final String message = "test"; + assertEquals("test", assertThrowsExactly(IOException.class, () -> { + try (InputStream in = CloseShieldInputStream.builder().setInputStream(byteArrayInputStream).setOnClose(is -> { + assertFalse(closed); + throw new IOException(message); + }).get()) { + assertEquals("test", assertThrowsExactly(IOException.class, in::close).getMessage()); + } + }).getMessage()); + assertFalse(closed); + } + @Test void testReadAfterCose() throws Exception { final InputStream shadow; diff --git a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java index e36598ab0b5..9f7d0d60fe5 100644 --- a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java @@ -30,7 +30,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomUtils; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -143,7 +142,7 @@ void testReadAfterClose() throws IOException { try (InputStream inputStream = newInputStream(file, 1024)) { inputStream.close(); // verify - Assertions.assertThrows(IOException.class, () -> IOUtils.toByteArray(inputStream)); + assertThrows(IOException.class, () -> IOUtils.toByteArray(inputStream)); } } diff --git a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java index 8f9892c0290..db89c24932a 100644 --- a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java @@ -327,6 +327,17 @@ void testReadEof() throws Exception { } } + @Test + void testSetReference() throws Exception { + final ByteArrayInputStream first = new ByteArrayInputStream(new byte[2]); + try (ProxyInputStream wrapped = new ProxyInputStreamFixture(first)) { + assertSame(first, wrapped.unwrap()); + final NullInputStream in = new NullInputStream(); + wrapped.setReference(in); + assertSame(in, wrapped.unwrap()); + } + } + @Test void testSubclassAfterReadConsumer() throws Exception { final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8); @@ -355,4 +366,12 @@ void testSubclassAfterReadConsumer() throws Exception { // @formatter:on } + @Test + void testUnwrap() throws Exception { + final ByteArrayInputStream first = new ByteArrayInputStream(new byte[2]); + try (ProxyInputStream wrapped = new ProxyInputStreamFixture(first)) { + assertSame(first, wrapped.unwrap()); + } + } + } diff --git a/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java b/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java index 681ab90eae0..51a4f52911e 100644 --- a/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java @@ -14,8 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io.input; +import static org.junit.jupiter.api.Assertions.assertSame; + import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; @@ -29,6 +32,7 @@ class ProxyReaderTest { /** Custom NullReader implementation. */ private static final class CustomNullReader extends NullReader { + CustomNullReader(final int len) { super(len); } @@ -51,6 +55,7 @@ public int read(final CharBuffer target) throws IOException { /** ProxyReader implementation. */ private static final class ProxyReaderImpl extends ProxyReader { + ProxyReaderImpl(final Reader proxy) { super(proxy); } @@ -70,4 +75,20 @@ void testNullCharBuffer() throws Exception { proxy.read((CharBuffer) null); } } + + @Test + void testSetReference() throws Exception { + try (CustomNullReader wrapped = new CustomNullReader(0); ProxyReader proxy = new ProxyReaderImpl(wrapped)) { + assertSame(wrapped, proxy.unwrap()); + proxy.setReference(NullReader.INSTANCE); + assertSame(NullReader.INSTANCE, proxy.unwrap()); + } + } + + @Test + void testUnwrap() throws Exception { + try (CustomNullReader wrapped = new CustomNullReader(0); ProxyReader proxy = new ProxyReaderImpl(wrapped)) { + assertSame(wrapped, proxy.unwrap()); + } + } } diff --git a/src/test/java/org/apache/commons/io/input/QueueInputStreamTest.java b/src/test/java/org/apache/commons/io/input/QueueInputStreamTest.java index 5ea695095f8..16639dfe245 100644 --- a/src/test/java/org/apache/commons/io/input/QueueInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/QueueInputStreamTest.java @@ -50,7 +50,6 @@ import org.apache.commons.io.output.QueueOutputStream; import org.apache.commons.io.output.QueueOutputStreamTest; import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; @@ -312,7 +311,8 @@ void testResetArguments() throws IOException { } @Test - @DisplayName("If read is interrupted while waiting, then exception is thrown") + + /** If read is interrupted while waiting, then exception is thrown */ void testTimeoutInterrupted() throws Exception { try (QueueInputStream inputStream = QueueInputStream.builder().setTimeout(Duration.ofMinutes(2)).get(); QueueOutputStream outputStream = inputStream.newQueueOutputStream()) { @@ -326,7 +326,7 @@ void testTimeoutInterrupted() throws Exception { assertTrue(Thread.currentThread().isInterrupted()); result.set(true); latch.countDown(); - }); + }, "commons-io-QueueInputStreamTest-testTimeoutInterrupted"); thread.setDaemon(true); thread.start(); @@ -338,7 +338,8 @@ void testTimeoutInterrupted() throws Exception { } @Test - @DisplayName("If data is not available in queue, then read will wait until wait time elapses") + + /** If data is not available in queue, then read will wait until wait time elapses */ void testTimeoutUnavailableData() throws IOException { try (QueueInputStream inputStream = QueueInputStream.builder().setTimeout(Duration.ofMillis(500)).get(); QueueOutputStream outputStream = inputStream.newQueueOutputStream()) { diff --git a/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java index 94d7b289edd..d121e3c4883 100644 --- a/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java @@ -14,24 +14,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io.input; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.nio.file.StandardOpenOption; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** - * Tests {@link ReadAheadInputStream}. - * - * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was called {@code ReadAheadInputStreamSuite}. + * Tests {@link ReadAheadInputStream}. This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was called + * {@code ReadAheadInputStreamSuite}. */ class ReadAheadInputStreamTest extends AbstractInputStreamTest { @SuppressWarnings("resource") @BeforeEach - public void setUpInputStreams() throws IOException { + void setUpInputStreams() throws IOException { inputStreams = new InputStream[] { // Tests equal and aligned buffers of wrapped an outer stream. new ReadAheadInputStream(new BufferedFileChannelInputStream(InputPath, 8 * 1024), 8 * 1024), @@ -57,4 +71,41 @@ public void setUpInputStreams() throws IOException { ReadAheadInputStream.builder().setPath(InputPath).setOpenOptions(StandardOpenOption.READ).get() }; } + @Test + @Timeout(value = 30, unit = TimeUnit.SECONDS) + synchronized void testCloseInterrupt() throws IOException, InterruptedException { + try (ReadAheadInputStream inputStream = ReadAheadInputStream.builder() + // @formatter:off + .setPath(InputPath) + .get()) { + // @formatter:on + final ReadAheadInputStream spy = spy(inputStream); + when(spy.shutdownAwait()).thenThrow(InterruptedException.class); + Thread.currentThread().interrupt(); + assertInstanceOf(InterruptedException.class, assertThrows(InterruptedIOException.class, spy::close).getCause()); + assertTrue(Thread.interrupted()); + } + } + + @Test + void testClosePlusExecutorService() throws IOException { + final ExecutorService externalExecutor = Executors.newSingleThreadExecutor(); + // We use an outer try-with-resources for only the test fixture instead of combining it with the ReadAheadInputStream allocation. + try (FileInputStream inputStream = new FileInputStream("src/test/resources/org/apache/commons/io/FileUtilsTestDataLF.bin")) { + try { + try (ReadAheadInputStream rais = ReadAheadInputStream.builder().setInputStream(inputStream).setExecutorService(externalExecutor).get()) { + assertEquals('1', rais.read()); + } + Thread.yield(); + // The underlying FileInputStream should be closed since ReadAheadInputStream is a FilterInputStream. + assertThrows(IOException.class, inputStream::available); + assertThrows(IOException.class, inputStream::read); + // The caller remains responsible for shutting down the executor. + } finally { + // TODO ExecutorService implements AutoCloseable in Java 19+. + externalExecutor.shutdown(); + } + } + } + } diff --git a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderSimpleTest.java b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderSimpleTest.java index b35300b8c06..5f16302f513 100644 --- a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderSimpleTest.java +++ b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderSimpleTest.java @@ -30,7 +30,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.io.TestResources; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -42,10 +41,10 @@ class ReversedLinesFileReaderSimpleTest { */ @ParameterizedTest @MethodSource("org.apache.commons.io.input.ReversedLinesFileReaderParamBlockSizeTest#blockSizes") - @Disabled void testEmptyFirstLine(final int blockSize) throws Exception { final File testFileEmptyFirstLine = TestResources.getFile("/empty-first-line.bin"); - try (ReversedLinesFileReader reversedLinesFileReader = new ReversedLinesFileReader(testFileEmptyFirstLine, 10, StandardCharsets.US_ASCII.name())) { + try (ReversedLinesFileReader reversedLinesFileReader = new ReversedLinesFileReader(testFileEmptyFirstLine, + blockSize, StandardCharsets.US_ASCII.name())) { assertEqualsAndNoLineBreaks("test2", reversedLinesFileReader.readLine()); assertEqualsAndNoLineBreaks("", reversedLinesFileReader.readLine()); assertEqualsAndNoLineBreaks("test1", reversedLinesFileReader.readLine()); diff --git a/src/test/java/org/apache/commons/io/input/TailerCloseTest.java b/src/test/java/org/apache/commons/io/input/TailerCloseTest.java new file mode 100644 index 00000000000..fbd94dd4af5 --- /dev/null +++ b/src/test/java/org/apache/commons/io/input/TailerCloseTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.input; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Tests {@link Tailer} for IO-889. + */ +public class TailerCloseTest { + + private class TailerTestListener extends TailerListenerAdapter { + + @Override + public void handle(final Exception ex) { + super.handle(ex); + result.completeExceptionally(ex); + } + + @Override + public void handle(final String line) { + super.handle(line); + result.complete(line); + } + } + + private static Thread newDaemonThread(final Runnable runnable) { + final Thread thread = new Thread(runnable, "commons-io-tailer"); + thread.setDaemon(true); + return thread; + } + + private ExecutorService executorService; + private Path path; + private final CompletableFuture result = new CompletableFuture<>(); + + @AfterEach + public void tearDown() throws Exception { + // wait for the tailer task and delete log file if previous delete failed + if (executorService != null) { + executorService.shutdown(); + executorService.awaitTermination(60, TimeUnit.SECONDS); + } + if (path != null && Files.exists(path)) { + Files.delete(path); + } + } + + @DisabledOnOs(value = OS.WINDOWS) + @Test + public void testCloseTailer() throws Exception { + path = Files.createTempFile("TailerTestFile", ".log"); + // same as default Tailer executor + executorService = Executors.newSingleThreadExecutor(TailerCloseTest::newDaemonThread); + try ( + // @formatter:off + Tailer tailer = Tailer + .builder() + .setExecutorService(executorService) + .setTailerListener(new TailerTestListener()) + .setFile(path.toFile()) + .get()) { + // @formatter:on + Files.write(path, "aaa\n".getBytes()); + // wait for the background thread to open the file + assertEquals("aaa", result.get(60, TimeUnit.SECONDS)); + } + // delete file after closing Tailer + Files.delete(path); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/io/input/TailerTest.java b/src/test/java/org/apache/commons/io/input/TailerTest.java index 4e764bf94ad..937f59755b0 100644 --- a/src/test/java/org/apache/commons/io/input/TailerTest.java +++ b/src/test/java/org/apache/commons/io/input/TailerTest.java @@ -231,17 +231,18 @@ private List expectLinesWithLongTimeout(final TestTailerListener listene } @Test - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" void testBufferBreak() throws Exception { final long delay = 50; final File file = new File(temporaryFolder, "testBufferBreak.txt"); createFile(file, 0); - writeStrings(file, "SBTOURIST\n"); + final String data = "SBTOURIST\n"; + writeStrings(file, data); final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = new Tailer(file, listener, delay, false, 1)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testBufferBreak"); thread.start(); List lines = listener.getLines(); + assertEquals(data.length(), tailer.getTailable().size()); while (lines.isEmpty() || !lines.get(lines.size() - 1).equals("SBTOURIST")) { lines = listener.getLines(); } @@ -346,7 +347,7 @@ void testInterrupt() throws Exception { final int delay = 1000; final int idle = 50; // allow time for thread to work try (Tailer tailer = new Tailer(file, listener, delay, false, IOUtils.DEFAULT_BUFFER_SIZE)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testInterrupt"); thread.setDaemon(true); thread.start(); TestUtils.sleep(idle); @@ -369,7 +370,7 @@ void testIO335() throws Exception { // test CR behavior createFile(file, 0); final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = new Tailer(file, listener, delayMillis, false)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testIO335"); thread.start(); // Write some lines to the file @@ -386,7 +387,6 @@ void testIO335() throws Exception { // test CR behavior } @Test - @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" void testLongFile() throws Exception { final long delay = 50; final File file = new File(temporaryFolder, "testLongFile.txt"); @@ -400,13 +400,14 @@ void testLongFile() throws Exception { final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = new Tailer(file, listener, delay, false)) { // final long start = System.currentTimeMillis(); - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testLongFile"); thread.start(); List lines = listener.getLines(); while (lines.isEmpty() || !lines.get(lines.size() - 1).equals("SBTOURIST")) { lines = listener.getLines(); } // System.out.println("Elapsed: " + (System.currentTimeMillis() - start)); + assertFalse(lines.isEmpty()); listener.clear(); } } @@ -424,7 +425,7 @@ void testMultiByteBreak() throws Exception { // Need to use UTF-8 to read & write the file otherwise it can be corrupted (depending on the default charset) final Charset charsetUTF8 = StandardCharsets.UTF_8; try (Tailer tailer = new Tailer(file, charsetUTF8, listener, delay, false, isWindows, IOUtils.DEFAULT_BUFFER_SIZE)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testMultiByteBreak"); thread.start(); try (Writer out = new OutputStreamWriter(Files.newOutputStream(file.toPath()), charsetUTF8); BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(origin.toPath()), charsetUTF8))) { @@ -457,7 +458,7 @@ void testSimpleConstructor() throws Exception { createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructor"); thread.start(); validateTailer(listener, file); } @@ -469,7 +470,7 @@ void testSimpleConstructorWithDelay() throws Exception { createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelay"); thread.start(); validateTailer(listener, file); } @@ -481,7 +482,7 @@ void testSimpleConstructorWithDelayAndFromStart() throws Exception { createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelayAndFromStart"); thread.start(); validateTailer(listener, file); } @@ -493,7 +494,7 @@ void testSimpleConstructorWithDelayAndFromStartWithBufferSize() throws Exception createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, TEST_BUFFER_SIZE)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelayAndFromStartWithBufferSize"); thread.start(); validateTailer(listener, file); } @@ -505,7 +506,7 @@ void testSimpleConstructorWithDelayAndFromStartWithReopen() throws Exception { createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, false)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelayAndFromStartWithReopen"); thread.start(); validateTailer(listener, file); } @@ -517,7 +518,7 @@ void testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSize() throws createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSize"); thread.start(); validateTailer(listener, file); } @@ -529,7 +530,7 @@ void testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSizeAndCharset createFile(file, 0); final TestTailerListener listener = new TestTailerListener(1); try (Tailer tailer = new Tailer(file, StandardCharsets.UTF_8, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSizeAndCharset"); thread.start(); validateTailer(listener, file); } @@ -586,7 +587,7 @@ void testTailer() throws Exception { final String osname = SystemProperties.getOsName(); final boolean isWindows = osname.startsWith("Windows"); try (Tailer tailer = new Tailer(file, listener, delayMillis, false, isWindows)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testTailer"); thread.start(); // Write some lines to the file writeLines(file, "Line one", "Line two"); @@ -648,7 +649,7 @@ void testTailerEndOfFileReached() throws Exception { final String osname = SystemProperties.getOsName(); final boolean isWindows = osname.startsWith("Windows"); try (Tailer tailer = new Tailer(file, listener, delayMillis, false, isWindows)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testTailerEndOfFileReached"); thread.start(); // write a few lines writeLines(file, "line1", "line2", "line3"); @@ -659,7 +660,7 @@ void testTailerEndOfFileReached() throws Exception { // write a few lines writeLines(file, "line7", "line8", "line9"); TestUtils.sleep(testDelayMillis); - // May be > 3 times due to underlying OS behavior wrt streams + // May be > 3 times due to underlying OS behavior and streams. assertTrue(listener.reachedEndOfFile >= 3, "end of file reached at least 3 times"); } } @@ -672,7 +673,7 @@ void testTailerEof() throws Exception { createFile(file, 0); final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = new Tailer(file, listener, delayMillis, false)) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testTailerEof"); thread.start(); // Write some lines to the file writeStrings(file, "Line"); @@ -697,7 +698,7 @@ void testTailerIgnoreTouch() throws Exception { final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = Tailer.builder().setFile(file).setTailerListener(listener).setDelayDuration(Duration.ofMillis(delayMillis)).setStartThread(false) .setIgnoreTouch(true).get()) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testTailerIgnoreTouch"); thread.start(); // Write some lines to the file writeLines(file, "Line one"); @@ -723,7 +724,7 @@ void testTailerReissueOnTouch() throws Exception { final TestTailerListener listener = new TestTailerListener(); try (Tailer tailer = Tailer.builder().setFile(file).setTailerListener(listener).setDelayDuration(Duration.ofMillis(delayMillis)).setStartThread(false) .setIgnoreTouch(false).get()) { - final Thread thread = new Thread(tailer); + final Thread thread = new Thread(tailer, "commons-io-tailer-testTailerReissueOnTouch"); thread.start(); // Write some lines to the file writeLines(file, "Line one"); diff --git a/src/test/java/org/apache/commons/io/input/ThrottledInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ThrottledInputStreamTest.java index d44f29746a5..4f66cb1254c 100644 --- a/src/test/java/org/apache/commons/io/input/ThrottledInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/ThrottledInputStreamTest.java @@ -18,12 +18,16 @@ package org.apache.commons.io.input; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -188,4 +192,20 @@ void testGet() throws IOException { } } + @Test + synchronized void testReadInterrupt() throws IOException { + try (ThrottledInputStream inputStream = ThrottledInputStream.builder() + // @formatter:off + .setInputStream(createOriginInputStream()) + .setMaxBytes(1, ChronoUnit.HOURS) + .get()) { + // @formatter:on + final ThrottledInputStream spy = spy(inputStream); + when(spy.getSleepMillis()).thenReturn(1L); + Thread.currentThread().interrupt(); + assertInstanceOf(InterruptedException.class, assertThrows(InterruptedIOException.class, spy::read).getCause()); + assertTrue(Thread.interrupted()); + } + } + } diff --git a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java index a47604020e1..1566dbc06ef 100644 --- a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java @@ -134,7 +134,7 @@ public int read(final byte[] buf, final int offset, final int length) { try { lock.wait(3000); } catch (final InterruptedException e) { - // Ignore + Thread.currentThread().interrupt(); } } return 1; @@ -148,7 +148,7 @@ public int read(final byte[] buf, final int offset, final int length) { } catch (final Exception e) { // Ignored } - }); + }, "commons-io-UnsynchronizedBufferedInputStream-close"); thread.start(); assertThrows(IOException.class, () -> bufin.read(new byte[100], 0, 99), "Should throw IOException"); } diff --git a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java index a144bc418d0..ec8121ac8b3 100644 --- a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java @@ -18,9 +18,12 @@ package org.apache.commons.io.input; import static org.apache.commons.io.IOUtils.EOF; +import static org.apache.commons.lang3.StringUtils.CR; +import static org.apache.commons.lang3.StringUtils.LF; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -31,11 +34,14 @@ import java.io.PipedReader; import java.io.Reader; import java.io.StringReader; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests {@link UnsynchronizedBufferedReader}. @@ -45,9 +51,11 @@ */ class UnsynchronizedBufferedReaderTest { - private UnsynchronizedBufferedReader br; + private UnsynchronizedBufferedReader reader; - private final String testString = "Test_All_Tests\nTest_java_io_BufferedInputStream\nTest_java_io_BufferedOutputStream\nTest_java_io_ByteArrayInputStream\n" + /** All line endings in the test fixture are LF (\n). */ + private static final String LINES = + "Test_All_Tests\nTest_java_io_BufferedInputStream\nTest_java_io_BufferedOutputStream\nTest_java_io_ByteArrayInputStream\n" + "Test_java_io_ByteArrayOutputStream\nTest_java_io_DataInputStream\nTest_java_io_File\nTest_java_io_FileDescriptor\nTest_java_io_FileInputStream\n" + "Test_java_io_FileNotFoundException\nTest_java_io_FileOutputStream\nTest_java_io_FilterInputStream\nTest_java_io_FilterOutputStream\n" + "Test_java_io_InputStream\nTest_java_io_IOException\nTest_java_io_OutputStream\nTest_java_io_PrintStream\nTest_java_io_RandomAccessFile\n" @@ -79,37 +87,42 @@ class UnsynchronizedBufferedReaderTest { */ @AfterEach protected void afterEach() { - IOUtils.closeQuietly(br); + IOUtils.closeQuietly(reader); } - private void assertLines(final String input, final String... lines) throws IOException { - assertReadLines(input, lines); - assertPeek(input, lines); + private void assertLines(final String input, final int expectedEolLen, final String... expectedLines) throws IOException { + assertReadLines(input, expectedEolLen, expectedLines); + assertPeek(input, expectedLines); } - private void assertPeek(final String input, final String... lines) throws IOException { + private void assertPeek(final String input, final String... expectedLines) throws IOException { try (UnsynchronizedBufferedReader bufferedReader = new UnsynchronizedBufferedReader(new StringReader(input))) { - for (final String line : lines) { + assertEquals(0, bufferedReader.getPosition()); + for (final String expectedLine : expectedLines) { // all - final char[] bufAFull = new char[line.length()]; + final char[] bufAFull = new char[expectedLine.length()]; + final long posPrePeek = bufferedReader.getPosition(); assertEquals(bufAFull.length, bufferedReader.peek(bufAFull)); - assertArrayEquals(line.toCharArray(), bufAFull); - if (!line.isEmpty()) { + assertEquals(posPrePeek, bufferedReader.getPosition()); + assertArrayEquals(expectedLine.toCharArray(), bufAFull); + if (!expectedLine.isEmpty()) { // one - assertEquals(line.charAt(0), bufferedReader.peek()); + assertEquals(expectedLine.charAt(0), bufferedReader.peek()); + assertEquals(posPrePeek, bufferedReader.getPosition()); // array - for (int peekLen = 0; peekLen < line.length(); peekLen++) { - assertPeekArray(bufferedReader, peekLen, line); + for (int peekLen = 0; peekLen < expectedLine.length(); peekLen++) { + assertPeekArray(bufferedReader, peekLen, expectedLine); } } // move test to the next fixture - assertEquals(line, bufferedReader.readLine()); + assertEquals(expectedLine, bufferedReader.readLine()); } assertNull(bufferedReader.readLine()); } } private void assertPeekArray(final UnsynchronizedBufferedReader bufferedReader, final int peekLen, final String line) throws IOException { + final long posPrePeek = bufferedReader.getPosition(); final char[] expectedBuf = new char[peekLen]; final int srcPeekLen = Math.min(peekLen, line.length()); line.getChars(0, srcPeekLen, expectedBuf, 0); @@ -117,12 +130,25 @@ private void assertPeekArray(final UnsynchronizedBufferedReader bufferedReader, final Supplier msg = () -> String.format("len=%,d, line='%s'", peekLen, line); assertEquals(actualBuf.length, bufferedReader.peek(actualBuf), msg); assertArrayEquals(expectedBuf, actualBuf, msg); + assertEquals(posPrePeek, bufferedReader.getPosition()); } - private void assertReadLines(final String input, final String... lines) throws IOException { + private void assertReadLines(final String input, final int expectedEolLen, final String... expectedLines) throws IOException { try (UnsynchronizedBufferedReader bufferedReader = new UnsynchronizedBufferedReader(new StringReader(input))) { - for (final String line : lines) { - assertEquals(line, bufferedReader.readLine()); + int pos = 0; + final AtomicInteger lineNo = new AtomicInteger(1); + final Supplier msg = () -> String.format("line %,d of %,d", lineNo.get(), expectedLines.length); + final boolean lastLineEol = input.endsWith(CR) || input.endsWith(LF); + assertEquals(pos, bufferedReader.getPosition(), msg); + for (final String expectedLine : expectedLines) { + assertEquals(pos, bufferedReader.getPosition(), msg); + assertEquals(expectedLine, bufferedReader.readLine(), msg); + final boolean atLastLine = lineNo.get() == expectedLines.length; + if (!atLastLine || lastLineEol) { + pos += expectedLine.length() + expectedEolLen; + } + assertEquals(pos, bufferedReader.getPosition(), msg); + lineNo.incrementAndGet(); } assertNull(bufferedReader.readLine()); } @@ -136,9 +162,9 @@ private void assertReadLines(final String input, final String... lines) throws I @Test void testClose() throws IOException { // Test for method void UnsynchronizedBufferedReader.close() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.close(); - assertThrows(IOException.class, br::read); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.close(); + assertThrows(IOException.class, reader::read); } @Test @@ -151,6 +177,80 @@ void testEmptyInput() throws Exception { } } + @Test + void testGetPosition() throws IOException { + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertEquals(0, reader.getPosition()); + reader.read(); + assertEquals(1, reader.getPosition()); + reader.read(new char[1]); + assertEquals(2, reader.getPosition()); + reader.read(new char[1], 0, 1); + assertEquals(3, reader.getPosition()); + reader.read(new char[2], 1, 1); + assertEquals(4, reader.getPosition()); + reader.read(new char[10], 0, 10); + assertEquals(14, reader.getPosition()); + IOUtils.toString(reader); + assertEquals(LINES.length(), reader.getPosition()); + reader.close(); + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.mark(LINES.length()); + assertEquals(LINES, IOUtils.toString(reader)); + assertEquals(LINES.length(), reader.getPosition()); + reader.reset(); + assertEquals(LINES, IOUtils.toString(reader)); + assertEquals(LINES.length(), reader.getPosition()); + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.skip(1); + assertEquals(1, reader.getPosition()); + reader.skip(Integer.MAX_VALUE); + assertEquals(LINES.length(), reader.getPosition()); + } + + @Test + void testGetPositionReadLine() throws IOException { + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertEquals(0, reader.getPosition()); + reader.read(); + assertEquals(1, reader.getPosition()); + reader.read(new char[1]); + assertEquals(2, reader.getPosition()); + reader.read(new char[1], 0, 1); + assertEquals(3, reader.getPosition()); + reader.read(new char[2], 1, 1); + assertEquals(4, reader.getPosition()); + reader.read(new char[10], 0, 10); + assertEquals(14, reader.getPosition()); + IOUtils.toString(reader); + assertEquals(LINES.length(), reader.getPosition()); + reader.close(); + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.mark(LINES.length()); + assertEquals(LINES, IOUtils.toString(reader)); + assertEquals(LINES.length(), reader.getPosition()); + reader.reset(); + assertEquals(LINES, IOUtils.toString(reader)); + assertEquals(LINES.length(), reader.getPosition()); + // new reader + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.skip(1); + assertEquals(1, reader.getPosition()); + reader.skip(Integer.MAX_VALUE); + assertEquals(LINES.length(), reader.getPosition()); + } + + @Test + void testIllegalSize() throws Exception { + assertThrows(IllegalArgumentException.class, () -> new UnsynchronizedBufferedReader(new StringReader(""), 0)); + assertThrows(IllegalArgumentException.class, () -> new UnsynchronizedBufferedReader(new StringReader(""), -1)); + } + /** * Tests {@link UnsynchronizedBufferedReader#mark(int)}. * @@ -160,20 +260,20 @@ void testEmptyInput() throws Exception { void testMark() throws IOException { // Test for method void UnsynchronizedBufferedReader.mark(int) char[] buf = null; - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.skip(500); - br.mark(1000); - br.skip(250); - br.reset(); - buf = new char[testString.length()]; - br.read(buf, 0, 500); - assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 500))); - - br = new UnsynchronizedBufferedReader(new StringReader(testString), 800); - br.skip(500); - br.mark(250); - br.read(buf, 0, 1000); - assertThrows(IOException.class, br::reset); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.skip(500); + reader.mark(1000); + reader.skip(250); + reader.reset(); + buf = new char[LINES.length()]; + reader.read(buf, 0, 500); + assertTrue(LINES.substring(500, 1000).equals(new String(buf, 0, 500))); + + reader = new UnsynchronizedBufferedReader(new StringReader(LINES), 800); + reader.skip(500); + reader.mark(250); + reader.read(buf, 0, 1000); + assertThrows(IOException.class, reader::reset); final char[] chars = new char[256]; for (int i = 0; i < 256; i++) { @@ -222,14 +322,25 @@ void testMark() throws IOException { } } + /** + * Tests {@link UnsynchronizedBufferedReader#mark(int)}. + * + * @throws IOException test failure. + */ + @Test + void testMarkIllegal() throws IOException { + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertThrows(IllegalArgumentException.class, () -> reader.mark(-1)); + } + /** * Tests {@link UnsynchronizedBufferedReader#markSupported()}. */ @Test void testMarkSupported() { // Test for method boolean UnsynchronizedBufferedReader.markSupported() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - assertTrue(br.markSupported()); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertTrue(reader.markSupported()); } /** @@ -240,14 +351,14 @@ void testMarkSupported() { @Test void testPeek() throws IOException { // Test for method int UnsynchronizedBufferedReader.read() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - final int p = br.peek(); - assertEquals(testString.charAt(0), p); - final int r = br.read(); - assertEquals(testString.charAt(0), r); - br = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); - assertEquals(br.peek(), '\u8765'); - assertEquals(br.read(), '\u8765'); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + final int p = reader.peek(); + assertEquals(LINES.charAt(0), p); + final int r = reader.read(); + assertEquals(LINES.charAt(0), r); + reader = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); + assertEquals(reader.peek(), '\u8765'); + assertEquals(reader.read(), '\u8765'); // chars '\0'...'\255' final char[] chars = new char[256]; for (int i = 0; i < 256; i++) { @@ -279,15 +390,15 @@ void testPeek() throws IOException { void testPeekArray() throws IOException { // Test for method int UnsynchronizedBufferedReader.read() final char[] peekBuf1 = new char[1]; - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - assertEquals(peekBuf1.length, br.peek(peekBuf1)); - assertEquals(testString.charAt(0), peekBuf1[0]); - final int r = br.read(); - assertEquals(testString.charAt(0), r); - br = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); - assertEquals(peekBuf1.length, br.peek(peekBuf1)); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertEquals(peekBuf1.length, reader.peek(peekBuf1)); + assertEquals(LINES.charAt(0), peekBuf1[0]); + final int r = reader.read(); + assertEquals(LINES.charAt(0), r); + reader = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); + assertEquals(peekBuf1.length, reader.peek(peekBuf1)); assertEquals(peekBuf1[0], '\u8765'); - assertEquals(br.read(), '\u8765'); + assertEquals(reader.read(), '\u8765'); // chars '\0'...'\255' final char[] chars = new char[256]; for (int i = 0; i < 256; i++) { @@ -323,11 +434,11 @@ void testPeekArray() throws IOException { @Test void testRead() throws IOException { // Test for method int UnsynchronizedBufferedReader.read() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - final int r = br.read(); - assertEquals(testString.charAt(0), r); - br = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); - assertEquals(br.read(), '\u8765'); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + final int r = reader.read(); + assertEquals(LINES.charAt(0), r); + reader = new UnsynchronizedBufferedReader(new StringReader(new String(new char[] { '\u8765' }))); + assertEquals(reader.read(), '\u8765'); // final char[] chars = new char[256]; for (int i = 0; i < 256; i++) { @@ -347,6 +458,33 @@ void testRead() throws IOException { } } + @Test + void testReadArray_HARMONY_54() throws IOException { + // Regression for HARMONY-54 + final char[] ch = {}; + @SuppressWarnings("resource") + final UnsynchronizedBufferedReader reader = new UnsynchronizedBufferedReader(new CharArrayReader(ch)); + // Check exception thrown when the reader is open. + assertThrows(NullPointerException.class, () -> reader.read(null, 1, 0)); + + // Now check IOException is thrown in preference to + // NullPointerexception when the reader is closed. + reader.close(); + assertThrows(IOException.class, () -> reader.read(null, 1, 0)); + + // And check that the IOException is thrown before + // ArrayIndexOutOfBoundException + assertThrows(IOException.class, () -> reader.read(ch, 0, 42)); + } + + @Test + void testReadArray_HARMONY_831() throws IOException { + // regression for HARMONY-831 + try (Reader reader = new UnsynchronizedBufferedReader(new PipedReader(), 9)) { + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(new char[] {}, 7, 0)); + } + } + /** * Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}. * @@ -375,28 +513,32 @@ void testReadArray1() throws IOException { void testReadArray2() throws IOException { final char[] ca = new char[2]; // Test to ensure that a drained stream returns 0 at EOF - try (UnsynchronizedBufferedReader toRet2 = new UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new byte[2])))) { - assertEquals(2, toRet2.read(ca, 0, 2)); - assertEquals(-1, toRet2.read(ca, 0, 2)); - assertEquals(0, toRet2.read(ca, 0, 0)); + try (UnsynchronizedBufferedReader reader = new UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new byte[2])))) { + assertEquals(0, reader.getPosition()); + assertEquals(2, reader.read(ca, 0, 2)); + assertEquals(2, reader.getPosition()); + assertEquals(-1, reader.read(ca, 0, 2)); + assertEquals(2, reader.getPosition()); + assertEquals(0, reader.read(ca, 0, 0)); + assertEquals(2, reader.getPosition()); } } @Test void testReadArray3() throws IOException { // Test for method int UnsynchronizedBufferedReader.read(char [], int, int) - final char[] buf = new char[testString.length()]; - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.read(buf, 50, 500); - assertTrue(new String(buf, 50, 500).equals(testString.substring(0, 500))); + final char[] buf = new char[LINES.length()]; + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.read(buf, 50, 500); + assertTrue(new String(buf, 50, 500).equals(LINES.substring(0, 500))); } @Test void testReadArray4() throws IOException { try (UnsynchronizedBufferedReader bufin = new UnsynchronizedBufferedReader(new Reader() { + int size = 2; int pos; - char[] contents = new char[size]; @Override @@ -437,33 +579,6 @@ public boolean ready() throws IOException { } } - @Test - void testReadArray_HARMONY_831() throws IOException { - // regression for HARMONY-831 - try (Reader reader = new UnsynchronizedBufferedReader(new PipedReader(), 9)) { - assertThrows(IndexOutOfBoundsException.class, () -> reader.read(new char[] {}, 7, 0)); - } - } - - @Test - void testReadArray_HARMONY_54() throws IOException { - // Regression for HARMONY-54 - final char[] ch = {}; - @SuppressWarnings("resource") - final UnsynchronizedBufferedReader reader = new UnsynchronizedBufferedReader(new CharArrayReader(ch)); - // Check exception thrown when the reader is open. - assertThrows(NullPointerException.class, () -> reader.read(null, 1, 0)); - - // Now check IOException is thrown in preference to - // NullPointerexception when the reader is closed. - reader.close(); - assertThrows(IOException.class, () -> reader.read(null, 1, 0)); - - // And check that the IOException is thrown before - // ArrayIndexOutOfBoundException - assertThrows(IOException.class, () -> reader.read(ch, 0, 42)); - } - /** * Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}. * @@ -471,28 +586,28 @@ void testReadArray_HARMONY_54() throws IOException { */ @Test void testReadArrayException() throws IOException { - br = new UnsynchronizedBufferedReader(new StringReader(testString)); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); final char[] nullCharArray = null; - final char[] charArray = testString.toCharArray(); - assertThrows(NullPointerException.class, () -> br.read(nullCharArray, -1, 0)); - assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 0, -1)); - assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 1, 1)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, -1, 0)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 0, -1)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, charArray.length, 1)); + final char[] charArray = LINES.toCharArray(); + assertThrows(NullPointerException.class, () -> reader.read(nullCharArray, -1, 0)); + assertThrows(NullPointerException.class, () -> reader.read(nullCharArray, 0, -1)); + assertThrows(NullPointerException.class, () -> reader.read(nullCharArray, 1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(charArray, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(charArray, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(charArray, charArray.length, 1)); - br.read(charArray, 0, 0); - br.read(charArray, 0, charArray.length); - br.read(charArray, charArray.length, 0); + reader.read(charArray, 0, 0); + reader.read(charArray, 0, charArray.length); + reader.read(charArray, charArray.length, 0); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, charArray.length + 1, 0)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, charArray.length + 1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(charArray, charArray.length + 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(charArray, charArray.length + 1, 1)); - br.close(); + reader.close(); - assertThrows(IOException.class, () -> br.read(nullCharArray, -1, -1)); - assertThrows(IOException.class, () -> br.read(charArray, -1, 0)); - assertThrows(IOException.class, () -> br.read(charArray, 0, -1)); + assertThrows(IOException.class, () -> reader.read(nullCharArray, -1, -1)); + assertThrows(IOException.class, () -> reader.read(charArray, -1, 0)); + assertThrows(IOException.class, () -> reader.read(charArray, 0, -1)); } /** @@ -500,12 +615,22 @@ void testReadArrayException() throws IOException { * * @throws IOException test failure. */ - @Test - void testReadLine() throws IOException { - // Test for method java.lang.String UnsynchronizedBufferedReader.readLine() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - final String r = br.readLine(); - assertEquals("Test_All_Tests", r); + @ParameterizedTest + @ValueSource(strings = {LF, CR, "\r\n"}) + void testReadLine(final String eol) throws IOException { + final int eolLen = eol.length(); + // All LINES are separated with LF + reader = new UnsynchronizedBufferedReader(new StringReader(LINES.replace(LF, eol))); + int pos = 0; + assertEquals(pos, reader.getPosition()); + String expected = "Test_All_Tests"; + assertEquals(expected, reader.readLine()); + pos += expected.length() + eolLen; + assertEquals(pos, reader.getPosition()); + expected = "Test_java_io_BufferedInputStream"; + pos += expected.length() + eolLen; + assertEquals(expected, reader.readLine()); + assertEquals(pos, reader.getPosition()); } /** @@ -516,22 +641,22 @@ void testReadLine() throws IOException { */ @Test void testReadLineIgnoresEbcdic85Characters() throws IOException { - assertLines("A\u0085B", "A\u0085B"); + assertLines("A\u0085B", 1, "A\u0085B"); } @Test void testReadLineSeparators() throws IOException { - assertLines("A\nB\nC", "A", "B", "C"); - assertLines("A\rB\rC", "A", "B", "C"); - assertLines("A\r\nB\r\nC", "A", "B", "C"); - assertLines("A\n\rB\n\rC", "A", "", "B", "", "C"); - assertLines("A\n\nB\n\nC", "A", "", "B", "", "C"); - assertLines("A\r\rB\r\rC", "A", "", "B", "", "C"); - assertLines("A\n\n", "A", ""); - assertLines("A\n\r", "A", ""); - assertLines("A\r\r", "A", ""); - assertLines("A\r\n", "A"); - assertLines("A\r\n\r\n", "A", ""); + assertLines("A\nB\nC", 1, "A", "B", "C"); + assertLines("A\rB\rC", 1, "A", "B", "C"); + assertLines("A\r\nB\r\nC", 2, "A", "B", "C"); + assertLines("A\n\rB\n\rC", 1, "A", "", "B", "", "C"); + assertLines("A\n\nB\n\nC", 1, "A", "", "B", "", "C"); + assertLines("A\r\rB\r\rC", 1, "A", "", "B", "", "C"); + assertLines("A\n\n", 1, "A", ""); + assertLines("A\n\r", 1, "A", ""); + assertLines("A\r\r", 1, "A", ""); + assertLines("A\r\n", 2, "A"); + assertLines("A\r\n\r\n", 2, "A", ""); } /** @@ -542,8 +667,10 @@ void testReadLineSeparators() throws IOException { @Test void testReady() throws IOException { // Test for method boolean UnsynchronizedBufferedReader.ready() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - assertTrue(br.ready()); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertEquals(0, reader.getPosition()); + assertTrue(reader.ready()); + assertEquals(0, reader.getPosition()); } /** @@ -554,40 +681,48 @@ void testReady() throws IOException { @Test void testReset() throws IOException { // Test for method void UnsynchronizedBufferedReader.reset() - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.skip(500); - br.mark(900); - br.skip(500); - br.reset(); - final char[] buf = new char[testString.length()]; - br.read(buf, 0, 500); - assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 500))); - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.skip(500); - assertThrows(IOException.class, br::reset); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertEquals(0, reader.getPosition()); + reader.skip(500); + assertEquals(500, reader.getPosition()); + reader.mark(900); + reader.skip(500); + assertEquals(1_000, reader.getPosition()); + reader.reset(); + assertEquals(500, reader.getPosition()); + final char[] buf = new char[LINES.length()]; + reader.read(buf, 0, 500); + assertEquals(1_000, reader.getPosition()); + assertTrue(LINES.substring(500, 1000).equals(new String(buf, 0, 500))); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.skip(500); + assertThrows(IOException.class, reader::reset); } @Test void testReset_IOException() throws Exception { final int[] expected = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', -1 }; - br = new UnsynchronizedBufferedReader(new StringReader("1234567890"), 9); - br.mark(9); + reader = new UnsynchronizedBufferedReader(new StringReader("1234567890"), 9); + reader.mark(9); for (int i = 0; i < 11; i++) { - assertEquals(expected[i], br.read()); + assertEquals(i, reader.getPosition()); + assertEquals(expected[i], reader.read()); } - assertThrows(IOException.class, br::reset); + assertThrows(IOException.class, reader::reset); for (int i = 0; i < 11; i++) { - assertEquals(-1, br.read()); + assertEquals(10, reader.getPosition()); + assertEquals(-1, reader.read()); } - - br = new UnsynchronizedBufferedReader(new StringReader("1234567890")); - br.mark(10); + reader = new UnsynchronizedBufferedReader(new StringReader("1234567890")); + reader.mark(10); for (int i = 0; i < 10; i++) { - assertEquals(expected[i], br.read()); + assertEquals(i, reader.getPosition()); + assertEquals(expected[i], reader.read()); } - br.reset(); + reader.reset(); for (int i = 0; i < 11; i++) { - assertEquals(expected[i], br.read()); + assertEquals(i, reader.getPosition()); + assertEquals(expected[i], reader.read()); } } @@ -599,10 +734,29 @@ void testReset_IOException() throws Exception { @Test void testSkip() throws IOException { // Test for method long UnsynchronizedBufferedReader.skip(long) - br = new UnsynchronizedBufferedReader(new StringReader(testString)); - br.skip(500); - final char[] buf = new char[testString.length()]; - br.read(buf, 0, 500); - assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 500))); + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + reader.skip(500); + final char[] buf = new char[LINES.length()]; + reader.read(buf, 0, 500); + assertTrue(LINES.substring(500, 1000).equals(new String(buf, 0, 500))); + } + + /** + * Tests {@link UnsynchronizedBufferedReader#skip(int)}. + * + * @throws IOException test failure. + */ + @Test + void testSkipIllegal() throws IOException { + reader = new UnsynchronizedBufferedReader(new StringReader(LINES)); + assertThrows(IllegalArgumentException.class, () -> reader.skip(-1)); + } + + @SuppressWarnings("resource") + @Test + void testUnwrap() throws IOException { + final StringReader sReader = new StringReader(LINES); + reader = new UnsynchronizedBufferedReader(sReader); + assertSame(sReader, reader.unwrap()); } } diff --git a/src/test/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStreamTest.java b/src/test/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStreamTest.java index d40fbef1570..b98aa8f93a6 100644 --- a/src/test/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStreamTest.java @@ -163,23 +163,17 @@ void testConstructor3() { @Test void testInvalidConstructor2OffsetUnder() { - assertThrows(IllegalArgumentException.class, () -> { - newStream(IOUtils.EMPTY_BYTE_ARRAY, -1); - }); + assertThrows(IllegalArgumentException.class, () -> newStream(IOUtils.EMPTY_BYTE_ARRAY, -1)); } @Test void testInvalidConstructor3LengthUnder() { - assertThrows(IllegalArgumentException.class, () -> { - newStream(IOUtils.EMPTY_BYTE_ARRAY, 0, -1); - }); + assertThrows(IllegalArgumentException.class, () -> newStream(IOUtils.EMPTY_BYTE_ARRAY, 0, -1)); } @Test void testInvalidConstructor3OffsetUnder() { - assertThrows(IllegalArgumentException.class, () -> { - newStream(IOUtils.EMPTY_BYTE_ARRAY, -1, 1); - }); + assertThrows(IllegalArgumentException.class, () -> newStream(IOUtils.EMPTY_BYTE_ARRAY, -1, 1)); } @Test @@ -187,9 +181,7 @@ void testInvalidConstructor3OffsetUnder() { void testInvalidReadArrayExplicitLenUnder() { final byte[] buf = IOUtils.EMPTY_BYTE_ARRAY; final UnsynchronizedByteArrayInputStream is = newStream(new byte[] { (byte) 0xa, (byte) 0xb, (byte) 0xc }); - assertThrows(IndexOutOfBoundsException.class, () -> { - is.read(buf, 0, -1); - }); + assertThrows(IndexOutOfBoundsException.class, () -> is.read(buf, 0, -1)); } @Test @@ -197,9 +189,7 @@ void testInvalidReadArrayExplicitOffsetUnder() { final byte[] buf = IOUtils.EMPTY_BYTE_ARRAY; @SuppressWarnings("resource") // not necessary to close these resources final UnsynchronizedByteArrayInputStream is = newStream(new byte[] { (byte) 0xa, (byte) 0xb, (byte) 0xc }); - assertThrows(IndexOutOfBoundsException.class, () -> { - is.read(buf, -1, 1); - }); + assertThrows(IndexOutOfBoundsException.class, () -> is.read(buf, -1, 1)); } @Test @@ -207,9 +197,7 @@ void testInvalidReadArrayExplicitRangeOver() { final byte[] buf = IOUtils.EMPTY_BYTE_ARRAY; @SuppressWarnings("resource") // not necessary to close these resources final UnsynchronizedByteArrayInputStream is = newStream(new byte[] { (byte) 0xa, (byte) 0xb, (byte) 0xc }); - assertThrows(IndexOutOfBoundsException.class, () -> { - is.read(buf, 0, 1); - }); + assertThrows(IndexOutOfBoundsException.class, () -> is.read(buf, 0, 1)); } @Test @@ -217,18 +205,14 @@ void testInvalidReadArrayNull() { final byte[] buf = null; @SuppressWarnings("resource") // not necessary to close these resources final UnsynchronizedByteArrayInputStream is = newStream(new byte[] { (byte) 0xa, (byte) 0xb, (byte) 0xc }); - assertThrows(NullPointerException.class, () -> { - is.read(buf); - }); + assertThrows(NullPointerException.class, () -> is.read(buf)); } @Test void testInvalidSkipNUnder() { @SuppressWarnings("resource") // not necessary to close these resources final UnsynchronizedByteArrayInputStream is = newStream(new byte[] { (byte) 0xa, (byte) 0xb, (byte) 0xc }); - assertThrows(IllegalArgumentException.class, () -> { - is.skip(-1); - }); + assertThrows(IllegalArgumentException.class, () -> is.skip(-1)); } @Test diff --git a/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java b/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java index d8e8a48e833..b70ad1ec12f 100644 --- a/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java @@ -150,12 +150,12 @@ private String getXML(final String bomType, final String xmlType, } /** - * @param bomType no-bom, UTF-16BE-bom, UTF-16LE-bom, UTF-8-bom - * @param xmlType xml, xml-prolog, xml-prolog-charset - * @param streamEnc encoding of the stream - * @param prologEnc encoding of the prolog - * @return XML stream - * @throws IOException If an I/O error occurs + * @param bomType no-bom, UTF-16BE-bom, UTF-16LE-bom, UTF-8-bom. + * @param xmlType xml, xml-prolog, xml-prolog-charset. + * @param streamEnc encoding of the stream. + * @param prologEnc encoding of the prolog. + * @return XML stream. + * @throws IOException If an I/O error occurs. */ protected InputStream getXmlInputStream(final String bomType, final String xmlType, final String streamEnc, final String prologEnc) throws IOException { diff --git a/src/test/java/org/apache/commons/io/input/XmlStreamReaderUtilitiesTest.java b/src/test/java/org/apache/commons/io/input/XmlStreamReaderUtilitiesTest.java index fb90ba4d0ec..04a8e9d2228 100644 --- a/src/test/java/org/apache/commons/io/input/XmlStreamReaderUtilitiesTest.java +++ b/src/test/java/org/apache/commons/io/input/XmlStreamReaderUtilitiesTest.java @@ -56,14 +56,14 @@ private static final class MockXmlStreamReader extends XmlStreamReader { protected String calculateHttpEncoding(final String httpContentType, final String bomEnc, final String xmlGuessEnc, final String xmlEnc, final boolean lenient, final String defaultEncoding) throws IOException { try (MockXmlStreamReader mock = new MockXmlStreamReader(defaultEncoding)) { - return mock.calculateHttpEncoding(bomEnc, xmlGuessEnc, xmlEnc, lenient, httpContentType); + return mock.toHttpEncoding(bomEnc, xmlGuessEnc, xmlEnc, lenient, httpContentType); } } protected String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc, final String defaultEncoding) throws IOException { try (MockXmlStreamReader mock = new MockXmlStreamReader(defaultEncoding)) { - return mock.calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); + return mock.toRawEncoding(bomEnc, xmlGuessEnc, xmlEnc); } } diff --git a/src/test/java/org/apache/commons/io/input/compatibility/XmlStreamReader.java b/src/test/java/org/apache/commons/io/input/compatibility/XmlStreamReader.java index 93737d3bcb4..3cf8a437961 100644 --- a/src/test/java/org/apache/commons/io/input/compatibility/XmlStreamReader.java +++ b/src/test/java/org/apache/commons/io/input/compatibility/XmlStreamReader.java @@ -455,7 +455,7 @@ public XmlStreamReader(final InputStream inputStream, final String httpContentTy * the charset encoding. * @param lenient indicates if the charset encoding detection should be * relaxed. - * @param defaultEncoding the default encoding to use + * @param defaultEncoding the default encoding to use. * @throws IOException thrown if there is a problem reading the file. * @throws XmlStreamReaderException thrown if the charset encoding could not * be determined according to the specification. @@ -612,12 +612,7 @@ String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, encoding = xmlEnc; } } else if (bomEnc.equals(UTF_8)) { - if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8)) { - throw new XmlStreamReaderException(RAW_EX_1 - .format(new Object[] { bomEnc, xmlGuessEnc, xmlEnc }), - bomEnc, xmlGuessEnc, xmlEnc, is); - } - if (xmlEnc != null && !xmlEnc.equals(UTF_8)) { + if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8) || xmlEnc != null && !xmlEnc.equals(UTF_8)) { throw new XmlStreamReaderException(RAW_EX_1 .format(new Object[] { bomEnc, xmlGuessEnc, xmlEnc }), bomEnc, xmlGuessEnc, xmlEnc, is); diff --git a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsInputStreamsBenchmark.java b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsInputStreamsBenchmark.java index 028f55a9fc5..68009ab3b8c 100644 --- a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsInputStreamsBenchmark.java +++ b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsInputStreamsBenchmark.java @@ -234,7 +234,7 @@ public boolean[] testFileRelease_2_8_0() throws IOException { } @Benchmark - void testStringCurrent(final Blackhole blackhole) throws IOException { + public void testStringCurrent(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (InputStream inputReader1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET); @@ -246,7 +246,7 @@ void testStringCurrent(final Blackhole blackhole) throws IOException { } @Benchmark - void testStringFileChannels(final Blackhole blackhole) throws IOException { + public void testStringFileChannels(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (InputStream input1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET); @@ -258,7 +258,7 @@ void testStringFileChannels(final Blackhole blackhole) throws IOException { } @Benchmark - void testStringPr118(final Blackhole blackhole) throws IOException { + public void testStringPr118(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (InputStream input1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET); @@ -270,7 +270,7 @@ void testStringPr118(final Blackhole blackhole) throws IOException { } @Benchmark - void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException { + public void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (InputStream input1 = IOUtils.toInputStream(STRINGS[i], DEFAULT_CHARSET); diff --git a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_22_0.java b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_22_0.java new file mode 100644 index 00000000000..1a3ac4929da --- /dev/null +++ b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_22_0.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.jmh; + +import static org.apache.commons.io.IOUtils.DEFAULT_BUFFER_SIZE; +import static org.apache.commons.io.IOUtils.EOF; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Test different implementations of {@link IOUtils#contentEquals(Reader, Reader)}. + * + *
      + * RESULTS:
      + * Benchmark                                                            Mode  Cnt          Score         Error  Units
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testFileCurrent          avgt    5     105274.452 ±    1466.048  ns/op
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testFileRelease2_22_0    avgt    5     107500.847 ±    1752.422  ns/op
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testFile_2_21_0          avgt    5     115720.416 ±    1209.652  ns/op
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testStringCurrent        avgt    5  113330719.330 ± 1187191.151  ns/op
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testStringRelease2_22_0  avgt    5  110389392.582 ±  785367.455  ns/op
      + * IOUtilsContentEqualsReadersBenchmark_2_22_0.testString_2_21_0        avgt    5  284939866.619 ± 9969793.485  ns/op
      + *
      + * Run: mvn clean test -P benchmark -Dbenchmark=IOUtilsContentEqualsReadersBenchmark_2_22_0
      + * 
      + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1, jvmArgs = {"-server"}) +public class IOUtilsContentEqualsReadersBenchmark_2_22_0 { + + private static final int STRING_LEN = 1 << 24; + private static final String TEST_PATH_A = "/org/apache/commons/io/testfileBOM.xml"; + private static final String TEST_PATH_16K_A = "/org/apache/commons/io/abitmorethan16k.txt"; + private static final String TEST_PATH_16K_A_COPY = "/org/apache/commons/io/abitmorethan16kcopy.txt"; + private static final String TEST_PATH_B = "/org/apache/commons/io/testfileNoBOM.xml"; + private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); + static String[] STRINGS = new String[5]; + + static { + STRINGS[0] = StringUtils.repeat("ab", STRING_LEN); + STRINGS[1] = STRINGS[0] + 'c'; + STRINGS[2] = STRINGS[0] + 'd'; + STRINGS[3] = StringUtils.repeat("ab\rab\n", STRING_LEN); + STRINGS[4] = StringUtils.repeat("ab\r\nab\r", STRING_LEN); + } + + static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", STRING_LEN) + '\n', 2); + static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", STRING_LEN) + '\n', 2); + + public static boolean contentEquals_2_21_0(final Reader input1, final Reader input2) throws IOException { + if (input1 == input2) { + return true; + } + if (input1 == null || input2 == null) { + return false; + } + final char[] array1 = new char[DEFAULT_BUFFER_SIZE]; + final char[] array2 = new char[DEFAULT_BUFFER_SIZE]; + int pos1; + int pos2; + int count1; + int count2; + while (true) { + pos1 = 0; + pos2 = 0; + for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) { + if (pos1 == index) { + do { + count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1); + } while (count1 == 0); + if (count1 == EOF) { + return pos2 == index && input2.read() == EOF; + } + pos1 += count1; + } + if (pos2 == index) { + do { + count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2); + } while (count2 == 0); + if (count2 == EOF) { + return pos1 == index && input1.read() == EOF; + } + pos2 += count2; + } + if (array1[index] != array2[index]) { + return false; + } + } + } + } + + /** + * Version 2.22.0 (December 2025). + */ + public static boolean contentEqualsRelease2_22_0(final Reader input1, final Reader input2) throws IOException { + if (input1 == input2) { + return true; + } + if (input1 == null || input2 == null) { + return false; + } + final char[] array1 = new char[DEFAULT_BUFFER_SIZE]; + final char[] array2 = new char[DEFAULT_BUFFER_SIZE]; + int read1; + int read2; + while (true) { + read1 = input1.read(array1, 0, DEFAULT_BUFFER_SIZE); + read2 = input2.read(array2, 0, DEFAULT_BUFFER_SIZE); + // If both read EOF here, they're equal. + if (read1 == EOF && read2 == EOF) { + return true; + } + // If only one read EOF or different amounts, they're not equal. + if (read1 != read2) { + return false; + } + // Compare the buffers - bulk comparison is faster than character-by-character + for (int i = 0; i < read1; i++) { + if (array1[i] != array2[i]) { + return false; + } + } + } + } + + @Benchmark + public boolean[] testFile_2_21_0() throws IOException { + final boolean[] res = new boolean[3]; + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_B), DEFAULT_CHARSET)) { + res[0] = contentEquals_2_21_0(input1, input1); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET)) { + res[1] = contentEquals_2_21_0(input1, input2); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A)); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A_COPY))) { + res[2] = contentEquals_2_21_0(input1, input2); + } + return res; + } + + @Benchmark + public boolean[] testFileCurrent() throws IOException { + final boolean[] res = new boolean[3]; + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_B), DEFAULT_CHARSET)) { + res[0] = IOUtils.contentEquals(input1, input1); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET)) { + res[1] = IOUtils.contentEquals(input1, input2); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A_COPY), + DEFAULT_CHARSET)) { + res[2] = IOUtils.contentEquals(input1, input2); + } + return res; + } + + @Benchmark + public boolean[] testFileRelease2_22_0() throws IOException { + final boolean[] res = new boolean[3]; + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_B), DEFAULT_CHARSET)) { + res[0] = contentEqualsRelease2_22_0(input1, input1); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_A), DEFAULT_CHARSET)) { + res[1] = contentEqualsRelease2_22_0(input1, input2); + } + try (Reader input1 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A), DEFAULT_CHARSET); + Reader input2 = new InputStreamReader(getClass().getResourceAsStream(TEST_PATH_16K_A_COPY), + DEFAULT_CHARSET)) { + res[2] = contentEqualsRelease2_22_0(input1, input2); + } + return res; + } + + @Benchmark + public void testString_2_21_0(final Blackhole blackhole) throws IOException { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + try (StringReader input1 = new StringReader(STRINGS[i]); + StringReader input2 = new StringReader(STRINGS[j])) { + blackhole.consume(contentEquals_2_21_0(input1, input2)); + } + } + } + } + + @Benchmark + public void testStringCurrent(final Blackhole blackhole) throws IOException { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + try (StringReader input1 = new StringReader(STRINGS[i]); + StringReader input2 = new StringReader(STRINGS[j])) { + blackhole.consume(IOUtils.contentEquals(input1, input2)); + } + } + } + } + + @Benchmark + public void testStringRelease2_22_0(final Blackhole blackhole) throws IOException { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + try (StringReader input1 = new StringReader(STRINGS[i]); + StringReader input2 = new StringReader(STRINGS[j])) { + blackhole.consume(contentEqualsRelease2_22_0(input1, input2)); + } + } + } + } + +} diff --git a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_9_0.java similarity index 97% rename from src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java rename to src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_9_0.java index d7282d8ea98..b548e10e760 100644 --- a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java +++ b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark_2_9_0.java @@ -59,7 +59,7 @@ @Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) @Fork(value = 1, jvmArgs = {"-server"}) -public class IOUtilsContentEqualsReadersBenchmark { +public class IOUtilsContentEqualsReadersBenchmark_2_9_0 { private static final int STRING_LEN = 1 << 24; private static final String TEST_PATH_A = "/org/apache/commons/io/testfileBOM.xml"; @@ -80,7 +80,6 @@ public class IOUtilsContentEqualsReadersBenchmark { static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", STRING_LEN) + '\n', 2); static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", STRING_LEN) + '\n', 2); - @SuppressWarnings("resource") public static boolean contentEquals_release_2_8_0(final Reader input1, final Reader input2) throws IOException { if (input1 == input2) { return true; @@ -203,7 +202,7 @@ public boolean[] testFileRelease_2_8_0() throws IOException { } @Benchmark - void testStringCurrent(final Blackhole blackhole) throws IOException { + public void testStringCurrent(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (StringReader input1 = new StringReader(STRINGS[i]); @@ -215,7 +214,7 @@ void testStringCurrent(final Blackhole blackhole) throws IOException { } @Benchmark - void testStringPr118(final Blackhole blackhole) throws IOException { + public void testStringPr118(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (StringReader input1 = new StringReader(STRINGS[i]); @@ -227,7 +226,7 @@ void testStringPr118(final Blackhole blackhole) throws IOException { } @Benchmark - void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException { + public void testStringRelease_2_8_0(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { try (StringReader input1 = new StringReader(STRINGS[i]); diff --git a/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java b/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java index 2fd9688cc83..92913525641 100644 --- a/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java +++ b/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java @@ -92,7 +92,7 @@ public boolean[] testCurrent_fileContentEquals() throws IOException { } @Benchmark - void testCurrent_fileContentEquals_Blackhole(final Blackhole blackhole) throws IOException { + public void testCurrent_fileContentEquals_Blackhole(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { blackhole.consume(PathUtils.fileContentEquals(bigFile1, bigFile2)); @@ -108,7 +108,7 @@ public boolean[] testProposal_contentEquals() throws IOException { } @Benchmark - void testProposal_contentEquals_Blackhole(final Blackhole blackhole) throws IOException { + public void testProposal_contentEquals_Blackhole(final Blackhole blackhole) throws IOException { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { blackhole.consume(newFileContentEquals(bigFile1, bigFile2)); diff --git a/src/test/java/org/apache/commons/io/jmh/jmh-result.IOUtilsContentEqualsReadersBenchmark_2_22_0.json b/src/test/java/org/apache/commons/io/jmh/jmh-result.IOUtilsContentEqualsReadersBenchmark_2_22_0.json new file mode 100644 index 00000000000..b9866381304 --- /dev/null +++ b/src/test/java/org/apache/commons/io/jmh/jmh-result.IOUtilsContentEqualsReadersBenchmark_2_22_0.json @@ -0,0 +1,316 @@ +[ + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testFileCurrent", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 105274.45150949701, + "scoreError" : 1466.048137250757, + "scoreConfidence" : [ + 103808.40337224625, + 106740.49964674778 + ], + "scorePercentiles" : { + "0.0" : 104886.00264178636, + "50.0" : 105288.5813030386, + "90.0" : 105680.8236754792, + "95.0" : 105680.8236754792, + "99.0" : 105680.8236754792, + "99.9" : 105680.8236754792, + "99.99" : 105680.8236754792, + "99.999" : 105680.8236754792, + "99.9999" : 105680.8236754792, + "100.0" : 105680.8236754792 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 104886.00264178636, + 104895.74661620239, + 105288.5813030386, + 105621.1033109785, + 105680.8236754792 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testFileRelease2_22_0", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 107500.84684379803, + "scoreError" : 1752.4217586039497, + "scoreConfidence" : [ + 105748.42508519409, + 109253.26860240198 + ], + "scorePercentiles" : { + "0.0" : 106873.3514249701, + "50.0" : 107628.91570477307, + "90.0" : 107940.15830545062, + "95.0" : 107940.15830545062, + "99.0" : 107940.15830545062, + "99.9" : 107940.15830545062, + "99.99" : 107940.15830545062, + "99.999" : 107940.15830545062, + "99.9999" : 107940.15830545062, + "100.0" : 107940.15830545062 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 106873.3514249701, + 107196.61074616629, + 107940.15830545062, + 107865.19803763006, + 107628.91570477307 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testFile_2_21_0", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 115720.41582367099, + "scoreError" : 1209.6519258037997, + "scoreConfidence" : [ + 114510.76389786719, + 116930.0677494748 + ], + "scorePercentiles" : { + "0.0" : 115404.00708540568, + "50.0" : 115555.87606112054, + "90.0" : 116064.94892289132, + "95.0" : 116064.94892289132, + "99.0" : 116064.94892289132, + "99.9" : 116064.94892289132, + "99.99" : 116064.94892289132, + "99.999" : 116064.94892289132, + "99.9999" : 116064.94892289132, + "100.0" : 116064.94892289132 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 115524.47655447145, + 115555.87606112054, + 115404.00708540568, + 116052.77049446598, + 116064.94892289132 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testStringCurrent", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 1.1333071932957098E8, + "scoreError" : 1187191.1514731636, + "scoreConfidence" : [ + 1.1214352817809781E8, + 1.1451791048104414E8 + ], + "scorePercentiles" : { + "0.0" : 1.130216048764045E8, + "50.0" : 1.1326928604494382E8, + "90.0" : 1.138494346590909E8, + "95.0" : 1.138494346590909E8, + "99.0" : 1.138494346590909E8, + "99.9" : 1.138494346590909E8, + "99.99" : 1.138494346590909E8, + "99.999" : 1.138494346590909E8, + "99.9999" : 1.138494346590909E8, + "100.0" : 1.138494346590909E8 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 1.138494346590909E8, + 1.1323600233707865E8, + 1.1327726873033708E8, + 1.130216048764045E8, + 1.1326928604494382E8 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testStringRelease2_22_0", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 1.1038939258241758E8, + "scoreError" : 785367.4549160029, + "scoreConfidence" : [ + 1.0960402512750158E8, + 1.1117476003733358E8 + ], + "scorePercentiles" : { + "0.0" : 1.1018472527472527E8, + "50.0" : 1.1037075183516483E8, + "90.0" : 1.106605673076923E8, + "95.0" : 1.106605673076923E8, + "99.0" : 1.106605673076923E8, + "99.9" : 1.106605673076923E8, + "99.99" : 1.106605673076923E8, + "99.999" : 1.106605673076923E8, + "99.9999" : 1.106605673076923E8, + "100.0" : 1.106605673076923E8 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 1.1052274404395604E8, + 1.1020817445054945E8, + 1.1037075183516483E8, + 1.1018472527472527E8, + 1.106605673076923E8 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "org.apache.commons.io.jmh.IOUtilsContentEqualsReadersBenchmark_2_22_0.testString_2_21_0", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/opt/homebrew/Cellar/openjdk@21/21.0.9/libexec/openjdk.jdk/Contents/Home/bin/java", + "jvmArgs" : [ + "-server" + ], + "jdkVersion" : "21.0.9", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "21.0.9", + "warmupIterations" : 5, + "warmupTime" : "10 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "10 s", + "measurementBatchSize" : 1, + "primaryMetric" : { + "score" : 2.849398666193651E8, + "scoreError" : 9969793.485202054, + "scoreConfidence" : [ + 2.74970073134163E8, + 2.9490966010456717E8 + ], + "scorePercentiles" : { + "0.0" : 2.803803090277778E8, + "50.0" : 2.858566214285714E8, + "90.0" : 2.866562731666667E8, + "95.0" : 2.866562731666667E8, + "99.0" : 2.866562731666667E8, + "99.9" : 2.866562731666667E8, + "99.99" : 2.866562731666667E8, + "99.999" : 2.866562731666667E8, + "99.9999" : 2.866562731666667E8, + "100.0" : 2.866562731666667E8 + }, + "scoreUnit" : "ns/op", + "rawData" : [ + [ + 2.8634049405714285E8, + 2.803803090277778E8, + 2.858566214285714E8, + 2.866562731666667E8, + 2.854656354166667E8 + ] + ] + }, + "secondaryMetrics" : { + } + } +] + + diff --git a/src/test/java/org/apache/commons/io/monitor/AbstractMonitorTest.java b/src/test/java/org/apache/commons/io/monitor/AbstractMonitorTest.java index 31d2bd7479f..d00feb3fdbb 100644 --- a/src/test/java/org/apache/commons/io/monitor/AbstractMonitorTest.java +++ b/src/test/java/org/apache/commons/io/monitor/AbstractMonitorTest.java @@ -53,7 +53,7 @@ public abstract class AbstractMonitorTest { /** * Check all the Collections are empty * - * @param label the label to use for this check + * @param label the label to use for this check. */ protected void checkCollectionsEmpty(final String label) { checkCollectionSizes("EMPTY-" + label, 0, 0, 0, 0, 0, 0); @@ -62,13 +62,13 @@ protected void checkCollectionsEmpty(final String label) { /** * Check all the Collections have the expected sizes. * - * @param label the label to use for this check - * @param dirCreate expected number of dirs created - * @param dirChange expected number of dirs changed - * @param dirDelete expected number of dirs deleted - * @param fileCreate expected number of files created - * @param fileChange expected number of files changed - * @param fileDelete expected number of files deleted + * @param label the label to use for this check. + * @param dirCreate expected number of dirs created. + * @param dirChange expected number of dirs changed. + * @param dirDelete expected number of dirs deleted. + * @param fileCreate expected number of files created. + * @param fileChange expected number of files changed. + * @param fileDelete expected number of files deleted. */ protected void checkCollectionSizes(String label, final int dirCreate, @@ -94,8 +94,8 @@ protected void checkCollectionSizes(String label, /** * Create a {@link FileAlterationObserver}. * - * @param file The directory to observe - * @param fileFilter The file filter to apply + * @param file The directory to observe. + * @param fileFilter The file filter to apply. */ protected void createObserver(final File file, final FileFilter fileFilter) { observer = FileAlterationObserver.builder().setFile(file).setFileFilter(fileFilter).getUnchecked(); @@ -127,8 +127,8 @@ public void setUp() { * Either creates a file if it doesn't exist or updates the last modified date/time * if it does. * - * @param file The file to touch - * @return The file + * @param file The file to touch. + * @return The file. * @throws IOException if an I/O error occurs. */ protected File touch(File file) throws IOException { diff --git a/src/test/java/org/apache/commons/io/monitor/CollectionFileListener.java b/src/test/java/org/apache/commons/io/monitor/CollectionFileListener.java index 309eb63b526..4e597d8e2f1 100644 --- a/src/test/java/org/apache/commons/io/monitor/CollectionFileListener.java +++ b/src/test/java/org/apache/commons/io/monitor/CollectionFileListener.java @@ -60,7 +60,7 @@ public void clear() { /** * Gets the set of changed directories. * - * @return Directories which have changed + * @return Directories which have changed. */ public Collection getChangedDirectories() { return changedDirectories; @@ -69,7 +69,7 @@ public Collection getChangedDirectories() { /** * Gets the set of changed files. * - * @return Files which have changed + * @return Files which have changed. */ public Collection getChangedFiles() { return changedFiles; @@ -78,7 +78,7 @@ public Collection getChangedFiles() { /** * Gets the set of created directories. * - * @return Directories which have been created + * @return Directories which have been created. */ public Collection getCreatedDirectories() { return createdDirectories; @@ -87,7 +87,7 @@ public Collection getCreatedDirectories() { /** * Gets the set of created files. * - * @return Files which have been created + * @return Files which have been created. */ public Collection getCreatedFiles() { return createdFiles; @@ -96,7 +96,7 @@ public Collection getCreatedFiles() { /** * Gets the set of deleted directories. * - * @return Directories which been deleted + * @return Directories which been deleted. */ public Collection getDeletedDirectories() { return deletedDirectories; @@ -105,7 +105,7 @@ public Collection getDeletedDirectories() { /** * Gets the set of deleted files. * - * @return Files which been deleted + * @return Files which been deleted. */ public Collection getDeletedFiles() { return deletedFiles; @@ -114,7 +114,7 @@ public Collection getDeletedFiles() { /** * Directory changed Event. * - * @param directory The directory changed + * @param directory The directory changed. */ @Override public void onDirectoryChange(final File directory) { @@ -124,7 +124,7 @@ public void onDirectoryChange(final File directory) { /** * Directory created Event. * - * @param directory The directory created + * @param directory The directory created. */ @Override public void onDirectoryCreate(final File directory) { @@ -134,7 +134,7 @@ public void onDirectoryCreate(final File directory) { /** * Directory deleted Event. * - * @param directory The directory deleted + * @param directory The directory deleted. */ @Override public void onDirectoryDelete(final File directory) { @@ -144,7 +144,7 @@ public void onDirectoryDelete(final File directory) { /** * File changed Event. * - * @param file The file changed + * @param file The file changed. */ @Override public void onFileChange(final File file) { @@ -154,7 +154,7 @@ public void onFileChange(final File file) { /** * File created Event. * - * @param file The file created + * @param file The file created. */ @Override public void onFileCreate(final File file) { @@ -164,7 +164,7 @@ public void onFileCreate(final File file) { /** * File deleted Event. * - * @param file The file deleted + * @param file The file deleted. */ @Override public void onFileDelete(final File file) { @@ -174,7 +174,7 @@ public void onFileDelete(final File file) { /** * File system observer started checking event. * - * @param observer The file system observer + * @param observer The file system observer. */ @Override public void onStart(final FileAlterationObserver observer) { @@ -186,7 +186,7 @@ public void onStart(final FileAlterationObserver observer) { /** * File system observer finished checking event. * - * @param observer The file system observer + * @param observer The file system observer. */ @Override public void onStop(final FileAlterationObserver observer) { diff --git a/src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java b/src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java index ce236dbb5b6..7882d6dbaf3 100644 --- a/src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java +++ b/src/test/java/org/apache/commons/io/monitor/FileAlterationMonitorTest.java @@ -128,8 +128,9 @@ void testDefaultConstructor() { } /** - * Test checkAndNotify() method - * @throws Exception + * Test checkAndNotify() method. + * + * @throws Exception Thrown on a test failure. */ @Test void testMonitor() throws Exception { @@ -201,7 +202,7 @@ public Thread newThread(final Runnable r) { /** * Test using a thread factory. - * @throws Exception + * @throws Exception. */ @Test void testThreadFactory() throws Exception { diff --git a/src/test/java/org/apache/commons/io/output/ChunkedOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ChunkedOutputStreamTest.java index f56cc4e9ebc..211438a5586 100644 --- a/src/test/java/org/apache/commons/io/output/ChunkedOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ChunkedOutputStreamTest.java @@ -48,7 +48,7 @@ public void write(final byte[] b, final int off, final int len) { /** * Tests the default chunk size with a ByteArrayOutputStream. * - * @throws IOException + * @throws IOException. */ @Test void testBuildSetByteArrayOutputStream() throws IOException { @@ -64,7 +64,7 @@ void testBuildSetByteArrayOutputStream() throws IOException { /** * Tests the default chunk size with a Path. * - * @throws IOException + * @throws IOException Thrown on test failure. */ @Test void testBuildSetPath() throws IOException { diff --git a/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java index ae3fe77d50d..e0b29fb599b 100644 --- a/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java @@ -148,7 +148,8 @@ void testAtThreshold(final int initialBufferSize) throws IOException { /** * Tests the case where the amount of data falls below the threshold, and is therefore confined to memory. - * @throws IOException + * + * @throws IOException Thrown on a test failure. */ @ParameterizedTest(name = "initialBufferSize = {0}") @MethodSource("data") @@ -237,7 +238,8 @@ void testTempFileAboveThreshold(final int initialBufferSize) throws IOException /** * Tests specifying a temporary file and the threshold is reached. - * @throws IOException + * + * @throws IOException Thrown on a test failure. */ @ParameterizedTest(name = "initialBufferSize = {0}") @MethodSource("data") @@ -278,7 +280,8 @@ void testTempFileAboveThresholdPrefixOnly(final int initialBufferSize) throws IO /** * Tests specifying a temporary file and the threshold not reached. - * @throws IOException + * + * @throws IOException Thrown on a test failure. */ @ParameterizedTest(name = "initialBufferSize = {0}") @MethodSource("data") @@ -302,7 +305,7 @@ void testTempFileBelowThreshold(final int initialBufferSize) throws IOException /** * Tests specifying a temporary file and the threshold is reached. * - * @throws Exception + * @throws IOException Thrown on a test failure. */ @Test void testTempFileError() throws Exception { @@ -338,7 +341,8 @@ void testThresholdNegative(final int initialBufferSize) throws IOException { /** * Tests the case where there are multiple writes beyond the threshold, to ensure that the * {@code thresholdReached()} method is only called once, as the threshold is crossed for the first time. - * @throws IOException + * + * @throws IOException Thrown on a test failure. */ @ParameterizedTest(name = "initialBufferSize = {0}") @MethodSource("data") @@ -418,7 +422,8 @@ void testWriteToLargeCtor(final int initialBufferSize) throws IOException { /** * Tests whether writeTo() properly writes small content. - * @throws IOException + * + * @throws IOException Thrown on a test failure. */ @ParameterizedTest(name = "initialBufferSize = {0}") @MethodSource("data") diff --git a/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java new file mode 100644 index 00000000000..3f840d05d08 --- /dev/null +++ b/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.output; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FlushShieldOutputStream}. + */ +class FlushShieldOutputStreamTest { + + /** Tracks whether the underlying stream's close() was called. */ + private AtomicBoolean closed; + + /** Tracks whether the underlying stream's flush() was called. */ + private AtomicBoolean flushed; + + /** The stream under test. */ + private FlushShieldOutputStream shielded; + + /** The underlying byte-array-backed stream used as the delegate. */ + private ByteArrayOutputStream target; + + @BeforeEach + void setUp() { + flushed = new AtomicBoolean(); + closed = new AtomicBoolean(); + target = new ByteArrayOutputStream() { + + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + + @Override + public void flush() throws IOException { + flushed.set(true); + super.flush(); + } + }; + shielded = new FlushShieldOutputStream(target); + } + + @Test + void testBuilderGet() throws IOException { + assertNotNull(FlushShieldOutputStream.builder().setOutputStream(target).get()); + } + + @Test + void testBuilderWithOutputStream() throws IOException { + try (FlushShieldOutputStream built = FlushShieldOutputStream.builder().setOutputStream(target).get()) { + assertNotNull(built); + assertInstanceOf(FlushShieldOutputStream.class, built); + // flush must be shielded for builder-created instances too + built.flush(); + assertFalse(flushed.get(), "flush() via builder instance must NOT reach the underlying stream."); + // writes must still work + built.write('B'); + assertEquals(1, target.size()); + assertEquals('B', target.toByteArray()[0]); + } + } + + @Test + void testCloseReachesUnderlying() throws IOException { + assertFalse(closed.get(), "close should not have been called yet."); + shielded.close(); + assertTrue(closed.get(), "close() must reach the underlying stream."); + } + + @Test + void testFlushCanBeCalledMultipleTimes() throws IOException { + shielded.flush(); + shielded.flush(); + shielded.flush(); + assertFalse(flushed.get(), "repeated flush() calls must NOT reach the underlying stream."); + } + + @Test + void testFlushDoesNotDelegateToUnderlying() throws IOException { + assertFalse(flushed.get(), "flush should not have been called yet."); + shielded.flush(); + assertFalse(flushed.get(), "flush() must NOT reach the underlying stream."); + } + + @Test + void testWriteByteArray() throws IOException { + final byte[] data = { 'H', 'i' }; + shielded.write(data); + assertEquals(2, target.size()); + assertArrayEquals(data, target.toByteArray()); + } + + @Test + void testWriteByteArrayWithOffset() throws IOException { + final byte[] data = { 'X', 'Y', 'Z' }; + shielded.write(data, 1, 2); + assertEquals(2, target.size()); + assertArrayEquals(new byte[] { 'Y', 'Z' }, target.toByteArray()); + } + + @Test + void testWriteInt() throws IOException { + shielded.write('A'); + assertEquals(1, target.size()); + assertEquals('A', target.toByteArray()[0]); + } +} diff --git a/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java index 45b9e759cd5..0669b68cdb2 100644 --- a/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java @@ -38,8 +38,8 @@ class ProxyOutputStreamTest { private ProxyOutputStream proxied; - private final AtomicBoolean hitByteArray = new AtomicBoolean(); - private final AtomicBoolean hitByteArrayAt = new AtomicBoolean(); + private final AtomicBoolean hitArray = new AtomicBoolean(); + private final AtomicBoolean hitArrayAt = new AtomicBoolean(); private final AtomicBoolean hitInt = new AtomicBoolean(); @BeforeEach @@ -48,13 +48,13 @@ public void setUp() { @Override public void write(final byte[] ba) { - hitByteArray.set(true); + hitArray.set(true); super.write(ba); } @Override public void write(final byte[] b, final int off, final int len) { - hitByteArrayAt.set(true); + hitArrayAt.set(true); super.write(b, off, len); } @@ -72,31 +72,35 @@ void testBuilder() throws Exception { assertSame(target, new ProxyOutputStream.Builder().setOutputStream(target).get().unwrap()); } - @SuppressWarnings("resource") @Test void testSetReference() throws Exception { - assertFalse(hitByteArray.get()); + assertFalse(hitArray.get()); proxied.setReference(new ByteArrayOutputStream()); proxied.write('y'); - assertFalse(hitByteArray.get()); + assertFalse(hitArray.get()); assertEquals(0, target.size()); assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, target.toByteArray()); } + @Test + void testUnrwap() throws Exception { + assertSame(target, proxied.unwrap()); + } + @Test void testWriteByteArray() throws Exception { - assertFalse(hitByteArray.get()); + assertFalse(hitArray.get()); proxied.write(new byte[] { 'y', 'z' }); - assertTrue(hitByteArray.get()); + assertTrue(hitArray.get()); assertEquals(2, target.size()); assertArrayEquals(new byte[] { 'y', 'z' }, target.toByteArray()); } @Test void testWriteByteArrayAt() throws Exception { - assertFalse(hitByteArrayAt.get()); + assertFalse(hitArrayAt.get()); proxied.write(new byte[] { 'y', 'z' }, 1, 1); - assertTrue(hitByteArrayAt.get()); + assertTrue(hitArrayAt.get()); assertEquals(1, target.size()); assertArrayEquals(new byte[] { 'z' }, target.toByteArray()); } @@ -105,25 +109,25 @@ void testWriteByteArrayAt() throws Exception { void testWriteByteArrayAtRepeat() throws Exception { // repeat -1 proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 0); - assertFalse(hitByteArrayAt.get()); - hitByteArray.set(false); + assertFalse(hitArrayAt.get()); + hitArray.set(false); assertEquals(0, target.size()); assertArrayEquals(new byte[] {}, target.toByteArray()); // repeat 0 proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 0); - assertFalse(hitByteArrayAt.get()); - hitByteArray.set(false); + assertFalse(hitArrayAt.get()); + hitArray.set(false); assertEquals(0, target.size()); assertArrayEquals(new byte[] {}, target.toByteArray()); // repeat 1 proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 1); - assertTrue(hitByteArrayAt.get()); - hitByteArray.set(false); + assertTrue(hitArrayAt.get()); + hitArray.set(false); assertEquals(1, target.size()); assertArrayEquals(new byte[] { 'z' }, target.toByteArray()); // repeat 2 proxied.writeRepeat(new byte[] { 'y', 'x' }, 1, 1, 2); - assertTrue(hitByteArrayAt.get()); + assertTrue(hitArrayAt.get()); assertEquals(3, target.size()); assertArrayEquals(new byte[] { 'z', 'x', 'x' }, target.toByteArray()); } @@ -132,25 +136,25 @@ void testWriteByteArrayAtRepeat() throws Exception { void testWriteByteArrayRepeat() throws Exception { // repeat -1 proxied.writeRepeat(new byte[] { 'y', 'z' }, -1); - assertFalse(hitByteArray.get()); - hitByteArray.set(false); + assertFalse(hitArray.get()); + hitArray.set(false); assertEquals(0, target.size()); assertArrayEquals(new byte[] {}, target.toByteArray()); // repeat 0 proxied.writeRepeat(new byte[] { 'y', 'z' }, 0); - assertFalse(hitByteArray.get()); - hitByteArray.set(false); + assertFalse(hitArray.get()); + hitArray.set(false); assertEquals(0, target.size()); assertArrayEquals(new byte[] {}, target.toByteArray()); // repeat 1 proxied.writeRepeat(new byte[] { 'y', 'z' }, 1); - assertTrue(hitByteArray.get()); - hitByteArray.set(false); + assertTrue(hitArray.get()); + hitArray.set(false); assertEquals(2, target.size()); assertArrayEquals(new byte[] { 'y', 'z' }, target.toByteArray()); // repeat 2 proxied.writeRepeat(new byte[] { 'y', 'z' }, 2); - assertTrue(hitByteArray.get()); + assertTrue(hitArray.get()); assertEquals(6, target.size()); assertArrayEquals(new byte[] { 'y', 'z', 'y', 'z' , 'y', 'z' }, target.toByteArray()); } @@ -196,11 +200,11 @@ void testWriteIntRepeat() throws Exception { @Test void testWriteNullArrayProxiesToUnderlying() throws Exception { - assertFalse(hitByteArray.get()); + assertFalse(hitArray.get()); final byte[] ba = null; assertThrows(NullPointerException.class, () -> target.write(ba)); - assertTrue(hitByteArray.get()); + assertTrue(hitArray.get()); assertThrows(NullPointerException.class, () -> proxied.write(ba)); - assertTrue(hitByteArray.get()); + assertTrue(hitArray.get()); } } diff --git a/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java b/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java index e810d39d726..65a5970d48e 100644 --- a/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java +++ b/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java @@ -17,13 +17,18 @@ package org.apache.commons.io.output; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.io.OutputStreamWriter; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** @@ -31,6 +36,39 @@ */ class ProxyWriterTest { + private StringWriter target; + + private ProxyWriter proxied; + + private final AtomicBoolean hitArray = new AtomicBoolean(); + private final AtomicBoolean hitArrayAt = new AtomicBoolean(); + private final AtomicBoolean hitInt = new AtomicBoolean(); + + @BeforeEach + public void setUp() { + target = new StringWriter() { + + @Override + public void write(final char[] ba) throws IOException { + hitArray.set(true); + super.write(ba); + } + + @Override + public void write(final char[] b, final int off, final int len) { + hitArrayAt.set(true); + super.write(b, off, len); + } + + @Override + public synchronized void write(final int ba) { + hitInt.set(true); + super.write(ba); + } + }; + proxied = new ProxyWriter(target); + } + @Test void testAppendChar() throws Exception { try (StringBuilderWriter writer = new StringBuilderWriter(); @@ -226,6 +264,21 @@ void testNullString() throws Exception { } } + @Test + void testSetReference() throws Exception { + assertFalse(hitArray.get()); + proxied.setReference(new StringWriter()); + proxied.write('y'); + assertFalse(hitArray.get()); + assertEquals(0, target.toString().length()); + assertEquals("", target.toString()); + } + + @Test + void testUnrwap() throws Exception { + assertSame(target, proxied.unwrap()); + } + @Test void testWriteCharArray() throws Exception { try (StringBuilderWriter writer = new StringBuilderWriter(); @@ -262,6 +315,7 @@ void testWriteString() throws Exception { } } + @Test void testWriteStringPartial() throws Exception { try (StringBuilderWriter writer = new StringBuilderWriter(); diff --git a/src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java index 5e1e2c00d9d..bd0f996493f 100644 --- a/src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java @@ -247,6 +247,26 @@ protected void thresholdReached() throws IOException { } } + @Test + void testThresholdReachedRetriedAfterException() throws IOException { + final int threshold = 1; + final AtomicInteger counter = new AtomicInteger(); + try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, tos -> { + if (counter.incrementAndGet() == 1) { + throw new IOException("Threshold reached."); + } + }, os -> new ByteArrayOutputStream(4))) { + assertThresholdingInitialState(out, threshold, 0); + out.write('a'); + assertFalse(out.isThresholdExceeded()); + assertThrows(IOException.class, () -> out.write('a')); + assertFalse(out.isThresholdExceeded()); + out.write('a'); + assertEquals(2, counter.get()); + assertTrue(out.isThresholdExceeded()); + } + } + @Test void testThresholdZero() throws IOException { final AtomicBoolean reached = new AtomicBoolean(); diff --git a/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java index ffa213ffd43..3985744d5da 100644 --- a/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java @@ -58,6 +58,22 @@ void testFlush() throws IOException { } } + @Test + void testIO887() throws IOException { + final StringWriter stringWriter1 = new StringWriter(); + final Charset charset = StandardCharsets.UTF_8; + try (WriterOutputStream writerOutputStream = new WriterOutputStream(stringWriter1, charset)) { + writerOutputStream.write("¿Cómo estás".getBytes("Cp850")); + } + final String expected = "?C?mo est?s"; + assertEquals(expected, stringWriter1.toString()); + final StringWriter stringWriter2 = new StringWriter(); + try (WriterOutputStream writerOutputStream = WriterOutputStream.builder().setWriter(stringWriter2).setCharset(charset).get()) { + writerOutputStream.write("¿Cómo estás".getBytes("Cp850")); + } + assertEquals(expected, stringWriter2.toString()); + } + @Test void testLargeUTF8CharsetWithBufferedWrite() throws IOException { testWithBufferedWrite(LARGE_TEST_STRING, UTF_8); diff --git a/src/test/java/org/apache/commons/io/serialization/AbstractCloseableListTest.java b/src/test/java/org/apache/commons/io/serialization/AbstractCloseableListTest.java index 43ec7d55d8e..6258a3cc0e2 100644 --- a/src/test/java/org/apache/commons/io/serialization/AbstractCloseableListTest.java +++ b/src/test/java/org/apache/commons/io/serialization/AbstractCloseableListTest.java @@ -37,7 +37,7 @@ public abstract class AbstractCloseableListTest { /** * Adds a {@link Closeable} to close after each test. * - * @param The Closeable type + * @param The Closeable type. * @param t The Closeable. * @return The Closeable. * @see Closeable diff --git a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java index 823a776486d..00858cc4f0c 100644 --- a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java @@ -33,6 +33,7 @@ import java.util.UUID; import java.util.regex.Pattern; +import org.apache.commons.io.input.BoundedInputStream; import org.apache.commons.io.serialization.ValidatingObjectInputStream.Builder; import org.apache.commons.lang3.SerializationUtils; import org.junit.jupiter.api.BeforeEach; @@ -169,12 +170,12 @@ void testJavadocExample() throws Exception { oos.flush(); byteArray = baos.toByteArray(); } - // Reading + // Deserializing try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() - .accept(HashMap.class, Number.class, Integer.class) - .setInputStream(bais) - .get()) { + .accept(HashMap.class, Number.class, Integer.class) + .setInputStream(bais) + .get()) { // String.class is automatically accepted final HashMap map2 = (HashMap) vois.readObject(); assertEquals(map1, map2); @@ -184,13 +185,45 @@ void testJavadocExample() throws Exception { .accept(HashMap.class, Number.class, Integer.class); try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() - .setPredicate(predicate) + .setPredicate(predicate) + .setInputStream(bais) + .get()) { + // String.class is automatically accepted + final HashMap map2 = (HashMap) vois.readObject(); + assertEquals(map1, map2); + } + // Deserializing with a size limit successfully + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .accept(HashMap.class, Number.class, Integer.class) + .setInputStream(BoundedInputStream.builder() + .setMaxCount(10_000) + .setOnMaxCount((remains, count) -> { + throw new IllegalArgumentException("Input exceeds limit."); + }) .setInputStream(bais) - .get()) { + .get()) + .get()) { // String.class is automatically accepted final HashMap map2 = (HashMap) vois.readObject(); assertEquals(map1, map2); } + // Deserializing with a size limit reaching the limit + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .accept(HashMap.class, Number.class, Integer.class) + .setInputStream(BoundedInputStream.builder() + .setMaxCount(10) + .setOnMaxCount((remains, count) -> { + throw new IllegalArgumentException("Input exceeds limit."); + }) + .setInputStream(bais) + .get()) + .get()) { + // String.class is automatically accepted + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> vois.readObject()); + assertEquals("Input exceeds limit.", e.getMessage()); + } // @formatter:on } @@ -260,7 +293,13 @@ void testRejectOnly() { @Test void testRejectPattern() { assertThrows(InvalidClassException.class, - () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(Pattern.compile("org.*")))); + () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(Pattern.compile("org\\..*")))); + } + + @Test + void testRejectPatternBuilder() { + assertThrows(InvalidClassException.class, + () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(Pattern.compile("org\\..*")).get()))); } @Test diff --git a/src/test/java/org/apache/commons/io/test/TestUtils.java b/src/test/java/org/apache/commons/io/test/TestUtils.java index a4278c14974..01256e21591 100644 --- a/src/test/java/org/apache/commons/io/test/TestUtils.java +++ b/src/test/java/org/apache/commons/io/test/TestUtils.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; @@ -48,9 +49,9 @@ public abstract class TestUtils { /** * Assert that the content of a file is equal to that in a byte[]. * - * @param b0 the expected contents - * @param file the file to check - * @throws IOException If an I/O error occurs while reading the file contents + * @param b0 the expected contents. + * @param file the file to check. + * @throws IOException If an I/O error occurs while reading the file contents. */ public static void assertEqualContent(final byte[] b0, final File file) throws IOException { assertEqualContent(b0, file.toPath()); @@ -59,9 +60,9 @@ public static void assertEqualContent(final byte[] b0, final File file) throws I /** * Assert that the content of a file is equal to that in a byte[]. * - * @param b0 the expected contents - * @param file the file to check - * @throws IOException If an I/O error occurs while reading the file contents + * @param b0 the expected contents. + * @param file the file to check. + * @throws IOException If an I/O error occurs while reading the file contents. */ public static void assertEqualContent(final byte[] b0, final Path file) throws IOException { int count = 0; @@ -82,9 +83,9 @@ public static void assertEqualContent(final byte[] b0, final Path file) throws I /** * Assert that the content of a file is equal to that in a char[]. * - * @param c0 the expected contents - * @param file the file to check - * @throws IOException If an I/O error occurs while reading the file contents + * @param c0 the expected contents. + * @param file the file to check. + * @throws IOException If an I/O error occurs while reading the file contents. */ public static void assertEqualContent(final char[] c0, final File file) throws IOException { assertEqualContent(c0, file.toPath()); @@ -93,9 +94,9 @@ public static void assertEqualContent(final char[] c0, final File file) throws I /** * Assert that the content of a file is equal to that in a char[]. * - * @param c0 the expected contents - * @param file the file to check - * @throws IOException If an I/O error occurs while reading the file contents + * @param c0 the expected contents. + * @param file the file to check. + * @throws IOException If an I/O error occurs while reading the file contents. */ public static void assertEqualContent(final char[] c0, final Path file) throws IOException { int count = 0; @@ -152,7 +153,7 @@ public static void checkFile(final File file, final File referenceFile) public static void checkWrite(final OutputStream output) { try { - new java.io.PrintStream(output).write(0); + new PrintStream(output).write(0); } catch (final Throwable t) { fail("The copy() method closed the stream when it shouldn't have. " + t.getMessage()); } @@ -160,7 +161,7 @@ public static void checkWrite(final OutputStream output) { public static void checkWrite(final Writer output) { try { - new java.io.PrintWriter(output).write('a'); + new PrintWriter(output).write('a'); } catch (final Throwable t) { fail("The copy() method closed the stream when it shouldn't have. " + t.getMessage()); } @@ -260,7 +261,7 @@ public static void sleepQuietly(final long millis) { try { sleep(millis); } catch (final InterruptedException ignored) { - // ignore InterruptedException. + Thread.currentThread().interrupt(); } } diff --git a/src/test/java/org/apache/commons/io/test/ThrowOnFlushAndCloseOutputStream.java b/src/test/java/org/apache/commons/io/test/ThrowOnFlushAndCloseOutputStream.java index f5096673a78..8d9d8b01a39 100644 --- a/src/test/java/org/apache/commons/io/test/ThrowOnFlushAndCloseOutputStream.java +++ b/src/test/java/org/apache/commons/io/test/ThrowOnFlushAndCloseOutputStream.java @@ -33,8 +33,8 @@ public class ThrowOnFlushAndCloseOutputStream extends ProxyOutputStream { /** * @param proxy OutputStream to delegate to. - * @param throwOnFlush True if flush() is forbidden - * @param throwOnClose True if close() is forbidden + * @param throwOnFlush True if flush() is forbidden. + * @param throwOnClose True if close() is forbidden. */ public ThrowOnFlushAndCloseOutputStream(final OutputStream proxy, final boolean throwOnFlush, final boolean throwOnClose) { diff --git a/src/test/resources/org/apache/commons/io/FileUtilsTestDataCR.dat b/src/test/resources/org/apache/commons/io/FileUtilsTestDataCR.bin similarity index 100% rename from src/test/resources/org/apache/commons/io/FileUtilsTestDataCR.dat rename to src/test/resources/org/apache/commons/io/FileUtilsTestDataCR.bin diff --git a/src/test/resources/org/apache/commons/io/FileUtilsTestDataCRLF.dat b/src/test/resources/org/apache/commons/io/FileUtilsTestDataCRLF.bin similarity index 100% rename from src/test/resources/org/apache/commons/io/FileUtilsTestDataCRLF.dat rename to src/test/resources/org/apache/commons/io/FileUtilsTestDataCRLF.bin diff --git a/src/test/resources/org/apache/commons/io/FileUtilsTestDataLF.dat b/src/test/resources/org/apache/commons/io/FileUtilsTestDataLF.bin similarity index 100% rename from src/test/resources/org/apache/commons/io/FileUtilsTestDataLF.dat rename to src/test/resources/org/apache/commons/io/FileUtilsTestDataLF.bin
      * boolean include = true;
    - * BOMInputStream bomIn = BOMInputStream.builder()
    - *     .setInputStream(in)
    - *     .setInclude(include)
    - *     .get();
    + * BOMInputStream bomIn = BOMInputStream.builder().setInputStream(in).setInclude(include).get();
      * if (bomIn.hasBOM()) {
      *     // has a UTF-8 BOM
      * }
    @@ -72,10 +69,8 @@
      * 

    Example 3 - Detecting Multiple BOMs

    * *
    - * BOMInputStream bomIn = BOMInputStream.builder()
    - *   .setInputStream(in)
    - *   .setByteOrderMarks(ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE)
    - *   .get();
    + * BOMInputStream bomIn = BOMInputStream.builder().setInputStream(in)
    + *         .setByteOrderMarks(ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE).get();
      * if (bomIn.hasBOM() == false) {
      *     // No BOM found
      * } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
    @@ -134,14 +129,13 @@ public static class Builder extends AbstractBuilder {
             /**
              * For test access.
              *
    -         * @return the default byte order mark
    +         * @return the default byte order mark.
              */
             static ByteOrderMark getDefaultByteOrderMark() {
                 return DEFAULT[0];
             }
     
             private ByteOrderMark[] byteOrderMarks = DEFAULT;
    -
             private boolean include;
     
             /**
    @@ -200,14 +194,13 @@ public Builder setByteOrderMarks(final ByteOrderMark... byteOrderMarks) {
              * The default is false.
              * 

    * - * @param include true to include the UTF-8 BOM or false to exclude it. return this; + * @param include true to include the UTF-8 BOM or false to exclude it. return this;. * @return {@code this} instance. */ public Builder setInclude(final boolean include) { this.include = include; return this; } - } /** @@ -229,65 +222,68 @@ public static Builder builder() { * BOMs are sorted from longest to shortest. */ private final List bomList; - - private ByteOrderMark byteOrderMark; + private final ByteOrderMark byteOrderMark; private int fbIndex; private int[] firstBytes; private final boolean include; private boolean markedAtStart; private int markFbIndex; + /** + * Constructs a new instance. + * + * @param builder The builder. + * @throws IOException if an error reading the first bytes of the stream occurs. + */ private BOMInputStream(final Builder builder) throws IOException { super(builder); if (IOUtils.length(builder.byteOrderMarks) == 0) { throw new IllegalArgumentException("No ByteOrderMark specified."); } this.include = builder.include; - final List list = Arrays.asList(builder.byteOrderMarks); + final List bomList = Arrays.asList(builder.byteOrderMarks); // Sort the BOMs to match the longest BOM first because some BOMs have the same starting two bytes. - list.sort(ByteOrderMarkLengthComparator); - this.bomList = list; + bomList.sort(ByteOrderMarkLengthComparator); + this.bomList = bomList; + this.byteOrderMark = readBom(); } /** * Constructs a new BOM InputStream that excludes a {@link ByteOrderMark#UTF_8} BOM. * - * @param delegate - * the InputStream to delegate to - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param delegate the InputStream to delegate to. + * @throws IOException if an error reading the first bytes of the stream occurs. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated - public BOMInputStream(final InputStream delegate) { + public BOMInputStream(final InputStream delegate) throws IOException { this(delegate, false, Builder.DEFAULT); } /** * Constructs a new BOM InputStream that detects a {@link ByteOrderMark#UTF_8} and optionally includes it. * - * @param delegate - * the InputStream to delegate to - * @param include - * true to include the UTF-8 BOM or false to exclude it - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param delegate the InputStream to delegate to. + * @param include true to include the UTF-8 BOM or false to exclude it. + * @throws IOException if an error reading the first bytes of the stream occurs. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated - public BOMInputStream(final InputStream delegate, final boolean include) { + public BOMInputStream(final InputStream delegate, final boolean include) throws IOException { this(delegate, include, Builder.DEFAULT); } /** * Constructs a new BOM InputStream that detects the specified BOMs and optionally includes them. * - * @param delegate - * the InputStream to delegate to - * @param include - * true to include the specified BOMs or false to exclude them - * @param boms - * The BOMs to detect and optionally exclude - * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} + * @param delegate the InputStream to delegate to. + * @param include true to include the specified BOMs or false to exclude them. + * @param boms The BOMs to detect and optionally exclude. + * @throws IOException if an error reading the first bytes of the stream occurs. + * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @Deprecated - public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) { + public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) throws IOException { super(delegate); if (IOUtils.length(boms) == 0) { throw new IllegalArgumentException("No BOMs specified"); @@ -297,19 +293,19 @@ public BOMInputStream(final InputStream delegate, final boolean include, final B // Sort the BOMs to match the longest BOM first because some BOMs have the same starting two bytes. list.sort(ByteOrderMarkLengthComparator); this.bomList = list; + this.byteOrderMark = readBom(); } /** * Constructs a new BOM InputStream that excludes the specified BOMs. * - * @param delegate - * the InputStream to delegate to - * @param boms - * The BOMs to detect and exclude + * @param delegate the InputStream to delegate to. + * @param boms The BOMs to detect and exclude. + * @throws IOException if an error reading the first bytes of the stream occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated - public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) { + public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) throws IOException { this(delegate, false, boms); } @@ -326,34 +322,26 @@ private ByteOrderMark find() { * Gets the ByteOrderMark (Byte Order Mark). * * @return The BOM or null if none matched. - * @throws IOException - * if an error reading the first bytes of the stream occurs. */ - public ByteOrderMark getBOM() throws IOException { - if (firstBytes == null) { - byteOrderMark = readBom(); - } + public ByteOrderMark getBOM() { return byteOrderMark; } /** * Gets the BOM charset Name - {@link ByteOrderMark#getCharsetName()}. * - * @return The BOM charset Name or null if no BOM found - * @throws IOException - * if an error reading the first bytes of the stream occurs + * @return The BOM charset Name or null if no BOM found. + * @throws IOException if an error reading the first bytes of the stream occurs. */ public String getBOMCharsetName() throws IOException { - getBOM(); return byteOrderMark == null ? null : byteOrderMark.getCharsetName(); } /** * Tests whether the stream contains one of the specified BOMs. * - * @return true if the stream has one of the specified BOMs, otherwise false if it does not - * @throws IOException - * if an error reading the first bytes of the stream occurs + * @return true if the stream has one of the specified BOMs, otherwise false if it does not. + * @throws IOException if an error reading the first bytes of the stream occurs. */ public boolean hasBOM() throws IOException { return getBOM() != null; @@ -362,13 +350,10 @@ public boolean hasBOM() throws IOException { /** * Tests whether the stream contains the specified BOM. * - * @param bom - * The BOM to check for - * @return true if the stream has the specified BOM, otherwise false if it does not - * @throws IllegalArgumentException - * if the BOM is not one the stream is configured to detect - * @throws IOException - * if an error reading the first bytes of the stream occurs + * @param bom The BOM to check for. + * @return true if the stream has the specified BOM, otherwise false if it does not. + * @throws IllegalArgumentException if the BOM is not one the stream is configured to detect. + * @throws IOException if an error reading the first bytes of the stream occurs. */ public boolean hasBOM(final ByteOrderMark bom) throws IOException { if (!bomList.contains(bom)) { @@ -378,10 +363,9 @@ public boolean hasBOM(final ByteOrderMark bom) throws IOException { } /** - * Invokes the delegate's {@code mark(int)} method. + * Invokes the delegate's {@link InputStream#mark(int)} method. * - * @param readLimit - * read ahead limit + * @param readLimit read ahead limit. */ @Override public synchronized void mark(final int readLimit) { @@ -393,9 +377,8 @@ public synchronized void mark(final int readLimit) { /** * Checks if the bytes match a BOM. * - * @param bom - * The BOM - * @return true if the bytes match the bom, otherwise false + * @param bom The BOM. + * @return true if the bytes match the BOM, otherwise false. */ private boolean matches(final ByteOrderMark bom) { return bom.matches(firstBytes); @@ -404,9 +387,8 @@ private boolean matches(final ByteOrderMark bom) { /** * Invokes the delegate's {@code read()} method, detecting and optionally skipping BOM. * - * @return the byte read (excluding BOM) or -1 if the end of stream - * @throws IOException - * if an I/O error occurs + * @return the byte read (excluding BOM) or -1 if the end of stream. + * @throws IOException if an I/O error occurs. */ @Override public int read() throws IOException { @@ -416,15 +398,12 @@ public int read() throws IOException { } /** - * Invokes the delegate's {@code read(byte[])} method, detecting and optionally skipping BOM. + * Invokes the delegate's {@link InputStream#read(byte[])} method, detecting and optionally skipping BOM. * - * @param buf - * the buffer to read the bytes into, never {@code null} - * @return the number of bytes read (excluding BOM) or -1 if the end of stream - * @throws NullPointerException - * if the buffer is {@code null} - * @throws IOException - * if an I/O error occurs + * @param buf the buffer to read the bytes into, never {@code null} + * @return the number of bytes read (excluding BOM) or -1 if the end of stream. + * @throws NullPointerException if the buffer is {@code null} + * @throws IOException if an I/O error occurs. */ @Override public int read(final byte[] buf) throws IOException { @@ -432,21 +411,15 @@ public int read(final byte[] buf) throws IOException { } /** - * Invokes the delegate's {@code read(byte[], int, int)} method, detecting and optionally skipping BOM. + * Invokes the delegate's {@link InputStream#read(byte[], int, int)} method, detecting and optionally skipping BOM. * - * @param buf - * the buffer to read the bytes into - * @param off - * The start offset - * @param len - * The number of bytes to read (excluding BOM) - * @return the number of bytes read or -1 if the end of stream - * @throws NullPointerException - * if the buffer is {@code null} - * @throws IndexOutOfBoundsException - * if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code buf.length} - * @throws IOException - * if an I/O error occurs + * @param buf the buffer to read the bytes into. + * @param off The start offset. + * @param len The number of bytes to read (excluding BOM). + * @return the number of bytes read or -1 if the end of stream. + * @throws NullPointerException if the buffer is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code buf.length}. + * @throws IOException if an I/O error occurs. */ @Override public int read(final byte[] buf, int off, int len) throws IOException { @@ -469,6 +442,12 @@ public int read(final byte[] buf, int off, int len) throws IOException { return secondCount < 0 ? firstCount > 0 ? firstCount : EOF : firstCount + secondCount; } + /** + * Reads the byte order mark. + * + * @return the byte order mark. + * @throws IOException if an error reading the first bytes of the stream occurs. + */ private ByteOrderMark readBom() throws IOException { int fbLength = 0; // BOMs are sorted from longest to shortest @@ -501,18 +480,16 @@ private ByteOrderMark readBom() throws IOException { * valid byte or -1 to indicate that the initial bytes have been processed already. * * @return the byte read (excluding BOM) or -1 if at the end of first bytes. - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs. */ private int readFirstBytes() throws IOException { - getBOM(); return fbIndex < firstBytes.length ? firstBytes[fbIndex++] : EOF; } /** - * Invokes the delegate's {@code reset()} method. + * Invokes the delegate's {@link InputStream#reset()} method. * - * @throws IOException - * if an I/O error occurs + * @throws IOException if an I/O error occurs. */ @Override public synchronized void reset() throws IOException { @@ -524,13 +501,11 @@ public synchronized void reset() throws IOException { } /** - * Invokes the delegate's {@code skip(long)} method, detecting and optionally skipping BOM. + * Invokes the delegate's {@link InputStream#skip(long)} method, detecting and optionally skipping BOM. * - * @param n - * the number of bytes to skip - * @return the number of bytes to skipped or -1 if the end of stream - * @throws IOException - * if an I/O error occurs + * @param n the number of bytes to skip. + * @return the number of bytes to skipped or -1 if the end of stream. + * @throws IOException if an I/O error occurs. */ @Override public long skip(final long n) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/BoundedInputStream.java b/src/main/java/org/apache/commons/io/input/BoundedInputStream.java index e3e2d86bf0f..d9fbac8b104 100644 --- a/src/main/java/org/apache/commons/io/input/BoundedInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BoundedInputStream.java @@ -82,6 +82,7 @@ * .get(); * } *
    + * * @see Builder * @since 2.0 */ diff --git a/src/main/java/org/apache/commons/io/input/BoundedReader.java b/src/main/java/org/apache/commons/io/input/BoundedReader.java index 5c1c255c0fe..5ba0dfe6b18 100644 --- a/src/main/java/org/apache/commons/io/input/BoundedReader.java +++ b/src/main/java/org/apache/commons/io/input/BoundedReader.java @@ -36,12 +36,10 @@ * * @since 2.5 */ -public class BoundedReader extends Reader { +public class BoundedReader extends ProxyReader { private static final int INVALID = -1; - private final Reader target; - private int charsRead; private int markedAt = INVALID; @@ -53,24 +51,14 @@ public class BoundedReader extends Reader { /** * Constructs a bounded reader * - * @param target The target stream that will be used - * @param maxCharsFromTargetReader The maximum number of characters that can be read from target + * @param target The target stream that will be used. + * @param maxCharsFromTargetReader The maximum number of characters that can be read from target. */ public BoundedReader(final Reader target, final int maxCharsFromTargetReader) { - this.target = target; + super(target); this.maxCharsFromTargetReader = maxCharsFromTargetReader; } - /** - * Closes the target - * - * @throws IOException If an I/O error occurs while calling the underlying reader's close method - */ - @Override - public void close() throws IOException { - target.close(); - } - /** * marks the target stream * @@ -78,49 +66,42 @@ public void close() throws IOException { * Note that this parameter is not validated with respect to maxCharsFromTargetReader. There * is no way to pass past maxCharsFromTargetReader, even if this value is greater. * - * @throws IOException If an I/O error occurs while calling the underlying reader's mark method + * @throws IOException If an I/O error occurs while calling the underlying reader's mark method. * @see Reader#mark(int) */ @Override public void mark(final int readAheadLimit) throws IOException { this.readAheadLimit = readAheadLimit - charsRead; - markedAt = charsRead; - - target.mark(readAheadLimit); + super.mark(readAheadLimit); } /** * Reads a single character * - * @return -1 on EOF or the character read - * @throws IOException If an I/O error occurs while calling the underlying reader's read method + * @return -1 on EOF or the character read. + * @throws IOException If an I/O error occurs while calling the underlying reader's read method. * @see Reader#read() */ @Override public int read() throws IOException { - - if (charsRead >= maxCharsFromTargetReader) { - return EOF; - } - - if (markedAt >= 0 && charsRead - markedAt >= readAheadLimit) { + if (charsRead >= maxCharsFromTargetReader || markedAt >= 0 && charsRead - markedAt >= readAheadLimit) { return EOF; } charsRead++; - return target.read(); + return super.read(); } /** * Reads into an array * - * @param cbuf The buffer to fill - * @param off The offset - * @param len The number of chars to read - * @return the number of chars read + * @param cbuf The buffer to fill. + * @param off The offset. + * @param len The number of chars to read. + * @return the number of chars read. * @throws NullPointerException if the buffer is {@code null}. * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}. - * @throws IOException If an I/O error occurs while calling the underlying reader's read method + * @throws IOException If an I/O error occurs while calling the underlying reader's read method. * @see Reader#read(char[], int, int) */ @Override @@ -140,12 +121,18 @@ public int read(final char[] cbuf, final int off, final int len) throws IOExcept /** * Resets the target to the latest mark, * - * @throws IOException If an I/O error occurs while calling the underlying reader's reset method + * @throws IOException If an I/O error occurs while calling the underlying reader's reset method. * @see Reader#reset() */ @Override public void reset() throws IOException { charsRead = markedAt; - target.reset(); + super.reset(); + } + + @Override + public long skip(final long n) throws IOException { + charsRead += n; + return super.skip(n); } } diff --git a/src/main/java/org/apache/commons/io/input/BrokenInputStream.java b/src/main/java/org/apache/commons/io/input/BrokenInputStream.java index bc491637dee..cd2e2fce9c3 100644 --- a/src/main/java/org/apache/commons/io/input/BrokenInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BrokenInputStream.java @@ -106,7 +106,7 @@ public void close() throws IOException { /** * Gets the Throwable to throw. Package-private for testing. * - * @return the Throwable to throw. + * @return the Throwable to throw. */ Throwable getThrowable() { return exceptionSupplier.get(); diff --git a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java index 8e81b34508a..4fab37a9838 100644 --- a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java @@ -137,6 +137,8 @@ public static Builder builder() { private final ByteBuffer byteBuffer; + private boolean clean; + private final FileChannel fileChannel; @SuppressWarnings("resource") @@ -150,7 +152,7 @@ private BufferedFileChannelInputStream(final Builder builder) throws IOException * Constructs a new instance for the given File. * * @param file The file to stream. - * @throws IOException If an I/O error occurs + * @throws IOException If an I/O error occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated @@ -163,7 +165,7 @@ public BufferedFileChannelInputStream(final File file) throws IOException { * * @param file The file to stream. * @param bufferSize buffer size. - * @throws IOException If an I/O error occurs + * @throws IOException If an I/O error occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated @@ -175,7 +177,7 @@ public BufferedFileChannelInputStream(final File file, final int bufferSize) thr * Constructs a new instance for the given Path. * * @param path The path to stream. - * @throws IOException If an I/O error occurs + * @throws IOException If an I/O error occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated @@ -188,7 +190,7 @@ public BufferedFileChannelInputStream(final Path path) throws IOException { * * @param path The path to stream. * @param bufferSize buffer size. - * @throws IOException If an I/O error occurs + * @throws IOException If an I/O error occurs. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()} */ @Deprecated @@ -198,10 +200,7 @@ public BufferedFileChannelInputStream(final Path path, final int bufferSize) thr @Override public synchronized int available() throws IOException { - if (!fileChannel.isOpen()) { - return 0; - } - if (!refill()) { + if (!fileChannel.isOpen() || !refill()) { return 0; } return byteBuffer.remaining(); @@ -212,26 +211,18 @@ public synchronized int available() throws IOException { * disposed buffer. However, neither the bytes allocated to direct buffers nor file descriptors opened for memory-mapped buffers put pressure on the garbage * collector. Waiting for garbage collection may lead to the depletion of off-heap memory or huge numbers of open files. There's unfortunately no standard * API to manually dispose of these kinds of buffers. - * - * @param buffer the buffer to clean. - */ - private void clean(final ByteBuffer buffer) { - if (buffer.isDirect()) { - cleanDirectBuffer(buffer); - } - } - - /** + *

    * In Java 8, the type of {@code sun.nio.ch.DirectBuffer.cleaner()} was {@code sun.misc.Cleaner}, and it was possible to access the method * {@code sun.misc.Cleaner.clean()} to invoke it. The type changed to {@code jdk.internal.ref.Cleaner} in later JDKs, and the {@code clean()} method is not * accessible even with reflection. However {@code sun.misc.Unsafe} added an {@code invokeCleaner()} method in JDK 9+ and this is still accessible with * reflection. - * - * @param buffer the buffer to clean. must be a DirectBuffer. + *

    + * @param buffer the buffer to clean. */ - private void cleanDirectBuffer(final ByteBuffer buffer) { - if (ByteBufferCleaner.isSupported()) { + private void clean(final ByteBuffer buffer) { + if (!clean && ByteBufferCleaner.isSupported()) { ByteBufferCleaner.clean(buffer); + clean = true; } } @@ -244,6 +235,10 @@ public synchronized void close() throws IOException { } } + boolean isClean() { + return clean; + } + @Override public synchronized int read() throws IOException { if (!refill()) { @@ -269,7 +264,7 @@ public synchronized int read(final byte[] b, final int offset, int len) throws I /** * Checks whether data is left to be read from the input stream. * - * @return true if data is left, false otherwise + * @return true if data is left, false otherwise. * @throws IOException if an I/O error occurs. */ private boolean refill() throws IOException { @@ -304,6 +299,7 @@ public synchronized long skip(final long n) throws IOException { return skippedFromBuffer + skipFromFileChannel(toSkipFromFileChannel); } + private long skipFromFileChannel(final long n) throws IOException { final long currentFilePosition = fileChannel.position(); final long size = fileChannel.size(); diff --git a/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java b/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java index 743ea44253c..6debbfb8449 100644 --- a/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java +++ b/src/main/java/org/apache/commons/io/input/ByteBufferCleaner.java @@ -14,19 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.commons.io.input; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import org.apache.commons.io.Buffers; + /** - * Cleans a direct {@link ByteBuffer}. Without manual intervention, direct ByteBuffers will be cleaned eventually upon - * garbage collection. However, this should not be relied upon since it may not occur in a timely fashion - - * especially since off heap ByeBuffers don't put pressure on the garbage collector. + * Cleans a direct {@link ByteBuffer}. Without manual intervention, direct ByteBuffers will be cleaned eventually upon garbage collection. However, this should + * not be relied upon since it may not occur in a timely fashion - especially since off heap ByeBuffers don't put pressure on the garbage collector. *

    - * Warning: Do not attempt to use a direct {@link ByteBuffer} that has been cleaned or bad things will happen. - * Don't use this class unless you can ensure that the cleaned buffer will not be accessed anymore. + * Warning: Do not attempt to use a direct {@link ByteBuffer} that has been cleaned or bad things will happen. Don't use this class unless you + * can ensure that the cleaned buffer will not be accessed anymore. *

    *

    * See JDK-4724038 @@ -35,6 +37,7 @@ final class ByteBufferCleaner { private interface Cleaner { + void clean(ByteBuffer buffer) throws ReflectiveOperationException; } @@ -86,7 +89,10 @@ public void clean(final ByteBuffer buffer) throws ReflectiveOperationException { */ static void clean(final ByteBuffer buffer) { try { - INSTANCE.clean(buffer); + if (buffer.isDirect()) { + Buffers.clearWritable(buffer); + INSTANCE.clean(buffer); + } } catch (final Exception e) { throw new IllegalStateException("Failed to clean direct buffer.", e); } @@ -105,8 +111,8 @@ private static Cleaner getCleaner() { } /** - * Tests if were able to load a suitable cleaner for the current JVM. Attempting to call - * {@code ByteBufferCleaner#clean(ByteBuffer)} when this method returns false will result in an exception. + * Tests if were able to load a suitable cleaner for the current JVM. Attempting to call {@code ByteBufferCleaner#clean(ByteBuffer)} when this method + * returns false will result in an exception. * * @return {@code true} if cleaning is supported, {@code false} otherwise. */ diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java b/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java index 874dcf1cb39..689424c062f 100644 --- a/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java +++ b/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java @@ -281,6 +281,7 @@ CharsetEncoder getCharsetEncoder() { /** * {@inheritDoc} + * * @param readLimit max read limit (ignored). */ @Override diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java index bb2be3692c9..60026b84429 100644 --- a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java @@ -99,8 +99,8 @@ public CharSequenceReader(final CharSequence charSequence) { *

    * * @param charSequence The character sequence, may be {@code null} - * @param start The start index in the character sequence, inclusive - * @throws IllegalArgumentException if the start index is negative + * @param start The start index in the character sequence, inclusive. + * @throws IllegalArgumentException if the start index is negative. * @since 2.7 */ public CharSequenceReader(final CharSequence charSequence, final int start) { @@ -120,9 +120,9 @@ public CharSequenceReader(final CharSequence charSequence, final int start) { *

    * * @param charSequence The character sequence, may be {@code null} - * @param start The start index in the character sequence, inclusive - * @param end The end index in the character sequence, exclusive - * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index + * @param start The start index in the character sequence, inclusive. + * @param end The end index in the character sequence, exclusive. + * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index. * @since 2.7 */ public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { @@ -168,7 +168,7 @@ private int end() { /** * Mark the current position. * - * @param readAheadLimit ignored + * @param readAheadLimit ignored. */ @Override public void mark(final int readAheadLimit) { @@ -202,10 +202,10 @@ public int read() { /** * Reads the specified number of characters into the array. * - * @param array The array to store the characters in - * @param offset The starting position in the array to store - * @param length The maximum number of characters to read - * @return The number of characters read or -1 if there are no more + * @param array The array to store the characters in. + * @param offset The starting position in the array to store. + * @param length The maximum number of characters to read. + * @return The number of characters read or -1 if there are no more. * @throws NullPointerException if the array is {@code null}. * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code array.length}. */ @@ -272,8 +272,8 @@ public void reset() { /** * Skip the specified number of characters. * - * @param n The number of characters to skip - * @return The actual number of characters skipped + * @param n The number of characters to skip. + * @return The actual number of characters skipped. */ @Override public long skip(final long n) { @@ -302,7 +302,7 @@ private int start() { * Gets a String representation of the underlying * character sequence. * - * @return The contents of the character sequence + * @return The contents of the character sequence. */ @Override public String toString() { diff --git a/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java b/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java index fef0d6cf17b..4b2828390e8 100644 --- a/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java @@ -40,10 +40,10 @@ public class ClassLoaderObjectInputStream extends ObjectInputStream { /** * Constructs a new ClassLoaderObjectInputStream. * - * @param classLoader the ClassLoader from which classes should be loaded - * @param inputStream the InputStream to work on - * @throws IOException in case of an I/O error - * @throws StreamCorruptedException if the stream is corrupted + * @param classLoader the ClassLoader from which classes should be loaded. + * @param inputStream the InputStream to work on. + * @throws IOException in case of an I/O error. + * @throws StreamCorruptedException if the stream is corrupted. */ public ClassLoaderObjectInputStream( final ClassLoader classLoader, final InputStream inputStream) @@ -56,10 +56,10 @@ public ClassLoaderObjectInputStream( * Resolve a class specified by the descriptor using the * specified ClassLoader or the super ClassLoader. * - * @param objectStreamClass descriptor of the class - * @return the Class object described by the ObjectStreamClass - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found + * @param objectStreamClass descriptor of the class. + * @return the Class object described by the ObjectStreamClass. + * @throws IOException in case of an I/O error. + * @throws ClassNotFoundException if the Class cannot be found. */ @Override protected Class resolveClass(final ObjectStreamClass objectStreamClass) @@ -76,10 +76,10 @@ protected Class resolveClass(final ObjectStreamClass objectStreamClass) * Create a proxy class that implements the specified interfaces using * the specified ClassLoader or the super ClassLoader. * - * @param interfaces the interfaces to implement - * @return a proxy class implementing the interfaces - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found + * @param interfaces the interfaces to implement. + * @return a proxy class implementing the interfaces. + * @throws IOException in case of an I/O error. + * @throws ClassNotFoundException if the Class cannot be found. * @see ObjectInputStream#resolveProxyClass(String[]) * @since 2.1 */ diff --git a/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java b/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java index 001bfca6f55..b58c6a891ac 100644 --- a/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java +++ b/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java @@ -16,8 +16,11 @@ */ package org.apache.commons.io.input; +import java.io.IOException; import java.io.InputStream; +import org.apache.commons.io.function.IOUnaryOperator; + /** * Proxy stream that prevents the underlying input stream from being closed. *

    @@ -30,6 +33,50 @@ */ public class CloseShieldInputStream extends ProxyInputStream { + /** + * Constructs a new builder for {@link CloseShieldInputStream}. + * + * @since 2.22.0 + */ + public static class Builder extends AbstractBuilder { + + private IOUnaryOperator onClose = is -> ClosedInputStream.INSTANCE; + + /** + * Constructs a new instance. + */ + public Builder() { + // empty + } + + @Override + public CloseShieldInputStream get() throws IOException { + return new CloseShieldInputStream(this); + } + + /** + * Sets the {@code onClose} function. By default, replaces the underlying input stream when {@link #close()} is called. + * + * @param onClose the onClose function. + * @return {@code this} instance. + */ + public Builder setOnClose(final IOUnaryOperator onClose) { + this.onClose = onClose; + return asThis(); + } + + } + + /** + * Constructs a new builder for {@link CloseShieldInputStream}. + * + * @return the new builder. + * @since 2.22.0 + */ + public static Builder builder() { + return new Builder(); + } + /** * Constructs a proxy that only shields {@link System#in} from closing. * @@ -44,18 +91,25 @@ public static InputStream systemIn(final InputStream inputStream) { /** * Constructs a proxy that shields the given input stream from being closed. * - * @param inputStream the input stream to wrap - * @return the created proxy + * @param inputStream the input stream to wrap. + * @return the created proxy. * @since 2.9.0 */ public static CloseShieldInputStream wrap(final InputStream inputStream) { return new CloseShieldInputStream(inputStream); } + private final IOUnaryOperator onClose; + + private CloseShieldInputStream(final Builder builder) throws IOException { + super(builder.getInputStream()); + this.onClose = builder.onClose; + } + /** * Constructs a proxy that shields the given input stream from being closed. * - * @param inputStream underlying input stream + * @param inputStream underlying input stream. * @deprecated Using this constructor prevents IDEs from warning if the * underlying input stream is never closed. Use * {@link #wrap(InputStream)} instead. @@ -63,16 +117,21 @@ public static CloseShieldInputStream wrap(final InputStream inputStream) { @Deprecated public CloseShieldInputStream(final InputStream inputStream) { super(inputStream); + this.onClose = builder().onClose; } /** - * Replaces the underlying input stream with a {@link ClosedInputStream} - * sentinel. The original input stream will remain open, but this proxy will - * appear closed. + * Applies the {@code onClose} function to the underlying input stream, replacing it with the result. + *

    + * By default, replaces the underlying input stream with a {@link ClosedInputStream} sentinel. The original input stream will remain open, but this proxy + * will appear closed. + *

    + * + * @throws IOException Thrown by the {@code onClose} function. */ @Override - public void close() { - in = ClosedInputStream.INSTANCE; + public void close() throws IOException { + in = onClose.apply(in); } } diff --git a/src/main/java/org/apache/commons/io/input/CloseShieldReader.java b/src/main/java/org/apache/commons/io/input/CloseShieldReader.java index c6f116db765..e271627ceea 100644 --- a/src/main/java/org/apache/commons/io/input/CloseShieldReader.java +++ b/src/main/java/org/apache/commons/io/input/CloseShieldReader.java @@ -33,8 +33,8 @@ public class CloseShieldReader extends ProxyReader { /** * Constructs a proxy that shields the given reader from being closed. * - * @param reader the reader to wrap - * @return the created proxy + * @param reader the reader to wrap. + * @return the created proxy. * @since 2.9.0 */ public static CloseShieldReader wrap(final Reader reader) { @@ -44,7 +44,7 @@ public static CloseShieldReader wrap(final Reader reader) { /** * Constructs a proxy that shields the given reader from being closed. * - * @param reader underlying reader + * @param reader underlying reader. * @deprecated Using this constructor prevents IDEs from warning if the * underlying reader is never closed. Use {@link #wrap(Reader)} * instead. diff --git a/src/main/java/org/apache/commons/io/input/CountingInputStream.java b/src/main/java/org/apache/commons/io/input/CountingInputStream.java index f14e7130507..1182309f07f 100644 --- a/src/main/java/org/apache/commons/io/input/CountingInputStream.java +++ b/src/main/java/org/apache/commons/io/input/CountingInputStream.java @@ -40,7 +40,7 @@ public class CountingInputStream extends ProxyInputStream { /** * Constructs a new CountingInputStream. * - * @param in the InputStream to delegate to + * @param in the InputStream to delegate to. */ public CountingInputStream(final InputStream in) { super(in); @@ -57,7 +57,7 @@ public CountingInputStream(final InputStream in) { /** * Adds the number of read bytes to the count. * - * @param n number of bytes read, or -1 if no more bytes are available + * @param n number of bytes read, or -1 if no more bytes are available. * @throws IOException Not thrown here but subclasses may throw. * @since 2.0 */ @@ -77,7 +77,7 @@ protected synchronized void afterRead(final int n) throws IOException { * result in incorrect count for files over 2GB. *

    * - * @return the number of bytes accumulated + * @return the number of bytes accumulated. * @since 1.3 */ public synchronized long getByteCount() { @@ -92,8 +92,8 @@ public synchronized long getByteCount() { * See {@link #getByteCount()} for a method using a {@code long}. *

    * - * @return the number of bytes accumulated - * @throws ArithmeticException if the byte count is too large + * @return the number of bytes accumulated. + * @throws ArithmeticException if the byte count is too large. * @deprecated Use {@link #getByteCount()}. */ @Deprecated @@ -113,7 +113,7 @@ public int getCount() { * result in incorrect count for files over 2GB. *

    * - * @return the count previous to resetting + * @return the count previous to resetting. * @since 1.3 */ public synchronized long resetByteCount() { @@ -130,8 +130,8 @@ public synchronized long resetByteCount() { * See {@link #resetByteCount()} for a method using a {@code long}. *

    * - * @return the count previous to resetting - * @throws ArithmeticException if the byte count is too large + * @return the count previous to resetting. + * @throws ArithmeticException if the byte count is too large. * @deprecated Use {@link #resetByteCount()}. */ @Deprecated @@ -147,8 +147,8 @@ public int resetCount() { * Skips the stream over the specified number of bytes, adding the skipped * amount to the count. * - * @param length the number of bytes to skip - * @return the actual number of bytes skipped + * @param length the number of bytes to skip. + * @return the actual number of bytes skipped. * @throws IOException if an I/O error occurs. * @see InputStream#skip(long) */ diff --git a/src/main/java/org/apache/commons/io/input/DemuxInputStream.java b/src/main/java/org/apache/commons/io/input/DemuxInputStream.java index ad49dca2793..3a0132dc8e6 100644 --- a/src/main/java/org/apache/commons/io/input/DemuxInputStream.java +++ b/src/main/java/org/apache/commons/io/input/DemuxInputStream.java @@ -40,8 +40,8 @@ public DemuxInputStream() { /** * Binds the specified stream to the current thread. * - * @param input the stream to bind - * @return the InputStream that was previously active + * @param input the stream to bind. + * @return the InputStream that was previously active. */ public InputStream bindStream(final InputStream input) { final InputStream oldValue = inputStreamLocal.get(); @@ -52,7 +52,7 @@ public InputStream bindStream(final InputStream input) { /** * Closes stream associated with current thread. * - * @throws IOException if an error occurs + * @throws IOException if an error occurs. */ @SuppressWarnings("resource") // we actually close the stream here @Override @@ -63,8 +63,8 @@ public void close() throws IOException { /** * Reads byte from stream associated with current thread. * - * @return the byte read from stream - * @throws IOException if an error occurs + * @return the byte read from stream. + * @throws IOException if an error occurs. */ @Override public int read() throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/Input.java b/src/main/java/org/apache/commons/io/input/Input.java index 70147681178..b5b626a42c6 100644 --- a/src/main/java/org/apache/commons/io/input/Input.java +++ b/src/main/java/org/apache/commons/io/input/Input.java @@ -18,14 +18,15 @@ package org.apache.commons.io.input; import java.io.IOException; +import java.io.InterruptedIOException; /** * Package-wide internals for input. */ -class Input { +final class Input { /** - * Throws an IOException on false input. + * Throws an {@link IOException} on false input. * * @param isOpen whether an input is open or not. * @throws IOException if {@code isOpen} is false indicating an input is closed. @@ -36,4 +37,21 @@ static void checkOpen(final boolean isOpen) throws IOException { } } + /** + * Converts an {@link InterruptedException} to an {@link InterruptedIOException}. + *

    + * The cause of the returned InterruptedIOException is set to the original. + *

    + * + * @param e The InterruptedException to convert. + * @return The converted InterruptedIOException. + * @see InterruptedIOException + * @see Throwable#initCause(Throwable) + * @see Throwable#getCause() + */ + static InterruptedIOException toInterruptedIOException(final InterruptedException e) { + final InterruptedIOException iio = new InterruptedIOException(e.getMessage()); + iio.initCause(e); + return iio; + } } diff --git a/src/main/java/org/apache/commons/io/input/MarkShieldInputStream.java b/src/main/java/org/apache/commons/io/input/MarkShieldInputStream.java index 44fb12ff56e..0ed0b0bfda6 100644 --- a/src/main/java/org/apache/commons/io/input/MarkShieldInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MarkShieldInputStream.java @@ -40,7 +40,7 @@ public class MarkShieldInputStream extends ProxyInputStream { * Constructs a proxy that shields the given input stream from being * marked or rest. * - * @param in underlying input stream + * @param in underlying input stream. */ public MarkShieldInputStream(final InputStream in) { super(in); diff --git a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java index 22fae70ca75..b8ddda491ec 100644 --- a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java @@ -172,7 +172,7 @@ public int available() throws IOException { } private void cleanBuffer() { - if (ByteBufferCleaner.isSupported() && buffer.isDirect()) { + if (ByteBufferCleaner.isSupported()) { ByteBufferCleaner.clean(buffer); } } diff --git a/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java b/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java index 5f2fb452ef0..7188c564502 100644 --- a/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java @@ -148,7 +148,7 @@ public static class MessageDigestMaintainingObserver extends Observer { /** * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. * - * @param messageDigest the message digest to use + * @param messageDigest the message digest to use. * @throws NullPointerException if messageDigest is null. */ public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { @@ -211,7 +211,7 @@ private MessageDigestCalculatingInputStream(final Builder builder) throws IOExce * The MD5 algorithm is weak and should not be used. *

    * - * @param inputStream the stream to calculate the message digest for + * @param inputStream the stream to calculate the message digest for. * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @@ -226,8 +226,8 @@ public MessageDigestCalculatingInputStream(final InputStream inputStream) throws * The MD5 cryptographic algorithm is weak and should not be used. *

    * - * @param inputStream the stream to calculate the message digest for - * @param messageDigest the message digest to use + * @param inputStream the stream to calculate the message digest for. + * @param messageDigest the message digest to use. * @throws NullPointerException if messageDigest is null. * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. */ @@ -243,7 +243,7 @@ public MessageDigestCalculatingInputStream(final InputStream inputStream, final * The MD5 cryptographic algorithm is weak and should not be used. *

    * - * @param inputStream the stream to calculate the message digest for + * @param inputStream the stream to calculate the message digest for. * @param algorithm the name of the algorithm requested. See the MessageDigest section in the * Java Cryptography * Architecture Standard Algorithm Name Documentation for information about standard algorithm names. @@ -262,7 +262,7 @@ public MessageDigestCalculatingInputStream(final InputStream inputStream, final * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. *

    * - * @return the message digest used + * @return the message digest used. */ public MessageDigest getMessageDigest() { return messageDigest; diff --git a/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java b/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java index dac3f2afb5e..c8c3af88347 100644 --- a/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java @@ -152,7 +152,7 @@ public static class MessageDigestMaintainingObserver extends Observer { /** * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. * - * @param messageDigest the message digest to use + * @param messageDigest the message digest to use. * @throws NullPointerException if messageDigest is null. */ public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { @@ -190,7 +190,7 @@ public static Builder builder() { * The MD5 cryptographic algorithm is weak and should not be used. *

    * - * @param builder A builder use to get the stream to calculate the message digest and the message digest to use + * @param builder A builder use to get the stream to calculate the message digest and the message digest to use. * @throws NullPointerException if messageDigest is null. */ private MessageDigestInputStream(final Builder builder) throws IOException { diff --git a/src/main/java/org/apache/commons/io/input/NullInputStream.java b/src/main/java/org/apache/commons/io/input/NullInputStream.java index 5fa9b817b5c..1f05410f636 100644 --- a/src/main/java/org/apache/commons/io/input/NullInputStream.java +++ b/src/main/java/org/apache/commons/io/input/NullInputStream.java @@ -272,7 +272,7 @@ public int read() throws IOException { /** * Reads some bytes into the specified array. * - * @param bytes The byte array to read into + * @param bytes The byte array to read into. * @return The number of bytes read or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}. * @throws NullPointerException if the byte array is {@code null}. * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}. diff --git a/src/main/java/org/apache/commons/io/input/NullReader.java b/src/main/java/org/apache/commons/io/input/NullReader.java index 28f3d37eb21..0405f3943d0 100644 --- a/src/main/java/org/apache/commons/io/input/NullReader.java +++ b/src/main/java/org/apache/commons/io/input/NullReader.java @@ -217,7 +217,7 @@ protected int processChar() { * This implementation leaves the character array unchanged. *

    * - * @param chars The character array + * @param chars The character array. * @param offset The offset to start at. * @param length The number of characters. */ @@ -250,7 +250,7 @@ public int read() throws IOException { /** * Reads some characters into the specified array. * - * @param chars The character array to read into + * @param chars The character array to read into. * @return The number of characters read or {@code -1} * if the end of file has been reached and * {@code throwEofException} is set to {@code false}. diff --git a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java index a1287b857f7..1909f859a65 100644 --- a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java @@ -144,7 +144,7 @@ public void data(final int value) throws IOException { /** * Called to indicate that an error occurred on the underlying stream. * - * @param exception the exception to throw + * @param exception the exception to throw. * @throws IOException if an I/O error occurs. */ public void error(final IOException exception) throws IOException { @@ -361,7 +361,7 @@ public int read(final byte[] buffer, final int offset, final int length) throws /** * Removes an Observer. * - * @param observer the observer to remove + * @param observer the observer to remove. */ public void remove(final Observer observer) { observers.remove(observer); diff --git a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java index b2e04c334b5..fdff24eac0e 100644 --- a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java @@ -27,7 +27,7 @@ import org.apache.commons.io.function.IOIntConsumer; /** - * A proxy stream which acts as a {@link FilterInputStream}, by passing all method calls on to the proxied stream, not changing which methods are called. + * An input stream proxy which delegates to the wrapped input stream. *

    * It is an alternative base class to {@link FilterInputStream} to increase reusability, because {@link FilterInputStream} changes the methods being called, * such as read(byte[]) to read(byte[], int, int). @@ -298,7 +298,7 @@ public int read() throws IOException { * @return the number of bytes read or {@link IOUtils#EOF EOF} if we reached the end of stream. * @throws IOException *

      - *
    • If the first byte cannot be read for any reason other than the end of the file, + *
    • If the first byte cannot be read for any reason other than the end of the file,
    • *
    • if the input stream has been closed, or
    • *
    • if some other I/O error occurs.
    • *
    @@ -325,7 +325,7 @@ public int read(final byte[] b) throws IOException { * @return the number of bytes read or {@link IOUtils#EOF EOF} if we reached the end of stream. * @throws IOException *
      - *
    • If the first byte cannot be read for any reason other than the end of the file, + *
    • If the first byte cannot be read for any reason other than the end of the file,
    • *
    • if the input stream has been closed, or
    • *
    • if some other I/O error occurs.
    • *
    @@ -359,6 +359,9 @@ public synchronized void reset() throws IOException { /** * Sets the underlying input stream. + *

    + * Use with caution. + *

    * * @param in The input stream to set in {@link java.io.FilterInputStream#in}. * @return {@code this} instance. @@ -389,7 +392,7 @@ public long skip(final long n) throws IOException { /** * Unwraps this instance by returning the underlying {@link InputStream}. *

    - * Use with caution; useful to query the underlying {@link InputStream}. + * Use with caution. *

    * * @return the underlying {@link InputStream}. diff --git a/src/main/java/org/apache/commons/io/input/ProxyReader.java b/src/main/java/org/apache/commons/io/input/ProxyReader.java index 7bfed636497..b450891ef27 100644 --- a/src/main/java/org/apache/commons/io/input/ProxyReader.java +++ b/src/main/java/org/apache/commons/io/input/ProxyReader.java @@ -26,9 +26,7 @@ import org.apache.commons.io.IOUtils; /** - * A Proxy stream which acts as expected, that is it passes the method - * calls on to the proxied stream and doesn't change which methods are - * being called. + * A reader proxy which delegates to the wrapped reader. *

    * It is an alternative base class to FilterReader * to increase reusability, because FilterReader changes the @@ -40,7 +38,7 @@ public abstract class ProxyReader extends FilterReader { /** * Constructs a new ProxyReader. * - * @param delegate the Reader to delegate to + * @param delegate the Reader to delegate to. */ public ProxyReader(final Reader delegate) { // the delegate is stored in a protected superclass variable named 'in' @@ -55,13 +53,15 @@ public ProxyReader(final Reader delegate) { * Subclasses can override this method to add common post-processing * functionality without having to override all the read methods. * The default implementation does nothing. + *

    *

    * Note this method is not called from {@link #skip(long)} or * {@link #reset()}. You need to explicitly override those methods if * you want to add post-processing steps also to them. + *

    * - * @param n number of chars read, or -1 if the end of stream was reached - * @throws IOException if the post-processing fails + * @param n number of chars read, or -1 if the end of stream was reached. + * @throws IOException if the post-processing fails. * @since 2.0 */ @SuppressWarnings("unused") // Possibly thrown from subclasses. @@ -78,13 +78,15 @@ protected void afterRead(final int n) throws IOException { * Subclasses can override this method to add common pre-processing * functionality without having to override all the read methods. * The default implementation does nothing. + *

    *

    * Note this method is not called from {@link #skip(long)} or * {@link #reset()}. You need to explicitly override those methods if * you want to add pre-processing steps also to them. + *

    * - * @param n number of chars that the caller asked to be read - * @throws IOException if the pre-processing fails + * @param n number of chars that the caller asked to be read. + * @throws IOException if the pre-processing fails. * @since 2.0 */ @SuppressWarnings("unused") // Possibly thrown from subclasses. @@ -94,6 +96,7 @@ protected void beforeRead(final int n) throws IOException { /** * Invokes the delegate's {@code close()} method. + * * @throws IOException if an I/O error occurs. */ @Override @@ -110,7 +113,9 @@ public void close() throws IOException { *

    * This method provides a point to implement custom exception * handling. The default behavior is to re-throw the exception. - * @param e The IOException thrown + *

    + * + * @param e The IOException thrown. * @throws IOException if an I/O error occurs. * @since 2.0 */ @@ -120,7 +125,8 @@ protected void handleIOException(final IOException e) throws IOException { /** * Invokes the delegate's {@code mark(int)} method. - * @param readAheadLimit read ahead limit + * + * @param readAheadLimit read ahead limit. * @throws IOException if an I/O error occurs. */ @Override @@ -134,7 +140,8 @@ public synchronized void mark(final int readAheadLimit) throws IOException { /** * Invokes the delegate's {@code markSupported()} method. - * @return true if mark is supported, otherwise false + * + * @return true if mark is supported, otherwise false. */ @Override public boolean markSupported() { @@ -143,7 +150,8 @@ public boolean markSupported() { /** * Invokes the delegate's {@code read()} method. - * @return the character read or -1 if the end of stream + * + * @return the character read or -1 if the end of stream. * @throws IOException if an I/O error occurs. */ @Override @@ -161,8 +169,9 @@ public int read() throws IOException { /** * Invokes the delegate's {@code read(char[])} method. - * @param chr the buffer to read the characters into - * @return the number of characters read or -1 if the end of stream + * + * @param chr the buffer to read the characters into. + * @return the number of characters read or -1 if the end of stream. * @throws IOException if an I/O error occurs. */ @Override @@ -180,10 +189,11 @@ public int read(final char[] chr) throws IOException { /** * Invokes the delegate's {@code read(char[], int, int)} method. - * @param chr the buffer to read the characters into - * @param st The start offset - * @param len The number of bytes to read - * @return the number of characters read or -1 if the end of stream + * + * @param chr the buffer to read the characters into. + * @param st The start offset. + * @param len The number of bytes to read. + * @return the number of characters read or -1 if the end of stream. * @throws IOException if an I/O error occurs. */ @Override @@ -201,8 +211,9 @@ public int read(final char[] chr, final int st, final int len) throws IOExceptio /** * Invokes the delegate's {@code read(CharBuffer)} method. - * @param target the char buffer to read the characters into - * @return the number of characters read or -1 if the end of stream + * + * @param target the char buffer to read the characters into. + * @return the number of characters read or -1 if the end of stream. * @throws IOException if an I/O error occurs. * @since 2.0 */ @@ -221,7 +232,8 @@ public int read(final CharBuffer target) throws IOException { /** * Invokes the delegate's {@code ready()} method. - * @return true if the stream is ready to be read + * + * @return true if the stream is ready to be read. * @throws IOException if an I/O error occurs. */ @Override @@ -236,6 +248,7 @@ public boolean ready() throws IOException { /** * Invokes the delegate's {@code reset()} method. + * * @throws IOException if an I/O error occurs. */ @Override @@ -247,10 +260,27 @@ public synchronized void reset() throws IOException { } } + /** + * Sets the underlying reader. + *

    + * Use with caution. + *

    + * + * @param in The input stream to set in {@code java.io.Reader#in}. + * @return {@code this} instance. + * @since 2.22.0 + */ + public ProxyReader setReference(final Reader in) { + this.in = in; + return this; + } + + /** * Invokes the delegate's {@code skip(long)} method. - * @param ln the number of bytes to skip - * @return the number of bytes to skipped or EOF if the end of stream + * + * @param ln the number of bytes to skip. + * @return the number of bytes to skipped or EOF if the end of stream. * @throws IOException if an I/O error occurs. */ @Override @@ -263,4 +293,17 @@ public long skip(final long ln) throws IOException { } } + /** + * Unwraps this instance by returning the underlying {@link Reader}. + *

    + * Use with caution. + *

    + * + * @return the underlying {@link Reader}. + * @since 2.22.0 + */ + public Reader unwrap() { + return in; + } + } diff --git a/src/main/java/org/apache/commons/io/input/QueueInputStream.java b/src/main/java/org/apache/commons/io/input/QueueInputStream.java index 5f701945cb3..d672d23fa2b 100644 --- a/src/main/java/org/apache/commons/io/input/QueueInputStream.java +++ b/src/main/java/org/apache/commons/io/input/QueueInputStream.java @@ -229,7 +229,7 @@ public int read() { /** * Reads up to {@code length} bytes of data from the input stream into - * an array of bytes. The first byte is read while honoring the timeout; the rest are read while not honoring + * an array of bytes. The first byte is read while honoring the timeout; the rest are read while not honoring * the timeout. The number of bytes actually read is returned as an integer. * * @param b the buffer into which the data is read. diff --git a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java index bcee009a431..152cd42a586 100644 --- a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java @@ -20,7 +20,6 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -63,6 +62,9 @@ public class ReadAheadInputStream extends FilterInputStream { * .setExecutorService(Executors.newSingleThreadExecutor(ReadAheadInputStream::newThread)) * .get();} *