Skip to content

Commit

Permalink
New crux-util ByteBufferInputStream, ByteInput, Unclosable input/outp…
Browse files Browse the repository at this point in the history
…ut stream utility classes
  • Loading branch information
jjlauer committed Nov 6, 2024
1 parent 3cad435 commit 59c392c
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.fizzed.crux.util;

import java.io.InputStream;
import java.nio.ByteBuffer;

public class ByteBufferInputStream extends InputStream {

private final ByteBuffer buf;

public ByteBufferInputStream(ByteBuffer buffer) {
if (buffer == null) throw new NullPointerException();
buf = buffer;
}

@Override
public synchronized int read() {
if (buf.hasRemaining()) return buf.get() & 0xff;
return -1;
}

@Override
public synchronized int read(byte b[]) {
return read(0, b.length, b);
}

@Override
public synchronized int read(byte b[], int off, int len) {
if ((off | len | off + len | b.length - (off + len)) < 0) {
throw new IndexOutOfBoundsException();
}
return read(off, len, b);
}

private int read(int off, int len, byte[] b) {
if (len == 0) return 0;

int rem = buf.remaining();
if (rem <= 0) return -1;

if (rem > len) rem = len;
buf.get(b, off, rem);
return rem;
}

@Override
public synchronized long skip(long n) {
if (n <= 0) return 0;

int rem = buf.remaining();
if (n > rem) n = rem;

buf.position((int)(buf.position() + n));
return n;
}

@Override
public synchronized int available() { return buf.remaining(); }

@Override
public synchronized void mark(int readAheadLimit) { buf.mark(); }

@Override
public synchronized void reset() { buf.reset(); }

@Override
public boolean markSupported() { return true; }

@Override
public void close() {}
}
119 changes: 119 additions & 0 deletions crux-util/src/main/java/com/fizzed/crux/util/ByteInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.fizzed.crux.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

/**
* Generic way of providing "byte inputs" as files, byte arrays, etc. to methods.
*/
public class ByteInput {

protected final Path file;
protected final byte[] bytes;
protected final ByteBuffer buffer;
protected final InputStream in;
protected final boolean closeable;

static public ByteInput byteInput(Path file) {
Objects.requireNonNull(file, "file cannot be null");
return new ByteInput(file, null, null, null, true);
}

static public ByteInput byteInput(File file) {
Objects.requireNonNull(file, "file cannot be null");
return new ByteInput(file.toPath(), null, null, null, true);
}

static public ByteInput byteInput(byte[] bytes) {
Objects.requireNonNull(bytes, "bytes cannot be null");
return new ByteInput(null, bytes, null, null, true);
}

static public ByteInput byteInput(ByteBuffer buffer) {
Objects.requireNonNull(buffer, "buffer cannot be null");
return new ByteInput(null, null, buffer, null, true);
}

static public ByteInput byteInput(InputStream in) {
Objects.requireNonNull(in, "input cannot be null");
return new ByteInput(null, null, null, in, true);
}

static public ByteInput byteInput(InputStream in, boolean closeable) {
Objects.requireNonNull(in, "input cannot be null");
return new ByteInput(null, null, null, in, closeable);
}

protected Long size;

protected ByteInput(Path file, byte[] bytes, ByteBuffer buffer, InputStream in, boolean closeable) {
this.file = file;
this.bytes = bytes;
this.buffer = buffer;
this.in = in;
this.closeable = closeable;
}

/**
* If the size is definitively known ahead of time, this is the number of bytes. Will work on files, byte arrays,
* and byte buffers, but others will return -1 since the size is not known definitively.
* @return The number of bytes this input represents or -1 if that size is not definitively known
* @throws IOException
*/
public long available() throws IOException {
if (this.file != null || this.bytes != null || this.buffer != null) {
return this.size();
} else if (this.in != null) {
return this.in.available();
}
return -1L;
}

/**
* If the size is definitively known ahead of time, this is the number of bytes. Will work on files, byte arrays,
* and byte buffers, but others will return -1 since the size is not known definitively.
* @return The number of bytes this input represents or -1 if that size is not definitively known
* @throws IOException
*/
public long size() throws IOException {
if (size == null) {
if (this.file != null) {
this.size = Files.size(this.file);
} else if (this.bytes != null) {
this.size = (long)this.bytes.length;
} else if (this.buffer != null) {
this.size = (long)this.buffer.remaining();
} else {
// otherwise we just don't know what size this is
this.size = -1l;
}
}
return this.size;
}

public InputStream open() throws IOException {
if (this.file != null) {
return Files.newInputStream(this.file);
} else if (this.bytes != null) {
return new ByteArrayInputStream(this.bytes);
} else if (this.buffer != null) {
return new ByteBufferInputStream(this.buffer);
} else if (this.in != null) {
if (this.closeable) {
return this.in;
} else {
// wrap input stream in an un-closeable version
return new UnclosableInputStream(this.in);
}
} else {
throw new IllegalStateException("All available inputs were null (file, bytes, input)");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.fizzed.crux.util;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class UnclosableInputStream extends FilterInputStream {

protected boolean closed;

public UnclosableInputStream(InputStream in) {
super(in);
}

public boolean isClosed() {
return this.closed;
}

@Override
public int read() throws IOException {
if (this.closed) return -1;
return super.read();
}

@Override
public int read(byte[] b) throws IOException {
if (this.closed) return -1;
return super.read(b);
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
if (this.closed) return -1;
return super.read(b, off, len);
}

@Override
public int available() throws IOException {
if (this.closed) throw new IOException("Stream closed");
return super.available();
}

@Override
public void close() throws IOException {
this.closed = true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.fizzed.crux.util;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class UnclosableOutputStream extends FilterOutputStream {

private boolean closed;

public UnclosableOutputStream(OutputStream output) {
super(output);
this.closed = false;
}

public boolean isClosed() {
return this.closed;
}

@Override
public void write(int b) throws IOException {
if (closed) throw new IOException("Stream closed");
super.write(b);
}

@Override
public void write(byte[] b) throws IOException {
if (closed) throw new IOException("Stream closed");
super.write(b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
if (closed) throw new IOException("Stream closed");
super.write(b, off, len);
}

@Override
public void flush() throws IOException {
if (closed) throw new IOException("Stream closed");
super.flush();
}

@Override
public void close() throws IOException {
this.closed = true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.fizzed.crux.util;

import org.junit.Test;

import java.nio.ByteBuffer;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

public class ByteBufferInputStreamTest {

@Test
public void read() {
final ByteBuffer buf = ByteBuffer.wrap("abcdef".getBytes());

final ByteBufferInputStream in = new ByteBufferInputStream(buf);
int read;

assertThat(in.available(), is(6));
assertThat(in.read(), is((int)'a'));

final byte[] bytes = new byte[2];
read = in.read(bytes);
assertThat(read, is(2));
assertThat(bytes[0], is((byte)'b'));
assertThat(bytes[1], is((byte)'c'));

read = in.read(bytes, 1, 1);
assertThat(read, is(1));
assertThat(bytes[1], is((byte)'d'));

assertThat(in.available(), is(2));
read = in.read(bytes);
assertThat(read, is(2));
assertThat(bytes[0], is((byte)'e'));
assertThat(bytes[1], is((byte)'f'));

// this should be EOF now
read = in.read(bytes);
assertThat(read, is(-1));
}

}
Loading

0 comments on commit 59c392c

Please sign in to comment.