/* Copyright (c) 2011 Danish Maritime Authority.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package net.maritimecloud.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
import junit.framework.TestCase;
/**
* Test methods with implementations in {@link Binary}, plus do some top-level "integration" tests.
*
* @author carlanton@google.com (Carl Haverl)
*/
// CHECKSTYLE:OFF
public class ByteStringTest extends TestCase {
private static final String UTF_16 = "UTF-16";
static byte[] getTestBytes(int size, long seed) {
Random random = new Random(seed);
byte[] result = new byte[size];
random.nextBytes(result);
return result;
}
private byte[] getTestBytes(int size) {
return getTestBytes(size, 445566L);
}
private byte[] getTestBytes() {
return getTestBytes(1000);
}
// Compare the entire left array with a subset of the right array.
private boolean isArrayRange(byte[] left, byte[] right, int rightOffset, int length) {
boolean stillEqual = left.length == length;
for (int i = 0; stillEqual && i < length; ++i) {
stillEqual = left[i] == right[rightOffset + i];
}
return stillEqual;
}
// Returns true only if the given two arrays have identical contents.
private boolean isArray(byte[] left, byte[] right) {
return left.length == right.length && isArrayRange(left, right, 0, left.length);
}
public void testSubstring_BeginIndex() {
byte[] bytes = getTestBytes();
Binary substring = Binary.copyFrom(bytes).substring(500);
assertTrue("substring must contain the tail of the string",
isArrayRange(substring.toByteArray(), bytes, 500, bytes.length - 500));
}
public void testCopyFrom_BytesOffsetSize() {
byte[] bytes = getTestBytes();
Binary byteString = Binary.copyFrom(bytes, 500, 200);
assertTrue("copyFrom sub-range must contain the expected bytes",
isArrayRange(byteString.toByteArray(), bytes, 500, 200));
}
public void testCopyFrom_Bytes() {
byte[] bytes = getTestBytes();
Binary byteString = Binary.copyFrom(bytes);
assertTrue("copyFrom must contain the expected bytes", isArray(byteString.toByteArray(), bytes));
}
public void testCopyFrom_ByteBufferSize() {
byte[] bytes = getTestBytes();
ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.position(500);
Binary byteString = Binary.copyFrom(byteBuffer, 200);
assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
isArrayRange(byteString.toByteArray(), bytes, 500, 200));
}
public void testCopyFrom_ByteBuffer() {
byte[] bytes = getTestBytes();
ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.position(500);
Binary byteString = Binary.copyFrom(byteBuffer);
assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500));
}
public void testCopyFrom_StringEncoding() throws UnsupportedEncodingException {
String testString = "I love unicode \u1234\u5678 characters";
Binary byteString = Binary.copyFrom(testString, UTF_16);
byte[] testBytes = testString.getBytes(UTF_16);
assertTrue("copyFrom string must respect the charset",
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
}
public void testCopyFrom_Utf8() throws UnsupportedEncodingException {
String testString = "I love unicode \u1234\u5678 characters";
Binary byteString = Binary.copyFromUtf8(testString);
byte[] testBytes = testString.getBytes("UTF-8");
assertTrue("copyFromUtf8 string must respect the charset",
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
}
public void testCopyFrom_Iterable() {
byte[] testBytes = getTestBytes(77777, 113344L);
final List<Binary> pieces = makeConcretePieces(testBytes);
// Call copyFrom() on a Collection
Binary byteString = Binary.copyFrom(pieces);
assertTrue("copyFrom a List must contain the expected bytes",
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
// Call copyFrom on an iteration that's not a collection
Binary byteStringAlt = Binary.copyFrom(new Iterable<Binary>() {
public Iterator<Binary> iterator() {
return pieces.iterator();
}
});
assertEquals("copyFrom from an Iteration must contain the expected bytes", byteString, byteStringAlt);
}
public void testCopyTo_TargetOffset() {
byte[] bytes = getTestBytes();
Binary byteString = Binary.copyFrom(bytes);
byte[] target = new byte[bytes.length + 1000];
byteString.copyTo(target, 400);
assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
isArrayRange(bytes, target, 400, bytes.length));
}
public void testReadFrom_emptyStream() throws IOException {
Binary byteString = Binary.readFrom(new ByteArrayInputStream(new byte[0]));
assertSame("reading an empty stream must result in the EMPTY constant " + "byte string", Binary.EMPTY,
byteString);
}
public void testReadFrom_smallStream() throws IOException {
assertReadFrom(getTestBytes(10));
}
public void testReadFrom_mutating() throws IOException {
byte[] capturedArray = null;
EvilInputStream eis = new EvilInputStream();
Binary byteString = Binary.readFrom(eis);
capturedArray = eis.capturedArray;
byte[] originalValue = byteString.toByteArray();
for (int x = 0; x < capturedArray.length; ++x) {
capturedArray[x] = (byte) 0;
}
byte[] newValue = byteString.toByteArray();
assertTrue("copyFrom byteBuffer must not grant access to underlying array",
Arrays.equals(originalValue, newValue));
}
// Tests sizes that are near the rope copy-out threshold.
public void testReadFrom_mediumStream() throws IOException {
assertReadFrom(getTestBytes(Binary.CONCATENATE_BY_COPY_SIZE - 1));
assertReadFrom(getTestBytes(Binary.CONCATENATE_BY_COPY_SIZE));
assertReadFrom(getTestBytes(Binary.CONCATENATE_BY_COPY_SIZE + 1));
assertReadFrom(getTestBytes(200));
}
// Tests sizes that are over multi-segment rope threshold.
public void testReadFrom_largeStream() throws IOException {
assertReadFrom(getTestBytes(0x100));
assertReadFrom(getTestBytes(0x101));
assertReadFrom(getTestBytes(0x110));
assertReadFrom(getTestBytes(0x1000));
assertReadFrom(getTestBytes(0x1001));
assertReadFrom(getTestBytes(0x1010));
assertReadFrom(getTestBytes(0x10000));
assertReadFrom(getTestBytes(0x10001));
assertReadFrom(getTestBytes(0x10010));
}
// Tests sizes that are near the read buffer size.
public void testReadFrom_byteBoundaries() throws IOException {
final int min = Binary.MIN_READ_FROM_CHUNK_SIZE;
final int max = Binary.MAX_READ_FROM_CHUNK_SIZE;
assertReadFrom(getTestBytes(min - 1));
assertReadFrom(getTestBytes(min));
assertReadFrom(getTestBytes(min + 1));
assertReadFrom(getTestBytes(min * 2 - 1));
assertReadFrom(getTestBytes(min * 2));
assertReadFrom(getTestBytes(min * 2 + 1));
assertReadFrom(getTestBytes(min * 4 - 1));
assertReadFrom(getTestBytes(min * 4));
assertReadFrom(getTestBytes(min * 4 + 1));
assertReadFrom(getTestBytes(min * 8 - 1));
assertReadFrom(getTestBytes(min * 8));
assertReadFrom(getTestBytes(min * 8 + 1));
assertReadFrom(getTestBytes(max - 1));
assertReadFrom(getTestBytes(max));
assertReadFrom(getTestBytes(max + 1));
assertReadFrom(getTestBytes(max * 2 - 1));
assertReadFrom(getTestBytes(max * 2));
assertReadFrom(getTestBytes(max * 2 + 1));
}
// Tests that IOExceptions propagate through ByteString.readFrom().
public void testReadFrom_IOExceptions() {
try {
Binary.readFrom(new FailStream());
fail("readFrom must throw the underlying IOException");
} catch (IOException e) {
assertEquals("readFrom must throw the expected exception", "synthetic failure", e.getMessage());
}
}
// Tests that ByteString.readFrom works with streams that don't
// always fill their buffers.
public void testReadFrom_reluctantStream() throws IOException {
final byte[] data = getTestBytes(0x1000);
Binary byteString = Binary.readFrom(new ReluctantStream(data));
assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), data));
// Same test as above, but with some specific chunk sizes.
assertReadFromReluctantStream(data, 100);
assertReadFromReluctantStream(data, 248);
assertReadFromReluctantStream(data, 249);
assertReadFromReluctantStream(data, 250);
assertReadFromReluctantStream(data, 251);
assertReadFromReluctantStream(data, 0x1000);
assertReadFromReluctantStream(data, 0x1001);
}
// Fails unless ByteString.readFrom reads the bytes correctly from a
// reluctant stream with the given chunkSize parameter.
private void assertReadFromReluctantStream(byte[] bytes, int chunkSize) throws IOException {
Binary b = Binary.readFrom(new ReluctantStream(bytes), chunkSize);
assertTrue("readFrom byte stream must contain the expected bytes", isArray(b.toByteArray(), bytes));
}
// Tests that ByteString.readFrom works with streams that implement
// available().
public void testReadFrom_available() throws IOException {
final byte[] data = getTestBytes(0x1001);
Binary byteString = Binary.readFrom(new AvailableStream(data));
assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), data));
}
// Fails unless ByteString.readFrom reads the bytes correctly.
private void assertReadFrom(byte[] bytes) throws IOException {
Binary byteString = Binary.readFrom(new ByteArrayInputStream(bytes));
assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), bytes));
}
public void testToStringUtf8() throws UnsupportedEncodingException {
String testString = "I love unicode \u1234\u5678 characters";
byte[] testBytes = testString.getBytes("UTF-8");
Binary byteString = Binary.copyFrom(testBytes);
assertEquals("copyToStringUtf8 must respect the charset", testString, byteString.toStringUtf8());
}
public void testNewOutput_InitialCapacity() throws IOException {
byte[] bytes = getTestBytes();
Binary.Output output = Binary.newOutput(bytes.length + 100);
output.write(bytes);
Binary byteString = output.toBinary();
assertTrue("String built from newOutput(int) must contain the expected bytes",
isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
}
// Test newOutput() using a variety of buffer sizes and a variety of (fixed)
// write sizes
public void testNewOutput_ArrayWrite() {
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = { 128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length };
int[] writeSizes = { 1, 4, 5, 7, 23, bytes.length };
for (int bufferSize : bufferSizes) {
for (int writeSize : writeSizes) {
// Test writing the entire output writeSize bytes at a time.
Binary.Output output = Binary.newOutput(bufferSize);
for (int i = 0; i < length; i += writeSize) {
output.write(bytes, i, Math.min(writeSize, length - i));
}
Binary byteString = output.toBinary();
assertTrue("String built from newOutput() must contain the expected bytes",
isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
}
}
}
// Test newOutput() using a variety of buffer sizes, but writing all the
// characters using write(byte);
public void testNewOutput_WriteChar() {
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = { 0, 1, 128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length };
for (int bufferSize : bufferSizes) {
Binary.Output output = Binary.newOutput(bufferSize);
for (byte byteValue : bytes) {
output.write(byteValue);
}
Binary byteString = output.toBinary();
assertTrue("String built from newOutput() must contain the expected bytes",
isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
}
}
// Test newOutput() in which we write the bytes using a variety of methods
// and sizes, and in which we repeatedly call toByteString() in the middle.
public void testNewOutput_Mixed() {
Random rng = new Random(1);
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = { 0, 1, 128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length };
for (int bufferSize : bufferSizes) {
// Test writing the entire output using a mixture of write sizes and
// methods;
Binary.Output output = Binary.newOutput(bufferSize);
int position = 0;
while (position < bytes.length) {
if (rng.nextBoolean()) {
int count = 1 + rng.nextInt(bytes.length - position);
output.write(bytes, position, count);
position += count;
} else {
output.write(bytes[position]);
position++;
}
assertEquals("size() returns the right value", position, output.size());
assertTrue("newOutput() substring must have correct bytes",
isArrayRange(output.toBinary().toByteArray(), bytes, 0, position));
}
Binary byteString = output.toBinary();
assertTrue("String built from newOutput() must contain the expected bytes",
isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
}
}
public void testNewOutputEmpty() {
// Make sure newOutput() correctly builds empty byte strings
Binary byteString = Binary.newOutput().toBinary();
assertEquals(Binary.EMPTY, byteString);
}
public void testNewOutput_Mutating() throws IOException {
Binary.Output os = Binary.newOutput(5);
os.write(new byte[] { 1, 2, 3, 4, 5 });
EvilOutputStream eos = new EvilOutputStream();
os.writeTo(eos);
byte[] capturedArray = eos.capturedArray;
Binary byteString = os.toBinary();
byte[] oldValue = byteString.toByteArray();
Arrays.fill(capturedArray, (byte) 0);
byte[] newValue = byteString.toByteArray();
assertTrue("Output must not provide access to the underlying byte array", Arrays.equals(oldValue, newValue));
}
//
// public void testNewCodedBuilder() throws IOException {
// byte[] bytes = getTestBytes();
// ByteString.CodedBuilder builder = ByteString.newCodedBuilder(bytes.length);
// builder.getCodedOutput().writeRawBytes(bytes);
// ByteString byteString = builder.build();
// assertTrue("String built from newCodedBuilder() must contain the expected bytes",
// isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
// }
public void testSubstringParity() {
byte[] bigBytes = getTestBytes(2048 * 1024, 113344L);
int start = 512 * 1024 - 3333;
int end = 512 * 1024 + 7777;
Binary concreteSubstring = Binary.copyFrom(bigBytes).substring(start, end);
boolean ok = true;
for (int i = start; ok && i < end; ++i) {
ok = bigBytes[i] == concreteSubstring.byteAt(i - start);
}
assertTrue("Concrete substring didn't capture the right bytes", ok);
Binary literalString = Binary.copyFrom(bigBytes, start, end - start);
assertTrue("Substring must be equal to literal string", concreteSubstring.equals(literalString));
assertEquals("Substring must have same hashcode as literal string", literalString.hashCode(),
concreteSubstring.hashCode());
}
public void testCompositeSubstring() {
byte[] referenceBytes = getTestBytes(77748, 113344L);
List<Binary> pieces = makeConcretePieces(referenceBytes);
Binary listString = Binary.copyFrom(pieces);
int from = 1000;
int to = 40000;
Binary compositeSubstring = listString.substring(from, to);
byte[] substringBytes = compositeSubstring.toByteArray();
boolean stillEqual = true;
for (int i = 0; stillEqual && i < to - from; ++i) {
stillEqual = referenceBytes[from + i] == substringBytes[i];
}
assertTrue("Substring must return correct bytes", stillEqual);
stillEqual = true;
for (int i = 0; stillEqual && i < to - from; ++i) {
stillEqual = referenceBytes[from + i] == compositeSubstring.byteAt(i);
}
assertTrue("Substring must support byteAt() correctly", stillEqual);
Binary literalSubstring = Binary.copyFrom(referenceBytes, from, to - from);
assertTrue("Composite substring must equal a literal substring over the same bytes",
compositeSubstring.equals(literalSubstring));
assertTrue("Literal substring must equal a composite substring over the same bytes",
literalSubstring.equals(compositeSubstring));
assertEquals("We must get the same hashcodes for composite and literal substrings",
literalSubstring.hashCode(), compositeSubstring.hashCode());
assertFalse("We can't be equal to a proper substring",
compositeSubstring.equals(literalSubstring.substring(0, literalSubstring.size() - 1)));
}
public void testCopyFromList() {
byte[] referenceBytes = getTestBytes(77748, 113344L);
Binary literalString = Binary.copyFrom(referenceBytes);
List<Binary> pieces = makeConcretePieces(referenceBytes);
Binary listString = Binary.copyFrom(pieces);
assertTrue("Composite string must be equal to literal string", listString.equals(literalString));
assertEquals("Composite string must have same hashcode as literal string", literalString.hashCode(),
listString.hashCode());
}
public void testConcat() {
byte[] referenceBytes = getTestBytes(77748, 113344L);
Binary literalString = Binary.copyFrom(referenceBytes);
List<Binary> pieces = makeConcretePieces(referenceBytes);
Iterator<Binary> iter = pieces.iterator();
Binary concatenatedString = iter.next();
while (iter.hasNext()) {
concatenatedString = concatenatedString.concat(iter.next());
}
assertTrue("Concatenated string must be equal to literal string", concatenatedString.equals(literalString));
assertEquals("Concatenated string must have same hashcode as literal string", literalString.hashCode(),
concatenatedString.hashCode());
}
/**
* Test the Rope implementation can deal with Empty nodes, even though we guard against them. See also
* {@link LiteralByteStringTest#testConcat_empty()}.
*/
public void testConcat_empty() {
byte[] referenceBytes = getTestBytes(7748, 113344L);
Binary literalString = Binary.copyFrom(referenceBytes);
Binary duo = RopeBinary.newInstanceForTest(literalString, literalString);
Binary temp = RopeBinary.newInstanceForTest(RopeBinary.newInstanceForTest(literalString, Binary.EMPTY),
RopeBinary.newInstanceForTest(Binary.EMPTY, literalString));
Binary quintet = RopeBinary.newInstanceForTest(temp, Binary.EMPTY);
assertTrue("String with concatenated nulls must equal simple concatenate", duo.equals(quintet));
assertEquals("String with concatenated nulls have same hashcode as simple concatenate", duo.hashCode(),
quintet.hashCode());
Binary.ByteIterator duoIter = duo.iterator();
Binary.ByteIterator quintetIter = quintet.iterator();
boolean stillEqual = true;
while (stillEqual && quintetIter.hasNext()) {
stillEqual = duoIter.nextByte() == quintetIter.nextByte();
}
assertTrue("We must get the same characters by iterating", stillEqual);
assertFalse("Iterator must be exhausted", duoIter.hasNext());
try {
duoIter.nextByte();
fail("Should have thrown an exception.");
} catch (NoSuchElementException e) {
// This is success
}
try {
quintetIter.nextByte();
fail("Should have thrown an exception.");
} catch (NoSuchElementException e) {
// This is success
}
// Test that even if we force empty strings in as rope leaves in this
// configuration, we always get a (possibly Bounded) LiteralByteString
// for a length 1 substring.
//
// It is possible, using the testing factory method to create deeply nested
// trees of empty leaves, to make a string that will fail this test.
for (int i = 1; i < duo.size(); ++i) {
assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
duo.substring(i - 1, i) instanceof LiteralBinary);
}
for (int i = 1; i < quintet.size(); ++i) {
assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
quintet.substring(i - 1, i) instanceof LiteralBinary);
}
}
public void testStartsWith() {
byte[] bytes = getTestBytes(1000, 1234L);
Binary string = Binary.copyFrom(bytes);
Binary prefix = Binary.copyFrom(bytes, 0, 500);
Binary suffix = Binary.copyFrom(bytes, 400, 600);
assertTrue(string.startsWith(Binary.EMPTY));
assertTrue(string.startsWith(string));
assertTrue(string.startsWith(prefix));
assertFalse(string.startsWith(suffix));
assertFalse(prefix.startsWith(suffix));
assertFalse(suffix.startsWith(prefix));
assertFalse(Binary.EMPTY.startsWith(prefix));
assertTrue(Binary.EMPTY.startsWith(Binary.EMPTY));
}
static List<Binary> makeConcretePieces(byte[] referenceBytes) {
List<Binary> pieces = new ArrayList<>();
// Starting length should be small enough that we'll do some concatenating by
// copying if we just concatenate all these pieces together.
for (int start = 0, length = 16; start < referenceBytes.length; start += length) {
length = (length << 1) - 1;
if (start + length > referenceBytes.length) {
length = referenceBytes.length - start;
}
pieces.add(Binary.copyFrom(referenceBytes, start, length));
}
return pieces;
}
// A stream that fails when read.
static final class FailStream extends InputStream {
@Override
public int read() throws IOException {
throw new IOException("synthetic failure");
}
}
// A stream that simulates blocking by only producing 250 characters
// per call to read(byte[]).
private static class ReluctantStream extends InputStream {
protected final byte[] data;
protected int pos;
public ReluctantStream(byte[] data) {
this.data = data;
}
@Override
public int read() {
if (pos == data.length) {
return -1;
} else {
return data[pos++];
}
}
@Override
public int read(byte[] buf) {
return read(buf, 0, buf.length);
}
@Override
public int read(byte[] buf, int offset, int size) {
if (pos == data.length) {
return -1;
}
int count = Math.min(Math.min(size, data.length - pos), 250);
System.arraycopy(data, pos, buf, offset, count);
pos += count;
return count;
}
}
// Same as above, but also implements available().
private static final class AvailableStream extends ReluctantStream {
public AvailableStream(byte[] data) {
super(data);
}
@Override
public int available() {
return Math.min(250, data.length - pos);
}
}
// A stream which exposes the byte array passed into read(byte[], int, int).
static class EvilInputStream extends InputStream {
public byte[] capturedArray;
@Override
public int read(byte[] buf, int off, int len) {
if (capturedArray != null) {
return -1;
} else {
capturedArray = buf;
for (int x = 0; x < len; ++x) {
buf[x] = (byte) x;
}
return len;
}
}
@Override
public int read() {
// Purposefully do nothing.
return -1;
}
}
// A stream which exposes the byte array passed into write(byte[], int, int).
static class EvilOutputStream extends OutputStream {
public byte[] capturedArray;
@Override
public void write(byte[] buf, int off, int len) {
if (capturedArray == null) {
capturedArray = buf;
}
}
@Override
public void write(int ignored) {
// Purposefully do nothing.
}
}
}
// CHECKSTYLE:ON