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