Skip to content

Commit 812d8a4

Browse files
committed
BoundedInputStream can count its bytes without wrapping a
CountingInputStream - Deprecate CountingInputStream in favor of BoundedInputStream - Add a BoundedInputStream builder
1 parent 2c927dc commit 812d8a4

File tree

4 files changed

+283
-70
lines changed

4 files changed

+283
-70
lines changed

src/changes/changes.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ The <action> type attribute can be add,update,fix,remove.
130130
<action dev="ggregory" type="add" due-to="Gary Gregory">Add AbstractStreamBuilder.getReader().</action>
131131
<action dev="ggregory" type="add" due-to="Gary Gregory">Add Maven property project.build.outputTimestamp for build reproducibility.</action>
132132
<action dev="ggregory" type="add" due-to="Gary Gregory">Add ProxyInputStream.unwrap().</action>
133+
<action dev="ggregory" type="add" due-to="Gary Gregory">Add a running count and builder to BoundedInputStream.</action>
134+
<action dev="ggregory" type="add" due-to="Gary Gregory">Add a builder to CountingInputStream.</action>
133135
<!-- UPDATE -->
134136
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.14.10 to 1.14.11 #534.</action>
135137
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 65 to 66.</action>

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

Lines changed: 182 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,153 @@
2424
import org.apache.commons.io.IOUtils;
2525
import org.apache.commons.io.build.AbstractStreamBuilder;
2626

27+
//@formatter:off
2728
/**
28-
* Reads bytes up to a maximum length, if its count goes above that, it stops.
29+
* Reads bytes up to a maximum count and stops once reached.
2930
* <p>
30-
* This is useful to wrap {@code ServletInputStream}s. The {@code ServletInputStream} will block if you try to read content from it that isn't there, because it
31-
* doesn't know whether the content hasn't arrived yet or whether the content has finished. So, one of these, initialized with the {@code Content-Length} sent
32-
* in the {@code ServletInputStream}'s header, will stop it blocking, providing it's been sent with a correct content length.
31+
* To build an instance, see {@link AbstractBuilder}.
3332
* </p>
3433
* <p>
35-
* To build an instance, use {@link Builder}.
34+
* By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
3635
* </p>
37-
*
36+
* <p>
37+
* You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
38+
* </p>
39+
* <h2>Using a ServletInputStream</h2>
40+
* <p>
41+
* A {@code ServletInputStream} can block if you try to read content that isn't there
42+
* because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
43+
* {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
44+
* length in the first place.
45+
* </p>
46+
* <h2>Using NIO</h2>
47+
* <pre>{@code
48+
* BoundedInputStream s = BoundedInputStream.builder()
49+
* .setPath(Paths.get("MyFile.xml"))
50+
* .setMaxCount(1024)
51+
* .setPropagateClose(false)
52+
* .get();
53+
* }
54+
* </pre>
55+
* <h2>Using IO</h2>
56+
* <pre>{@code
57+
* BoundedInputStream s = BoundedInputStream.builder()
58+
* .setFile(new File("MyFile.xml"))
59+
* .setMaxCount(1024)
60+
* .setPropagateClose(false)
61+
* .get();
62+
* }
63+
* </pre>
64+
* <h2>Counting Bytes</h2>
65+
* <p>You can set the running count when building, which is most useful when starting from another stream:
66+
* <pre>{@code
67+
* InputStream in = ...;
68+
* BoundedInputStream s = BoundedInputStream.builder()
69+
* .setInputStream(in)
70+
* .setCount(12)
71+
* .setMaxCount(1024)
72+
* .setPropagateClose(false)
73+
* .get();
74+
* }
75+
* </pre>
3876
* @see Builder
3977
* @since 2.0
4078
*/
79+
//@formatter:on
4180
public class BoundedInputStream extends ProxyInputStream {
4281

43-
// TODO For 3.0, extend CountingInputStream. Or, add a max feature to CountingInputStream.
82+
/**
83+
* For subclassing builders from {@link BoundedInputStream} subclassses.
84+
*
85+
* @param <T> The subclass.
86+
*/
87+
static abstract class AbstractBuilder<T extends AbstractBuilder<T>> extends AbstractStreamBuilder<BoundedInputStream, T> {
88+
89+
/** The current count of bytes counted. */
90+
private long count;
91+
92+
/** The max count of bytes to read. */
93+
private long maxCount = EOF;
94+
95+
/** Flag if {@link #close()} should be propagated, {@code true} by default. */
96+
private boolean propagateClose = true;
97+
98+
long getCount() {
99+
return count;
100+
}
101+
102+
long getMaxCount() {
103+
return maxCount;
104+
}
105+
106+
boolean isPropagateClose() {
107+
return propagateClose;
108+
}
109+
110+
/**
111+
* Sets the current number of bytes counted.
112+
* <p>
113+
* Useful when building from another stream to carry forward a read count.
114+
* </p>
115+
* <p>
116+
* Default is {@code 0}, negative means 0.
117+
* </p>
118+
*
119+
* @param count The current number of bytes counted.
120+
* @return this.
121+
*/
122+
public T setCount(final long count) {
123+
this.count = Math.max(0, count);
124+
return asThis();
125+
}
126+
127+
/**
128+
* Sets the maximum number of bytes to return.
129+
* <p>
130+
* Default is {@value IOUtils#EOF}, negative means unbound.
131+
* </p>
132+
*
133+
* @param maxCount The maximum number of bytes to return.
134+
* @return this.
135+
*/
136+
public T setMaxCount(final long maxCount) {
137+
this.maxCount = Math.max(EOF, maxCount);
138+
return asThis();
139+
}
140+
141+
/**
142+
* Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
143+
* <p>
144+
* Default is {@code true}.
145+
* </p>
146+
*
147+
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if
148+
* it does not.
149+
* @return this.
150+
*/
151+
public T setPropagateClose(final boolean propagateClose) {
152+
this.propagateClose = propagateClose;
153+
return asThis();
154+
}
155+
156+
}
44157

45158
//@formatter:off
46159
/**
47160
* Builds a new {@link BoundedInputStream}.
48-
*
161+
* <p>
162+
* By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
163+
* </p>
164+
* <p>
165+
* You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
166+
* </p>
167+
* <h2>Using a ServletInputStream</h2>
168+
* <p>
169+
* A {@code ServletInputStream} can block if you try to read content that isn't there
170+
* because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
171+
* {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
172+
* length in the first place.
173+
* </p>
49174
* <h2>Using NIO</h2>
50175
* <pre>{@code
51176
* BoundedInputStream s = BoundedInputStream.builder()
@@ -64,18 +189,24 @@ public class BoundedInputStream extends ProxyInputStream {
64189
* .get();
65190
* }
66191
* </pre>
192+
* <h2>Counting Bytes</h2>
193+
* <p>You can set the running count when building, which is most useful when starting from another stream:
194+
* <pre>{@code
195+
* InputStream in = ...;
196+
* BoundedInputStream s = BoundedInputStream.builder()
197+
* .setInputStream(in)
198+
* .setCount(12)
199+
* .setMaxCount(1024)
200+
* .setPropagateClose(false)
201+
* .get();
202+
* }
203+
* </pre>
67204
*
68205
* @see #get()
69206
* @since 2.16.0
70207
*/
71208
//@formatter:on
72-
public static class Builder extends AbstractStreamBuilder<BoundedInputStream, Builder> {
73-
74-
/** The max count of bytes to read. */
75-
private long maxCount = EOF;
76-
77-
/** Flag if close should be propagated. */
78-
private boolean propagateClose = true;
209+
public static class Builder extends AbstractBuilder<Builder> {
79210

80211
/**
81212
* Builds a new {@link BoundedInputStream}.
@@ -100,50 +231,24 @@ public static class Builder extends AbstractStreamBuilder<BoundedInputStream, Bu
100231
@SuppressWarnings("resource")
101232
@Override
102233
public BoundedInputStream get() throws IOException {
103-
return new BoundedInputStream(getInputStream(), maxCount, propagateClose);
104-
}
105-
106-
/**
107-
* Sets the maximum number of bytes to return.
108-
* <p>
109-
* Default is {@value IOUtils#EOF}.
110-
* </p>
111-
*
112-
* @param maxCount The maximum number of bytes to return.
113-
* @return this.
114-
*/
115-
public Builder setMaxCount(final long maxCount) {
116-
this.maxCount = maxCount;
117-
return this;
118-
}
119-
120-
/**
121-
* Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
122-
* <p>
123-
* Default is true.
124-
* </p>
125-
*
126-
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if
127-
* it does not.
128-
* @return this.
129-
*/
130-
public Builder setPropagateClose(final boolean propagateClose) {
131-
this.propagateClose = propagateClose;
132-
return this;
234+
return new BoundedInputStream(getInputStream(), getCount(), getMaxCount(), isPropagateClose());
133235
}
134236

135237
}
136238

137239
/**
138-
* Constructs a new {@link Builder}.
240+
* Constructs a new {@link AbstractBuilder}.
139241
*
140-
* @return a new {@link Builder}.
242+
* @return a new {@link AbstractBuilder}.
141243
* @since 2.16.0
142244
*/
143245
public static Builder builder() {
144246
return new Builder();
145247
}
146248

249+
/** The current count of bytes counted. */
250+
private long count;
251+
147252
/** The max count of bytes to read. */
148253
private final long maxCount;
149254

@@ -158,7 +263,7 @@ public static Builder builder() {
158263
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and is unlimited.
159264
*
160265
* @param in The wrapped input stream.
161-
* @deprecated Use {@link Builder#get()}.
266+
* @deprecated Use {@link AbstractBuilder#get()}.
162267
*/
163268
@Deprecated
164269
public BoundedInputStream(final InputStream in) {
@@ -169,33 +274,49 @@ public BoundedInputStream(final InputStream in) {
169274
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
170275
*
171276
* @param inputStream The wrapped input stream.
172-
* @param maxCount The maximum number of bytes to return.
173-
* @deprecated Use {@link Builder#get()}.
277+
* @param maxCount The maximum number of bytes to return.
278+
* @deprecated Use {@link AbstractBuilder#get()}.
174279
*/
175280
@Deprecated
176281
public BoundedInputStream(final InputStream inputStream, final long maxCount) {
177282
// Some badly designed methods - e.g. the Servlet API - overload length
178283
// such that "-1" means stream finished
179-
this(inputStream, maxCount, true);
284+
this(inputStream, 0, maxCount, true);
180285
}
181286

182287
/**
183288
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
184289
*
185290
* @param inputStream The wrapped input stream.
186-
* @param maxCount The maximum number of bytes to return.
291+
* @param count The current number of bytes read.
292+
* @param maxCount The maximum number of bytes to return.
187293
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
188294
* does not.
189295
*/
190-
@SuppressWarnings("resource") // Caller closes.
191-
private BoundedInputStream(final InputStream inputStream, final long maxCount, final boolean propagateClose) {
296+
BoundedInputStream(final InputStream inputStream, final long count, final long maxCount, final boolean propagateClose) {
192297
// Some badly designed methods - e.g. the Servlet API - overload length
193298
// such that "-1" means stream finished
194-
super(new CountingInputStream(inputStream));
299+
// Can't throw because we start from an InputStream.
300+
super(inputStream);
301+
this.count = count;
195302
this.maxCount = maxCount;
196303
this.propagateClose = propagateClose;
197304
}
198305

306+
/**
307+
* Adds the number of read bytes to the count.
308+
*
309+
* @param n number of bytes read, or -1 if no more bytes are available
310+
* @throws IOException Not thrown here but subclasses may throw.
311+
* @since 2.0
312+
*/
313+
@Override
314+
protected synchronized void afterRead(final int n) throws IOException {
315+
if (n != EOF) {
316+
count += n;
317+
}
318+
}
319+
199320
/**
200321
* {@inheritDoc}
201322
*/
@@ -226,13 +347,8 @@ public void close() throws IOException {
226347
* @return The count of bytes read.
227348
* @since 2.12.0
228349
*/
229-
@SuppressWarnings("resource") // no allocation
230350
public long getCount() {
231-
return getCountingInputStream().getByteCount();
232-
}
233-
234-
private CountingInputStream getCountingInputStream() {
235-
return (CountingInputStream) in;
351+
return count;
236352
}
237353

238354
/**
@@ -264,7 +380,7 @@ public long getMaxLength() {
264380
* @since 2.16.0
265381
*/
266382
public long getRemaining() {
267-
return getMaxCount() - getCount();
383+
return Math.max(0, getMaxCount() - getCount());
268384
}
269385

270386
private boolean isMaxCount() {
@@ -373,7 +489,7 @@ public synchronized void reset() throws IOException {
373489
*
374490
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
375491
* does not.
376-
* @deprecated Use {@link Builder#setPropagateClose(boolean)}.
492+
* @deprecated Use {@link AbstractBuilder#setPropagateClose(boolean)}.
377493
*/
378494
@Deprecated
379495
public void setPropagateClose(final boolean propagateClose) {
@@ -389,7 +505,9 @@ public void setPropagateClose(final boolean propagateClose) {
389505
*/
390506
@Override
391507
public long skip(final long n) throws IOException {
392-
return super.skip(toReadLen(n));
508+
final long skip = super.skip(toReadLen(n));
509+
count += skip;
510+
return skip;
393511
}
394512

395513
private long toReadLen(final long len) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
* A typical use case would be during debugging, to ensure that data is being
2929
* read as expected.
3030
* </p>
31+
* @deprecated Use {@link BoundedInputStream} (unbounded by default).
3132
*/
33+
@Deprecated
3234
public class CountingInputStream extends ProxyInputStream {
3335

3436
/** The count of bytes that have passed. */

0 commit comments

Comments
 (0)