/** * */ package com.emc.vipr.transform.compression; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import SevenZip.Compression.LZMA.Decoder; /** * @author cwikj * */ public class LZMAInputStream extends InputStream implements Runnable { private static ThreadGroup tg = new ThreadGroup("LZMA Decompress"); private InputStream compressedStream; private Thread decompressionThread; private PipedOutputStream uncompressedPipeOut; private PipedInputStream uncompressedPipeIn; boolean closed; private Exception decompressionException; private Decoder lzma; private Boolean decompressionComplete; /** * @throws IOException * */ public LZMAInputStream(InputStream compressedStream) throws IOException { this(compressedStream, 4096); } public LZMAInputStream(InputStream compressedStream, int bufferSize) throws IOException { this.compressedStream = compressedStream; lzma = new Decoder(); // Read the stream properties from the stream byte[] properties = new byte[5]; int c = compressedStream.read(properties); if(c != properties.length) { throw new IOException("Unable to compression settings from stream"); } if(!lzma.SetDecoderProperties(properties)) { throw new IOException("LZMA decoder rejected compression settings from stream"); } // Build a pipe to read data from the decoder. uncompressedPipeIn = new PipedInputStream(bufferSize); uncompressedPipeOut = new PipedOutputStream(uncompressedPipeIn); // Start the auxilary thread to do the decompression. decompressionThread = new Thread(tg, this); closed = false; setDecompressionComplete(Boolean.FALSE); decompressionThread.start(); } /* (non-Javadoc) * @see java.io.InputStream#read() */ @Override public int read() throws IOException { checkStream(); try { return uncompressedPipeIn.read(); } catch(IOException e) { if(getDecompressionComplete()) { return -1; } throw e; } } @Override public int available() throws IOException { return uncompressedPipeIn.available(); } @Override public void close() throws IOException { if(closed) { return; } // Close the parent stream compressedStream.close(); // Wait for decompression to end try { decompressionThread.join(); } catch (InterruptedException e) { // Ignore. } // Close the pipe ends. uncompressedPipeIn.close(); uncompressedPipeOut.close(); // dereference the decoder so it can get collected asap. lzma = null; } @Override public boolean markSupported() { return false; } @Override public int read(byte[] b, int off, int len) throws IOException { checkStream(); try { return uncompressedPipeIn.read(b, off, len); } catch(IOException e) { if(getDecompressionComplete()) { return -1; } throw e; } } @Override public int read(byte[] b) throws IOException { checkStream(); try { return uncompressedPipeIn.read(b); } catch(IOException e) { if(getDecompressionComplete()) { return -1; } throw e; } } @Override public long skip(long n) throws IOException { checkStream(); try { return uncompressedPipeIn.skip(n); } catch(IOException e) { if(getDecompressionComplete()) { return -1; } throw e; } } private void checkStream() throws IOException { if(decompressionException != null) { throw new IOException("Error decompressing data", decompressionException); } if(closed) { throw new IOException("Stream closed"); } } @Override public void run() { try { lzma.Code(compressedStream, uncompressedPipeOut, -1); // Tell the pipe we're done uncompressedPipeOut.close(); } catch(Exception e) { decompressionException = e; } setDecompressionComplete(Boolean.TRUE); } private synchronized Boolean getDecompressionComplete() { return decompressionComplete; } private synchronized void setDecompressionComplete(Boolean decompressionComplete) { this.decompressionComplete = decompressionComplete; } }