Skip to content

Commit f48c5a6

Browse files
committed
Add support to BoundedInputStream for setting a consumer for
ProxyInputStream.afterRead(int)
1 parent f830a4a commit f48c5a6

3 files changed

Lines changed: 168 additions & 53 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
6868
<action dev="ggregory" type="add" issue="IO-861" due-to="Gary Gregory">Add ProxyInputStream.AbstractBuilder. Supports setting a consumer for ProxyInputStream.afterRead(int).</action>
6969
<action dev="ggregory" type="add" due-to="Gary Gregory">Add support to AutoCloseInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action>
7070
<action dev="ggregory" type="add" due-to="Gary Gregory">Add support to BOMInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action>
71+
<action dev="ggregory" type="add" issue="IO-861" due-to="Gary Gregory">Add support to BoundedInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action>
7172
<!-- UPDATE -->
7273
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 74 to 78 #670, #676, #679, #688.</action>
7374
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.15.1 to 1.15.10 #672, #673, #685, #686, #694, #696, #698.</action>

src/main/java/org/apache/commons/io/input/BoundedInputStream.java

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import java.io.InputStream;
2323

2424
import org.apache.commons.io.IOUtils;
25-
import org.apache.commons.io.build.AbstractStreamBuilder;
25+
import org.apache.commons.io.function.IOBiConsumer;
2626

2727
//@formatter:off
2828
/**
@@ -73,6 +73,15 @@
7373
* .get();
7474
* }
7575
* </pre>
76+
* <h2>Listening for the max count reached</h2>
77+
* <pre>{@code
78+
* BoundedInputStream s = BoundedInputStream.builder()
79+
* .setPath(Paths.get("MyFile.xml"))
80+
* .setMaxCount(1024)
81+
* .setOnMaxCount((max, count) -> System.out.printf("Max count %,d reached with a last read count of %,d%n", max, count))
82+
* .get();
83+
* }
84+
* </pre>
7685
* @see Builder
7786
* @since 2.0
7887
*/
@@ -84,14 +93,16 @@ public class BoundedInputStream extends ProxyInputStream {
8493
*
8594
* @param <T> The subclass.
8695
*/
87-
static abstract class AbstractBuilder<T extends AbstractBuilder<T>> extends AbstractStreamBuilder<BoundedInputStream, T> {
96+
static abstract class AbstractBuilder<T extends AbstractBuilder<T>> extends ProxyInputStream.AbstractBuilder<BoundedInputStream, T> {
8897

8998
/** The current count of bytes counted. */
9099
private long count;
91100

92101
/** The max count of bytes to read. */
93102
private long maxCount = EOF;
94103

104+
private IOBiConsumer<Long, Long> onMaxCount = IOBiConsumer.noop();
105+
95106
/** Flag if {@link #close()} should be propagated, {@code true} by default. */
96107
private boolean propagateClose = true;
97108

@@ -103,6 +114,10 @@ long getMaxCount() {
103114
return maxCount;
104115
}
105116

117+
IOBiConsumer<Long, Long> getOnMaxCount() {
118+
return onMaxCount;
119+
}
120+
106121
boolean isPropagateClose() {
107122
return propagateClose;
108123
}
@@ -138,6 +153,25 @@ public T setMaxCount(final long maxCount) {
138153
return asThis();
139154
}
140155

156+
/**
157+
* Sets the default {@link BoundedInputStream#onMaxLength(long, long)} behavior, {@code null} resets to a NOOP.
158+
* <p>
159+
* The first Long is the max count of bytes to read. The second Long is the count of bytes read.
160+
* </p>
161+
* <p>
162+
* This does <em>not</em> override a {@code BoundedInputStream} subclass' implementation of the {@link BoundedInputStream#onMaxLength(long, long)}
163+
* method.
164+
* </p>
165+
*
166+
* @param onMaxCount the {@link ProxyInputStream#afterRead(int)} behavior.
167+
* @return this instance.
168+
* @since 2.18.0
169+
*/
170+
public T setOnMaxCount(final IOBiConsumer<Long, Long> onMaxCount) {
171+
this.onMaxCount = onMaxCount != null ? onMaxCount : IOBiConsumer.noop();
172+
return asThis();
173+
}
174+
141175
/**
142176
* Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
143177
* <p>
@@ -218,8 +252,11 @@ public static class Builder extends AbstractBuilder<Builder> {
218252
* </p>
219253
* <ul>
220254
* <li>{@link #getInputStream()}</li>
221-
* <li>maxCount</li>
222-
* <li>propagateClose</li>
255+
* <li>{@link #getAfterRead()}</li>
256+
* <li>{@link #getCount()}</li>
257+
* <li>{@link #getMaxCount()}</li>
258+
* <li>{@link #isPropagateClose()}</li>
259+
* <li>{@link #getOnMaxCount()}</li>
223260
* </ul>
224261
*
225262
* @return a new instance.
@@ -228,10 +265,9 @@ public static class Builder extends AbstractBuilder<Builder> {
228265
* @throws IOException if an I/O error occurs.
229266
* @see #getInputStream()
230267
*/
231-
@SuppressWarnings("resource")
232268
@Override
233269
public BoundedInputStream get() throws IOException {
234-
return new BoundedInputStream(getInputStream(), getCount(), getMaxCount(), isPropagateClose());
270+
return new BoundedInputStream(this);
235271
}
236272

237273
}
@@ -255,13 +291,23 @@ public static Builder builder() {
255291
/** The max count of bytes to read. */
256292
private final long maxCount;
257293

294+
private final IOBiConsumer<Long, Long> onMaxCount;
295+
258296
/**
259297
* Flag if close should be propagated.
260298
*
261299
* TODO Make final in 3.0.
262300
*/
263301
private boolean propagateClose = true;
264302

303+
BoundedInputStream(final Builder builder) throws IOException {
304+
super(builder);
305+
this.count = builder.getCount();
306+
this.maxCount = builder.getMaxCount();
307+
this.propagateClose = builder.isPropagateClose();
308+
this.onMaxCount = builder.getOnMaxCount();
309+
}
310+
265311
/**
266312
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and is unlimited.
267313
*
@@ -273,6 +319,14 @@ public BoundedInputStream(final InputStream in) {
273319
this(in, EOF);
274320
}
275321

322+
BoundedInputStream(final InputStream inputStream, final Builder builder) {
323+
super(inputStream, builder);
324+
this.count = builder.getCount();
325+
this.maxCount = builder.getMaxCount();
326+
this.propagateClose = builder.isPropagateClose();
327+
this.onMaxCount = builder.getOnMaxCount();
328+
}
329+
276330
/**
277331
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
278332
*
@@ -284,26 +338,7 @@ public BoundedInputStream(final InputStream in) {
284338
public BoundedInputStream(final InputStream inputStream, final long maxCount) {
285339
// Some badly designed methods - e.g. the Servlet API - overload length
286340
// such that "-1" means stream finished
287-
this(inputStream, 0, maxCount, true);
288-
}
289-
290-
/**
291-
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
292-
*
293-
* @param inputStream The wrapped input stream.
294-
* @param count The current number of bytes read.
295-
* @param maxCount The maximum number of bytes to return.
296-
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
297-
* does not.
298-
*/
299-
BoundedInputStream(final InputStream inputStream, final long count, final long maxCount, final boolean propagateClose) {
300-
// Some badly designed methods - e.g. the Servlet API - overload length
301-
// such that "-1" means stream finished
302-
// Can't throw because we start from an InputStream.
303-
super(inputStream);
304-
this.count = count;
305-
this.maxCount = maxCount;
306-
this.propagateClose = propagateClose;
341+
this(inputStream, builder().setMaxCount(maxCount));
307342
}
308343

309344
/**
@@ -318,6 +353,7 @@ protected synchronized void afterRead(final int n) throws IOException {
318353
if (n != EOF) {
319354
count += n;
320355
}
356+
super.afterRead(n);
321357
}
322358

323359
/**
@@ -422,15 +458,19 @@ public boolean markSupported() {
422458

423459
/**
424460
* A caller has caused a request that would cross the {@code maxLength} boundary.
461+
* <p>
462+
* Delegates to the consumer set in {@link Builder#setOnMaxCount(IOBiConsumer)}.
463+
* </p>
425464
*
426-
* @param maxLength The max count of bytes to read.
465+
* @param max The max count of bytes to read.
427466
* @param count The count of bytes read.
428467
* @throws IOException Subclasses may throw.
429468
* @since 2.12.0
430469
*/
431470
@SuppressWarnings("unused")
432-
protected void onMaxLength(final long maxLength, final long count) throws IOException {
433-
// for subclasses
471+
// TODO Rename to onMaxCount for 3.0
472+
protected void onMaxLength(final long max, final long count) throws IOException {
473+
onMaxCount.accept(max, count);
434474
}
435475

436476
/**

0 commit comments

Comments
 (0)