I also wanted a hash of the whole data stream to add to the file meta-data.
I considered making a decorator output stream that would calculate the hash of all the data that passed through it, but then also decided that it would be simpler to build a sink that does the hashing, and a tee to send the data to the two sinks (hashing, and encoding). Another argument for not using a filter is that the hashing does not change the data that is passing through.
The tee looks like this:
package org.yi.happy.archive;
import java.io.IOException;
import java.io.OutputStream;
/**
* An output stream that writes to two output streams.
*/
public class TeeOutputStream extends OutputStream {
private final OutputStream out1;
private final OutputStream out2;
/**
* create an output stream that writes to two output streams.
*
* @param out1
* the first stream to write to.
* @param out2
* the second stream to write to.
*/
public TeeOutputStream(OutputStream out1, OutputStream out2) {
try {
this.out1 = out1;
} finally {
this.out2 = out2;
}
}
@Override
public void write(int b) throws IOException {
try {
out1.write(b);
} finally {
out2.write(b);
}
}
@Override
public void write(byte[] b) throws IOException {
try {
out1.write(b);
} finally {
out2.write(b);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
out1.write(b, off, len);
} finally {
out2.write(b, off, len);
}
}
@Override
public void flush() throws IOException {
try {
out1.flush();
} finally {
out2.flush();
}
}
@Override
public void close() throws IOException {
try {
out1.close();
} finally {
out2.close();
}
}
}
Also the hashing output stream looks like this:
package org.yi.happy.archive;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import org.yi.happy.archive.key.HashValue;
/**
* An output stream that calculates the digest of whatever is written to it.
*/
public class DigestOutputStream extends OutputStream {
private MessageDigest md;
private HashValue hash;
private long size;
/**
* set up an output stream that calculates the digest of whatever is written
* to it.
*
* @param md
* the {@link MessageDigest} to use. The {@link MessageDigest} is
* assumed to be freshly created and not shared.
*/
public DigestOutputStream(MessageDigest md) {
this.md = md;
this.hash = null;
this.size = 0;
}
@Override
public void write(int b) throws IOException {
if (md == null) {
throw new ClosedException();
}
md.update((byte) b);
size += 1;
}
@Override
public void write(byte[] b) throws IOException {
if (md == null) {
throw new ClosedException();
}
md.update(b);
size += b.length;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (md == null) {
throw new ClosedException();
}
md.update(b, off, len);
size += len;
}
@Override
public void close() throws IOException {
if (md == null) {
return;
}
hash = new HashValue(md.digest());
md = null;
}
/**
* Get the final digest value.
*
* @return the final digest value.
* @throws IllegalStateException
* if {@link #close()} has not been called.
*/
public HashValue getHash() throws IllegalStateException {
if (hash == null) {
throw new IllegalStateException();
}
return hash;
}
/**
* @return the number of bytes written to this stream.
*/
public long getSize() {
return size;
}
}
HashValue is a value object that represents strings of bytes that are hashes. ClosedException is an IOException.
I also noticed that there is a DigestOutputStream in the Java standard library which is an output stream filter.
No comments:
Post a Comment