Skip to content

Commit f130a80

Browse files
committed
Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(*, long)
1 parent fb54aba commit f130a80

File tree

3 files changed

+161
-37
lines changed

3 files changed

+161
-37
lines changed

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ The <action> type attribute can be add,update,fix,remove.
5353
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.</action>
5454
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">Add org.apache.commons.io.FileUtils.ONE_RB #763.</action>
5555
<action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">Add org.apache.commons.io.FileUtils.ONE_QB #763.</action>
56+
<action dev="ggregory" type="add" due-to="Gary Gregory">Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], int, int, long).</action>
57+
<action dev="ggregory" type="add" due-to="Gary Gregory">Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[], long).</action>
58+
<action dev="ggregory" type="add" due-to="Gary Gregory">Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(int, long).</action>
5659
<!-- UPDATE -->
5760
<action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons-codec:commons-codec from 1.18.0 to 1.19.0.</action>
5861
</release>

src/main/java/org/apache/commons/io/output/ProxyOutputStream.java

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.io.output;
1819

1920
import java.io.FilterOutputStream;
@@ -24,13 +25,10 @@
2425
import org.apache.commons.io.build.AbstractStreamBuilder;
2526

2627
/**
27-
* A Proxy stream which acts as expected, that is it passes the method
28-
* calls on to the proxied stream and doesn't change which methods are
29-
* being called. It is an alternative base class to FilterOutputStream
30-
* to increase reusability.
28+
* A Proxy stream which acts as expected, that is it passes the method calls on to the proxied stream and doesn't change which methods are being called. It is
29+
* an alternative base class to FilterOutputStream to increase reusability.
3130
* <p>
32-
* See the protected methods for ways in which a subclass can easily decorate
33-
* a stream with custom pre-, post- or error processing functionality.
31+
* See the protected methods for ways in which a subclass can easily decorate a stream with custom pre-, post- or error processing functionality.
3432
* </p>
3533
*/
3634
public class ProxyOutputStream extends FilterOutputStream {
@@ -72,7 +70,6 @@ public Builder() {
7270
public ProxyOutputStream get() throws IOException {
7371
return new ProxyOutputStream(this);
7472
}
75-
7673
}
7774

7875
@SuppressWarnings("resource") // caller closes
@@ -84,22 +81,19 @@ public ProxyOutputStream get() throws IOException {
8481
/**
8582
* Constructs a new ProxyOutputStream.
8683
*
87-
* @param delegate the OutputStream to delegate to.
84+
* @param delegate the OutputStream to delegate to.
8885
*/
8986
public ProxyOutputStream(final OutputStream delegate) {
9087
// the delegate is stored in a protected superclass variable named 'out'
9188
super(delegate);
9289
}
9390

9491
/**
95-
* Invoked by the write methods after the proxied call has returned
96-
* successfully. The number of bytes written (1 for the
97-
* {@link #write(int)} method, buffer length for {@link #write(byte[])},
98-
* etc.) is given as an argument.
92+
* Invoked by the write methods after the proxied call has returned successfully. The number of bytes written (1 for the {@link #write(int)} method, buffer
93+
* length for {@link #write(byte[])}, etc.) is given as an argument.
9994
* <p>
100-
* Subclasses can override this method to add common post-processing
101-
* functionality without having to override all the write methods.
102-
* The default implementation does nothing.
95+
* Subclasses can override this method to add common post-processing functionality without having to override all the write methods. The default
96+
* implementation does nothing.
10397
* </p>
10498
*
10599
* @param n number of bytes written.
@@ -112,13 +106,11 @@ protected void afterWrite(final int n) throws IOException {
112106
}
113107

114108
/**
115-
* Invoked by the write methods before the call is proxied. The number
116-
* of bytes to be written (1 for the {@link #write(int)} method, buffer
117-
* length for {@link #write(byte[])}, etc.) is given as an argument.
109+
* Invoked by the write methods before the call is proxied. The number of bytes to be written (1 for the {@link #write(int)} method, buffer length for
110+
* {@link #write(byte[])}, etc.) is given as an argument.
118111
* <p>
119-
* Subclasses can override this method to add common pre-processing
120-
* functionality without having to override all the write methods.
121-
* The default implementation does nothing.
112+
* Subclasses can override this method to add common pre-processing functionality without having to override all the write methods. The default
113+
* implementation does nothing.
122114
* </p>
123115
*
124116
* @param n number of bytes to be written.
@@ -157,8 +149,7 @@ public void flush() throws IOException {
157149
/**
158150
* Handle any IOExceptions thrown.
159151
* <p>
160-
* This method provides a point to implement custom exception.
161-
* handling. The default behavior is to re-throw the exception.
152+
* This method provides a point to implement custom exception. handling. The default behavior is to re-throw the exception.
162153
* </p>
163154
*
164155
* @param e The IOException thrown.
@@ -214,7 +205,7 @@ public void write(final byte[] b) throws IOException {
214205
/**
215206
* Invokes the delegate's {@code write(byte[])} method.
216207
*
217-
* @param b the bytes to write.
208+
* @param b the bytes to write.
218209
* @param off The start offset.
219210
* @param len The number of bytes to write.
220211
* @throws IOException if an I/O error occurs.
@@ -247,4 +238,50 @@ public void write(final int b) throws IOException {
247238
}
248239
}
249240

241+
/**
242+
* Invokes the delegate's {@code write(byte[])} method for the {@code repeat} count.
243+
*
244+
* @param b the bytes to write.
245+
* @param off The start offset.
246+
* @param len The number of bytes to write.
247+
* @param repeat How many times to write the bytes in {@code b}.
248+
* @throws IOException if an I/O error occurs.
249+
* @since 2.21.0
250+
*/
251+
public void writeRepeat(final byte[] b, final int off, final int len, final long repeat) throws IOException {
252+
long remains = repeat;
253+
while (remains-- > 0) {
254+
write(b, off, len);
255+
}
256+
}
257+
258+
/**
259+
* Invokes the delegate's {@code write(byte[])} method for the {@code repeat} count.
260+
*
261+
* @param b the bytes to write.
262+
* @param repeat How many times to write the bytes in {@code b}.
263+
* @throws IOException if an I/O error occurs.
264+
* @since 2.21.0
265+
*/
266+
public void writeRepeat(final byte[] b, final long repeat) throws IOException {
267+
long remains = repeat;
268+
while (remains-- > 0) {
269+
write(b);
270+
}
271+
}
272+
273+
/**
274+
* Invokes the delegate's {@code write(int)} method.
275+
*
276+
* @param b the byte to write.
277+
* @param repeat How many times to write the byte in {@code b}.
278+
* @throws IOException if an I/O error occurs.
279+
* @since 2.21.0
280+
*/
281+
public void writeRepeat(final int b, final long repeat) throws IOException {
282+
long remains = repeat;
283+
while (remains-- > 0) {
284+
write(b);
285+
}
286+
}
250287
}

src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
*/
3535
class ProxyOutputStreamTest {
3636

37-
private ByteArrayOutputStream original;
37+
private ByteArrayOutputStream target;
3838

3939
private ProxyOutputStream proxied;
4040

@@ -44,7 +44,7 @@ class ProxyOutputStreamTest {
4444

4545
@BeforeEach
4646
public void setUp() {
47-
original = new ByteArrayOutputStream() {
47+
target = new ByteArrayOutputStream() {
4848

4949
@Override
5050
public void write(final byte[] ba) {
@@ -64,12 +64,12 @@ public synchronized void write(final int ba) {
6464
super.write(ba);
6565
}
6666
};
67-
proxied = new ProxyOutputStream(original);
67+
proxied = new ProxyOutputStream(target);
6868
}
6969

7070
@Test
7171
void testBuilder() throws Exception {
72-
assertSame(original, new ProxyOutputStream.Builder().setOutputStream(original).get().unwrap());
72+
assertSame(target, new ProxyOutputStream.Builder().setOutputStream(target).get().unwrap());
7373
}
7474

7575
@SuppressWarnings("resource")
@@ -79,42 +79,126 @@ void testSetReference() throws Exception {
7979
proxied.setReference(new ByteArrayOutputStream());
8080
proxied.write('y');
8181
assertFalse(hitByteArray.get());
82-
assertEquals(0, original.size());
83-
assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, original.toByteArray());
82+
assertEquals(0, target.size());
83+
assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, target.toByteArray());
8484
}
8585

8686
@Test
8787
void testWriteByteArray() throws Exception {
8888
assertFalse(hitByteArray.get());
8989
proxied.write(new byte[] { 'y', 'z' });
9090
assertTrue(hitByteArray.get());
91-
assertEquals(2, original.size());
92-
assertArrayEquals(new byte[] { 'y', 'z' }, original.toByteArray());
91+
assertEquals(2, target.size());
92+
assertArrayEquals(new byte[] { 'y', 'z' }, target.toByteArray());
9393
}
9494

9595
@Test
9696
void testWriteByteArrayAt() throws Exception {
9797
assertFalse(hitByteArrayAt.get());
9898
proxied.write(new byte[] { 'y', 'z' }, 1, 1);
9999
assertTrue(hitByteArrayAt.get());
100-
assertEquals(1, original.size());
101-
assertArrayEquals(new byte[] { 'z' }, original.toByteArray());
100+
assertEquals(1, target.size());
101+
assertArrayEquals(new byte[] { 'z' }, target.toByteArray());
102+
}
103+
104+
@Test
105+
void testWriteByteArrayAtRepeat() throws Exception {
106+
// repeat -1
107+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 0);
108+
assertFalse(hitByteArrayAt.get());
109+
hitByteArray.set(false);
110+
assertEquals(0, target.size());
111+
assertArrayEquals(new byte[] {}, target.toByteArray());
112+
// repeat 0
113+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 0);
114+
assertFalse(hitByteArrayAt.get());
115+
hitByteArray.set(false);
116+
assertEquals(0, target.size());
117+
assertArrayEquals(new byte[] {}, target.toByteArray());
118+
// repeat 1
119+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 1, 1, 1);
120+
assertTrue(hitByteArrayAt.get());
121+
hitByteArray.set(false);
122+
assertEquals(1, target.size());
123+
assertArrayEquals(new byte[] { 'z' }, target.toByteArray());
124+
// repeat 2
125+
proxied.writeRepeat(new byte[] { 'y', 'x' }, 1, 1, 2);
126+
assertTrue(hitByteArrayAt.get());
127+
assertEquals(3, target.size());
128+
assertArrayEquals(new byte[] { 'z', 'x', 'x' }, target.toByteArray());
129+
}
130+
131+
@Test
132+
void testWriteByteArrayRepeat() throws Exception {
133+
// repeat -1
134+
proxied.writeRepeat(new byte[] { 'y', 'z' }, -1);
135+
assertFalse(hitByteArray.get());
136+
hitByteArray.set(false);
137+
assertEquals(0, target.size());
138+
assertArrayEquals(new byte[] {}, target.toByteArray());
139+
// repeat 0
140+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 0);
141+
assertFalse(hitByteArray.get());
142+
hitByteArray.set(false);
143+
assertEquals(0, target.size());
144+
assertArrayEquals(new byte[] {}, target.toByteArray());
145+
// repeat 1
146+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 1);
147+
assertTrue(hitByteArray.get());
148+
hitByteArray.set(false);
149+
assertEquals(2, target.size());
150+
assertArrayEquals(new byte[] { 'y', 'z' }, target.toByteArray());
151+
// repeat 2
152+
proxied.writeRepeat(new byte[] { 'y', 'z' }, 2);
153+
assertTrue(hitByteArray.get());
154+
assertEquals(6, target.size());
155+
assertArrayEquals(new byte[] { 'y', 'z', 'y', 'z' , 'y', 'z' }, target.toByteArray());
102156
}
103157

104158
@Test
105159
void testWriteInt() throws Exception {
106160
assertFalse(hitInt.get());
107161
proxied.write('y');
108162
assertTrue(hitInt.get());
109-
assertEquals(1, original.size());
110-
assertEquals('y', original.toByteArray()[0]);
163+
assertEquals(1, target.size());
164+
assertEquals('y', target.toByteArray()[0]);
165+
}
166+
167+
@Test
168+
void testWriteIntRepeat() throws Exception {
169+
// repeat -1
170+
assertFalse(hitInt.get());
171+
proxied.writeRepeat('y', -1);
172+
assertFalse(hitInt.get());
173+
assertEquals(0, target.size());
174+
assertArrayEquals(new byte[] {}, target.toByteArray());
175+
// repeat 0
176+
assertFalse(hitInt.get());
177+
proxied.writeRepeat('y', 0);
178+
assertFalse(hitInt.get());
179+
assertEquals(0, target.size());
180+
assertArrayEquals(new byte[] {}, target.toByteArray());
181+
// repeat 1
182+
assertFalse(hitInt.get());
183+
proxied.writeRepeat('y', 1);
184+
assertTrue(hitInt.get());
185+
hitInt.set(false);
186+
assertEquals(1, target.size());
187+
assertArrayEquals(new byte[] { 'y' }, target.toByteArray());
188+
// repeat 2
189+
assertFalse(hitInt.get());
190+
proxied.writeRepeat('z', 2);
191+
assertTrue(hitInt.get());
192+
hitInt.set(false);
193+
assertEquals(3, target.size());
194+
assertArrayEquals(new byte[] { 'y', 'z', 'z' }, target.toByteArray());
111195
}
112196

113197
@Test
114198
void testWriteNullArrayProxiesToUnderlying() throws Exception {
115199
assertFalse(hitByteArray.get());
116200
final byte[] ba = null;
117-
assertThrows(NullPointerException.class, () -> original.write(ba));
201+
assertThrows(NullPointerException.class, () -> target.write(ba));
118202
assertTrue(hitByteArray.get());
119203
assertThrows(NullPointerException.class, () -> proxied.write(ba));
120204
assertTrue(hitByteArray.get());

0 commit comments

Comments
 (0)