Reduce internal boilerplate
Make the internal scratch byte and char buffers auto-closeable
diff --git a/src/main/java/org/apache/commons/io/CopyUtils.java b/src/main/java/org/apache/commons/io/CopyUtils.java
index fdb137d..b3243a3 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},
@@ -278,8 +280,8 @@ public static int copy(
final Reader input,
final Writer output)
throws IOException {
- final char[] buffer = IOUtils.ScratchBufferHolder.getScratchCharArray();
- try {
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ final char[] buffer = scratch.array();
int count = 0;
int n;
while (EOF != (n = input.read(buffer))) {
@@ -287,8 +289,6 @@ public static int copy(
count += n;
}
return count;
- } finally {
- IOUtils.ScratchBufferHolder.releaseScratchCharArray(buffer);
}
}
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index fb6bc6c..5d3d7cc 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -133,7 +133,7 @@ public class IOUtils {
// or return one of them.
/**
- * Holder for per-thread internal scratch buffers.
+ * Holder for per-thread internal scratch buffer.
*
* <p>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.</p>
@@ -141,80 +141,120 @@ public class IOUtils {
* <p>Typical usage:</p>
*
* <pre>{@code
- * final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
- * try {
+ * try (ScratchBytes scratch = ScratchBytes.get()) {
* // use the buffer
- * } finally {
- * ScratchBufferHolder.releaseScratchByteArray(buffer);
+ * byte[] bytes = scratch.array();
+ * // ...
* }
* }</pre>
*/
- static final class ScratchBufferHolder {
+ static final class ScratchBytes implements AutoCloseable {
/**
- * Holder for internal byte array buffer.
+ * Wraps an internal byte array.
+ *
+ * [0] boolean in use.
+ * [1] byte[] buffer.
*/
- private static final ThreadLocal<Object[]> SCRATCH_BYTE_BUFFER_HOLDER = ThreadLocal.withInitial(() -> new Object[] { false, byteArray() });
-
- /**
- * Holder for internal char array buffer.
- */
- private static final ThreadLocal<Object[]> SCRATCH_CHAR_BUFFER_HOLDER = ThreadLocal.withInitial(() -> new Object[] { false, charArray() });
-
+ private static final ThreadLocal<Object[]> LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, new ScratchBytes(byteArray()) });
/**
* Gets the internal byte array buffer.
*
* @return the internal byte array buffer.
*/
- static byte[] getScratchByteArray() {
- final Object[] holder = SCRATCH_BYTE_BUFFER_HOLDER.get();
+ static ScratchBytes get() {
+ final Object[] holder = LOCAL.get();
// If already in use, return a new array
if ((boolean) holder[0]) {
- return byteArray();
+ return new ScratchBytes(byteArray());
}
holder[0] = true;
- return (byte[]) holder[1];
+ return (ScratchBytes) holder[1];
+ }
+
+ private final byte[] buffer;
+
+ private ScratchBytes(final byte[] buffer) {
+ this.buffer = buffer;
+ }
+
+ byte[] array() {
+ return buffer;
}
/**
- * Gets the char array buffer.
- *
- * @return the char array buffer.
+ * If the buffer is the internal array, clear and release it for reuse.
*/
- static char[] getScratchCharArray() {
- final Object[] holder = SCRATCH_CHAR_BUFFER_HOLDER.get();
- // If already in use, return a new array
- if ((boolean) holder[0]) {
- return charArray();
- }
- holder[0] = true;
- return (char[]) holder[1];
- }
-
-
- /**
- * If the argument is the internal byte array, release it for reuse.
- *
- * @param array the byte array to release.
- */
- static void releaseScratchByteArray(final byte[] array) {
- final Object[] holder = SCRATCH_BYTE_BUFFER_HOLDER.get();
- if (array == holder[1]) {
- Arrays.fill(array, (byte) 0);
+ @Override
+ public void close() {
+ final Object[] holder = LOCAL.get();
+ if (buffer == ((ScratchBytes) holder[1]).buffer) {
+ Arrays.fill(buffer, (byte) 0);
holder[0] = false;
}
}
+ }
+
+ /**
+ * Holder for per-thread internal scratch buffer.
+ *
+ * <p>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.</p>
+ *
+ * <p>Typical usage:</p>
+ *
+ * <pre>{@code
+ * try (ScratchChars scratch = ScratchChars.get()) {
+ * // use the buffer
+ * char[] bytes = scratch.array();
+ * // ...
+ * }
+ * }</pre>
+ */
+ static final class ScratchChars implements AutoCloseable {
/**
- * If the argument is the internal char array, release it for reuse.
+ * Wraps an internal char array.
*
- * @param array the char array to release.
+ * [0] boolean in use.
+ * [1] char[] buffer.
*/
- static void releaseScratchCharArray(final char[] array) {
- final Object[] holder = SCRATCH_CHAR_BUFFER_HOLDER.get();
- if (array == holder[1]) {
- Arrays.fill(array, (char) 0);
+ private static final ThreadLocal<Object[]> LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, new ScratchChars(charArray()) });
+
+ /**
+ * Gets the internal char array buffer.
+ *
+ * @return the internal char array buffer.
+ */
+ static ScratchChars get() {
+ final Object[] holder = LOCAL.get();
+ // If already in use, return a new array
+ if ((boolean) holder[0]) {
+ return new ScratchChars(charArray());
+ }
+ holder[0] = true;
+ return (ScratchChars) holder[1];
+ }
+
+ private final char[] buffer;
+
+ private ScratchChars(final char[] buffer) {
+ this.buffer = buffer;
+ }
+
+ char[] array() {
+ return buffer;
+ }
+
+ /**
+ * If the buffer is the internal array, clear and release it for reuse.
+ */
+ @Override
+ public void close() {
+ final Object[] holder = LOCAL.get();
+ if (buffer == ((ScratchChars) holder[1]).buffer) {
+ Arrays.fill(buffer, (char) 0);
holder[0] = false;
}
}
@@ -660,8 +700,8 @@ static void checkFromToIndex(final int fromIndex, final int toIndex, final int l
* @see IO#clear()
*/
static void clear() {
- ScratchBufferHolder.SCRATCH_BYTE_BUFFER_HOLDER.remove();
- ScratchBufferHolder.SCRATCH_CHAR_BUFFER_HOLDER.remove();
+ ScratchBytes.LOCAL.remove();
+ ScratchChars.LOCAL.remove();
}
/**
@@ -1205,14 +1245,14 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
}
// reuse one
- final char[] array1 = ScratchBufferHolder.getScratchCharArray();
- // but allocate another
- final char[] array2 = charArray();
- int pos1;
- int pos2;
- int count1;
- int count2;
- try {
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ final char[] array1 = scratch.array();
+ // but allocate another
+ final char[] array2 = charArray();
+ int pos1;
+ int pos2;
+ int count1;
+ int count2;
while (true) {
pos1 = 0;
pos2 = 0;
@@ -1240,8 +1280,6 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
}
}
}
- } finally {
- ScratchBufferHolder.releaseScratchCharArray(array1);
}
}
@@ -1722,11 +1760,8 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o
* @since 2.2
*/
public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length) throws IOException {
- final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
- try {
- return copyLarge(input, output, inputOffset, length, buffer);
- } finally {
- ScratchBufferHolder.releaseScratchByteArray(buffer);
+ try (ScratchBytes scratch = ScratchBytes.get()) {
+ return copyLarge(input, output, inputOffset, length, scratch.array());
}
}
@@ -1797,11 +1832,8 @@ public static long copyLarge(final InputStream input, final OutputStream output,
* @since 1.3
*/
public static long copyLarge(final Reader reader, final Writer writer) throws IOException {
- final char[] buffer = ScratchBufferHolder.getScratchCharArray();
- try {
- return copyLarge(reader, writer, buffer);
- } finally {
- ScratchBufferHolder.releaseScratchCharArray(buffer);
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ return copyLarge(reader, writer, scratch.array());
}
}
@@ -1849,11 +1881,8 @@ public static long copyLarge(final Reader reader, final Writer writer, final cha
* @since 2.2
*/
public static long copyLarge(final Reader reader, final Writer writer, final long inputOffset, final long length) throws IOException {
- final char[] buffer = ScratchBufferHolder.getScratchCharArray();
- try {
- return copyLarge(reader, writer, inputOffset, length, buffer);
- } finally {
- ScratchBufferHolder.releaseScratchCharArray(buffer);
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ return copyLarge(reader, writer, inputOffset, length, scratch.array());
}
}
@@ -2553,11 +2582,8 @@ public static URL resourceToURL(final String name, final ClassLoader classLoader
* @since 2.0
*/
public static long skip(final InputStream input, final long skip) throws IOException {
- final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
- try {
- return skip(input, skip, () -> buffer);
- } finally {
- ScratchBufferHolder.releaseScratchByteArray(buffer);
+ try (ScratchBytes scratch = ScratchBytes.get()) {
+ return skip(input, skip, scratch::array);
}
}
@@ -2665,18 +2691,16 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti
throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip);
}
long remain = toSkip;
- final char[] charArray = ScratchBufferHolder.getScratchCharArray();
- try {
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ final char[] chars = scratch.array();
while (remain > 0) {
// See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()
- final long n = reader.read(charArray, 0, (int) Math.min(remain, charArray.length));
+ final long n = reader.read(chars, 0, (int) Math.min(remain, chars.length));
if (n < 0) { // EOF
break;
}
remain -= n;
}
- } finally {
- ScratchBufferHolder.releaseScratchCharArray(charArray);
}
return toSkip - remain;
}
diff --git a/src/test/java/org/apache/commons/io/IOCaseTest.java b/src/test/java/org/apache/commons/io/IOCaseTest.java
index 1121b38..54073da 100644
--- a/src/test/java/org/apache/commons/io/IOCaseTest.java
+++ b/src/test/java/org/apache/commons/io/IOCaseTest.java
@@ -30,6 +30,8 @@
import java.io.ObjectOutputStream;
import java.util.Arrays;
+import org.apache.commons.io.IOUtils.ScratchBytes;
+import org.apache.commons.io.IOUtils.ScratchChars;
import org.junit.jupiter.api.Test;
/**
@@ -297,49 +299,48 @@ void test_getName() {
@Test
void test_getScratchByteArray() {
- final byte[] array = IOUtils.ScratchBufferHolder.getScratchByteArray();
- try {
+ final byte[] array;
+ try (ScratchBytes scratch = IOUtils.ScratchBytes.get()) {
+ array = scratch.array();
assert0(array);
Arrays.fill(array, (byte) 1);
// Get another array, while the first is still in use
- final byte[] array2 = IOUtils.ScratchBufferHolder.getScratchByteArray();
- assert0(array2);
- assertNotSame(array, array2);
- } finally {
- // Release first array
- IOUtils.ScratchBufferHolder.releaseScratchByteArray(array);
+ // The test doesn't need the try here but that's the pattern.
+ try (ScratchBytes scratch2 = IOUtils.ScratchBytes.get()) {
+ assertNotSame(scratch, scratch2);
+ final byte[] array2 = scratch2.array();
+ assert0(array2);
+ assertNotSame(array, array2);
+ }
}
// The first array should be reset and reusable
- final byte[] array3 = IOUtils.ScratchBufferHolder.getScratchByteArray();
- try {
+ try (ScratchBytes scratch = IOUtils.ScratchBytes.get()) {
+ final byte[] array3 = scratch.array();
assert0(array3);
assertSame(array, array3);
- } finally {
- IOUtils.ScratchBufferHolder.releaseScratchByteArray(array3);
}
}
@Test
void test_getScratchCharArray() {
- final char[] array = IOUtils.ScratchBufferHolder.getScratchCharArray();
- try {
+ final char[] array;
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ array = scratch.array();
assert0(array);
Arrays.fill(array, (char) 1);
// Get another array, while the first is still in use
- final char[] array2 = IOUtils.ScratchBufferHolder.getScratchCharArray();
- assert0(array2);
- assertNotSame(array, array2);
- } finally {
- // Release first array
- IOUtils.ScratchBufferHolder.releaseScratchCharArray(array);
+ // The test doesn't need the try here but that's the pattern.
+ try (ScratchChars scratch2 = IOUtils.ScratchChars.get()) {
+ final char[] array2 = scratch2.array();
+ assert0(array2);
+ assertNotSame(array, array2);
+ }
}
// The first array should be reset and reusable
- final char[] array3 = IOUtils.ScratchBufferHolder.getScratchCharArray();
- try {
+ try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
+ final char[] array3 = scratch.array();
assert0(array3);
assertSame(array, array3);
- } finally {
- IOUtils.ScratchBufferHolder.releaseScratchCharArray(array3);
}
}