Package warrenfalk.lzma

Source Code of warrenfalk.lzma.LzmaOutputStream

/*
* Copyright 2011 Warren Falk
* This program is distributed under the terms of the GNU General Public License (LGPL)
*
* This file is part of lzma-java
*
* lzma-java is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* See https://bitbucket.org/warren/lzma-java for the latest version
*/
package warrenfalk.lzma;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;

import warrenfalk.util.BinCoder;

import SevenZip.Compression.LZMA.Encoder;

/**
* This particular implementation exposes an output stream which, when written to, exposes
* its data as an input stream to an Lzma SDK encoder on another thread.
*
* The drawback of this is that exceptions on the lzma encoding (output stream) side of
* the operation are not thrown in the thread that's supplying them with data as is usually
* the case, so these will be marshaled to the calling thread and rethrown at the next
* available opportunity (i.e. next client call).
*
* You must call close() when completed writing data to ensure you receive any exception
* which occurs in the output thread.  The close() call will block until the operation is
* completed (either successfully or with failure)
* @author warren
*
*/
public class LzmaOutputStream extends OutputStream implements
    WritableByteChannel {
  public final Marshaler marshaler;
  public final EncodeThread thread;
  public long remain;
 
  /**
   * Creates an LzmaOutputStream with default settings
   * @param stream
   * @throws IOException
   */
  public LzmaOutputStream(OutputStream stream) throws IOException {
    this(stream, -1, 4096);
  }
 
  public LzmaOutputStream(OutputStream stream, long origSize, int marshalBufferSize) throws IOException {
    this(stream, origSize, new byte[marshalBufferSize]);
  }
 
  public LzmaOutputStream(OutputStream stream, long origSize, byte[] marshalBuffer) throws IOException {
    this(stream, origSize, marshalBuffer, (1 << 22), 3, 0, 2, 0x20);
  }
 
  public LzmaOutputStream(OutputStream stream, long origSize, byte[] marshalBuffer, int dictionarySize, int literalContextBits, int literalPosStateBits, int posStateBits, int fastBytes) throws IOException {
    this.remain = origSize;
    marshaler = new Marshaler(marshalBuffer);
    thread = new EncodeThread(stream, origSize, dictionarySize, literalContextBits, literalPosStateBits, posStateBits, fastBytes);
    thread.start();
  }
 
  public class EncodeThread extends Thread {
    final Encoder encoder;
    final OutputStream stream;
   
    public EncodeThread(OutputStream stream, long origSize, int dictionarySize, int literalContextBits, int literalPosStateBits, int posStateBits, int fastBytes) throws IOException {
      this.encoder = new Encoder();
      this.encoder.SetEndMarkerMode(origSize < 0);
      this.encoder.WriteCoderProperties(stream);
      BinCoder.putInt8(origSize, stream);
      this.encoder.SetDictionarySize(dictionarySize);
      // note: in the 9.20 SDK, setting the algorithm does nothing
      //this.encoder.SetAlgorithm(algorithm);
      this.encoder.SetLcLpPb(literalContextBits, literalPosStateBits, posStateBits);
      this.encoder.SetNumFastBytes(fastBytes);
      this.stream = stream;
    }
   
    @Override
    public void run() {
      try {
        // TODO: allow passing in a progress updater here
        // note that the inSize and outSize parameters are not ever used
        // the example code sets them to -1 so that's what I'm doing.
        encoder.Code(marshaler, stream, -1, -1, null);
      }
      catch (Throwable t) {
        marshaler._throw(t);
      }
    }
  }
 
  public class Marshaler extends InputStream {
    final byte[] buffer;
    int wcursor;
    int rcursor;
    int size;
    boolean eos;
    Throwable t;

    public Marshaler(byte[] marshalBuffer) {
      this.buffer = marshalBuffer;
    }

    @Override
    public int read() throws IOException {
      throw new RuntimeException("Marshaler.read() is not implemented yet");
    }
   
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      try {
        synchronized (buffer) {
          while (size == 0) {
            if (eos)
              return -1;
            buffer.wait();
          }
          boolean wasFull = buffer.length == size;
          if (len > size)
            len = size;
          // a copy operation can be split into two parts if the data to be read is wrapped to the beginning of the buffer
          int len1 = len;
          int end = rcursor + len;
          int len2 = end - buffer.length;
          if (len2 > 0) {
            len1 -= len2;
            System.arraycopy(buffer, 0, b, off + len1, len2);
          }
          if (len1 > 0) {
            System.arraycopy(buffer, rcursor, b, off, len1);
          }
          rcursor = (rcursor + len) % buffer.length;
          size -= len;
          if (wasFull)
            buffer.notify();
        }
        return len;
      }
      catch (InterruptedException ie) {
        throw new IOException(ie);
      }
    }
   
    public int write(byte[] b, int off, int len) throws IOException {
      try {
        synchronized (buffer) {
          if (eos)
            throw new IOException("Cannot write to a stream after it has been closed");
          while (buffer.length == size)
            buffer.wait();
          boolean wasEmpty = 0 == size;
          int avail = buffer.length - size;
          if (len > avail)
            len = avail;
          if (len > 0) {
            // a copy operation can be split into two parts if the data to be written is wrapped to the beginning of the buffer
            int len1 = len;
            int end = wcursor + len;
            int len2 = end - buffer.length;
            if (len2 > 0) {
              len1 -= len2;
              System.arraycopy(b, off + len1, buffer, 0, len2);
            }
            if (len1 > 0) {
              System.arraycopy(b, off, buffer, wcursor, len1);
            }
            size += len;
            wcursor = (wcursor + len) % buffer.length;
            if (wasEmpty)
              buffer.notify();
          }
          rethrow();
        }
        return len;
      }
      catch (InterruptedException ie) {
        throw new IOException(ie);
      }
    }
   
    public int write(ByteBuffer b) throws IOException {
      try {
        int len;
        synchronized (buffer) {
          if (eos)
            throw new IOException("Cannot write to a stream after it has been closed");
          while (buffer.length == size)
            buffer.wait();
          boolean wasEmpty = 0 == size;
          int avail = buffer.length - size;
          len = b.remaining();
          if (len > avail)
            len = avail;
          if (len > 0) {
            // a copy operation can be split into two parts if the data to be written is wrapped to the beginning of the buffer
            int len1 = len;
            int end = wcursor + len;
            int len2 = end - buffer.length;
            if (len2 > 0)
              len1 -= len2;
            if (len1 > 0) {
              b.get(buffer, wcursor, len1);
            }
            if (len2 > 0)
              b.get(buffer, 0, len2);
            size += len;
            wcursor = (wcursor + len) % buffer.length;
            if (wasEmpty)
              buffer.notify();
          }
          rethrow();
        }
        return len;
      }
      catch (InterruptedException ie) {
        throw new IOException(ie);
      }
    }
   
    public void write(int b) throws IOException {
      try {
        synchronized (buffer) {
          if (eos)
            throw new IOException("Cannot write to a stream after it has been closed");
          while (buffer.length == size)
            buffer.wait();
          boolean wasEmpty = 0 == size;
          buffer[wcursor++] = (byte)b;
          if (wcursor == buffer.length)
            wcursor = 0;
          size++;
          if (wasEmpty)
            buffer.notify();
          rethrow();
        }
      }
      catch (InterruptedException ie) {
        throw new IOException(ie);
      }
    }

    public void end() throws IOException {
      synchronized (buffer) {
        eos = true;
        buffer.notify();
        rethrow();
      }
    }
   
    void _throw(Throwable t) {
      this.t = t;
    }

    public void rethrow() throws IOException {
      if (this.t == null)
        return;
      if (this.t instanceof IOException)
        throw (IOException)this.t;
      if (this.t instanceof RuntimeException)
        throw (RuntimeException)this.t;
      if (this.t instanceof Error)
        throw (Error)this.t;
      throw new IOException(t);
    }
  }

  @Override
  public boolean isOpen() {
    return !marshaler.eos;
  }

  @Override
  public int write(ByteBuffer b) throws IOException {
    if (remain >= 0 && b.remaining() > remain)
      throw new IOException("Attempt to write past end of stream");
    remain -= b.remaining();
    return marshaler.write(b);
  }

  @Override
  public void write(int b) throws IOException {
    if (remain == 0)
      throw new IOException();
    remain--;
    marshaler.write(b);
  }
 
  @Override
  public void write(byte[] b, int off, int len) throws IOException {
    if (remain >= 0 && len > remain)
      throw new IOException("Attempt to write past end of stream");
    remain -= len;
    while (len > 0) {
      int written = marshaler.write(b, off, len);
      len -= written;
      off += written;
    }
  }
 
  @Override
  public void close() throws IOException {
    marshaler.end();
    try {
      thread.join();
    }
    catch (InterruptedException ie) {
      throw new IOException(ie);
    }
    marshaler.rethrow();
    this.thread.stream.close();
    super.close();
  }

}
TOP

Related Classes of warrenfalk.lzma.LzmaOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.