Skip to content

Commit 0698bd9

Browse files
committed
Reduce internal boilerplate
Make the internal scratch byte and char buffers auto-closeable
1 parent 3925b50 commit 0698bd9

File tree

3 files changed

+132
-107
lines changed

3 files changed

+132
-107
lines changed

src/main/java/org/apache/commons/io/CopyUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.io.Writer;
3030
import java.nio.charset.Charset;
3131

32+
import org.apache.commons.io.IOUtils.ScratchChars;
33+
3234
/**
3335
* This class provides static utility methods for buffered
3436
* copying between sources ({@link InputStream}, {@link Reader},
@@ -278,17 +280,15 @@ public static int copy(
278280
final Reader input,
279281
final Writer output)
280282
throws IOException {
281-
final char[] buffer = IOUtils.ScratchBufferHolder.getScratchCharArray();
282-
try {
283+
try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
284+
final char[] buffer = scratch.array();
283285
int count = 0;
284286
int n;
285287
while (EOF != (n = input.read(buffer))) {
286288
output.write(buffer, 0, n);
287289
count += n;
288290
}
289291
return count;
290-
} finally {
291-
IOUtils.ScratchBufferHolder.releaseScratchCharArray(buffer);
292292
}
293293
}
294294

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 103 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -133,88 +133,128 @@ public class IOUtils {
133133
// or return one of them.
134134

135135
/**
136-
* Holder for per-thread internal scratch buffers.
136+
* Holder for per-thread internal scratch buffer.
137137
*
138138
* <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
139139
* is allocated to avoid data corruption.</p>
140140
*
141141
* <p>Typical usage:</p>
142142
*
143143
* <pre>{@code
144-
* final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
145-
* try {
144+
* try (ScratchBytes scratch = ScratchBytes.get()) {
146145
* // use the buffer
147-
* } finally {
148-
* ScratchBufferHolder.releaseScratchByteArray(buffer);
146+
* byte[] bytes = scratch.array();
147+
* // ...
149148
* }
150149
* }</pre>
151150
*/
152-
static final class ScratchBufferHolder {
151+
static final class ScratchBytes implements AutoCloseable {
153152

154153
/**
155-
* Holder for internal byte array buffer.
154+
* Wraps an internal byte array.
155+
*
156+
* [0] boolean in use.
157+
* [1] byte[] buffer.
156158
*/
157-
private static final ThreadLocal<Object[]> SCRATCH_BYTE_BUFFER_HOLDER = ThreadLocal.withInitial(() -> new Object[] { false, byteArray() });
158-
159-
/**
160-
* Holder for internal char array buffer.
161-
*/
162-
private static final ThreadLocal<Object[]> SCRATCH_CHAR_BUFFER_HOLDER = ThreadLocal.withInitial(() -> new Object[] { false, charArray() });
163-
159+
private static final ThreadLocal<Object[]> LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, new ScratchBytes(byteArray()) });
164160

165161
/**
166162
* Gets the internal byte array buffer.
167163
*
168164
* @return the internal byte array buffer.
169165
*/
170-
static byte[] getScratchByteArray() {
171-
final Object[] holder = SCRATCH_BYTE_BUFFER_HOLDER.get();
166+
static ScratchBytes get() {
167+
final Object[] holder = LOCAL.get();
172168
// If already in use, return a new array
173169
if ((boolean) holder[0]) {
174-
return byteArray();
170+
return new ScratchBytes(byteArray());
175171
}
176172
holder[0] = true;
177-
return (byte[]) holder[1];
173+
return (ScratchBytes) holder[1];
174+
}
175+
176+
private final byte[] buffer;
177+
178+
private ScratchBytes(final byte[] buffer) {
179+
this.buffer = buffer;
180+
}
181+
182+
byte[] array() {
183+
return buffer;
178184
}
179185

180186
/**
181-
* Gets the char array buffer.
182-
*
183-
* @return the char array buffer.
187+
* If the buffer is the internal array, clear and release it for reuse.
184188
*/
185-
static char[] getScratchCharArray() {
186-
final Object[] holder = SCRATCH_CHAR_BUFFER_HOLDER.get();
187-
// If already in use, return a new array
188-
if ((boolean) holder[0]) {
189-
return charArray();
189+
@Override
190+
public void close() {
191+
final Object[] holder = LOCAL.get();
192+
if (buffer == ((ScratchBytes) holder[1]).buffer) {
193+
Arrays.fill(buffer, (byte) 0);
194+
holder[0] = false;
190195
}
191-
holder[0] = true;
192-
return (char[]) holder[1];
193196
}
197+
}
198+
199+
/**
200+
* Holder for per-thread internal scratch buffer.
201+
*
202+
* <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
203+
* is allocated to avoid data corruption.</p>
204+
*
205+
* <p>Typical usage:</p>
206+
*
207+
* <pre>{@code
208+
* try (ScratchChars scratch = ScratchChars.get()) {
209+
* // use the buffer
210+
* char[] bytes = scratch.array();
211+
* // ...
212+
* }
213+
* }</pre>
214+
*/
215+
static final class ScratchChars implements AutoCloseable {
194216

217+
/**
218+
* Wraps an internal char array.
219+
*
220+
* [0] boolean in use.
221+
* [1] char[] buffer.
222+
*/
223+
private static final ThreadLocal<Object[]> LOCAL = ThreadLocal.withInitial(() -> new Object[] { false, new ScratchChars(charArray()) });
195224

196225
/**
197-
* If the argument is the internal byte array, release it for reuse.
226+
* Gets the internal char array buffer.
198227
*
199-
* @param array the byte array to release.
228+
* @return the internal char array buffer.
200229
*/
201-
static void releaseScratchByteArray(final byte[] array) {
202-
final Object[] holder = SCRATCH_BYTE_BUFFER_HOLDER.get();
203-
if (array == holder[1]) {
204-
Arrays.fill(array, (byte) 0);
205-
holder[0] = false;
230+
static ScratchChars get() {
231+
final Object[] holder = LOCAL.get();
232+
// If already in use, return a new array
233+
if ((boolean) holder[0]) {
234+
return new ScratchChars(charArray());
206235
}
236+
holder[0] = true;
237+
return (ScratchChars) holder[1];
238+
}
239+
240+
private final char[] buffer;
241+
242+
private ScratchChars(final char[] buffer) {
243+
this.buffer = buffer;
244+
}
245+
246+
char[] array() {
247+
return buffer;
207248
}
208249

209250
/**
210-
* If the argument is the internal char array, release it for reuse.
211-
*
212-
* @param array the char array to release.
251+
* If the buffer is the internal array, clear and release it for reuse.
213252
*/
214-
static void releaseScratchCharArray(final char[] array) {
215-
final Object[] holder = SCRATCH_CHAR_BUFFER_HOLDER.get();
216-
if (array == holder[1]) {
217-
Arrays.fill(array, (char) 0);
253+
@Override
254+
public void close() {
255+
final Object[] holder = LOCAL.get();
256+
if (buffer == ((ScratchChars) holder[1]).buffer) {
257+
Arrays.fill(buffer, (char) 0);
218258
holder[0] = false;
219259
}
220260
}
@@ -660,8 +700,8 @@ static void checkFromToIndex(final int fromIndex, final int toIndex, final int l
660700
* @see IO#clear()
661701
*/
662702
static void clear() {
663-
ScratchBufferHolder.SCRATCH_BYTE_BUFFER_HOLDER.remove();
664-
ScratchBufferHolder.SCRATCH_CHAR_BUFFER_HOLDER.remove();
703+
ScratchBytes.LOCAL.remove();
704+
ScratchChars.LOCAL.remove();
665705
}
666706

667707
/**
@@ -1205,14 +1245,14 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
12051245
}
12061246

12071247
// reuse one
1208-
final char[] array1 = ScratchBufferHolder.getScratchCharArray();
1209-
// but allocate another
1210-
final char[] array2 = charArray();
1211-
int pos1;
1212-
int pos2;
1213-
int count1;
1214-
int count2;
1215-
try {
1248+
try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
1249+
final char[] array1 = scratch.array();
1250+
// but allocate another
1251+
final char[] array2 = charArray();
1252+
int pos1;
1253+
int pos2;
1254+
int count1;
1255+
int count2;
12161256
while (true) {
12171257
pos1 = 0;
12181258
pos2 = 0;
@@ -1240,8 +1280,6 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
12401280
}
12411281
}
12421282
}
1243-
} finally {
1244-
ScratchBufferHolder.releaseScratchCharArray(array1);
12451283
}
12461284
}
12471285

@@ -1722,11 +1760,8 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o
17221760
* @since 2.2
17231761
*/
17241762
public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length) throws IOException {
1725-
final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
1726-
try {
1727-
return copyLarge(input, output, inputOffset, length, buffer);
1728-
} finally {
1729-
ScratchBufferHolder.releaseScratchByteArray(buffer);
1763+
try (ScratchBytes scratch = ScratchBytes.get()) {
1764+
return copyLarge(input, output, inputOffset, length, scratch.array());
17301765
}
17311766
}
17321767

@@ -1797,11 +1832,8 @@ public static long copyLarge(final InputStream input, final OutputStream output,
17971832
* @since 1.3
17981833
*/
17991834
public static long copyLarge(final Reader reader, final Writer writer) throws IOException {
1800-
final char[] buffer = ScratchBufferHolder.getScratchCharArray();
1801-
try {
1802-
return copyLarge(reader, writer, buffer);
1803-
} finally {
1804-
ScratchBufferHolder.releaseScratchCharArray(buffer);
1835+
try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
1836+
return copyLarge(reader, writer, scratch.array());
18051837
}
18061838
}
18071839

@@ -1849,11 +1881,8 @@ public static long copyLarge(final Reader reader, final Writer writer, final cha
18491881
* @since 2.2
18501882
*/
18511883
public static long copyLarge(final Reader reader, final Writer writer, final long inputOffset, final long length) throws IOException {
1852-
final char[] buffer = ScratchBufferHolder.getScratchCharArray();
1853-
try {
1854-
return copyLarge(reader, writer, inputOffset, length, buffer);
1855-
} finally {
1856-
ScratchBufferHolder.releaseScratchCharArray(buffer);
1884+
try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
1885+
return copyLarge(reader, writer, inputOffset, length, scratch.array());
18571886
}
18581887
}
18591888

@@ -2553,11 +2582,8 @@ public static URL resourceToURL(final String name, final ClassLoader classLoader
25532582
* @since 2.0
25542583
*/
25552584
public static long skip(final InputStream input, final long skip) throws IOException {
2556-
final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
2557-
try {
2558-
return skip(input, skip, () -> buffer);
2559-
} finally {
2560-
ScratchBufferHolder.releaseScratchByteArray(buffer);
2585+
try (ScratchBytes scratch = ScratchBytes.get()) {
2586+
return skip(input, skip, scratch::array);
25612587
}
25622588
}
25632589

@@ -2665,18 +2691,16 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti
26652691
throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip);
26662692
}
26672693
long remain = toSkip;
2668-
final char[] charArray = ScratchBufferHolder.getScratchCharArray();
2669-
try {
2694+
try (ScratchChars scratch = IOUtils.ScratchChars.get()) {
2695+
final char[] chars = scratch.array();
26702696
while (remain > 0) {
26712697
// See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()
2672-
final long n = reader.read(charArray, 0, (int) Math.min(remain, charArray.length));
2698+
final long n = reader.read(chars, 0, (int) Math.min(remain, chars.length));
26732699
if (n < 0) { // EOF
26742700
break;
26752701
}
26762702
remain -= n;
26772703
}
2678-
} finally {
2679-
ScratchBufferHolder.releaseScratchCharArray(charArray);
26802704
}
26812705
return toSkip - remain;
26822706
}

0 commit comments

Comments
 (0)