/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.internal.util.collection;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* {@link ByteBufferInputStream} unit tests.
*
* @author Marek Potociar (marek.potociar at oracle.com)
*/
public class ByteBufferInputStreamTest {
@Test
public void testBlockingReadAByteEmptyStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
bbis.closeQueue();
assertEquals(-1, bbis.read());
}
@Test
public void testNonBlockingReadAByteEmptyStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
bbis.closeQueue();
assertEquals(-1, bbis.tryRead());
}
@Test
public void testBlockingReadByteArrayEmptyStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
bbis.closeQueue();
byte[] buf = new byte[1024];
assertEquals(-1, bbis.read(buf));
}
@Test
public void testNonBlockingReadByteArrayEmptyStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
bbis.closeQueue();
byte[] buf = new byte[1024];
assertEquals(-1, bbis.tryRead(buf));
}
@Test
public void testBlockingReadByteArrayFromFinishedExactLengthStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
byte[] sourceData = new byte[1024];
new Random().nextBytes(sourceData);
ByteBuffer byteBuf = ByteBuffer.wrap(sourceData);
bbis.put(byteBuf);
bbis.closeQueue();
byte[] buf = new byte[1024];
assertEquals(1024, bbis.read(buf));
// no more data to read; so it should return -1
assertEquals(-1, bbis.read(buf));
}
@Test
public void testNonBlockingReadByteArrayFromFinishedExactLengthStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
byte[] sourceData = new byte[1024];
new Random().nextBytes(sourceData);
ByteBuffer byteBuf = ByteBuffer.wrap(sourceData);
bbis.put(byteBuf);
byte[] buf = new byte[1024];
assertEquals(1024, bbis.tryRead(buf));
// the queue has not been close; so it should return 0
assertEquals(0, bbis.tryRead(buf));
bbis.closeQueue();
}
@Test
public void testBlockingReadByteArrayFromUnfinishedExactLengthStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
byte[] sourceData = new byte[1024];
new Random().nextBytes(sourceData);
ByteBuffer byteBuf = ByteBuffer.wrap(sourceData);
bbis.put(byteBuf);
final byte[] buf = new byte[1024];
assertEquals(1024, bbis.read(buf));
final AtomicBoolean closed = new AtomicBoolean(false);
final Semaphore s = new Semaphore(1);
s.acquire();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
// it should return -1 since there is no more data
assertEquals(-1, bbis.read(buf));
// it should only reach here if the stream has been closed
assertTrue(closed.get());
} catch (IOException e) {
e.printStackTrace();
} finally {
s.release();
}
}
});
t.start();
Thread.sleep(500);
closed.set(true);
bbis.closeQueue();
// wait until the job is done
s.acquire();
}
@Test
public void testNonBlockingReadByteArrayFromUnfinishedExactLengthStream() throws Exception {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
byte[] sourceData = new byte[1024];
new Random().nextBytes(sourceData);
ByteBuffer byteBuf = ByteBuffer.wrap(sourceData);
bbis.put(byteBuf);
bbis.closeQueue();
byte[] buf = new byte[1024];
assertEquals(1024, bbis.tryRead(buf));
assertEquals(-1, bbis.tryRead(buf));
}
/**
* Test for non blocking single-byte read of the stream.
*
* @throws Exception in case of error.
*/
@Test
public void testNonBlockingReadSingleByte() throws Exception {
final int ROUNDS = 1000;
final int BUFFER_SIZE = 769;
final ByteBufferInputStream bbis = new ByteBufferInputStream();
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < ROUNDS; i++) {
final ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Got interrupted.");
return;
}
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) (i & 0xFF));
}
data.flip();
if (!bbis.put(data)) {
System.out.println("Pipe sink closed before writing all the data.");
return;
}
Thread.sleep(1); // Give the other thread a chance to run.
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
bbis.closeQueue();
}
}
});
try {
int i = 0;
int j = 0;
int c;
while ((c = bbis.tryRead()) != -1) {
if (c == Integer.MIN_VALUE) {
// nothing to read
Thread.yield(); // Give the other thread a chance to run.
continue;
}
assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) (c & 0xFF));
if (++j % BUFFER_SIZE == 0) {
i++;
Thread.yield(); // Give the other thread a chance to run.
}
}
assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j);
} finally {
executor.shutdownNow();
bbis.close();
}
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Waiting for the task to finish has timed out.");
}
}
/**
* Test for non blocking byte buffer based read of the stream.
*
* @throws Exception in case of error.
*/
@Test
public void testNonBlockingReadByteArray() throws Exception {
final int ROUNDS = 1000;
final int BUFFER_SIZE = 769;
final ByteBufferInputStream bbis = new ByteBufferInputStream();
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < ROUNDS; i++) {
final ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Got interrupted.");
return;
}
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) (i & 0xFF));
}
data.flip();
if (!bbis.put(data)) {
System.out.println("Pipe sink closed before writing all the data.");
return;
}
Thread.sleep(1); // Give the other thread a chance to run.
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
bbis.closeQueue();
}
}
});
try {
int i = 0;
int j = 0;
int c;
byte[] buffer = new byte[443];
while ((c = bbis.tryRead(buffer)) != -1) {
if (c == 0) {
// nothing to read
Thread.yield(); // Give the other thread a chance to run.
continue;
}
for (int p = 0; p < c; p++) {
assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) buffer[p]);
if (++j % BUFFER_SIZE == 0) {
i++;
Thread.yield(); // Give the other thread a chance to run.
}
}
}
assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j);
} finally {
executor.shutdownNow();
bbis.close();
}
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Waiting for the task to finish has timed out.");
}
}
/**
* Test for blocking single-byte read of the stream.
*
* @throws Exception in case of error.
*/
@Test
public void testBlockingReadSingleByte() throws Exception {
final int ROUNDS = 1000;
final int BUFFER_SIZE = 769;
final ByteBufferInputStream bbis = new ByteBufferInputStream();
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < ROUNDS; i++) {
final ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Got interrupted.");
return;
}
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) (i & 0xFF));
}
data.flip();
if (!bbis.put(data)) {
System.out.println("Pipe sink closed before writing all the data.");
return;
}
Thread.sleep(1); // Give the other thread a chance to run.
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
bbis.closeQueue();
}
}
});
try {
int i = 0;
int j = 0;
int c;
while ((c = bbis.read()) != -1) {
assertNotEquals("Should not read 'nothing' in blocking mode.", Integer.MIN_VALUE, c);
assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) c);
if (++j % BUFFER_SIZE == 0) {
i++;
Thread.yield(); // Give the other thread a chance to run.
}
}
assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j);
} finally {
executor.shutdownNow();
bbis.close();
}
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Waiting for the task to finish has timed out.");
}
}
/**
* Test for blocking byte buffer based read of the stream.
*
* @throws Exception in case of error.
*/
@Test
public void testBlockingReadByteArray() throws Exception {
final int ROUNDS = 1000;
final int BUFFER_SIZE = 769;
final ByteBufferInputStream bbis = new ByteBufferInputStream();
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < ROUNDS; i++) {
final ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Got interrupted.");
return;
}
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) (i & 0xFF));
}
data.flip();
if (!bbis.put(data)) {
System.out.println("Pipe sink closed before writing all the data.");
return;
}
Thread.sleep(1); // Give the other thread a chance to run.
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
bbis.closeQueue();
}
}
});
try {
int i = 0;
int j = 0;
int c;
byte[] buffer = new byte[443];
while ((c = bbis.read(buffer)) != -1) {
assertNotEquals("Should not read 0 bytes in blocking mode.", 0, c);
for (int p = 0; p < c; p++) {
assertEquals("At position: " + j, (byte) (i & 0xFF), buffer[p]);
if (++j % BUFFER_SIZE == 0) {
i++;
Thread.yield(); // Give the other thread a chance to run.
}
}
}
assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j);
} finally {
executor.shutdownNow();
bbis.close();
}
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Waiting for the task to finish has timed out.");
}
}
/**
* Test for available() method.
*
* @throws Exception in case of error.
*/
@Test
public void testAvailable() throws Exception {
final int BUFFER_SIZE = 769;
final ByteBufferInputStream bbis = new ByteBufferInputStream();
ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) 'A');
}
data.flip();
bbis.put(data);
assertEquals("Available bytes", BUFFER_SIZE, bbis.available());
data = ByteBuffer.allocate(BUFFER_SIZE);
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) 'B');
}
data.flip();
bbis.put(data);
assertEquals("Available bytes", 2 * BUFFER_SIZE, bbis.available());
int c = bbis.read();
assertEquals("Byte read", 'A', c);
assertEquals("Available bytes", 2 * BUFFER_SIZE - 1, bbis.available());
byte[] buff = new byte[199];
int l = bbis.read(buff);
assertEquals("Number of bytes read", buff.length, l);
assertEquals("Available bytes", 2 * BUFFER_SIZE - 200, bbis.available());
buff = new byte[1000];
l = bbis.read(buff);
assertEquals("Number of bytes read", buff.length, l);
assertEquals("Available bytes", 2 * BUFFER_SIZE - 1200, bbis.available());
bbis.closeQueue();
l = bbis.read(buff);
assertEquals("Number of bytes read", 2 * BUFFER_SIZE - 1200, l);
assertEquals("Available bytes", 0, bbis.available());
bbis.close();
}
/**
* Test for available() method.
*
* @throws Exception in case of error.
*/
@Test
public void testCloseWithThrowable() throws Exception {
final int BUFFER_SIZE = 769;
ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
data.clear();
for (int j = 0; j < data.capacity(); j++) {
data.put((byte) 'A');
}
data.flip();
// first invocation of available should fail with exception, but not subsequently due to closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().available();
}
}, "FAILED", false);
// first invocation of read should fail with exception, and subsequently due to closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().read();
}
}, "FAILED", true);
// first invocation of read should fail with exception, and subsequently due to closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().read(new byte[10]);
}
}, "FAILED", true);
// first invocation of tryRead should fail with exception, and subsequently due to closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().tryRead();
}
}, "FAILED", true);
// first invocation of tryRead should fail with exception, and subsequently due to closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().tryRead(new byte[10]);
}
}, "FAILED", true);
// first invocation of close should fail with exception, but not subsequently due closed stream
testAction(new Task(createClosedExceptionStream(data, "FAILED")) {
@Override
public void run() throws IOException {
bbis().close();
}
}, "FAILED", false);
}
private ByteBufferInputStream createClosedExceptionStream(ByteBuffer data, String exMsg) throws InterruptedException {
final ByteBufferInputStream bbis = new ByteBufferInputStream();
bbis.put(data);
bbis.closeQueue(new Exception(exMsg));
return bbis;
}
private void testAction(Task task, String exMsg, boolean retryFailOnClosed) throws IOException {
try {
task.run();
fail("IOException expected.");
} catch (IOException ex) {
assertNotNull("Custom exception cause", ex.getCause());
assertEquals("Custom exception cause message", exMsg, ex.getCause().getMessage());
}
if (retryFailOnClosed) {
try {
task.run();
fail("IOException expected.");
} catch (IOException ex) {
assertEquals("Closed IOException message", LocalizationMessages.INPUT_STREAM_CLOSED(), ex.getMessage());
assertNull("Closed IOException cause", ex.getCause());
}
} else {
task.run();
}
}
private abstract static class Task {
private final ByteBufferInputStream bbis;
protected Task(ByteBufferInputStream bbis) {
this.bbis = bbis;
}
protected final ByteBufferInputStream bbis() {
return bbis;
}
public abstract void run() throws IOException;
}
}