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/pull_request_template.md b/.github/pull_request_template.md
index e17973cb0c5..4cbe168c3e8 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -22,7 +22,9 @@ Thanks for your contribution to [Apache Commons](https://commons.apache.org/)! Y
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.
- [ ] 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 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 commits might be squashed by a maintainer on merge.
+- [ ] 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 4f6d2427a9c..276e14f41de 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -50,10 +50,10 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@@ -62,7 +62,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # 3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,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@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # 3.29.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -87,4 +87,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # 3.29.5
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 33573cf948f..a657a4ae2cd 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review PR'
- uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
+ uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 24ba38ed379..ee6b9c99a12 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -18,6 +18,8 @@ name: Java CI
on:
workflow_dispatch:
push:
+ branches:
+ - 'master'
paths-ignore:
- '**/workflows/*.yml'
- '!**/workflows/maven.yml'
@@ -37,7 +39,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-13]
- java: [ 8, 11, 17, 21 ]
+ java: [ 8, 11, 17, 21, 25 ]
experimental: [false]
# Keep the same parameter order as the matrix above
include:
@@ -46,40 +48,29 @@ jobs:
java: 21
experimental: false
deploy: true
- # Experimental builds: Java 24-ea
+ # Experimental builds: Java 26-ea
- os: ubuntu-latest
- java: 24
+ java: 26-ea
experimental: true
- os: windows-latest
- java: 24
+ java: 26-ea
experimental: true
- os: macos-latest
- java: 24
+ java: 26-ea
experimental: true
- # Experimental builds: Java 24-ea
- - os: ubuntu-latest
- java: 25-ea
- experimental: true
- - os: windows-latest
- java: 25-ea
- experimental: true
- - os: macos-latest
- java: 25-ea
- experimental: true
- fail-fast: false
-
+ fail-fast: false
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
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@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
+ uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index 6ffc5bf007a..3215897f03b 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # 5.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # 3.29.5
with:
sarif_file: results.sarif
diff --git a/README.md b/README.md
index bb31ce854a6..36a82594a8e 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Apache Commons IO
[](https://github.com/apache/commons-io/actions/workflows/maven.yml)
[](https://search.maven.org/artifact/commons-io/commons-io)
-[](https://javadoc.io/doc/commons-io/commons-io/2.20.0)
+[](https://javadoc.io/doc/commons-io/commons-io/2.21.0)
[](https://github.com/apache/commons-io/actions/workflows/codeql-analysis.yml)
[](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-iocommons-io
- 2.20.0
+ 2.21.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).
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 1f30f8b0f5a..3f84b517499 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,4 +1,82 @@
+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.
diff --git a/pom.xml b/pom.xml
index d545a1e0de3..59e438881c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,12 +19,12 @@
org.apache.commonscommons-parent
- 85
+ 914.0.0commons-iocommons-io
- 2.20.0
+ 2.21.0Apache Commons IO2002
@@ -47,7 +47,7 @@ file comparators, endian transformation classes, and much more.
scm:git:https://gitbox.apache.org/repos/asf/commons-io.gitscm:git:https://gitbox.apache.org/repos/asf/commons-io.githttps://gitbox.apache.org/repos/asf?p=commons-io.git
- rel/commons-io-2.20.0
+ rel/commons-io-2.21.0GitHub
@@ -87,19 +87,19 @@ file comparators, endian transformation classes, and much more.
com.google.jimfsjimfs
- 1.3.0
+ 1.3.1testorg.apache.commonscommons-lang3
- 3.18.0
+ 3.19.0testcommons-codeccommons-codec
- 1.18.0
+ 1.19.0test
@@ -115,11 +115,11 @@ file comparators, endian transformation classes, and much more.
ioorg.apache.commons.ioRC1
- 2.19.0
- 2.20.0
- 2.20.1
+ 2.20.0
+ 2.21.0
+ 2.21.1
- 2025-07-14T21:18:06Z
+ 2025-11-04T20:17:29Z(requires Java 8)IO12310477
@@ -146,7 +146,7 @@ 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.6
+ 1.17.8falsetrue
@@ -158,6 +158,8 @@ file comparators, endian transformation classes, and much more.
0.850.900.85
+
+
@@ -217,7 +219,7 @@ file comparators, endian transformation classes, and much more.
false
- ${argLine} -Xmx25M
+ ${argLine} -Xmx25M ${EnableDynamicAgentLoading}**/*Test*.class
@@ -345,6 +347,15 @@ file comparators, endian transformation classes, and much more.
8
+
+ java21-up
+
+ [21,)
+
+
+ -XX:+EnableDynamicAgentLoading
+
+ benchmark
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0080206dcc3..23a7d3b40d6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -45,7 +45,43 @@ The type attribute can be add,update,fix,remove.
Apache Commons IO Release Notes
-
+
+
+ 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.
+ ValidatingObjectInputStream does not validate dynamic proxy interfaces.
+ BoundedInputStream.getRemaining() now reports Long.MAX_VALUE instead of 0 when no limit is set.
+ BoundedInputStream.available() correctly accounts for the maximum read limit.
+ 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.
+ Add org.apache.commons.io.FileUtils.ONE_QB #763.
+ Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], int, int, long).
+ Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], long).
+ Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(int, long).
+ Add length unit support in FileSystem limits.
+ Add IOUtils.toByteArray(InputStream, int, int) for safer chunked reading with size validation.
+ Add org.apache.commons.io.file.PathUtils.getPath(String, String).
+ Add org.apache.commons.io.channels.ByteArraySeekableByteChannel.
+ Add IOIterable.asIterable().
+ Add NIO channel support to `AbstractStreamBuilder`.
+ 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 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.8 #769.
+ [test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.
+
+ Inline private constant field ProxyInputStream.exceptionHandler #780.
+
+ [javadoc] Rename parameter of ProxyOutputStream.write(int) #740.CopyDirectoryVisitor ignores fileFilter #743.
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/conf/spotbugs-exclude-filter.xml b/src/conf/spotbugs-exclude-filter.xml
index 24382785a47..bf35c1f1c9b 100644
--- a/src/conf/spotbugs-exclude-filter.xml
+++ b/src/conf/spotbugs-exclude-filter.xml
@@ -111,4 +111,9 @@
+
+
+
+
+
diff --git a/src/main/java/org/apache/commons/io/ByteBuffers.java b/src/main/java/org/apache/commons/io/ByteBuffers.java
index b8841433e3b..91efd8ed3e2 100644
--- a/src/main/java/org/apache/commons/io/ByteBuffers.java
+++ b/src/main/java/org/apache/commons/io/ByteBuffers.java
@@ -62,7 +62,7 @@ public static ByteBuffer littleEndian(final ByteBuffer allocate) {
*
* @param capacity The new buffer's capacity, in bytes.
* @return The new byte buffer.
- * @throws IllegalArgumentException If the capacity is negative.
+ * @throws IllegalArgumentException If the {@code capacity} is negative.
*/
public static ByteBuffer littleEndian(final int capacity) {
return littleEndian(ByteBuffer.allocate(capacity));
diff --git a/src/main/java/org/apache/commons/io/ByteOrderMark.java b/src/main/java/org/apache/commons/io/ByteOrderMark.java
index 0067f7d2d6f..9624a0ba8b4 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
*/
diff --git a/src/main/java/org/apache/commons/io/CopyUtils.java b/src/main/java/org/apache/commons/io/CopyUtils.java
index ad426952e4e..bb9ac470c75 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
@@ -146,7 +148,7 @@ public static void copy(final byte[] input, final Writer output) throws IOExcept
* @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
*/
@@ -179,7 +181,7 @@ 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
@@ -204,7 +206,7 @@ public static void copy(
* @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
*/
@@ -221,7 +223,7 @@ 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
@@ -249,7 +251,7 @@ public static void copy(
* @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
* @since 2.5
@@ -278,14 +280,16 @@ 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,7 +297,7 @@ 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
@@ -323,7 +327,7 @@ public static void copy(
* @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
* @since 2.5
diff --git a/src/main/java/org/apache/commons/io/EndianUtils.java b/src/main/java/org/apache/commons/io/EndianUtils.java
index 9fa1913c731..6b4d58eeba3 100644
--- a/src/main/java/org/apache/commons/io/EndianUtils.java
+++ b/src/main/java/org/apache/commons/io/EndianUtils.java
@@ -26,7 +26,7 @@
/**
* Helps with reading and writing primitive numeric types ({@code short},
* {@code int}, {@code long}, {@code float}, and {@code double}) that are
- * encoded in little endian using two's complement or unsigned representations.
+ * encoded in little-endian using two's complement or unsigned representations.
*
* Different computer architectures have different conventions for
* byte ordering. In "Little Endian" architectures (e.g. X86),
@@ -35,8 +35,8 @@
* (e.g. Motorola 680X0), the situation is reversed.
* Most methods and classes throughout Java — e.g. {@code DataInputStream} and
* {@code Double.longBitsToDouble()} — assume data is laid out
- * in big endian order with the most significant byte first.
- * The methods in this class read and write data in little endian order,
+ * in big-endian order with the most significant byte first.
+ * The methods in this class read and write data in little-endian order,
* generally by reversing the bytes and then using the
* regular Java methods to convert the swapped bytes to a primitive type.
*
@@ -63,7 +63,7 @@ private static int read(final InputStream input) throws IOException {
}
/**
- * Reads a little endian {@code double} value from a byte array at a given offset.
+ * 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
@@ -75,7 +75,7 @@ public static double readSwappedDouble(final byte[] data, final int offset) {
}
/**
- * Reads a little endian {@code double} value from an InputStream.
+ * Reads a little-endian {@code double} value from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -86,7 +86,7 @@ public static double readSwappedDouble(final InputStream input) throws IOExcepti
}
/**
- * Reads a little endian {@code float} value from a byte array at a given offset.
+ * 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
@@ -98,7 +98,7 @@ public static float readSwappedFloat(final byte[] data, final int offset) {
}
/**
- * Reads a little endian {@code float} value from an InputStream.
+ * Reads a little-endian {@code float} value from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -109,7 +109,7 @@ public static float readSwappedFloat(final InputStream input) throws IOException
}
/**
- * Reads a little endian {@code int} value from a byte array at a given offset.
+ * 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
@@ -125,7 +125,7 @@ public static int readSwappedInteger(final byte[] data, final int offset) {
}
/**
- * Reads a little endian {@code int} value from an InputStream.
+ * Reads a little-endian {@code int} value from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -140,7 +140,7 @@ public static int readSwappedInteger(final InputStream input) throws IOException
}
/**
- * Reads a little endian {@code long} value from a byte array at a given offset.
+ * 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
@@ -155,7 +155,7 @@ public static long readSwappedLong(final byte[] data, final int offset) {
}
/**
- * Reads a little endian {@code long} value from an InputStream.
+ * Reads a little-endian {@code long} value from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -170,7 +170,7 @@ public static long readSwappedLong(final InputStream input) throws IOException {
}
/**
- * Reads a little endian {@code short} value from a byte array at a given offset.
+ * 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
@@ -183,7 +183,7 @@ public static short readSwappedShort(final byte[] data, final int offset) {
}
/**
- * Reads a little endian {@code short} value from an InputStream.
+ * Reads a little-endian {@code short} value from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -194,7 +194,7 @@ public static short readSwappedShort(final InputStream input) throws IOException
}
/**
- * Reads a little endian unsigned integer (32-bit) value from a byte array at a given
+ * Reads a little-endian unsigned integer (32-bit) value from a byte array at a given
* offset.
*
* @param data source byte array
@@ -212,7 +212,7 @@ public static long readSwappedUnsignedInteger(final byte[] data, final int offse
}
/**
- * Reads a little endian unsigned integer (32-bit) from an InputStream.
+ * Reads a little-endian unsigned integer (32-bit) from an InputStream.
*
* @param input source InputStream
* @return the value just read
@@ -229,7 +229,7 @@ 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
+ * Reads an unsigned short (16-bit) value from a byte array in little-endian order at a given
* offset.
*
* @param data source byte array
@@ -243,7 +243,7 @@ public static int readSwappedUnsignedShort(final byte[] data, final int offset)
}
/**
- * Reads an unsigned short (16-bit) from an InputStream in little endian order.
+ * Reads an unsigned short (16-bit) from an InputStream in little-endian order.
*
* @param input source InputStream
* @return the value just read
@@ -257,7 +257,7 @@ public static int readSwappedUnsignedShort(final InputStream input) throws IOExc
}
/**
- * Converts a {@code double} value from big endian to little endian
+ * Converts a {@code double} value from big-endian to little-endian
* and vice versa. That is, it converts the {@code double} to bytes,
* reverses the bytes, and then reinterprets those bytes as a new {@code double}.
* This can be useful if you have a number that was read from the
@@ -271,7 +271,7 @@ public static double swapDouble(final double value) {
}
/**
- * Converts a {@code float} value from big endian to little endian and vice versa.
+ * Converts a {@code float} value from big-endian to little-endian and vice versa.
*
* @param value value to convert
* @return the converted value
@@ -281,7 +281,7 @@ public static float swapFloat(final float value) {
}
/**
- * Converts an {@code int} value from big endian to little endian and vice versa.
+ * Converts an {@code int} value from big-endian to little-endian and vice versa.
*
* @param value value to convert
* @return the converted value
@@ -295,7 +295,7 @@ public static int swapInteger(final int value) {
}
/**
- * Converts a {@code long} value from big endian to little endian and vice versa.
+ * Converts a {@code long} value from big-endian to little-endian and vice versa.
*
* @param value value to convert
* @return the converted value
@@ -313,7 +313,7 @@ public static long swapLong(final long value) {
}
/**
- * Converts a {@code short} value from big endian to little endian and vice versa.
+ * Converts a {@code short} value from big-endian to little-endian and vice versa.
*
* @param value value to convert
* @return the converted value
@@ -338,7 +338,7 @@ 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.
+ * 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
@@ -350,7 +350,7 @@ 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.
+ * 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
@@ -361,7 +361,7 @@ 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.
+ * 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
@@ -373,7 +373,7 @@ 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.
+ * 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
@@ -384,7 +384,7 @@ 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.
+ * 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
@@ -400,7 +400,7 @@ 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.
+ * 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
@@ -414,7 +414,7 @@ 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.
+ * 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
@@ -434,7 +434,7 @@ 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.
+ * 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
@@ -452,7 +452,7 @@ 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.
+ * 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
@@ -466,7 +466,7 @@ 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.
+ * 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
diff --git a/src/main/java/org/apache/commons/io/FileSystem.java b/src/main/java/org/apache/commons/io/FileSystem.java
index 0e0ddf05aff..4169e1a53b1 100644
--- a/src/main/java/org/apache/commons/io/FileSystem.java
+++ b/src/main/java/org/apache/commons/io/FileSystem.java
@@ -17,6 +17,16 @@
package org.apache.commons.io;
+import static java.nio.charset.StandardCharsets.UTF_16;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
@@ -36,7 +46,12 @@ public enum FileSystem {
/**
* Generic file system.
*/
- GENERIC(4096, false, false, Integer.MAX_VALUE, Integer.MAX_VALUE, new int[] { 0 }, new String[] {}, false, false, '/'),
+ GENERIC(4096, false, false, 1020, 1024 * 1024, new int[] {
+ // @formatter:off
+ // ASCII NUL
+ 0
+ // @formatter:on
+ }, new String[] {}, false, false, '/', NameLengthStrategy.BYTES),
/**
* Linux file system.
@@ -48,7 +63,7 @@ public enum FileSystem {
0,
'/'
// @formatter:on
- }, new String[] {}, false, false, '/'),
+ }, new String[] {}, false, false, '/', NameLengthStrategy.BYTES),
/**
* MacOS file system.
@@ -61,7 +76,7 @@ public enum FileSystem {
'/',
':'
// @formatter:on
- }, new String[] {}, false, false, '/'),
+ }, new String[] {}, false, false, '/', NameLengthStrategy.BYTES),
/**
* Windows file system.
@@ -78,7 +93,7 @@ public enum FileSystem {
*/
// @formatter:off
WINDOWS(4096, false, true,
- 255, 32000, // KEEP THIS ARRAY SORTED!
+ 255, 32767, // KEEP THIS ARRAY SORTED!
new int[] {
// KEEP THIS ARRAY SORTED!
// ASCII NUL
@@ -95,13 +110,127 @@ public enum FileSystem {
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
"LPT\u00b2", "LPT\u00b3", "LPT\u00b9", // Superscript 2 3 1 in that order
"NUL", "PRN"
- }, true, true, '\\');
+ }, true, true, '\\', NameLengthStrategy.UTF16_CODE_UNITS);
// @formatter:on
/**
- *
+ * Strategy for measuring and truncating file or path names in different units.
+ * Implementations measure length and can truncate to a specified limit.
+ */
+ enum NameLengthStrategy {
+ /** Length measured as encoded bytes. */
+ BYTES {
+ @Override
+ int getLength(final CharSequence value, final Charset charset) {
+ final CharsetEncoder enc = charset.newEncoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ try {
+ return enc.encode(CharBuffer.wrap(value)).remaining();
+ } catch (final CharacterCodingException e) {
+ // Unencodable, does not fit any byte limit.
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
+ final CharsetEncoder encoder = charset.newEncoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ if (!encoder.canEncode(value)) {
+ throw new IllegalArgumentException("The value " + value + " cannot be encoded using " + charset.name());
+ }
+ // Fast path: if even the worst-case expansion fits, we're done.
+ if (value.length() <= Math.floor(limit / encoder.maxBytesPerChar())) {
+ return value;
+ }
+ // Slow path: encode into a fixed-size byte buffer.
+ // 1. Compute length of extension in bytes (if any).
+ final CharSequence[] parts = splitExtension(value);
+ final int extensionLength = getLength(parts[1], charset);
+ if (extensionLength > 0 && extensionLength >= limit) {
+ // Extension itself does not fit
+ throw new IllegalArgumentException("The extension of " + value + " is too long to fit within " + limit + " bytes");
+ }
+ // 2. Compute the character part that fits within the remaining byte budget.
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(limit - extensionLength);
+ final CharBuffer charBuffer = CharBuffer.wrap(parts[0]);
+ // Encode until the first character that would exceed the byte budget.
+ final CoderResult cr = encoder.encode(charBuffer, byteBuffer, true);
+ if (cr.isUnderflow()) {
+ // Entire candidate fit within maxFileNameLength bytes.
+ return value;
+ }
+ final CharSequence truncated = safeTruncate(value, charBuffer.position());
+ return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
+ }
+ },
+
+ /** Length measured as UTF-16 code units (i.e., {@code CharSequence.length()}). */
+ UTF16_CODE_UNITS {
+ @Override
+ int getLength(final CharSequence value, final Charset charset) {
+ return value.length();
+ }
+
+ @Override
+ CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
+ if (!UTF_16.newEncoder().canEncode(value)) {
+ throw new IllegalArgumentException("The value " + value + " can not be encoded using " + UTF_16.name());
+ }
+ // Fast path: no truncation needed.
+ if (value.length() <= limit) {
+ return value;
+ }
+ // Slow path: truncate to limit.
+ // 1. Compute length of extension in chars (if any).
+ final CharSequence[] parts = splitExtension(value);
+ final int extensionLength = parts[1].length();
+ if (extensionLength > 0 && extensionLength >= limit) {
+ // Extension itself does not fit
+ throw new IllegalArgumentException("The extension of " + value + " is too long to fit within " + limit + " characters");
+ }
+ // 2. Truncate the non-extension part and append the extension (if any).
+ final CharSequence truncated = safeTruncate(value, limit - extensionLength);
+ return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
+ }
+ };
+
+ /**
+ * Gets the measured length in this strategy’s unit.
+ *
+ * @param value The value to measure, not null.
+ * @param charset The charset to use when measuring in bytes.
+ * @return The length in this strategy’s unit.
+ */
+ abstract int getLength(CharSequence value, Charset charset);
+
+ /**
+ * Tests if the measured length is less or equal the {@code limit}.
+ *
+ * @param value The value to measure, not null.
+ * @param limit The limit to compare to.
+ * @param charset The charset to use when measuring in bytes.
+ * @return {@code true} if the measured length is less or equal the {@code limit}, {@code false} otherwise.
+ */
+ final boolean isWithinLimit(final CharSequence value, final int limit, final Charset charset) {
+ return getLength(value, charset) <= limit;
+ }
+
+ /**
+ * Truncates to {@code limit} in this strategy’s unit (no-op if already within limit).
+ *
+ * @param value The value to truncate, not null.
+ * @param limit The limit to truncate to.
+ * @param charset The charset to use when measuring in bytes.
+ * @return The truncated value, not null.
+ */
+ abstract CharSequence truncate(CharSequence value, int limit, Charset charset);
+ }
+
+ /**
* Is {@code true} if this is Linux.
- *
*
* The field will return {@code false} if {@code OS_NAME} is {@code null}.
*
@@ -109,9 +238,7 @@ public enum FileSystem {
private static final boolean IS_OS_LINUX = getOsMatchesName("Linux");
/**
- *
* Is {@code true} if this is Mac.
- *
*
* The field will return {@code false} if {@code OS_NAME} is {@code null}.
*
@@ -124,9 +251,7 @@ public enum FileSystem {
private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
/**
- *
* Is {@code true} if this is Windows.
- *
*
* The field will return {@code false} if {@code OS_NAME} is {@code null}.
*
* Gets a System property, defaulting to {@code null} if the property cannot be read.
- *
*
* If a {@link SecurityException} is caught, the return value is {@code null} and a message is written to
* {@code System.err}.
@@ -200,74 +323,16 @@ private static String getSystemProperty(final String property) {
}
}
- /**
- * Copied from Apache Commons Lang CharSequenceUtils.
- *
- * Returns the index within {@code cs} of the first occurrence of the
- * specified character, starting the search at the specified index.
- *
- * If a character with value {@code searchChar} occurs in the
- * character sequence represented by the {@code cs}
- * object at an index no smaller than {@code start}, then
- * the index of the first such occurrence is returned. For values
- * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
- * this is the smallest value k such that:
- *
- * is true. In either case, if no such character occurs in {@code cs}
- * at or after position {@code start}, then
- * {@code -1} is returned.
- *
- *
- * There is no restriction on the value of {@code start}. If it
- * is negative, it has the same effect as if it were zero: the entire
- * {@link CharSequence} may be searched. If it is greater than
- * the length of {@code cs}, it has the same effect as if it were
- * equal to the length of {@code cs}: {@code -1} is returned.
- *
- *
All indices are specified in {@code char} values
- * (Unicode code units).
- *
- *
- * @param cs the {@link CharSequence} to be processed, not null
- * @param searchChar the char to be searched for
- * @param start the start index, negative starts at the string start
- * @return the index where the search char was found, -1 if not found
- * @since 3.6 updated to behave more like {@link String}
+ /*
+ * Finds the index of the first dot in a CharSequence.
*/
- private static int indexOf(final CharSequence cs, final int searchChar, int start) {
+ private static int indexOfFirstDot(final CharSequence cs) {
if (cs instanceof String) {
- return ((String) cs).indexOf(searchChar, start);
- }
- final int sz = cs.length();
- if (start < 0) {
- start = 0;
+ return ((String) cs).indexOf('.');
}
- if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
- for (int i = start; i < sz; i++) {
- if (cs.charAt(i) == searchChar) {
- return i;
- }
- }
- return -1;
- }
- //supplementary characters (LANG1300)
- if (searchChar <= Character.MAX_CODE_POINT) {
- final char[] chars = Character.toChars(searchChar);
- for (int i = start; i < sz - 1; i++) {
- final char high = cs.charAt(i);
- final char low = cs.charAt(i + 1);
- if (high == chars[0] && low == chars[1]) {
- return i;
- }
+ for (int i = 0; i < cs.length(); i++) {
+ if (cs.charAt(i) == '.') {
+ return i;
}
}
return -1;
@@ -304,6 +369,42 @@ private static String replace(final String path, final char oldChar, final char
return path == null ? null : path.replace(oldChar, newChar);
}
+ /**
+ * Truncates a string respecting grapheme cluster boundaries.
+ *
+ * @param value The value to truncate.
+ * @param limit The maximum length.
+ * @return The truncated value.
+ * @throws IllegalArgumentException If the first grapheme cluster is longer than the limit.
+ */
+ private static CharSequence safeTruncate(final CharSequence value, final int limit) {
+ if (value.length() <= limit) {
+ return value;
+ }
+ final BreakIterator boundary = BreakIterator.getCharacterInstance(Locale.ROOT);
+ final String text = value.toString();
+ boundary.setText(text);
+ final int end = boundary.preceding(limit + 1);
+ assert end != BreakIterator.DONE;
+ if (end == 0) {
+ final String limitMessage = limit <= 1 ? "1 character" : limit + " characters";
+ throw new IllegalArgumentException("The value " + value + " can not be truncated to " + limitMessage
+ + " without breaking the first codepoint or grapheme cluster");
+ }
+ return text.substring(0, end);
+ }
+ static CharSequence[] splitExtension(final CharSequence value) {
+ final int index = indexOfFirstDot(value);
+ // An initial dot is not an extension
+ return index < 1
+ ? new CharSequence[] {value, ""}
+ : new CharSequence[] {value.subSequence(0, index), value.subSequence(index, value.length())};
+ }
+ static CharSequence trimExtension(final CharSequence cs) {
+ final int index = indexOfFirstDot(cs);
+ // An initial dot is not an extension
+ return index < 1 ? cs : cs.subSequence(0, index);
+ }
private final int blockSize;
private final boolean casePreserving;
private final boolean caseSensitive;
@@ -312,10 +413,15 @@ private static String replace(final String path, final char oldChar, final char
private final int maxPathLength;
private final String[] reservedFileNames;
private final boolean reservedFileNamesExtensions;
+
private final boolean supportsDriveLetter;
+
private final char nameSeparator;
+
private final char nameSeparatorOther;
+ private final NameLengthStrategy nameLengthStrategy;
+
/**
* Constructs a new instance.
*
@@ -326,13 +432,15 @@ private static String replace(final String path, final char oldChar, final char
* @param maxPathLength The maximum length of the path to a file. This can include folders.
* @param illegalFileNameChars Illegal characters for this file system.
* @param reservedFileNames The reserved file names.
- * @param reservedFileNamesExtensions TODO
+ * @param reservedFileNamesExtensions The reserved file name extensions.
* @param supportsDriveLetter Whether this file system support driver letters.
* @param nameSeparator The name separator, '\\' on Windows, '/' on Linux.
+ * @param nameLengthStrategy The strategy for measuring and truncating file and path names.
*/
FileSystem(final int blockSize, final boolean caseSensitive, final boolean casePreserving,
final int maxFileLength, final int maxPathLength, final int[] illegalFileNameChars,
- final String[] reservedFileNames, final boolean reservedFileNamesExtensions, final boolean supportsDriveLetter, final char nameSeparator) {
+ final String[] reservedFileNames, final boolean reservedFileNamesExtensions, final boolean supportsDriveLetter,
+ final char nameSeparator, final NameLengthStrategy nameLengthStrategy) {
this.blockSize = blockSize;
this.maxFileNameLength = maxFileLength;
this.maxPathLength = maxPathLength;
@@ -345,10 +453,12 @@ private static String replace(final String path, final char oldChar, final char
this.supportsDriveLetter = supportsDriveLetter;
this.nameSeparator = nameSeparator;
this.nameSeparatorOther = FilenameUtils.flipSeparator(nameSeparator);
+ this.nameLengthStrategy = nameLengthStrategy;
}
/**
* Gets the file allocation block size in bytes.
+ *
* @return the file allocation block size in bytes.
* @since 2.12.0
*/
@@ -380,23 +490,66 @@ public int[] getIllegalFileNameCodePoints() {
}
/**
- * Gets the maximum length for file names. The file name does not include folders.
+ * Gets the maximum length for file names (excluding any folder path).
+ *
+ *
+ * This limit applies only to the file name itself, excluding any parent directories.
+ *
+ *
+ *
+ * The value is expressed in Java {@code char} units (UTF-16 code units).
+ *
+ *
+ *
+ * Note: Because many file systems enforce limits in bytes using a specific encoding rather than in UTF-16 code units, a name that
+ * fits this limit may still be rejected by the underlying file system.
+ *
+ *
+ *
+ * Use {@link #isLegalFileName} to check whether a given name is valid for the current file system and charset.
+ *
*
- * @return the maximum length for file names.
+ *
+ * However, any file name longer than this limit is guaranteed to be invalid on the current file system.
+ *
+ *
+ * @return the maximum file name length in characters.
*/
public int getMaxFileNameLength() {
return maxFileNameLength;
}
/**
- * Gets the maximum length of the path to a file. This can include folders.
+ * Gets the maximum length for file paths (may include folders).
+ *
+ *
+ * This value is inclusive of all path components and separators. For a limit of each path component see {@link #getMaxFileNameLength()}.
+ *
+ *
+ *
+ * The value is expressed in Java {@code char} units (UTF-16 code units) and represents the longest path that can be safely passed to Java
+ * {@link java.io.File} and {@link java.nio.file.Path} APIs.
+ *
+ *
+ *
+ * Note: many operating systems and file systems enforce path length limits in bytes using a specific encoding, rather than in
+ * UTF-16 code units. As a result, a path that fits within this limit may still be rejected by the underlying platform.
+ *
+ *
+ *
+ * Conversely, any path longer than this limit is guaranteed to fail with at least some operating system API calls.
+ *
*
- * @return the maximum length of the path to a file.
+ * @return the maximum file path length in characters.
*/
public int getMaxPathLength() {
return maxPathLength;
}
+ NameLengthStrategy getNameLengthStrategy() {
+ return nameLengthStrategy;
+ }
+
/**
* Gets the name separator, '\\' on Windows, '/' on Linux.
*
@@ -438,7 +591,7 @@ public boolean isCaseSensitive() {
* Tests if the given character is illegal in a file name, {@code false} otherwise.
*
* @param c
- * the character to test
+ * the character to test.
* @return {@code true} if the given character is illegal in a file name, {@code false} otherwise.
*/
private boolean isIllegalFileNameChar(final int c) {
@@ -446,29 +599,53 @@ private boolean isIllegalFileNameChar(final int c) {
}
/**
- * Tests if a candidate file name (without a path) such as {@code "filename.ext"} or {@code "filename"} is a
- * potentially legal file name. If the file name length exceeds {@link #getMaxFileNameLength()}, or if it contains
- * an illegal character then the check fails.
+ * Tests if a candidate file name (without a path) is a legal file name.
+ *
+ *
Takes a file name like {@code "filename.ext"} or {@code "filename"} and checks:
+ *
+ *
if the file name length is legal
+ *
if the file name is not a reserved file name
+ *
if the file name does not contain illegal characters
+ *
*
* @param candidate
- * a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}
- * @return {@code true} if the candidate name is legal
+ * A candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}.
+ * @return {@code true} if the candidate name is legal.
*/
public boolean isLegalFileName(final CharSequence candidate) {
- if (candidate == null || candidate.length() == 0 || candidate.length() > maxFileNameLength) {
- return false;
- }
- if (isReservedFileName(candidate)) {
- return false;
- }
- return candidate.chars().noneMatch(this::isIllegalFileNameChar);
+ return isLegalFileName(candidate, Charset.defaultCharset());
+ }
+
+ /**
+ * Tests if a candidate file name (without a path) is a legal file name.
+ *
+ *
Takes a file name like {@code "filename.ext"} or {@code "filename"} and checks:
+ *
+ *
if the file name length is legal
+ *
if the file name is not a reserved file name
+ *
if the file name does not contain illegal characters
+ *
+ *
+ * @param candidate
+ * A candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}.
+ * @param charset
+ * The charset to use when the file name length is measured in bytes.
+ * @return {@code true} if the candidate name is legal.
+ * @since 2.21.0
+ */
+ public boolean isLegalFileName(final CharSequence candidate, final Charset charset) {
+ return candidate != null
+ && candidate.length() != 0
+ && nameLengthStrategy.isWithinLimit(candidate, getMaxFileNameLength(), charset)
+ && !isReservedFileName(candidate)
+ && candidate.chars().noneMatch(this::isIllegalFileNameChar);
}
/**
* Tests whether the given string is a reserved file name.
*
* @param candidate
- * the string to test
+ * the string to test.
* @return {@code true} if the given string is a reserved file name.
*/
public boolean isReservedFileName(final CharSequence candidate) {
@@ -479,8 +656,8 @@ public boolean isReservedFileName(final CharSequence candidate) {
/**
* Converts all separators to the Windows separator of backslash.
*
- * @param path the path to be changed, null ignored
- * @return the updated path
+ * @param path the path to be changed, null ignored.
+ * @return the updated path.
* @since 2.12.0
*/
public String normalizeSeparators(final String path) {
@@ -504,30 +681,56 @@ public boolean supportsDriveLetter() {
}
/**
- * Converts a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} to a legal file
- * name. Illegal characters in the candidate name are replaced by the {@code replacement} character. If the file
- * name length exceeds {@link #getMaxFileNameLength()}, then the name is truncated to
- * {@link #getMaxFileNameLength()}.
+ * Converts a candidate file name (without a path) to a legal file name.
+ *
+ *
Takes a file name like {@code "filename.ext"} or {@code "filename"} and:
+ *
+ *
replaces illegal characters by the given replacement character
+ *
truncates the name to {@link #getMaxFileNameLength()} if necessary
+ *
*
* @param candidate
- * a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}
+ * A candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}.
* @param replacement
- * Illegal characters in the candidate name are replaced by this character
- * @return a String without illegal characters
- */
- public String toLegalFileName(final String candidate, final char replacement) {
+ * Illegal characters in the candidate name are replaced by this character.
+ * @param charset
+ * The charset to use when the file name length is measured in bytes.
+ * @return a String without illegal characters.
+ * @since 2.21.0
+ */
+ public String toLegalFileName(final CharSequence candidate, final char replacement, final Charset charset) {
+ Objects.requireNonNull(candidate, "candidate");
+ if (candidate.length() == 0) {
+ throw new IllegalArgumentException("The candidate file name is empty");
+ }
if (isIllegalFileNameChar(replacement)) {
// %s does not work properly with NUL
throw new IllegalArgumentException(String.format("The replacement character '%s' cannot be one of the %s illegal characters: %s",
replacement == '\0' ? "\\0" : replacement, name(), Arrays.toString(illegalFileNameChars)));
}
- final String truncated = candidate.length() > maxFileNameLength ? candidate.substring(0, maxFileNameLength) : candidate;
+ final CharSequence truncated = nameLengthStrategy.truncate(candidate, getMaxFileNameLength(), charset);
final int[] array = truncated.chars().map(i -> isIllegalFileNameChar(i) ? replacement : i).toArray();
return new String(array, 0, array.length);
}
- CharSequence trimExtension(final CharSequence cs) {
- final int index = indexOf(cs, '.', 0);
- return index < 0 ? cs : cs.subSequence(0, index);
+
+ /**
+ * Converts a candidate file name (without a path) to a legal file name.
+ *
+ *
Takes a file name like {@code "filename.ext"} or {@code "filename"} and:
+ *
+ *
replaces illegal characters by the given replacement character
+ *
truncates the name to {@link #getMaxFileNameLength()} if necessary
+ *
+ *
+ * @param candidate
+ * A candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}.
+ * @param replacement
+ * Illegal characters in the candidate name are replaced by this character.
+ * @return a String without illegal characters.
+ */
+ public String toLegalFileName(final String candidate, final char replacement) {
+ return toLegalFileName(candidate, replacement, Charset.defaultCharset());
}
+
}
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java
index ffd59f92ab5..523c2c1fd26 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -194,13 +194,27 @@ public class FileUtils {
/**
* The number of bytes in a zettabyte.
*/
- public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB));
+ public static final BigInteger ONE_ZB = ONE_KB_BI.multiply(ONE_EB_BI);
/**
* The number of bytes in a yottabyte.
*/
public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB);
+ /**
+ * The number of bytes in a ronnabyte.
+ *
+ * @since 2.21.0
+ */
+ public static final BigInteger ONE_RB = ONE_KB_BI.multiply(ONE_YB);
+
+ /**
+ * The number of bytes in a quettabyte.
+ *
+ * @since 2.21.0
+ */
+ public static final BigInteger ONE_QB = ONE_KB_BI.multiply(ONE_RB);
+
/**
* An empty array of type {@link File}.
*/
@@ -217,7 +231,7 @@ public class FileUtils {
*
*
* @param size the number of bytes
- * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or 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
@@ -226,8 +240,15 @@ public class FileUtils {
public static String byteCountToDisplaySize(final BigInteger size) {
Objects.requireNonNull(size, "size");
final String displaySize;
-
- if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
+ if (size.divide(ONE_QB).compareTo(BigInteger.ZERO) > 0) {
+ displaySize = size.divide(ONE_QB) + " QB";
+ } else if (size.divide(ONE_RB).compareTo(BigInteger.ZERO) > 0) {
+ displaySize = size.divide(ONE_RB) + " RB";
+ } else if (size.divide(ONE_YB).compareTo(BigInteger.ZERO) > 0) {
+ displaySize = size.divide(ONE_YB) + " YB";
+ } else if (size.divide(ONE_ZB).compareTo(BigInteger.ZERO) > 0) {
+ displaySize = size.divide(ONE_ZB) + " ZB";
+ } else if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
displaySize = size.divide(ONE_EB_BI) + " EB";
} else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) {
displaySize = size.divide(ONE_PB_BI) + " PB";
@@ -2082,12 +2103,12 @@ public static boolean isSymlink(final File file) {
* All files found are filtered by an IOFileFilter.
*
*
- * @param directory the directory to search in
+ * @param directory The directory to search.
* @param fileFilter filter to apply when finding files.
* @param dirFilter optional filter to apply when finding subdirectories.
* If this parameter is {@code null}, subdirectories will not be included in the
* search. Use TrueFileFilter.INSTANCE to match all directories.
- * @return an iterator of {@link File} for the matching files
+ * @return an iterator of {@link File} for the matching files.
* @see org.apache.commons.io.filefilter.FileFilterUtils
* @see org.apache.commons.io.filefilter.NameFileFilter
* @since 1.2
@@ -2103,11 +2124,11 @@ public static Iterator iterateFiles(final File directory, final IOFileFilt
* The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
*
*
- * @param directory the directory to search in
- * @param extensions an array of extensions, for example, {"java","xml"}. If this
+ * @param directory The directory to search.
+ * @param extensions an array of extensions, for example, {"java", "xml"}. If this
* parameter is {@code null}, all files are returned.
- * @param recursive if true all subdirectories are searched as well
- * @return an iterator of {@link File} with the matching files
+ * @param recursive if true all subdirectories are searched as well.
+ * @return an iterator of {@link File} with the matching files.
* @since 1.2
*/
public static Iterator iterateFiles(final File directory, final String[] extensions, final boolean recursive) {
@@ -2127,12 +2148,12 @@ public static Iterator iterateFiles(final File directory, final String[] e
* The resulting iterator includes the subdirectories themselves.
*
*
- * @param directory the directory to search in
+ * @param directory The directory to search.
* @param fileFilter filter to apply when finding files.
* @param dirFilter optional filter to apply when finding subdirectories.
* If this parameter is {@code null}, subdirectories will not be included in the
* search. Use TrueFileFilter.INSTANCE to match all directories.
- * @return an iterator of {@link File} for the matching files
+ * @return an iterator of {@link File} for the matching files.
* @see org.apache.commons.io.filefilter.FileFilterUtils
* @see org.apache.commons.io.filefilter.NameFileFilter
* @since 2.2
@@ -2213,8 +2234,8 @@ public static long lastModifiedUnchecked(final File file) {
/**
* Returns an Iterator for the lines in a {@link File} using the default encoding for the VM.
*
- * @param file the file to open for input, must not be {@code null}
- * @return an Iterator of the lines in the file, never {@code null}
+ * @param file the file to open for input, must not be {@code null}.
+ * @return an Iterator of the lines in the file, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
* other reason cannot be opened for reading.
@@ -2242,7 +2263,7 @@ public static LineIterator lineIterator(final File file) throws IOException {
* try {
* while (it.hasNext()) {
* String line = it.nextLine();
- * /// do something with line
+ * // do something with line
* }
* } finally {
* LineIterator.closeQuietly(iterator);
@@ -2253,8 +2274,8 @@ public static LineIterator lineIterator(final File file) throws IOException {
* underlying stream is closed.
*
*
- * @param file the file to open for input, must not be {@code null}
- * @param charsetName the name of the requested charset, {@code null} means platform default
+ * @param file the file to open for input, must not be {@code null}.
+ * @param charsetName the name of the requested charset, {@code null} means platform default.
* @return a LineIterator for lines in the file, never {@code null}; MUST be closed by the caller.
* @throws NullPointerException if file is {@code null}.
* @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
@@ -2328,7 +2349,7 @@ private static File[] listFiles(final File directory, final FileFilter fileFilte
*
*
* An example: If you want to search through all directories called
- * "temp" you pass in {@code FileFilterUtils.NameFileFilter("temp")}
+ * "temp" you pass in {@code FileFilterUtils.NameFileFilter("temp")}.
*
*
* Another common usage of this method is find files in a directory
@@ -2336,13 +2357,13 @@ private static File[] listFiles(final File directory, final FileFilter fileFilte
* in {@code FileFilterUtils.makeCVSAware(null)}.
*
*
- * @param directory the directory to search in
+ * @param directory The directory to search.
* @param fileFilter filter to apply when finding files. Must not be {@code null},
* use {@link TrueFileFilter#INSTANCE} to match all files in selected directories.
* @param dirFilter optional filter to apply when finding subdirectories.
* If this parameter is {@code null}, subdirectories will not be included in the
* search. Use {@link TrueFileFilter#INSTANCE} to match all directories.
- * @return a collection of {@link File} with the matching files
+ * @return a collection of {@link File} with the matching files.
* @see org.apache.commons.io.filefilter.FileFilterUtils
* @see org.apache.commons.io.filefilter.NameFileFilter
*/
@@ -2359,9 +2380,10 @@ public static Collection listFiles(final File directory, final IOFileFilte
* @param files The list to add found Files, not null.
* @param recursive Whether or not to recurse into subdirectories.
* @param filter How to filter files, not null.
+ * @return The given list.
*/
@SuppressWarnings("null")
- private static void listFiles(final File directory, final List files, final boolean recursive, final FilenameFilter filter) {
+ private static List listFiles(final File directory, final List files, final boolean recursive, final FilenameFilter filter) {
final File[] listFiles = directory.listFiles();
if (listFiles != null) {
// Only allocate if you must.
@@ -2377,24 +2399,21 @@ private static void listFiles(final File directory, final List files, fina
dirs.forEach(d -> listFiles(d, files, true, filter));
}
}
+ return files;
}
/**
* Lists files within a given directory (and optionally its subdirectories)
* which match an array of extensions.
*
- * @param directory the directory to search in
- * @param extensions an array of extensions, for example, {"java","xml"}. If this
+ * @param directory The directory to search.
+ * @param extensions an array of extensions, for example, {"java", "xml"}. If this
* parameter is {@code null}, all files are returned.
- * @param recursive if true all subdirectories are searched as well
- * @return a collection of {@link File} with the matching files
+ * @param recursive if true all subdirectories are searched as well.
+ * @return a collection of {@link File} with the matching files.
*/
public static Collection listFiles(final File directory, final String[] extensions, final boolean recursive) {
- // IO-856: Don't use NIO to path walk, allocate as little as possible while traversing.
- final List files = new ArrayList<>();
- final FilenameFilter filter = extensions != null ? toSuffixFileFilter(extensions) : TrueFileFilter.INSTANCE;
- listFiles(directory, files, recursive, filter);
- return files;
+ return listFiles(directory, new ArrayList<>(), recursive, extensions != null ? toSuffixFileFilter(extensions) : TrueFileFilter.INSTANCE);
}
/**
@@ -2405,12 +2424,12 @@ public static Collection listFiles(final File directory, final String[] ex
* any subdirectories that match the directory filter.
*
*
- * @param directory the directory to search in
+ * @param directory The directory to search.
* @param fileFilter filter to apply when finding files.
* @param dirFilter optional filter to apply when finding subdirectories.
* If this parameter is {@code null}, subdirectories will not be included in the
* search. Use TrueFileFilter.INSTANCE to match all directories.
- * @return a collection of {@link File} with the matching files
+ * @return a collection of {@link File} with the matching files.
* @see org.apache.commons.io.FileUtils#listFiles
* @see org.apache.commons.io.filefilter.FileFilterUtils
* @see org.apache.commons.io.filefilter.NameFileFilter
@@ -2454,7 +2473,7 @@ private static File mkdirs(final File directory) throws IOException {
* @param srcDir the directory to be moved.
* @param destDir the destination directory.
* @throws NullPointerException if any of the given {@link File}s are {@code null}.
- * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory
+ * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory.
* @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
@@ -2522,7 +2541,7 @@ public static void moveDirectoryToDirectory(final File source, final File destDi
* @throws NullPointerException if any of the given {@link File}s are {@code null}.
* @throws FileExistsException if the destination file exists.
* @throws FileNotFoundException if the source file does not exist.
- * @throws IllegalArgumentException if {@code srcFile} is a directory
+ * @throws IllegalArgumentException if {@code srcFile} is a directory.
* @throws IOException if an error occurs.
* @since 1.4
*/
@@ -2542,7 +2561,7 @@ public static void moveFile(final File srcFile, final File destFile) throws IOEx
* @throws NullPointerException if any of the given {@link File}s are {@code null}.
* @throws FileExistsException if the destination file exists.
* @throws FileNotFoundException if the source file does not exist.
- * @throws IllegalArgumentException if {@code srcFile} is a directory
+ * @throws IllegalArgumentException if {@code srcFile} is a directory.
* @throws IOException if an error occurs or setting the last-modified time didn't succeed.
* @since 2.9.0
*/
@@ -2568,7 +2587,7 @@ public static void moveFile(final File srcFile, final File destFile, final CopyO
*
*
* @param srcFile the file to be moved.
- * @param destDir the directory to move the file into
+ * @param destDir the directory to move the file into.
* @param createDestDir if {@code true} create the destination directory. If {@code false} throw an
* IOException if the destination directory does not already exist.
* @throws NullPointerException if any of the given {@link File}s are {@code null}.
@@ -2578,7 +2597,7 @@ public static void moveFile(final File srcFile, final File destFile, final CopyO
* @throws IOException if the directory was not created along with all its parent directories, if enabled.
* @throws IOException if an error occurs or setting the last-modified time didn't succeed.
* @throws SecurityException See {@link File#mkdirs()}.
- * @throws IllegalArgumentException if {@code destDir} exists but is not a directory
+ * @throws IllegalArgumentException if {@code destDir} exists but is not a directory.
* @since 1.4
*/
public static void moveFileToDirectory(final File srcFile, final File destDir, final boolean createDestDir) throws IOException {
@@ -2645,8 +2664,8 @@ public static OutputStream newOutputStream(final File file, final boolean append
* directory. An exception is thrown if the file exists but cannot be read.
*
*
- * @param file the file to open for input, must not be {@code null}
- * @return a new {@link FileInputStream} for the specified file
+ * @param file the file to open for input, must not be {@code null}.
+ * @return a new {@link FileInputStream} for the specified file.
* @throws NullPointerException if file is {@code null}.
* @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
* other reason cannot be opened for reading.
@@ -2673,7 +2692,7 @@ public static FileInputStream openInputStream(final File file) throws IOExceptio
* An exception is thrown if the parent directory cannot be created.
*
*
- * @param file the file to open for output, must not be {@code null}
+ * @param file the file to open for output, must not be {@code null}.
* @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
@@ -2700,13 +2719,13 @@ public static FileOutputStream openOutputStream(final File file) throws IOExcept
* An exception is thrown if the parent directory cannot be created.
*
*
- * @param file the file to open for output, must not be {@code null}
+ * @param file the file to open for output, must not be {@code null}.
* @param append if {@code true}, then bytes will be added to the
- * end of the file rather than overwriting
- * @return a new {@link FileOutputStream} for the specified file
+ * end of the file rather than overwriting.
+ * @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 IOException if the directories could not be created, or the file is not writable
+ * @throws IllegalArgumentException if the file object is a directory.
+ * @throws IOException if the directories could not be created, or the file is not writable.
* @since 2.1
*/
public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
@@ -2723,8 +2742,8 @@ public static FileOutputStream openOutputStream(final File file, final boolean a
* Reads the contents of a file into a byte array.
* The file is always closed.
*
- * @param file the file to read, must not be {@code null}
- * @return the file contents, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @return the file contents, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
* regular file, or for some other reason why the file cannot be opened for reading.
@@ -2736,16 +2755,16 @@ 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}
- * @return the file contents, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @return the file contents, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a regular file, or for some other
* reason why the file cannot be opened for reading.
* @since 1.3.1
- * @deprecated Use {@link #readFileToString(File, Charset)} instead (and specify the appropriate encoding)
+ * @deprecated Use {@link #readFileToString(File, Charset)} instead (and specify the appropriate encoding).
*/
@Deprecated
public static String readFileToString(final File file) throws IOException {
@@ -2756,9 +2775,9 @@ public static String readFileToString(final File file) throws IOException {
* Reads the contents of a file into a String.
* The file is always closed.
*
- * @param file the file to read, must not be {@code null}
- * @param charsetName the name of the requested charset, {@code null} means platform default
- * @return the file contents, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @param charsetName the name of the requested charset, {@code null} means platform default.
+ * @return the file contents, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
* regular file, or for some other reason why the file cannot be opened for reading.
@@ -2771,9 +2790,9 @@ public static String readFileToString(final File file, final Charset charsetName
/**
* Reads the contents of a file into a String. The file is always closed.
*
- * @param file the file to read, must not be {@code null}
- * @param charsetName the name of the requested charset, {@code null} means platform default
- * @return the file contents, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @param charsetName the name of the requested charset, {@code null} means platform default.
+ * @return the file contents, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
* regular file, or for some other reason why the file cannot be opened for reading.
@@ -2785,16 +2804,16 @@ 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}
- * @return the list of Strings representing each line in the file, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @return the list of Strings representing each line in the file, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a regular file, or for some other
* reason why the file cannot be opened for reading.
* @since 1.3
- * @deprecated Use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding)
+ * @deprecated Use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding).
*/
@Deprecated
public static List readLines(final File file) throws IOException {
@@ -2805,9 +2824,9 @@ public static List readLines(final File file) throws IOException {
* Reads the contents of a file line by line to a List of Strings.
* The file is always closed.
*
- * @param file the file to read, must not be {@code null}
- * @param charset the charset to use, {@code null} means platform default
- * @return the list of Strings representing each line in the file, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @param charset the charset to use, {@code null} means platform default.
+ * @return the list of Strings representing each line in the file, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
* regular file, or for some other reason why the file cannot be opened for reading.
@@ -2820,9 +2839,9 @@ public static List readLines(final File file, final Charset charset) thr
/**
* Reads the contents of a file line by line to a List of Strings. The file is always closed.
*
- * @param file the file to read, must not be {@code null}
- * @param charsetName the name of the requested charset, {@code null} means platform default
- * @return the list of Strings representing each line in the file, never {@code null}
+ * @param file the file to read, must not be {@code null}.
+ * @param charsetName the name of the requested charset, {@code null} means platform default.
+ * @return the list of Strings representing each line in the file, never {@code null}.
* @throws NullPointerException if file is {@code null}.
* @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
* regular file, or for some other reason why the file cannot be opened for reading.
@@ -2861,7 +2880,7 @@ private static void requireCanonicalPathsNotEquals(final File file1, final File
* @param directory The {@link File} to check.
* @param name The parameter name to use in the exception message in case of null input or if the file is not a directory.
* @throws NullPointerException if the given {@link File} is {@code null}.
- * @throws FileNotFoundException if the given {@link File} does not exist
+ * @throws FileNotFoundException if the given {@link File} does not exist.
* @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
*/
private static void requireDirectoryExists(final File directory, final String name) throws FileNotFoundException {
@@ -2894,8 +2913,7 @@ private static void requireDirectoryIfExists(final File directory, final String
*
* @param sourceFile The source file to query.
* @param targetFile The target file or directory to set.
- * @return {@code true} if and only if the operation succeeded;
- * {@code false} otherwise
+ * @return {@code true} if and only if the operation succeeded; {@code false} otherwise.
* @throws NullPointerException if sourceFile is {@code null}.
* @throws NullPointerException if targetFile is {@code null}.
*/
@@ -2977,7 +2995,7 @@ public static BigInteger sizeOfAsBigInteger(final File file) {
* @param directory directory to inspect, must not be {@code null}.
* @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total
* is greater than {@link Long#MAX_VALUE}.
- * @throws IllegalArgumentException if the given {@link File} exists but is not a directory
+ * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
* @throws NullPointerException if the directory is {@code null}.
* @throws UncheckedIOException if an IO error occurs.
*/
@@ -2995,7 +3013,7 @@ public static long sizeOfDirectory(final File directory) {
*
* @param directory directory to inspect, must not be {@code null}.
* @return size of directory in bytes, 0 if directory is security restricted.
- * @throws IllegalArgumentException if the given {@link File} exists but is not a directory
+ * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
* @throws NullPointerException if the directory is {@code null}.
* @throws UncheckedIOException if an IO error occurs.
* @since 2.4
@@ -3017,9 +3035,9 @@ public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) {
* closed stream causes a {@link IllegalStateException}.
*
*
- * @param directory the directory to search in
- * @param recursive if true all subdirectories are searched as well
- * @param extensions an array of extensions, for example, {"java","xml"}. If this parameter is {@code null}, all files are returned.
+ * @param directory The directory to search.
+ * @param recursive if true all subdirectories are searched as well.
+ * @param extensions an array of extensions, for example, {"java", "xml"}. If this parameter is {@code null}, all files are returned.
* @return a Stream of {@link File} for matching files.
* @throws IOException if an I/O error is thrown when accessing the starting file.
* @since 2.9.0
@@ -3036,16 +3054,12 @@ public static Stream streamFiles(final File directory, final boolean recur
/**
* Converts from a {@link URL} to a {@link File}.
*
- * Syntax such as {@code file:///my%20docs/file.txt} will be
- * correctly decoded to {@code /my docs/file.txt}.
- * UTF-8 is used to decode percent-encoded octets to characters.
- * Additionally, malformed percent-encoded octets are handled leniently by
- * passing them through literally.
+ * Syntax such as {@code file:///my%20docs/file.txt} will be correctly decoded to {@code /my docs/file.txt}. UTF-8 is used to decode percent-encoded octets
+ * to characters. Additionally, malformed percent-encoded octets are handled leniently by passing them through literally.
*
*
- * @param url the file URL to convert, {@code null} returns {@code null}
- * @return the equivalent {@link File} object, or {@code null}
- * if the URL's protocol is not {@code file}
+ * @param url the file URL to convert, {@code null} returns {@code null}.
+ * @return the equivalent {@link File} object, or {@code null} if the URL's protocol is not {@code file}.
*/
public static File toFile(final URL url) {
if (url == null || !isFileProtocol(url)) {
@@ -3058,22 +3072,17 @@ public static File toFile(final URL url) {
/**
* Converts each of an array of {@link URL} to a {@link File}.
*
- * Returns an array of the same size as the input.
- * If the input is {@code null}, an empty array is returned.
- * If the input contains {@code null}, the output array contains {@code null} at the same
- * index.
+ * Returns an array of the same size as the input. If the input is {@code null}, an empty array is returned. If the input contains {@code null}, the output
+ * array contains {@code null} at the same index.
*
*
- * This method will decode the URL.
- * Syntax such as {@code file:///my%20docs/file.txt} will be
- * correctly decoded to {@code /my docs/file.txt}.
+ * This method will decode the URL. Syntax such as {@code file:///my%20docs/file.txt} will be correctly decoded to {@code /my docs/file.txt}.
*
*
- * @param urls the file URLs to convert, {@code null} returns empty array
- * @return a non-{@code null} array of Files matching the input, with a {@code null} item
- * if there was a {@code null} at that index in the input array
- * @throws IllegalArgumentException if any file is not a URL file
- * @throws IllegalArgumentException if any file is incorrectly encoded
+ * @param urls the file URLs to convert, {@code null} returns empty array.
+ * @return a non-{@code null} array of Files matching the input, with a {@code null} item if there was a {@code null} at that index in the input array.
+ * @throws IllegalArgumentException if any file is not a URL file.
+ * @throws IllegalArgumentException if any file is incorrectly encoded.
* @since 1.1
*/
public static File[] toFiles(final URL... urls) {
@@ -3109,8 +3118,8 @@ private static List toList(final Stream stream) {
/**
* Converts whether or not to recurse into a recursion max depth.
*
- * @param recursive whether or not to recurse
- * @return the recursion depth
+ * @param recursive whether or not to recurse.
+ * @return the recursion depth.
*/
private static int toMaxDepth(final boolean recursive) {
return recursive ? Integer.MAX_VALUE : 1;
@@ -3119,9 +3128,9 @@ private static int toMaxDepth(final boolean recursive) {
/**
* Converts an array of file extensions to suffixes.
*
- * @param extensions an array of extensions. Format: {"java", "xml"}
- * @return an array of suffixes. Format: {".java", ".xml"}
- * @throws NullPointerException if the parameter is null
+ * @param extensions an array of extensions, for example: {@code ["java", "xml"]}.
+ * @return an array of suffixes, for example: {@code [".java", ".xml"]}.
+ * @throws NullPointerException if the parameter is null.
*/
private static String[] toSuffixes(final String... extensions) {
return Stream.of(Objects.requireNonNull(extensions, "extensions")).map(s -> s.charAt(0) == '.' ? s : "." + s).toArray(String[]::new);
@@ -3150,10 +3159,10 @@ public static void touch(final File file) throws IOException {
* Returns an array of the same size as the input.
*
*
- * @param files the files to convert, must not be {@code null}
- * @return an array of URLs matching the input
- * @throws IOException if a file cannot be converted
- * @throws NullPointerException if any argument is null
+ * @param files the files to convert, must not be {@code null}.
+ * @return an array of URLs matching the input.
+ * @throws IOException if a file cannot be converted.
+ * @throws NullPointerException if any argument is null.
*/
public static URL[] toURLs(final File... files) throws IOException {
Objects.requireNonNull(files, "files");
@@ -3192,10 +3201,10 @@ private static void validateMoveParameters(final File source, final File destina
* true up to the maximum time specified in seconds.
*
*
- * @param file the file to check, must not be {@code null}
- * @param seconds the maximum time in seconds to wait
- * @return true if file exists
- * @throws NullPointerException if the file is {@code null}
+ * @param file the file to check, must not be {@code null}.
+ * @param seconds the maximum time in seconds to wait.
+ * @return true if file exists.
+ * @throws NullPointerException if the file is {@code null}.
*/
public static boolean waitFor(final File file, final int seconds) {
Objects.requireNonNull(file, PROTOCOL_FILE);
@@ -3203,13 +3212,13 @@ 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
- * @throws IOException in case of an I/O error
+ * @param file the file to write.
+ * @param data the content to write to the file.
+ * @throws IOException in case of an I/O error.
* @since 2.0
- * @deprecated Use {@link #write(File, CharSequence, Charset)} instead (and specify the appropriate encoding)
+ * @deprecated Use {@link #write(File, CharSequence, Charset)} instead (and specify the appropriate encoding).
*/
@Deprecated
public static void write(final File file, final CharSequence data) throws IOException {
@@ -3217,14 +3226,14 @@ 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
- * @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
+ * @param file the file to write.
+ * @param data the content to write to the file.
+ * @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.
* @since 2.1
- * @deprecated Use {@link #write(File, CharSequence, Charset, boolean)} instead (and specify the appropriate encoding)
+ * @deprecated Use {@link #write(File, CharSequence, Charset, boolean)} instead (and specify the appropriate encoding).
*/
@Deprecated
public static void write(final File file, final CharSequence data, final boolean append) throws IOException {
@@ -3234,10 +3243,10 @@ public static void write(final File file, final CharSequence data, final boolean
/**
* 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 charset the name of the requested charset, {@code null} means platform default
- * @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 charset the name of the requested charset, {@code null} means platform default.
+ * @throws IOException in case of an I/O error.
* @since 2.3
*/
public static void write(final File file, final CharSequence data, final Charset charset) throws IOException {
@@ -3247,12 +3256,12 @@ 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 charset the charset to use, {@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
+ * @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 data will be added to the.
+ * end of the file rather than overwriting.
+ * @throws IOException in case of an I/O error.
* @since 2.3
*/
public static void write(final File file, final CharSequence data, final Charset charset, final boolean append) throws IOException {
@@ -3492,7 +3501,7 @@ 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
@@ -3505,7 +3514,7 @@ 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
diff --git a/src/main/java/org/apache/commons/io/HexDump.java b/src/main/java/org/apache/commons/io/HexDump.java
index cb7efa7b2f2..41c026b2fb1 100644
--- a/src/main/java/org/apache/commons/io/HexDump.java
+++ b/src/main/java/org/apache/commons/io/HexDump.java
@@ -169,7 +169,7 @@ 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
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index aa3752dc4f1..09a018b036d 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -65,6 +65,7 @@
import org.apache.commons.io.function.IOConsumer;
import org.apache.commons.io.function.IOSupplier;
import org.apache.commons.io.function.IOTriFunction;
+import org.apache.commons.io.input.BoundedInputStream;
import org.apache.commons.io.input.CharSequenceReader;
import org.apache.commons.io.input.QueueInputStream;
import org.apache.commons.io.output.AppendableWriter;
@@ -72,7 +73,6 @@
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.io.output.StringBuilderWriter;
-import org.apache.commons.io.output.ThresholdingOutputStream;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
/**
@@ -132,6 +132,144 @@ 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.