Skip to content

Commit 7515b30

Browse files
mkargBrian Burkhalter
authored and
Brian Burkhalter
committed
8279283: BufferedInputStream should override transferTo
Reviewed-by: bpb
1 parent 7401fe0 commit 7515b30

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

src/java.base/share/classes/java/io/BufferedInputStream.java

+29
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
package java.io;
2727

28+
import java.util.Objects;
29+
2830
import jdk.internal.misc.InternalLock;
2931
import jdk.internal.misc.Unsafe;
3032
import jdk.internal.util.ArraysSupport;
@@ -583,4 +585,31 @@ public void close() throws IOException {
583585
// Else retry in case a new buf was CASed in fill()
584586
}
585587
}
588+
589+
@Override
590+
public long transferTo(OutputStream out) throws IOException {
591+
Objects.requireNonNull(out, "out");
592+
if (lock != null) {
593+
lock.lock();
594+
try {
595+
return implTransferTo(out);
596+
} finally {
597+
lock.unlock();
598+
}
599+
} else {
600+
synchronized (this) {
601+
return implTransferTo(out);
602+
}
603+
}
604+
}
605+
606+
private long implTransferTo(OutputStream out) throws IOException {
607+
if (getClass() == BufferedInputStream.class
608+
&& ((count - pos) <= 0) && (markpos == -1)) {
609+
return getInIfOpen().transferTo(out);
610+
} else {
611+
return super.transferTo(out);
612+
}
613+
}
614+
586615
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.io.BufferedInputStream;
25+
import java.io.ByteArrayInputStream;
26+
import java.io.ByteArrayOutputStream;
27+
import java.io.InputStream;
28+
import java.io.IOException;
29+
import java.io.OutputStream;
30+
import java.nio.ByteBuffer;
31+
import java.nio.channels.Channels;
32+
import java.nio.channels.FileChannel;
33+
import java.nio.channels.IllegalBlockingModeException;
34+
import java.nio.channels.Pipe;
35+
import java.nio.channels.ReadableByteChannel;
36+
import java.nio.channels.SelectableChannel;
37+
import java.nio.channels.WritableByteChannel;
38+
import java.nio.file.Files;
39+
import java.nio.file.Path;
40+
import java.util.Arrays;
41+
import java.util.Random;
42+
import java.util.concurrent.CompletableFuture;
43+
import java.util.concurrent.ExecutionException;
44+
import java.util.concurrent.Future;
45+
import java.util.concurrent.atomic.AtomicReference;
46+
import java.util.function.Consumer;
47+
import java.util.function.Supplier;
48+
49+
import org.testng.annotations.Test;
50+
51+
import jdk.test.lib.RandomFactory;
52+
53+
import static java.lang.String.format;
54+
import static java.nio.file.StandardOpenOption.*;
55+
56+
import static org.testng.Assert.assertEquals;
57+
import static org.testng.Assert.assertThrows;
58+
import static org.testng.Assert.assertTrue;
59+
60+
/*
61+
* @test
62+
* @library /test/lib
63+
* @build jdk.test.lib.RandomFactory
64+
* @run testng/othervm/timeout=180 TransferTo
65+
* @bug 8279283
66+
* @summary Tests whether java.io.BufferedInputStream.transferTo conforms to the
67+
* InputStream.transferTo specification
68+
* @key randomness
69+
*/
70+
public class TransferTo {
71+
private static final int MIN_SIZE = 10_000;
72+
private static final int MAX_SIZE_INCR = 100_000_000 - MIN_SIZE;
73+
74+
private static final int ITERATIONS = 10;
75+
76+
private static final Random RND = RandomFactory.getRandom();
77+
78+
/*
79+
* Testing API compliance: input stream must throw NullPointerException
80+
* when parameter "out" is null.
81+
*/
82+
@Test
83+
public void testNullPointerException() throws Exception {
84+
// factory for incoming data provider
85+
InputStreamProvider inputStreamProvider = byteArrayInput();
86+
87+
// tests empty input stream
88+
assertThrows(NullPointerException.class,
89+
() -> inputStreamProvider.input().transferTo(null));
90+
91+
// tests single-byte input stream
92+
assertThrows(NullPointerException.class,
93+
() -> inputStreamProvider.input((byte) 1).transferTo(null));
94+
95+
// tests dual-byte input stream
96+
assertThrows(NullPointerException.class,
97+
() -> inputStreamProvider.input((byte) 1, (byte) 2).transferTo(null));
98+
}
99+
100+
/*
101+
* Testing API compliance: complete content of input stream must be
102+
* transferred to output stream.
103+
*/
104+
@Test
105+
public void testStreamContents() throws Exception {
106+
// factory for incoming data provider
107+
InputStreamProvider inputStreamProvider = byteArrayInput();
108+
109+
// factory for outgoing data recorder
110+
OutputStreamProvider outputStreamProvider = byteArrayOutput();
111+
112+
// tests empty input stream
113+
checkTransferredContents(inputStreamProvider,
114+
outputStreamProvider, new byte[0]);
115+
116+
// tests input stream with a length between 1k and 4k
117+
checkTransferredContents(inputStreamProvider,
118+
outputStreamProvider, createRandomBytes(1024, 4096));
119+
120+
// tests input stream with several data chunks, as 16k is more than a
121+
// single chunk can hold
122+
checkTransferredContents(inputStreamProvider,
123+
outputStreamProvider, createRandomBytes(16384, 16384));
124+
125+
// tests randomly chosen starting positions within source and
126+
// target stream and random buffer level
127+
for (int i = 0; i < ITERATIONS; i++) {
128+
byte[] inBytes = createRandomBytes(MIN_SIZE, MAX_SIZE_INCR);
129+
int posIn = RND.nextInt(inBytes.length);
130+
int posOut = RND.nextInt(MIN_SIZE);
131+
int bufferBytes = RND.nextInt(inBytes.length - posIn);
132+
boolean markAndReset = RND.nextBoolean();
133+
checkTransferredContents(inputStreamProvider,
134+
outputStreamProvider, inBytes, posIn, posOut, bufferBytes, markAndReset);
135+
}
136+
137+
// tests reading beyond source EOF (must not transfer any bytes)
138+
checkTransferredContents(inputStreamProvider,
139+
outputStreamProvider, createRandomBytes(4096, 0), 4096, 0, 0, false);
140+
141+
// tests writing beyond target EOF (must extend output stream)
142+
checkTransferredContents(inputStreamProvider,
143+
outputStreamProvider, createRandomBytes(4096, 0), 0, 4096, 0, false);
144+
}
145+
146+
/*
147+
* Asserts that the transferred content is correct, i.e., compares the bytes
148+
* actually transferred to those expected. The position of the input and
149+
* output streams before the transfer are zero (BOF).
150+
*/
151+
private static void checkTransferredContents(InputStreamProvider inputStreamProvider,
152+
OutputStreamProvider outputStreamProvider, byte[] inBytes) throws Exception {
153+
checkTransferredContents(inputStreamProvider,
154+
outputStreamProvider, inBytes, 0, 0, 0, false);
155+
}
156+
157+
/*
158+
* Asserts that the transferred content is correct, i. e. compares the bytes
159+
* actually transferred to those expected. The positions of the input and
160+
* output streams before the transfer are provided by the caller.
161+
*/
162+
private static void checkTransferredContents(InputStreamProvider inputStreamProvider,
163+
OutputStreamProvider outputStreamProvider, byte[] inBytes, int posIn,
164+
int posOut, int bufferBytes, boolean markAndReset) throws Exception {
165+
AtomicReference<Supplier<byte[]>> recorder = new AtomicReference<>();
166+
try (InputStream in = inputStreamProvider.input(inBytes);
167+
OutputStream out = outputStreamProvider.output(recorder::set)) {
168+
// skip bytes until starting position
169+
in.skipNBytes(posIn);
170+
out.write(new byte[posOut]);
171+
172+
// fill buffer by reading some bytes before transferTo
173+
byte[] bytes = new byte[bufferBytes];
174+
in.read(bytes);
175+
out.write(bytes);
176+
177+
// set mark at current position for later replay
178+
if (markAndReset) {
179+
in.mark(Integer.MAX_VALUE);
180+
}
181+
182+
long reported = in.transferTo(out);
183+
int count = inBytes.length - posIn;
184+
int expected = count - bufferBytes;
185+
186+
assertEquals(reported, expected,
187+
format("transferred %d bytes but should report %d", reported, expected));
188+
189+
byte[] outBytes = recorder.get().get();
190+
assertTrue(Arrays.equals(inBytes, posIn, posIn + count,
191+
outBytes, posOut, posOut + count),
192+
format("inBytes.length=%d, outBytes.length=%d", count, outBytes.length));
193+
194+
// replay from marked position
195+
if (markAndReset) {
196+
in.reset();
197+
198+
reported = in.transferTo(out);
199+
expected = count - bufferBytes;
200+
201+
assertEquals(reported, expected,
202+
format("replayed %d bytes but should report %d", reported, expected));
203+
204+
outBytes = recorder.get().get();
205+
assertTrue(Arrays.equals(inBytes, posIn + bufferBytes, inBytes.length,
206+
outBytes, posOut + count, outBytes.length),
207+
format("inBytes.length=%d, outBytes.length=%d",
208+
inBytes.length - posIn - bufferBytes,
209+
outBytes.length - posOut - count));
210+
}
211+
}
212+
}
213+
214+
/*
215+
* Creates an array of random size (between min and min + maxRandomAdditive)
216+
* filled with random bytes
217+
*/
218+
private static byte[] createRandomBytes(int min, int maxRandomAdditive) {
219+
byte[] bytes = new byte[min +
220+
(maxRandomAdditive == 0 ? 0 : RND.nextInt(maxRandomAdditive))];
221+
RND.nextBytes(bytes);
222+
return bytes;
223+
}
224+
225+
private interface InputStreamProvider {
226+
InputStream input(byte... bytes) throws Exception;
227+
}
228+
229+
private interface OutputStreamProvider {
230+
OutputStream output(Consumer<Supplier<byte[]>> spy) throws Exception;
231+
}
232+
233+
private static InputStreamProvider byteArrayInput() {
234+
return bytes -> new BufferedInputStream(new ByteArrayInputStream(bytes));
235+
}
236+
237+
private static OutputStreamProvider byteArrayOutput() {
238+
return new OutputStreamProvider() {
239+
@Override
240+
public OutputStream output(Consumer<Supplier<byte[]>> spy) {
241+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
242+
spy.accept(outputStream::toByteArray);
243+
return outputStream;
244+
}
245+
};
246+
}
247+
248+
}

0 commit comments

Comments
 (0)