Clean tukaani.xz
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* ARMOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.ARM;
|
||||
|
||||
/**
|
||||
* BCJ filter for little endian ARM instructions.
|
||||
*/
|
||||
public class ARMOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 4;
|
||||
|
||||
public ARMOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new ARM(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new ARM(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.ARM_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* ARMThumbOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.ARMThumb;
|
||||
|
||||
/**
|
||||
* BCJ filter for little endian ARM-Thumb instructions.
|
||||
*/
|
||||
public class ARMThumbOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 2;
|
||||
|
||||
public ARMThumbOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new ARMThumb(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new ARMThumb(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.ARMTHUMB_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ abstract class BCJCoder implements FilterCoder {
|
||||
public static final long X86_FILTER_ID = 0x04;
|
||||
public static final long POWERPC_FILTER_ID = 0x05;
|
||||
public static final long IA64_FILTER_ID = 0x06;
|
||||
public static final long ARM_FILTER_ID = 0x07;
|
||||
public static final long ARMTHUMB_FILTER_ID = 0x08;
|
||||
public static final long SPARC_FILTER_ID = 0x09;
|
||||
|
||||
public static boolean isBCJFilterID(long filterID) {
|
||||
|
||||
@@ -52,10 +52,6 @@ class BCJDecoder extends BCJCoder implements FilterDecoder {
|
||||
simpleFilter = new PowerPC(false, startOffset);
|
||||
else if (filterID == IA64_FILTER_ID)
|
||||
simpleFilter = new IA64(false, startOffset);
|
||||
else if (filterID == ARM_FILTER_ID)
|
||||
simpleFilter = new ARM(false, startOffset);
|
||||
else if (filterID == ARMTHUMB_FILTER_ID)
|
||||
simpleFilter = new ARMThumb(false, startOffset);
|
||||
else if (filterID == SPARC_FILTER_ID)
|
||||
simpleFilter = new SPARC(false, startOffset);
|
||||
else
|
||||
|
||||
@@ -159,13 +159,7 @@ class BlockInputStream extends InputStream {
|
||||
FilterDecoder[] filters = new FilterDecoder[filterIDs.length];
|
||||
|
||||
for (int i = 0; i < filters.length; ++i)
|
||||
if (filterIDs[i] == LZMA2Coder.FILTER_ID)
|
||||
filters[i] = new LZMA2Decoder(filterProps[i]);
|
||||
|
||||
else if (filterIDs[i] == DeltaCoder.FILTER_ID)
|
||||
filters[i] = new DeltaDecoder(filterProps[i]);
|
||||
|
||||
else if (BCJDecoder.isBCJFilterID(filterIDs[i]))
|
||||
if (BCJDecoder.isBCJFilterID(filterIDs[i]))
|
||||
filters[i] = new BCJDecoder(filterIDs[i], filterProps[i]);
|
||||
|
||||
else
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* DeltaCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class DeltaCoder implements FilterCoder {
|
||||
|
||||
public static final long FILTER_ID = 0x03;
|
||||
|
||||
public boolean changesSize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean nonLastOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean lastOK() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* DeltaDecoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
class DeltaDecoder extends DeltaCoder implements FilterDecoder {
|
||||
|
||||
private final int distance;
|
||||
|
||||
DeltaDecoder(byte[] props) throws UnsupportedOptionsException {
|
||||
if (props.length != 1)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported Delta filter properties");
|
||||
|
||||
distance = (props[0] & 0xFF) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemoryUsage() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new DeltaInputStream(in, distance);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* DeltaEncoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
class DeltaEncoder extends DeltaCoder implements FilterEncoder {
|
||||
|
||||
private final DeltaOptions options;
|
||||
private final byte[] props = new byte[1];
|
||||
|
||||
DeltaEncoder(DeltaOptions options) {
|
||||
props[0] = (byte) (options.getDistance() - 1);
|
||||
this.options = (DeltaOptions) options.clone();
|
||||
}
|
||||
|
||||
public long getFilterID() {
|
||||
return FILTER_ID;
|
||||
}
|
||||
|
||||
public byte[] getFilterProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public boolean supportsFlushing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return options.getOutputStream(out);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* DeltaInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.delta.DeltaDecoder;
|
||||
|
||||
/**
|
||||
* Decodes raw Delta-filtered data (no XZ headers).
|
||||
* <p>
|
||||
* The delta filter doesn't change the size of the data and thus it cannot have
|
||||
* an end-of-payload marker. It will simply decode until its input stream
|
||||
* indicates end of input.
|
||||
*/
|
||||
public class DeltaInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Smallest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MIN = 1;
|
||||
|
||||
/**
|
||||
* Largest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MAX = 256;
|
||||
|
||||
private InputStream in;
|
||||
private final DeltaDecoder delta;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Creates a new Delta decoder with the given delta calculation distance.
|
||||
*
|
||||
* @param in input stream from which Delta filtered data is read
|
||||
*
|
||||
* @param distance delta calculation distance, must be in the range
|
||||
* [<code>DISTANCE_MIN</code>, <code>DISTANCE_MAX</code>]
|
||||
*/
|
||||
public DeltaInputStream(InputStream in, int distance) {
|
||||
// Check for null because otherwise null isn't detect
|
||||
// in this constructor.
|
||||
if (in == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.in = in;
|
||||
this.delta = new DeltaDecoder(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the next byte from this input stream.
|
||||
*
|
||||
* @return the next decoded byte, or <code>-1</code> to indicate the end of
|
||||
* input on the input stream <code>in</code>
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode into an array of bytes.
|
||||
* <p>
|
||||
* This calls <code>in.read(buf, off, len)</code> and defilters the returned
|
||||
* data.
|
||||
*
|
||||
* @param buf target buffer for decoded data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the input stream <code>in</code>
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws IOException may be thrown by underlaying input stream
|
||||
* <code>in</code>
|
||||
*/
|
||||
@Override
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
int size;
|
||||
try {
|
||||
size = in.read(buf, off, len);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (size == -1)
|
||||
return -1;
|
||||
|
||||
delta.decode(buf, off, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>in.available()</code>.
|
||||
*
|
||||
* @return the value returned by <code>in.available()</code>
|
||||
*/
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return in.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* DeltaOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Delta filter options. The Delta filter can be used only as a non-last filter
|
||||
* in the chain, for example Delta + LZMA2.
|
||||
* <p>
|
||||
* Currently only simple byte-wise delta is supported. The only option is the
|
||||
* delta distance, which you should set to match your data. It's not possible to
|
||||
* provide a generic default value for it.
|
||||
* <p>
|
||||
* For example, with distance = 2 and eight-byte input A1 B1 A2 B3 A3 B5 A4 B7,
|
||||
* the output will be A1 B1 01 02 01 02 01 02.
|
||||
* <p>
|
||||
* The Delta filter can be good with uncompressed bitmap images. It can also
|
||||
* help with PCM audio, although special-purpose compressors like FLAC will give
|
||||
* much smaller result at much better compression speed.
|
||||
*/
|
||||
public class DeltaOptions extends FilterOptions {
|
||||
|
||||
/**
|
||||
* Smallest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MIN = 1;
|
||||
|
||||
/**
|
||||
* Largest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MAX = 256;
|
||||
|
||||
private int distance = DISTANCE_MIN;
|
||||
|
||||
/**
|
||||
* Creates new Delta options and sets the delta distance to 1 byte.
|
||||
*/
|
||||
public DeltaOptions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new Delta options and sets the distance to the given value.
|
||||
*/
|
||||
public DeltaOptions(int distance) throws UnsupportedOptionsException {
|
||||
setDistance(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delta distance in bytes. The new distance must be in the range
|
||||
* [DISTANCE_MIN, DISTANCE_MAX].
|
||||
*/
|
||||
public void setDistance(int distance) throws UnsupportedOptionsException {
|
||||
if (distance < DISTANCE_MIN || distance > DISTANCE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Delta distance must be in the range [" + DISTANCE_MIN
|
||||
+ ", " + DISTANCE_MAX + "]: " + distance);
|
||||
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the delta distance.
|
||||
*/
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEncoderMemoryUsage() {
|
||||
return DeltaOutputStream.getMemoryUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new DeltaOutputStream(out, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoderMemoryUsage() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new DeltaInputStream(in, distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new DeltaEncoder(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* DeltaOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.delta.DeltaEncoder;
|
||||
|
||||
class DeltaOutputStream extends FinishableOutputStream {
|
||||
|
||||
private static final int FILTER_BUF_SIZE = 4096;
|
||||
|
||||
private FinishableOutputStream out;
|
||||
private final DeltaEncoder delta;
|
||||
private final byte[] filterBuf = new byte[FILTER_BUF_SIZE];
|
||||
|
||||
private boolean finished = false;
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
static int getMemoryUsage() {
|
||||
return 1 + FILTER_BUF_SIZE / 1024;
|
||||
}
|
||||
|
||||
DeltaOutputStream(FinishableOutputStream out, DeltaOptions options) {
|
||||
this.out = out;
|
||||
delta = new DeltaEncoder(options.getDistance());
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished");
|
||||
|
||||
try {
|
||||
while (len > FILTER_BUF_SIZE) {
|
||||
delta.encode(buf, off, FILTER_BUF_SIZE, filterBuf);
|
||||
out.write(filterBuf);
|
||||
off += FILTER_BUF_SIZE;
|
||||
len -= FILTER_BUF_SIZE;
|
||||
}
|
||||
|
||||
delta.encode(buf, off, len, filterBuf);
|
||||
out.write(filterBuf, 0, len);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
if (!finished) {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
try {
|
||||
out.finish();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
if (exception == null)
|
||||
exception = e;
|
||||
}
|
||||
|
||||
out = null;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* LZMA2Coder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class LZMA2Coder implements FilterCoder {
|
||||
|
||||
public static final long FILTER_ID = 0x21;
|
||||
|
||||
public boolean changesSize() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean nonLastOK() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean lastOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* LZMA2Decoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
class LZMA2Decoder extends LZMA2Coder implements FilterDecoder {
|
||||
|
||||
private int dictSize;
|
||||
|
||||
LZMA2Decoder(byte[] props) throws UnsupportedOptionsException {
|
||||
// Up to 1.5 GiB dictionary is supported. The bigger ones
|
||||
// are too big for int.
|
||||
if (props.length != 1 || (props[0] & 0xFF) > 37)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported LZMA2 properties");
|
||||
|
||||
dictSize = 2 | (props[0] & 1);
|
||||
dictSize <<= (props[0] >>> 1) + 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemoryUsage() {
|
||||
return LZMA2InputStream.getMemoryUsage(dictSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new LZMA2InputStream(in, dictSize);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* LZMA2Encoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
class LZMA2Encoder extends LZMA2Coder implements FilterEncoder {
|
||||
|
||||
private final LZMA2Options options;
|
||||
private final byte[] props = new byte[1];
|
||||
|
||||
LZMA2Encoder(LZMA2Options options) {
|
||||
if (options.getPresetDict() != null)
|
||||
throw new IllegalArgumentException(
|
||||
"XZ doesn't support a preset dictionary for now");
|
||||
|
||||
if (options.getMode() == LZMA2Options.MODE_UNCOMPRESSED)
|
||||
props[0] = (byte) 0;
|
||||
else {
|
||||
int d = Math.max(options.getDictSize(),
|
||||
LZMA2Options.DICT_SIZE_MIN);
|
||||
props[0] = (byte) (LZMAEncoder.getDistSlot(d - 1) - 23);
|
||||
}
|
||||
|
||||
// Make a private copy so that the caller is free to change its copy.
|
||||
this.options = (LZMA2Options) options.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFilterID() {
|
||||
return FILTER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getFilterProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFlushing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return options.getOutputStream(out);
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* LZMA2InputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZDecoder;
|
||||
import org.tukaani.xz.rangecoder.RangeDecoderFromBuffer;
|
||||
import org.tukaani.xz.lzma.LZMADecoder;
|
||||
|
||||
/**
|
||||
* Decompresses a raw LZMA2 stream (no XZ headers).
|
||||
*/
|
||||
public class LZMA2InputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Smallest valid LZMA2 dictionary size.
|
||||
* <p>
|
||||
* Very tiny dictionaries would be a performance problem, so the minimum is
|
||||
* 4 KiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_MIN = 4096;
|
||||
|
||||
/**
|
||||
* Largest dictionary size supported by this implementation.
|
||||
* <p>
|
||||
* The LZMA2 algorithm allows dictionaries up to one byte less than 4 GiB.
|
||||
* This implementation supports only 16 bytes less than 2 GiB for raw LZMA2
|
||||
* streams, and for .xz files the maximum is 1.5 GiB. This limitation is due
|
||||
* to Java using signed 32-bit integers for array indexing. The limitation
|
||||
* shouldn't matter much in practice since so huge dictionaries are not
|
||||
* normally used.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = Integer.MAX_VALUE & ~15;
|
||||
|
||||
private static final int COMPRESSED_SIZE_MAX = 1 << 16;
|
||||
|
||||
private DataInputStream in;
|
||||
|
||||
private final LZDecoder lz;
|
||||
private final RangeDecoderFromBuffer rc
|
||||
= new RangeDecoderFromBuffer(COMPRESSED_SIZE_MAX);
|
||||
private LZMADecoder lzma;
|
||||
|
||||
private int uncompressedSize = 0;
|
||||
private boolean isLZMAChunk;
|
||||
|
||||
private boolean needDictReset = true;
|
||||
private boolean needProps = true;
|
||||
private boolean endReached = false;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size.
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize) {
|
||||
// The base state is around 30-40 KiB (probabilities etc.),
|
||||
// range decoder needs COMPRESSED_SIZE_MAX bytes for buffering,
|
||||
// and LZ decoder needs a dictionary buffer.
|
||||
return 40 + COMPRESSED_SIZE_MAX / 1024 + getDictSize(dictSize) / 1024;
|
||||
}
|
||||
|
||||
private static int getDictSize(int dictSize) {
|
||||
if (dictSize < DICT_SIZE_MIN || dictSize > DICT_SIZE_MAX)
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported dictionary size " + dictSize);
|
||||
|
||||
// Round dictionary size upward to a multiple of 16. This way LZMA
|
||||
// can use LZDecoder.getPos() for calculating LZMA's posMask.
|
||||
// Note that this check is needed only for raw LZMA2 streams; it is
|
||||
// redundant with .xz.
|
||||
return (dictSize + 15) & ~15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA2 data from
|
||||
* <code>in</code>.
|
||||
* <p>
|
||||
* The caller needs to know the dictionary size used when compressing; the
|
||||
* dictionary size isn't stored as part of a raw LZMA2 stream.
|
||||
* <p>
|
||||
* Specifying a too small dictionary size will prevent decompressing the
|
||||
* stream. Specifying a too big dictionary is waste of memory but
|
||||
* decompression will work.
|
||||
* <p>
|
||||
* There is no need to specify a dictionary bigger than the uncompressed
|
||||
* size of the data even if a bigger dictionary was used when compressing.
|
||||
* If you know the uncompressed size of the data, this might allow saving
|
||||
* some memory.
|
||||
*
|
||||
* @param in input stream from which LZMA2-compressed data is read
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*/
|
||||
public LZMA2InputStream(InputStream in, int dictSize) {
|
||||
this(in, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new LZMA2 decompressor using a preset dictionary.
|
||||
* <p>
|
||||
* This is like <code>LZMA2InputStream(InputStream, int)</code> except that
|
||||
* the dictionary may be initialized using a preset dictionary. If a preset
|
||||
* dictionary was used when compressing the data, the same preset dictionary
|
||||
* must be provided when decompressing.
|
||||
*
|
||||
* @param in input stream from which LZMA2-compressed data is read
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*/
|
||||
public LZMA2InputStream(InputStream in, int dictSize, byte[] presetDict) {
|
||||
// Check for null because otherwise null isn't detect
|
||||
// in this constructor.
|
||||
if (in == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.in = new DataInputStream(in);
|
||||
this.lz = new LZDecoder(getDictSize(dictSize), presetDict);
|
||||
|
||||
if (presetDict != null && presetDict.length > 0)
|
||||
needDictReset = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the next byte from this input stream.
|
||||
* <p>
|
||||
* Reading lots of data with <code>read()</code> from this input stream may
|
||||
* be inefficient. Wrap it in <code>java.io.BufferedInputStream</code> if
|
||||
* you need to read lots of data one byte at a time.
|
||||
*
|
||||
* @return the next decompressed byte, or <code>-1</code> to indicate the
|
||||
* end of the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses into an array of bytes.
|
||||
* <p>
|
||||
* If <code>len</code> is zero, no bytes are read and <code>0</code> is
|
||||
* returned. Otherwise this will block until <code>len</code> bytes have
|
||||
* been decompressed, the end of the LZMA2 stream is reached, or an
|
||||
* exception is thrown.
|
||||
*
|
||||
* @param buf target buffer for uncompressed data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of uncompressed bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
int size = 0;
|
||||
|
||||
while (len > 0) {
|
||||
if (uncompressedSize == 0) {
|
||||
decodeChunkHeader();
|
||||
if (endReached)
|
||||
return size == 0 ? -1 : size;
|
||||
}
|
||||
|
||||
int copySizeMax = Math.min(uncompressedSize, len);
|
||||
|
||||
if (!isLZMAChunk)
|
||||
lz.copyUncompressed(in, copySizeMax);
|
||||
else {
|
||||
lz.setLimit(copySizeMax);
|
||||
lzma.decode();
|
||||
if (!rc.isInBufferOK())
|
||||
throw new CorruptedInputException();
|
||||
}
|
||||
|
||||
int copiedSize = lz.flush(buf, off);
|
||||
off += copiedSize;
|
||||
len -= copiedSize;
|
||||
size += copiedSize;
|
||||
uncompressedSize -= copiedSize;
|
||||
|
||||
if (uncompressedSize == 0)
|
||||
if (!rc.isFinished() || lz.hasPending())
|
||||
throw new CorruptedInputException();
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeChunkHeader() throws IOException {
|
||||
int control = in.readUnsignedByte();
|
||||
|
||||
if (control == 0x00) {
|
||||
endReached = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (control >= 0xE0 || control == 0x01) {
|
||||
needProps = true;
|
||||
needDictReset = false;
|
||||
lz.reset();
|
||||
} else if (needDictReset)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
if (control >= 0x80) {
|
||||
isLZMAChunk = true;
|
||||
|
||||
uncompressedSize = (control & 0x1F) << 16;
|
||||
uncompressedSize += in.readUnsignedShort() + 1;
|
||||
|
||||
int compressedSize = in.readUnsignedShort() + 1;
|
||||
|
||||
if (control >= 0xC0) {
|
||||
needProps = false;
|
||||
decodeProps();
|
||||
|
||||
} else if (needProps)
|
||||
throw new CorruptedInputException();
|
||||
else if (control >= 0xA0)
|
||||
lzma.reset();
|
||||
|
||||
rc.prepareInputBuffer(in, compressedSize);
|
||||
|
||||
} else if (control > 0x02)
|
||||
throw new CorruptedInputException();
|
||||
else {
|
||||
isLZMAChunk = false;
|
||||
uncompressedSize = in.readUnsignedShort() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeProps() throws IOException {
|
||||
int props = in.readUnsignedByte();
|
||||
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
int pb = props / (9 * 5);
|
||||
props -= pb * 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
if (lc + lp > 4)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
lzma = new LZMADecoder(lz, rc, lc, lp, pb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of uncompressed bytes that can be read without
|
||||
* blocking. The value is returned with an assumption that the compressed
|
||||
* input data will be valid. If the compressed data is corrupt,
|
||||
* <code>CorruptedInputException</code> may get thrown before the number of
|
||||
* bytes claimed to be available have been read from this input stream.
|
||||
* <p>
|
||||
* In LZMA2InputStream, the return value will be non-zero when the
|
||||
* decompressor is in the middle of an LZMA2 chunk. The return value will
|
||||
* then be the number of uncompressed bytes remaining from that chunk.
|
||||
*
|
||||
* @return the number of uncompressed bytes that can be read without
|
||||
* blocking
|
||||
*/
|
||||
public int available() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,562 +0,0 @@
|
||||
/*
|
||||
* LZMA2Options
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
/**
|
||||
* LZMA2 compression options.
|
||||
* <p>
|
||||
* While this allows setting the LZMA2 compression options in detail, often you
|
||||
* only need <code>LZMA2Options()</code> or <code>LZMA2Options(int)</code>.
|
||||
*/
|
||||
public class LZMA2Options extends FilterOptions {
|
||||
|
||||
/**
|
||||
* Minimum valid compression preset level is 0.
|
||||
*/
|
||||
public static final int PRESET_MIN = 0;
|
||||
|
||||
/**
|
||||
* Maximum valid compression preset level is 9.
|
||||
*/
|
||||
public static final int PRESET_MAX = 9;
|
||||
|
||||
/**
|
||||
* Default compression preset level is 6.
|
||||
*/
|
||||
public static final int PRESET_DEFAULT = 6;
|
||||
|
||||
/**
|
||||
* Minimum dictionary size is 4 KiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_MIN = 4096;
|
||||
|
||||
/**
|
||||
* Maximum dictionary size for compression is 768 MiB.
|
||||
* <p>
|
||||
* The decompressor supports bigger dictionaries, up to almost 2 GiB. With
|
||||
* HC4 the encoder would support dictionaries bigger than 768 MiB. The 768
|
||||
* MiB limit comes from the current implementation of BT4 where we would
|
||||
* otherwise hit the limits of signed ints in array indexing.
|
||||
* <p>
|
||||
* If you really need bigger dictionary for decompression, use
|
||||
* {@link LZMA2InputStream} directly.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = 768 << 20;
|
||||
|
||||
/**
|
||||
* The default dictionary size is 8 MiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_DEFAULT = 8 << 20;
|
||||
|
||||
/**
|
||||
* Maximum value for lc + lp is 4.
|
||||
*/
|
||||
public static final int LC_LP_MAX = 4;
|
||||
|
||||
/**
|
||||
* The default number of literal context bits is 3.
|
||||
*/
|
||||
public static final int LC_DEFAULT = 3;
|
||||
|
||||
/**
|
||||
* The default number of literal position bits is 0.
|
||||
*/
|
||||
public static final int LP_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Maximum value for pb is 4.
|
||||
*/
|
||||
public static final int PB_MAX = 4;
|
||||
|
||||
/**
|
||||
* The default number of position bits is 2.
|
||||
*/
|
||||
public static final int PB_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Compression mode: uncompressed. The data is wrapped into a LZMA2 stream
|
||||
* without compression.
|
||||
*/
|
||||
public static final int MODE_UNCOMPRESSED = 0;
|
||||
|
||||
/**
|
||||
* Compression mode: fast. This is usually combined with a hash chain match
|
||||
* finder.
|
||||
*/
|
||||
public static final int MODE_FAST = LZMAEncoder.MODE_FAST;
|
||||
|
||||
/**
|
||||
* Compression mode: normal. This is usually combined with a binary tree
|
||||
* match finder.
|
||||
*/
|
||||
public static final int MODE_NORMAL = LZMAEncoder.MODE_NORMAL;
|
||||
|
||||
/**
|
||||
* Minimum value for <code>niceLen</code> is 8.
|
||||
*/
|
||||
public static final int NICE_LEN_MIN = 8;
|
||||
|
||||
/**
|
||||
* Maximum value for <code>niceLen</code> is 273.
|
||||
*/
|
||||
public static final int NICE_LEN_MAX = 273;
|
||||
|
||||
/**
|
||||
* Match finder: Hash Chain 2-3-4
|
||||
*/
|
||||
public static final int MF_HC4 = LZEncoder.MF_HC4;
|
||||
|
||||
/**
|
||||
* Match finder: Binary tree 2-3-4
|
||||
*/
|
||||
public static final int MF_BT4 = LZEncoder.MF_BT4;
|
||||
|
||||
private static final int[] presetToDictSize = {
|
||||
1 << 18, 1 << 20, 1 << 21, 1 << 22, 1 << 22,
|
||||
1 << 23, 1 << 23, 1 << 24, 1 << 25, 1 << 26 };
|
||||
|
||||
private static final int[] presetToDepthLimit = { 4, 8, 24, 48 };
|
||||
|
||||
private int dictSize;
|
||||
private byte[] presetDict = null;
|
||||
private int lc;
|
||||
private int lp;
|
||||
private int pb;
|
||||
private int mode;
|
||||
private int niceLen;
|
||||
private int mf;
|
||||
private int depthLimit;
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the default values. This is
|
||||
* equivalent to <code>LZMA2Options(PRESET_DEFAULT)</code>.
|
||||
*/
|
||||
public LZMA2Options() {
|
||||
try {
|
||||
setPreset(PRESET_DEFAULT);
|
||||
} catch (UnsupportedOptionsException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the given preset.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>preset</code> is not supported
|
||||
*/
|
||||
public LZMA2Options(int preset) throws UnsupportedOptionsException {
|
||||
setPreset(preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the given custom values.
|
||||
*
|
||||
* @throws UnsupportedOptionsException unsupported options were specified
|
||||
*/
|
||||
public LZMA2Options(int dictSize, int lc, int lp, int pb, int mode,
|
||||
int niceLen, int mf, int depthLimit)
|
||||
throws UnsupportedOptionsException {
|
||||
setDictSize(dictSize);
|
||||
setLcLp(lc, lp);
|
||||
setPb(pb);
|
||||
setMode(mode);
|
||||
setNiceLen(niceLen);
|
||||
setMatchFinder(mf);
|
||||
setDepthLimit(depthLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression options to the given preset.
|
||||
* <p>
|
||||
* The presets 0-3 are fast presets with medium compression. The presets 4-6
|
||||
* are fairly slow presets with high compression. The default preset
|
||||
* (<code>PRESET_DEFAULT</code>) is 6.
|
||||
* <p>
|
||||
* The presets 7-9 are like the preset 6 but use bigger dictionaries and
|
||||
* have higher compressor and decompressor memory requirements. Unless the
|
||||
* uncompressed size of the file exceeds 8 MiB, 16 MiB, or
|
||||
* 32 MiB, it is waste of memory to use the presets 7, 8, or 9,
|
||||
* respectively.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>preset</code> is not supported
|
||||
*/
|
||||
public void setPreset(int preset) throws UnsupportedOptionsException {
|
||||
if (preset < 0 || preset > 9)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported preset: " + preset);
|
||||
|
||||
lc = LC_DEFAULT;
|
||||
lp = LP_DEFAULT;
|
||||
pb = PB_DEFAULT;
|
||||
dictSize = presetToDictSize[preset];
|
||||
|
||||
if (preset <= 3) {
|
||||
mode = MODE_FAST;
|
||||
mf = MF_HC4;
|
||||
niceLen = preset <= 1 ? 128 : NICE_LEN_MAX;
|
||||
depthLimit = presetToDepthLimit[preset];
|
||||
} else {
|
||||
mode = MODE_NORMAL;
|
||||
mf = MF_BT4;
|
||||
niceLen = (preset == 4) ? 16 : (preset == 5) ? 32 : 64;
|
||||
depthLimit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dictionary size in bytes.
|
||||
* <p>
|
||||
* The dictionary (or history buffer) holds the most recently seen
|
||||
* uncompressed data. Bigger dictionary usually means better compression.
|
||||
* However, using a dictioanary bigger than the size of the uncompressed
|
||||
* data is waste of memory.
|
||||
* <p>
|
||||
* Any value in the range [DICT_SIZE_MIN, DICT_SIZE_MAX] is valid, but sizes
|
||||
* of 2^n and 2^n + 2^(n-1) bytes are somewhat recommended.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>dictSize</code> is not
|
||||
* supported
|
||||
*/
|
||||
public void setDictSize(int dictSize) throws UnsupportedOptionsException {
|
||||
if (dictSize < DICT_SIZE_MIN)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA2 dictionary size must be at least 4 KiB: "
|
||||
+ dictSize + " B");
|
||||
|
||||
if (dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA2 dictionary size must not exceed "
|
||||
+ (DICT_SIZE_MAX >> 20) + " MiB: " + dictSize + " B");
|
||||
|
||||
this.dictSize = dictSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dictionary size in bytes.
|
||||
*/
|
||||
public int getDictSize() {
|
||||
return dictSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a preset dictionary. Use null to disable the use of a preset
|
||||
* dictionary. By default there is no preset dictionary.
|
||||
* <p>
|
||||
* <b>The .xz format doesn't support a preset dictionary for now. Do not set
|
||||
* a preset dictionary unless you use raw LZMA2.</b>
|
||||
* <p>
|
||||
* Preset dictionary can be useful when compressing many similar, relatively
|
||||
* small chunks of data independently from each other. A preset dictionary
|
||||
* should contain typical strings that occur in the files being compressed.
|
||||
* The most probable strings should be near the end of the preset
|
||||
* dictionary. The preset dictionary used for compression is also needed for
|
||||
* decompression.
|
||||
*/
|
||||
public void setPresetDict(byte[] presetDict) {
|
||||
this.presetDict = presetDict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preset dictionary.
|
||||
*/
|
||||
public byte[] getPresetDict() {
|
||||
return presetDict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal context bits and literal position bits.
|
||||
* <p>
|
||||
* The sum of <code>lc</code> and <code>lp</code> is limited to 4. Trying to
|
||||
* exceed it will throw an exception. This function lets you change both at
|
||||
* the same time.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lc</code> and <code>lp</code>
|
||||
* are invalid
|
||||
*/
|
||||
public void setLcLp(int lc, int lp) throws UnsupportedOptionsException {
|
||||
if (lc < 0 || lp < 0 || lc > LC_LP_MAX || lp > LC_LP_MAX
|
||||
|| lc + lp > LC_LP_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"lc + lp must not exceed " + LC_LP_MAX + ": "
|
||||
+ lc + " + " + lp);
|
||||
|
||||
this.lc = lc;
|
||||
this.lp = lp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal context bits.
|
||||
* <p>
|
||||
* All bytes that cannot be encoded as matches are encoded as literals. That
|
||||
* is, literals are simply 8-bit bytes that are encoded one at a time.
|
||||
* <p>
|
||||
* The literal coding makes an assumption that the highest <code>lc</code>
|
||||
* bits of the previous uncompressed byte correlate with the next byte. For
|
||||
* example, in typical English text, an upper-case letter is often followed
|
||||
* by a lower-case letter, and a lower-case letter is usually followed by
|
||||
* another lower-case letter. In the US-ASCII character set, the highest
|
||||
* three bits are 010 for upper-case letters and 011 for lower-case letters.
|
||||
* When <code>lc</code> is at least 3, the literal coding can take advantage
|
||||
* of this property in the uncompressed data.
|
||||
* <p>
|
||||
* The default value (3) is usually good. If you want maximum compression,
|
||||
* try <code>setLc(4)</code>. Sometimes it helps a little, and sometimes it
|
||||
* makes compression worse. If it makes it worse, test for example
|
||||
* <code>setLc(2)</code> too.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lc</code> is invalid, or the
|
||||
* sum of <code>lc</code> and <code>lp</code> exceed LC_LP_MAX
|
||||
*/
|
||||
public void setLc(int lc) throws UnsupportedOptionsException {
|
||||
setLcLp(lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal position bits.
|
||||
* <p>
|
||||
* This affets what kind of alignment in the uncompressed data is assumed
|
||||
* when encoding literals. See {@link #setPb(int) setPb} for more
|
||||
* information about alignment.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lp</code> is invalid, or the
|
||||
* sum of <code>lc</code> and <code>lp</code> exceed LC_LP_MAX
|
||||
*/
|
||||
public void setLp(int lp) throws UnsupportedOptionsException {
|
||||
setLcLp(lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of literal context bits.
|
||||
*/
|
||||
public int getLc() {
|
||||
return lc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of literal position bits.
|
||||
*/
|
||||
public int getLp() {
|
||||
return lp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of position bits.
|
||||
* <p>
|
||||
* This affects what kind of alignment in the uncompressed data is assumed
|
||||
* in general. The default (2) means four-byte alignment (2^<code>pb</code>
|
||||
* = 2^2 = 4), which is often a good choice when there's no better guess.
|
||||
* <p>
|
||||
* When the alignment is known, setting the number of position bits
|
||||
* accordingly may reduce the file size a little. For example with text
|
||||
* files having one-byte alignment (US-ASCII, ISO-8859-*, UTF-8), using
|
||||
* <code>setPb(0)</code> can improve compression slightly. For UTF-16 text,
|
||||
* <code>setPb(1)</code> is a good choice. If the alignment is an odd number
|
||||
* like 3 bytes, <code>setPb(0)</code> might be the best choice.
|
||||
* <p>
|
||||
* Even though the assumed alignment can be adjusted with <code>setPb</code>
|
||||
* and <code>setLp</code>, LZMA2 still slightly favors 16-byte alignment. It
|
||||
* might be worth taking into account when designing file formats that are
|
||||
* likely to be often compressed with LZMA2.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>pb</code> is invalid
|
||||
*/
|
||||
public void setPb(int pb) throws UnsupportedOptionsException {
|
||||
if (pb < 0 || pb > PB_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"pb must not exceed " + PB_MAX + ": " + pb);
|
||||
|
||||
this.pb = pb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of position bits.
|
||||
*/
|
||||
public int getPb() {
|
||||
return pb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression mode.
|
||||
* <p>
|
||||
* This specifies the method to analyze the data produced by a match finder.
|
||||
* The default is <code>MODE_FAST</code> for presets 0-3 and
|
||||
* <code>MODE_NORMAL</code> for presets 4-9.
|
||||
* <p>
|
||||
* Usually <code>MODE_FAST</code> is used with Hash Chain match finders and
|
||||
* <code>MODE_NORMAL</code> with Binary Tree match finders. This is also
|
||||
* what the presets do.
|
||||
* <p>
|
||||
* The special mode <code>MODE_UNCOMPRESSED</code> doesn't try to compress
|
||||
* the data at all (and doesn't use a match finder) and will simply wrap it
|
||||
* in uncompressed LZMA2 chunks.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>mode</code> is not supported
|
||||
*/
|
||||
public void setMode(int mode) throws UnsupportedOptionsException {
|
||||
if (mode < MODE_UNCOMPRESSED || mode > MODE_NORMAL)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported compression mode: " + mode);
|
||||
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compression mode.
|
||||
*/
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nice length of matches. Once a match of at least
|
||||
* <code>niceLen</code> bytes is found, the algorithm stops looking for
|
||||
* better matches. Higher values tend to give better compression at the
|
||||
* expense of speed. The default depends on the preset.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>niceLen</code> is invalid
|
||||
*/
|
||||
public void setNiceLen(int niceLen) throws UnsupportedOptionsException {
|
||||
if (niceLen < NICE_LEN_MIN)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Minimum nice length of matches is "
|
||||
+ NICE_LEN_MIN + " bytes: " + niceLen);
|
||||
|
||||
if (niceLen > NICE_LEN_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Maximum nice length of matches is " + NICE_LEN_MAX
|
||||
+ ": " + niceLen);
|
||||
|
||||
this.niceLen = niceLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nice length of matches.
|
||||
*/
|
||||
public int getNiceLen() {
|
||||
return niceLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the match finder type.
|
||||
* <p>
|
||||
* Match finder has a major effect on compression speed, memory usage, and
|
||||
* compression ratio. Usually Hash Chain match finders are faster than
|
||||
* Binary Tree match finders. The default depends on the preset: 0-3 use
|
||||
* <code>MF_HC4</code> and 4-9 use <code>MF_BT4</code>.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>mf</code> is not supported
|
||||
*/
|
||||
public void setMatchFinder(int mf) throws UnsupportedOptionsException {
|
||||
if (mf != MF_HC4 && mf != MF_BT4)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported match finder: " + mf);
|
||||
|
||||
this.mf = mf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the match finder type.
|
||||
*/
|
||||
public int getMatchFinder() {
|
||||
return mf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the match finder search depth limit.
|
||||
* <p>
|
||||
* The default is a special value of <code>0</code> which indicates that the
|
||||
* depth limit should be automatically calculated by the selected match
|
||||
* finder from the nice length of matches.
|
||||
* <p>
|
||||
* Reasonable depth limit for Hash Chain match finders is 4-100 and 16-1000
|
||||
* for Binary Tree match finders. Using very high values can make the
|
||||
* compressor extremely slow with some files. Avoid settings higher than
|
||||
* 1000 unless you are prepared to interrupt the compression in case it is
|
||||
* taking far too long.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>depthLimit</code> is invalid
|
||||
*/
|
||||
public void setDepthLimit(int depthLimit)
|
||||
throws UnsupportedOptionsException {
|
||||
if (depthLimit < 0)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Depth limit cannot be negative: " + depthLimit);
|
||||
|
||||
this.depthLimit = depthLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the match finder search depth limit.
|
||||
*/
|
||||
public int getDepthLimit() {
|
||||
return depthLimit;
|
||||
}
|
||||
|
||||
public int getEncoderMemoryUsage() {
|
||||
return (mode == MODE_UNCOMPRESSED)
|
||||
? UncompressedLZMA2OutputStream.getMemoryUsage()
|
||||
: LZMA2OutputStream.getMemoryUsage(this);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
if (mode == MODE_UNCOMPRESSED)
|
||||
return new UncompressedLZMA2OutputStream(out);
|
||||
|
||||
return new LZMA2OutputStream(out, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets how much memory the LZMA2 decoder will need to decompress the data
|
||||
* that was encoded with these options and stored in a .xz file.
|
||||
* <p>
|
||||
* The returned value may bigger than the value returned by a direct call to
|
||||
* {@link LZMA2InputStream#getMemoryUsage(int)} if the dictionary size is
|
||||
* not 2^n or 2^n + 2^(n-1) bytes. This is because the .xz headers
|
||||
* store the dictionary size in such a format and other values are rounded
|
||||
* up to the next such value. Such rounding is harmess except it might waste
|
||||
* some memory if an unsual dictionary size is used.
|
||||
* <p>
|
||||
* If you use raw LZMA2 streams and unusual dictioanary size, call
|
||||
* {@link LZMA2InputStream#getMemoryUsage} directly to get raw decoder
|
||||
* memory requirements.
|
||||
*/
|
||||
public int getDecoderMemoryUsage() {
|
||||
// Round the dictionary size up to the next 2^n or 2^n + 2^(n-1).
|
||||
int d = dictSize - 1;
|
||||
d |= d >>> 2;
|
||||
d |= d >>> 3;
|
||||
d |= d >>> 4;
|
||||
d |= d >>> 8;
|
||||
d |= d >>> 16;
|
||||
return LZMA2InputStream.getMemoryUsage(d + 1);
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) throws IOException {
|
||||
return new LZMA2InputStream(in, dictSize);
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new LZMA2Encoder(this);
|
||||
}
|
||||
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* LZMA2OutputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.rangecoder.RangeEncoder;
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
class LZMA2OutputStream extends FinishableOutputStream {
|
||||
|
||||
static final int COMPRESSED_SIZE_MAX = 64 << 10;
|
||||
|
||||
private FinishableOutputStream out;
|
||||
private final DataOutputStream outData;
|
||||
|
||||
private final LZEncoder lz;
|
||||
private final RangeEncoder rc;
|
||||
private final LZMAEncoder lzma;
|
||||
|
||||
private final int props; // Cannot change props on the fly for now.
|
||||
private boolean dictResetNeeded = true;
|
||||
private boolean stateResetNeeded = true;
|
||||
private boolean propsNeeded = true;
|
||||
|
||||
private int pendingSize = 0;
|
||||
private boolean finished = false;
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
private static int getExtraSizeBefore(int dictSize) {
|
||||
return COMPRESSED_SIZE_MAX > dictSize
|
||||
? COMPRESSED_SIZE_MAX - dictSize : 0;
|
||||
}
|
||||
|
||||
static int getMemoryUsage(LZMA2Options options) {
|
||||
// 64 KiB buffer for the range encoder + a little extra + LZMAEncoder
|
||||
int dictSize = options.getDictSize();
|
||||
int extraSizeBefore = getExtraSizeBefore(dictSize);
|
||||
return 70 + LZMAEncoder.getMemoryUsage(options.getMode(),
|
||||
dictSize, extraSizeBefore,
|
||||
options.getMatchFinder());
|
||||
}
|
||||
|
||||
LZMA2OutputStream(FinishableOutputStream out, LZMA2Options options) {
|
||||
if (out == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.out = out;
|
||||
outData = new DataOutputStream(out);
|
||||
rc = new RangeEncoder(COMPRESSED_SIZE_MAX);
|
||||
|
||||
int dictSize = options.getDictSize();
|
||||
int extraSizeBefore = getExtraSizeBefore(dictSize);
|
||||
lzma = LZMAEncoder.getInstance(rc,
|
||||
options.getLc(), options.getLp(), options.getPb(),
|
||||
options.getMode(),
|
||||
dictSize, extraSizeBefore, options.getNiceLen(),
|
||||
options.getMatchFinder(), options.getDepthLimit());
|
||||
|
||||
lz = lzma.getLZEncoder();
|
||||
|
||||
byte[] presetDict = options.getPresetDict();
|
||||
if (presetDict != null && presetDict.length > 0) {
|
||||
lz.setPresetDict(dictSize, presetDict);
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc();
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
while (len > 0) {
|
||||
int used = lz.fillWindow(buf, off, len);
|
||||
off += used;
|
||||
len -= used;
|
||||
pendingSize += used;
|
||||
|
||||
if (lzma.encodeForLZMA2())
|
||||
writeChunk();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChunk() throws IOException {
|
||||
int compressedSize = rc.finish();
|
||||
int uncompressedSize = lzma.getUncompressedSize();
|
||||
|
||||
assert compressedSize > 0 : compressedSize;
|
||||
assert uncompressedSize > 0 : uncompressedSize;
|
||||
|
||||
// +2 because the header of a compressed chunk is 2 bytes
|
||||
// bigger than the header of an uncompressed chunk.
|
||||
if (compressedSize + 2 < uncompressedSize)
|
||||
writeLZMA(uncompressedSize, compressedSize);
|
||||
else {
|
||||
lzma.reset();
|
||||
uncompressedSize = lzma.getUncompressedSize();
|
||||
assert uncompressedSize > 0 : uncompressedSize;
|
||||
writeUncompressed(uncompressedSize);
|
||||
}
|
||||
|
||||
pendingSize -= uncompressedSize;
|
||||
lzma.resetUncompressedSize();
|
||||
rc.reset();
|
||||
}
|
||||
|
||||
private void writeLZMA(int uncompressedSize, int compressedSize)
|
||||
throws IOException {
|
||||
int control;
|
||||
|
||||
if (propsNeeded)
|
||||
if (dictResetNeeded)
|
||||
control = 0x80 + (3 << 5);
|
||||
else
|
||||
control = 0x80 + (2 << 5);
|
||||
else if (stateResetNeeded)
|
||||
control = 0x80 + (1 << 5);
|
||||
else
|
||||
control = 0x80;
|
||||
|
||||
control |= (uncompressedSize - 1) >>> 16;
|
||||
outData.writeByte(control);
|
||||
|
||||
outData.writeShort(uncompressedSize - 1);
|
||||
outData.writeShort(compressedSize - 1);
|
||||
|
||||
if (propsNeeded)
|
||||
outData.writeByte(props);
|
||||
|
||||
rc.write(out);
|
||||
|
||||
propsNeeded = false;
|
||||
stateResetNeeded = false;
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
private void writeUncompressed(int uncompressedSize) throws IOException {
|
||||
while (uncompressedSize > 0) {
|
||||
int chunkSize = Math.min(uncompressedSize, COMPRESSED_SIZE_MAX);
|
||||
outData.writeByte(dictResetNeeded ? 0x01 : 0x02);
|
||||
outData.writeShort(chunkSize - 1);
|
||||
lz.copyUncompressed(out, uncompressedSize, chunkSize);
|
||||
uncompressedSize -= chunkSize;
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
stateResetNeeded = true;
|
||||
}
|
||||
|
||||
private void writeEndMarker() throws IOException {
|
||||
assert !finished;
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
lz.setFinishing();
|
||||
|
||||
try {
|
||||
while (pendingSize > 0) {
|
||||
lzma.encodeForLZMA2();
|
||||
writeChunk();
|
||||
}
|
||||
|
||||
out.write(0x00);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
lz.setFlushing();
|
||||
|
||||
while (pendingSize > 0) {
|
||||
lzma.encodeForLZMA2();
|
||||
writeChunk();
|
||||
}
|
||||
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
if (!finished) {
|
||||
writeEndMarker();
|
||||
|
||||
try {
|
||||
out.finish();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (out != null) {
|
||||
if (!finished)
|
||||
try {
|
||||
writeEndMarker();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
if (exception == null)
|
||||
exception = e;
|
||||
}
|
||||
|
||||
out = null;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
@@ -1,548 +0,0 @@
|
||||
/*
|
||||
* LZMAInputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZDecoder;
|
||||
import org.tukaani.xz.rangecoder.RangeDecoderFromStream;
|
||||
import org.tukaani.xz.lzma.LZMADecoder;
|
||||
|
||||
/**
|
||||
* Decompresses legacy .lzma files and raw LZMA streams (no .lzma header).
|
||||
* <p>
|
||||
* <b>IMPORTANT:</b> In contrast to other classes in this package, this class
|
||||
* reads data from its input stream one byte at a time. If the input stream is
|
||||
* for example {@link java.io.FileInputStream}, wrapping it into
|
||||
* {@link java.io.BufferedInputStream} tends to improve performance a lot. This
|
||||
* is not automatically done by this class because there may be use cases where
|
||||
* it is desired that this class won't read any bytes past the end of the LZMA
|
||||
* stream.
|
||||
* <p>
|
||||
* Even when using <code>BufferedInputStream</code>, the performance tends to be
|
||||
* worse (maybe 10-20 % slower) than with {@link LZMA2InputStream} or
|
||||
* {@link XZInputStream} (when the .xz file contains LZMA2-compressed data).
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public class LZMAInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Largest dictionary size supported by this implementation.
|
||||
* <p>
|
||||
* LZMA allows dictionaries up to one byte less than 4 GiB. This
|
||||
* implementation supports only 16 bytes less than 2 GiB. This limitation is
|
||||
* due to Java using signed 32-bit integers for array indexing. The
|
||||
* limitation shouldn't matter much in practice since so huge dictionaries
|
||||
* are not normally used.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = Integer.MAX_VALUE & ~15;
|
||||
|
||||
private InputStream in;
|
||||
private LZDecoder lz;
|
||||
private RangeDecoderFromStream rc;
|
||||
private LZMADecoder lzma;
|
||||
|
||||
private boolean endReached = false;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Number of uncompressed bytes left to be decompressed, or -1 if the end
|
||||
* marker is used.
|
||||
*/
|
||||
private long remainingSize;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size and LZMA properties byte (lc, lp, and pb).
|
||||
*
|
||||
* @param dictSize LZMA dictionary size as bytes, should be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param propsByte LZMA properties byte that encodes the values of lc, lp,
|
||||
* and pb
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*
|
||||
* @throws UnsupportedOptionsException if <code>dictSize</code> is outside
|
||||
* the range [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize, byte propsByte)
|
||||
throws UnsupportedOptionsException, CorruptedInputException {
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
int props = propsByte & 0xFF;
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException("Invalid LZMA properties byte");
|
||||
|
||||
props %= 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
return getMemoryUsage(dictSize, lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size, lc, and lp. Note that pb isn't needed.
|
||||
*
|
||||
* @param dictSize LZMA dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param lc number of literal context bits, must be in the range [0,
|
||||
* 8]
|
||||
*
|
||||
* @param lp number of literal position bits, must be in the range [0,
|
||||
* 4]
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize, int lc, int lp) {
|
||||
if (lc < 0 || lc > 8 || lp < 0 || lp > 4)
|
||||
throw new IllegalArgumentException("Invalid lc or lp");
|
||||
|
||||
// Probability variables have the type "short". There are
|
||||
// 0x300 (768) probability variables in each literal subcoder.
|
||||
// The number of literal subcoders is 2^(lc + lp).
|
||||
//
|
||||
// Roughly 10 KiB for the base state + LZ decoder's dictionary buffer
|
||||
// + sizeof(short) * number probability variables per literal subcoder
|
||||
// * number of literal subcoders
|
||||
return 10 + getDictSize(dictSize) / 1024
|
||||
+ ((2 * 0x300) << (lc + lp)) / 1024;
|
||||
}
|
||||
|
||||
private static int getDictSize(int dictSize) {
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new IllegalArgumentException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
// For performance reasons, use a 4 KiB dictionary if something
|
||||
// smaller was requested. It's a rare situation and the performance
|
||||
// difference isn't huge, and it starts to matter mostly when the
|
||||
// dictionary is just a few bytes. But we need to handle the special
|
||||
// case of dictSize == 0 anyway, which is an allowed value but in
|
||||
// practice means one-byte dictionary.
|
||||
//
|
||||
// Note that using a dictionary bigger than specified in the headers
|
||||
// can hide errors if there is a reference to data beyond the original
|
||||
// dictionary size but is still within 4 KiB.
|
||||
if (dictSize < 4096)
|
||||
dictSize = 4096;
|
||||
|
||||
// Round dictionary size upward to a multiple of 16. This way LZMA
|
||||
// can use LZDecoder.getPos() for calculating LZMA's posMask.
|
||||
return (dictSize + 15) & ~15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new .lzma file format decompressor without a memory usage
|
||||
* limit.
|
||||
*
|
||||
* @param in input stream from which .lzma data is read; it might be a good
|
||||
* idea to wrap it in <code>BufferedInputStream</code>, see the note at the
|
||||
* top of this page
|
||||
*
|
||||
* @throws CorruptedInputException file is corrupt or perhaps not in the
|
||||
* .lzma format at all
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws EOFException file is truncated or perhaps not in
|
||||
* the .lzma format
|
||||
* at all
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in) throws IOException {
|
||||
this(in, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new .lzma file format decompressor with an optional memory
|
||||
* usage limit.
|
||||
*
|
||||
* @param in input stream from which .lzma data is read; it might
|
||||
* be a good
|
||||
* idea to wrap it in <code>BufferedInputStream</code>, see the note at the
|
||||
* top of this page
|
||||
*
|
||||
* @param memoryLimit memory usage limit in kibibytes (KiB) or
|
||||
* <code>-1</code> to impose no memory usage limit
|
||||
*
|
||||
* @throws CorruptedInputException file is corrupt or perhaps not in the
|
||||
* .lzma format at all
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws MemoryLimitException memory usage limit was exceeded
|
||||
*
|
||||
* @throws EOFException file is truncated or perhaps not in
|
||||
* the .lzma format
|
||||
* at all
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, int memoryLimit)
|
||||
throws IOException {
|
||||
DataInputStream inData = new DataInputStream(in);
|
||||
|
||||
// Properties byte (lc, lp, and pb)
|
||||
byte propsByte = inData.readByte();
|
||||
|
||||
// Dictionary size is an unsigned 32-bit little endian integer.
|
||||
int dictSize = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
dictSize |= inData.readUnsignedByte() << (8 * i);
|
||||
|
||||
// Uncompressed size is an unsigned 64-bit little endian integer.
|
||||
// The maximum 64-bit value is a special case (becomes -1 here)
|
||||
// which indicates that the end marker is used instead of knowing
|
||||
// the uncompressed size beforehand.
|
||||
long uncompSize = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
uncompSize |= (long) inData.readUnsignedByte() << (8 * i);
|
||||
|
||||
// Check the memory usage limit.
|
||||
int memoryNeeded = getMemoryUsage(dictSize, propsByte);
|
||||
if (memoryLimit != -1 && memoryNeeded > memoryLimit)
|
||||
throw new MemoryLimitException(memoryNeeded, memoryLimit);
|
||||
|
||||
initialize(in, uncompSize, propsByte, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code>.
|
||||
* <p>
|
||||
* The caller needs to know if the "end of payload marker (EOPM)" alias "end
|
||||
* of stream marker (EOS marker)" alias "end marker" present. If the end
|
||||
* marker isn't used, the caller must know the exact uncompressed size of
|
||||
* the stream.
|
||||
* <p>
|
||||
* The caller also needs to provide the LZMA properties byte that encodes
|
||||
* the number of literal context bits (lc), literal position bits (lp), and
|
||||
* position bits (pb).
|
||||
* <p>
|
||||
* The dictionary size used when compressing is also needed. Specifying a
|
||||
* too small dictionary size will prevent decompressing the stream.
|
||||
* Specifying a too big dictionary is waste of memory but decompression will
|
||||
* work.
|
||||
* <p>
|
||||
* There is no need to specify a dictionary bigger than the uncompressed
|
||||
* size of the data even if a bigger dictionary was used when compressing.
|
||||
* If you know the uncompressed size of the data, this might allow saving
|
||||
* some memory.
|
||||
*
|
||||
* @param in input stream from which compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param propsByte LZMA properties byte that has the encoded values for
|
||||
* literal context bits (lc), literal position bits (lp), and position bits
|
||||
* (pb)
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
* or
|
||||
* the first input byte is not 0x00
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
*
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize) throws IOException {
|
||||
initialize(in, uncompSize, propsByte, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code> optionally with a preset dictionary.
|
||||
*
|
||||
* @param in input stream from which LZMA-compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param propsByte LZMA properties byte that has the encoded values for
|
||||
* literal context bits (lc), literal position bits (lp), and position bits
|
||||
* (pb)
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
* or
|
||||
* the first input byte is not 0x00
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws EOFException file is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
initialize(in, uncompSize, propsByte, dictSize, presetDict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code> optionally with a preset dictionary.
|
||||
*
|
||||
* @param in input stream from which LZMA-compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param lc number of literal context bits, must be in the range
|
||||
* [0, 8]
|
||||
*
|
||||
* @param lp number of literal position bits, must be in the range
|
||||
* [0, 4]
|
||||
*
|
||||
* @param pb number position bits, must be in the range [0, 4]
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*
|
||||
* @throws CorruptedInputException if the first input byte is not 0x00
|
||||
*
|
||||
* @throws EOFException file is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize,
|
||||
int lc, int lp, int pb,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
initialize(in, uncompSize, lc, lp, pb, dictSize, presetDict);
|
||||
}
|
||||
|
||||
private void initialize(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
// Validate the uncompressed size since the other "initialize" throws
|
||||
// IllegalArgumentException if uncompSize < -1.
|
||||
if (uncompSize < -1)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Uncompressed size is too big");
|
||||
|
||||
// Decode the properties byte. In contrast to LZMA2, there is no
|
||||
// limit of lc + lp <= 4.
|
||||
int props = propsByte & 0xFF;
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException("Invalid LZMA properties byte");
|
||||
|
||||
int pb = props / (9 * 5);
|
||||
props -= pb * 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
// Validate the dictionary size since the other "initialize" throws
|
||||
// IllegalArgumentException if dictSize is not supported.
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
initialize(in, uncompSize, lc, lp, pb, dictSize, presetDict);
|
||||
}
|
||||
|
||||
private void initialize(InputStream in, long uncompSize,
|
||||
int lc, int lp, int pb,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
// getDictSize validates dictSize and gives a message in
|
||||
// the exception too, so skip validating dictSize here.
|
||||
if (uncompSize < -1 || lc < 0 || lc > 8 || lp < 0 || lp > 4
|
||||
|| pb < 0 || pb > 4)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
this.in = in;
|
||||
|
||||
// If uncompressed size is known, use it to avoid wasting memory for
|
||||
// a uselessly large dictionary buffer.
|
||||
dictSize = getDictSize(dictSize);
|
||||
if (uncompSize >= 0 && dictSize > uncompSize)
|
||||
dictSize = getDictSize((int) uncompSize);
|
||||
|
||||
lz = new LZDecoder(getDictSize(dictSize), presetDict);
|
||||
rc = new RangeDecoderFromStream(in);
|
||||
lzma = new LZMADecoder(lz, rc, lc, lp, pb);
|
||||
remainingSize = uncompSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the next byte from this input stream.
|
||||
* <p>
|
||||
* Reading lots of data with <code>read()</code> from this input stream may
|
||||
* be inefficient. Wrap it in <code>java.io.BufferedInputStream</code> if
|
||||
* you need to read lots of data one byte at a time.
|
||||
*
|
||||
* @return the next decompressed byte, or <code>-1</code> to indicate the
|
||||
* end of the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses into an array of bytes.
|
||||
* <p>
|
||||
* If <code>len</code> is zero, no bytes are read and <code>0</code> is
|
||||
* returned. Otherwise this will block until <code>len</code> bytes have
|
||||
* been decompressed, the end of the LZMA stream is reached, or an exception
|
||||
* is thrown.
|
||||
*
|
||||
* @param buf target buffer for uncompressed data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of uncompressed bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
int size = 0;
|
||||
|
||||
while (len > 0) {
|
||||
// If uncompressed size is known and thus no end marker will
|
||||
// be present, set the limit so that the uncompressed size
|
||||
// won't be exceeded.
|
||||
int copySizeMax = len;
|
||||
if (remainingSize >= 0 && remainingSize < len)
|
||||
copySizeMax = (int) remainingSize;
|
||||
|
||||
lz.setLimit(copySizeMax);
|
||||
|
||||
// Decode into the dictionary buffer.
|
||||
try {
|
||||
lzma.decode();
|
||||
} catch (CorruptedInputException e) {
|
||||
// The end marker is encoded with a LZMA symbol that
|
||||
// indicates maximum match distance. This is larger
|
||||
// than any supported dictionary and thus causes
|
||||
// CorruptedInputException from LZDecoder.repeat.
|
||||
if (remainingSize != -1 || !lzma.endMarkerDetected())
|
||||
throw e;
|
||||
|
||||
endReached = true;
|
||||
|
||||
// The exception makes lzma.decode() miss the last range
|
||||
// decoder normalization, so do it here. This might
|
||||
// cause an IOException if it needs to read a byte
|
||||
// from the input stream.
|
||||
rc.normalize();
|
||||
}
|
||||
|
||||
// Copy from the dictionary to buf.
|
||||
int copiedSize = lz.flush(buf, off);
|
||||
off += copiedSize;
|
||||
len -= copiedSize;
|
||||
size += copiedSize;
|
||||
|
||||
if (remainingSize >= 0) {
|
||||
// Update the number of bytes left to be decompressed.
|
||||
remainingSize -= copiedSize;
|
||||
assert remainingSize >= 0;
|
||||
|
||||
if (remainingSize == 0)
|
||||
endReached = true;
|
||||
}
|
||||
|
||||
if (endReached) {
|
||||
// Checking these helps a lot when catching corrupt
|
||||
// or truncated .lzma files. LZMA Utils doesn't do
|
||||
// the first check and thus it accepts many invalid
|
||||
// files that this implementation and XZ Utils don't.
|
||||
if (!rc.isFinished() || lz.hasPending())
|
||||
throw new CorruptedInputException();
|
||||
|
||||
return size == 0 ? -1 : size;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* UncompressedLZMA2OutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
class UncompressedLZMA2OutputStream extends FinishableOutputStream {
|
||||
|
||||
private FinishableOutputStream out;
|
||||
private final DataOutputStream outData;
|
||||
|
||||
private final byte[] uncompBuf
|
||||
= new byte[LZMA2OutputStream.COMPRESSED_SIZE_MAX];
|
||||
private int uncompPos = 0;
|
||||
private boolean dictResetNeeded = true;
|
||||
|
||||
private boolean finished = false;
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
static int getMemoryUsage() {
|
||||
// uncompBuf + a little extra
|
||||
return 70;
|
||||
}
|
||||
|
||||
UncompressedLZMA2OutputStream(FinishableOutputStream out) {
|
||||
if (out == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.out = out;
|
||||
outData = new DataOutputStream(out);
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
while (len > 0) {
|
||||
int copySize = Math.min(uncompBuf.length - uncompPos, len);
|
||||
System.arraycopy(buf, off, uncompBuf, uncompPos, copySize);
|
||||
len -= copySize;
|
||||
uncompPos += copySize;
|
||||
|
||||
if (uncompPos == uncompBuf.length)
|
||||
writeChunk();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChunk() throws IOException {
|
||||
outData.writeByte(dictResetNeeded ? 0x01 : 0x02);
|
||||
outData.writeShort(uncompPos - 1);
|
||||
outData.write(uncompBuf, 0, uncompPos);
|
||||
uncompPos = 0;
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
private void writeEndMarker() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
if (uncompPos > 0)
|
||||
writeChunk();
|
||||
|
||||
out.write(0x00);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
if (uncompPos > 0)
|
||||
writeChunk();
|
||||
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
if (!finished) {
|
||||
writeEndMarker();
|
||||
|
||||
try {
|
||||
out.finish();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (out != null) {
|
||||
if (!finished)
|
||||
try {
|
||||
writeEndMarker();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
if (exception == null)
|
||||
exception = e;
|
||||
}
|
||||
|
||||
out = null;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* DeltaCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.delta;
|
||||
|
||||
abstract class DeltaCoder {
|
||||
|
||||
static final int DISTANCE_MIN = 1;
|
||||
static final int DISTANCE_MAX = 256;
|
||||
static final int DISTANCE_MASK = DISTANCE_MAX - 1;
|
||||
|
||||
final int distance;
|
||||
final byte[] history = new byte[DISTANCE_MAX];
|
||||
int pos = 0;
|
||||
|
||||
DeltaCoder(int distance) {
|
||||
if (distance < DISTANCE_MIN || distance > DISTANCE_MAX)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
this.distance = distance;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* DeltaDecoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.delta;
|
||||
|
||||
public class DeltaDecoder extends DeltaCoder {
|
||||
|
||||
public DeltaDecoder(int distance) {
|
||||
super(distance);
|
||||
}
|
||||
|
||||
public void decode(byte[] buf, int off, int len) {
|
||||
int end = off + len;
|
||||
for (int i = off; i < end; ++i) {
|
||||
buf[i] += history[(distance + pos) & DISTANCE_MASK];
|
||||
history[pos-- & DISTANCE_MASK] = buf[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* DeltaEncoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.delta;
|
||||
|
||||
public class DeltaEncoder extends DeltaCoder {
|
||||
|
||||
public DeltaEncoder(int distance) {
|
||||
super(distance);
|
||||
}
|
||||
|
||||
public void encode(byte[] in, int in_off, int len, byte[] out) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
byte tmp = history[(distance + pos) & DISTANCE_MASK];
|
||||
history[pos-- & DISTANCE_MASK] = in[in_off + i];
|
||||
out[i] = (byte) (in[in_off + i] - tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* LZMACoder
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
import org.tukaani.xz.rangecoder.RangeCoder;
|
||||
|
||||
abstract class LZMACoder {
|
||||
|
||||
static final int POS_STATES_MAX = 1 << 4;
|
||||
|
||||
static final int MATCH_LEN_MIN = 2;
|
||||
static final int MATCH_LEN_MAX = MATCH_LEN_MIN + LengthCoder.LOW_SYMBOLS
|
||||
+ LengthCoder.MID_SYMBOLS
|
||||
+ LengthCoder.HIGH_SYMBOLS - 1;
|
||||
|
||||
static final int DIST_STATES = 4;
|
||||
static final int DIST_SLOTS = 1 << 6;
|
||||
static final int DIST_MODEL_START = 4;
|
||||
static final int DIST_MODEL_END = 14;
|
||||
static final int FULL_DISTANCES = 1 << (DIST_MODEL_END / 2);
|
||||
|
||||
static final int ALIGN_BITS = 4;
|
||||
static final int ALIGN_SIZE = 1 << ALIGN_BITS;
|
||||
static final int ALIGN_MASK = ALIGN_SIZE - 1;
|
||||
|
||||
static final int REPS = 4;
|
||||
|
||||
final int posMask;
|
||||
|
||||
final int[] reps = new int[REPS];
|
||||
final State state = new State();
|
||||
|
||||
final short[][] isMatch = new short[State.STATES][POS_STATES_MAX];
|
||||
final short[] isRep = new short[State.STATES];
|
||||
final short[] isRep0 = new short[State.STATES];
|
||||
final short[] isRep1 = new short[State.STATES];
|
||||
final short[] isRep2 = new short[State.STATES];
|
||||
final short[][] isRep0Long = new short[State.STATES][POS_STATES_MAX];
|
||||
final short[][] distSlots = new short[DIST_STATES][DIST_SLOTS];
|
||||
final short[][] distSpecial = { new short[2], new short[2],
|
||||
new short[4], new short[4],
|
||||
new short[8], new short[8],
|
||||
new short[16], new short[16],
|
||||
new short[32], new short[32] };
|
||||
final short[] distAlign = new short[ALIGN_SIZE];
|
||||
|
||||
static final int getDistState(int len) {
|
||||
return len < DIST_STATES + MATCH_LEN_MIN
|
||||
? len - MATCH_LEN_MIN
|
||||
: DIST_STATES - 1;
|
||||
}
|
||||
|
||||
LZMACoder(int pb) {
|
||||
posMask = (1 << pb) - 1;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
reps[0] = 0;
|
||||
reps[1] = 0;
|
||||
reps[2] = 0;
|
||||
reps[3] = 0;
|
||||
state.reset();
|
||||
|
||||
for (int i = 0; i < isMatch.length; ++i)
|
||||
RangeCoder.initProbs(isMatch[i]);
|
||||
|
||||
RangeCoder.initProbs(isRep);
|
||||
RangeCoder.initProbs(isRep0);
|
||||
RangeCoder.initProbs(isRep1);
|
||||
RangeCoder.initProbs(isRep2);
|
||||
|
||||
for (int i = 0; i < isRep0Long.length; ++i)
|
||||
RangeCoder.initProbs(isRep0Long[i]);
|
||||
|
||||
for (int i = 0; i < distSlots.length; ++i)
|
||||
RangeCoder.initProbs(distSlots[i]);
|
||||
|
||||
for (int i = 0; i < distSpecial.length; ++i)
|
||||
RangeCoder.initProbs(distSpecial[i]);
|
||||
|
||||
RangeCoder.initProbs(distAlign);
|
||||
}
|
||||
|
||||
abstract class LiteralCoder {
|
||||
|
||||
private final int lc;
|
||||
private final int literalPosMask;
|
||||
|
||||
LiteralCoder(int lc, int lp) {
|
||||
this.lc = lc;
|
||||
this.literalPosMask = (1 << lp) - 1;
|
||||
}
|
||||
|
||||
final int getSubcoderIndex(int prevByte, int pos) {
|
||||
int low = prevByte >> (8 - lc);
|
||||
int high = (pos & literalPosMask) << lc;
|
||||
return low + high;
|
||||
}
|
||||
|
||||
abstract class LiteralSubcoder {
|
||||
|
||||
final short[] probs = new short[0x300];
|
||||
|
||||
void reset() {
|
||||
RangeCoder.initProbs(probs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class LengthCoder {
|
||||
|
||||
static final int LOW_SYMBOLS = 1 << 3;
|
||||
static final int MID_SYMBOLS = 1 << 3;
|
||||
static final int HIGH_SYMBOLS = 1 << 8;
|
||||
|
||||
final short[] choice = new short[2];
|
||||
final short[][] low = new short[POS_STATES_MAX][LOW_SYMBOLS];
|
||||
final short[][] mid = new short[POS_STATES_MAX][MID_SYMBOLS];
|
||||
final short[] high = new short[HIGH_SYMBOLS];
|
||||
|
||||
void reset() {
|
||||
RangeCoder.initProbs(choice);
|
||||
|
||||
for (int i = 0; i < low.length; ++i)
|
||||
RangeCoder.initProbs(low[i]);
|
||||
|
||||
for (int i = 0; i < low.length; ++i)
|
||||
RangeCoder.initProbs(mid[i]);
|
||||
|
||||
RangeCoder.initProbs(high);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* LZMADecoder
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZDecoder;
|
||||
import org.tukaani.xz.rangecoder.RangeDecoder;
|
||||
|
||||
public final class LZMADecoder extends LZMACoder {
|
||||
|
||||
private final LZDecoder lz;
|
||||
private final RangeDecoder rc;
|
||||
private final LiteralDecoder literalDecoder;
|
||||
private final LengthDecoder matchLenDecoder = new LengthDecoder();
|
||||
private final LengthDecoder repLenDecoder = new LengthDecoder();
|
||||
|
||||
public LZMADecoder(LZDecoder lz, RangeDecoder rc, int lc, int lp, int pb) {
|
||||
super(pb);
|
||||
this.lz = lz;
|
||||
this.rc = rc;
|
||||
this.literalDecoder = new LiteralDecoder(lc, lp);
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
super.reset();
|
||||
literalDecoder.reset();
|
||||
matchLenDecoder.reset();
|
||||
repLenDecoder.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if LZMA end marker was detected. It is encoded as the
|
||||
* maximum match distance which with signed ints becomes -1. This function
|
||||
* is needed only for LZMA1. LZMA2 doesn't use the end marker in the LZMA
|
||||
* layer.
|
||||
*/
|
||||
public boolean endMarkerDetected() {
|
||||
return reps[0] == -1;
|
||||
}
|
||||
|
||||
public void decode() throws IOException {
|
||||
lz.repeatPending();
|
||||
|
||||
while (lz.hasSpace()) {
|
||||
int posState = lz.getPos() & posMask;
|
||||
|
||||
if (rc.decodeBit(isMatch[state.get()], posState) == 0)
|
||||
literalDecoder.decode();
|
||||
else {
|
||||
int len = rc.decodeBit(isRep, state.get()) == 0
|
||||
? decodeMatch(posState)
|
||||
: decodeRepMatch(posState);
|
||||
|
||||
// NOTE: With LZMA1 streams that have the end marker,
|
||||
// this will throw CorruptedInputException. LZMAInputStream
|
||||
// handles it specially.
|
||||
lz.repeat(reps[0], len);
|
||||
}
|
||||
}
|
||||
|
||||
rc.normalize();
|
||||
}
|
||||
|
||||
private int decodeMatch(int posState) throws IOException {
|
||||
state.updateMatch();
|
||||
|
||||
reps[3] = reps[2];
|
||||
reps[2] = reps[1];
|
||||
reps[1] = reps[0];
|
||||
|
||||
int len = matchLenDecoder.decode(posState);
|
||||
int distSlot = rc.decodeBitTree(distSlots[getDistState(len)]);
|
||||
|
||||
if (distSlot < DIST_MODEL_START)
|
||||
reps[0] = distSlot;
|
||||
else {
|
||||
int limit = (distSlot >> 1) - 1;
|
||||
reps[0] = (2 | (distSlot & 1)) << limit;
|
||||
|
||||
if (distSlot < DIST_MODEL_END)
|
||||
reps[0] |= rc.decodeReverseBitTree(
|
||||
distSpecial[distSlot - DIST_MODEL_START]);
|
||||
else {
|
||||
reps[0] |= rc.decodeDirectBits(limit - ALIGN_BITS)
|
||||
<< ALIGN_BITS;
|
||||
reps[0] |= rc.decodeReverseBitTree(distAlign);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
private int decodeRepMatch(int posState) throws IOException {
|
||||
if (rc.decodeBit(isRep0, state.get()) == 0) {
|
||||
if (rc.decodeBit(isRep0Long[state.get()], posState) == 0) {
|
||||
state.updateShortRep();
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
int tmp;
|
||||
|
||||
if (rc.decodeBit(isRep1, state.get()) == 0)
|
||||
tmp = reps[1];
|
||||
else {
|
||||
if (rc.decodeBit(isRep2, state.get()) == 0)
|
||||
tmp = reps[2];
|
||||
else {
|
||||
tmp = reps[3];
|
||||
reps[3] = reps[2];
|
||||
}
|
||||
|
||||
reps[2] = reps[1];
|
||||
}
|
||||
|
||||
reps[1] = reps[0];
|
||||
reps[0] = tmp;
|
||||
}
|
||||
|
||||
state.updateLongRep();
|
||||
|
||||
return repLenDecoder.decode(posState);
|
||||
}
|
||||
|
||||
private class LiteralDecoder extends LiteralCoder {
|
||||
|
||||
LiteralSubdecoder[] subdecoders;
|
||||
|
||||
LiteralDecoder(int lc, int lp) {
|
||||
super(lc, lp);
|
||||
|
||||
subdecoders = new LiteralSubdecoder[1 << (lc + lp)];
|
||||
for (int i = 0; i < subdecoders.length; ++i)
|
||||
subdecoders[i] = new LiteralSubdecoder();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
for (int i = 0; i < subdecoders.length; ++i)
|
||||
subdecoders[i].reset();
|
||||
}
|
||||
|
||||
void decode() throws IOException {
|
||||
int i = getSubcoderIndex(lz.getByte(0), lz.getPos());
|
||||
subdecoders[i].decode();
|
||||
}
|
||||
|
||||
private class LiteralSubdecoder extends LiteralSubcoder {
|
||||
|
||||
void decode() throws IOException {
|
||||
int symbol = 1;
|
||||
|
||||
if (state.isLiteral())
|
||||
do
|
||||
symbol = (symbol << 1) | rc.decodeBit(probs, symbol);
|
||||
while (symbol < 0x100);
|
||||
else {
|
||||
int matchByte = lz.getByte(reps[0]);
|
||||
int offset = 0x100;
|
||||
int matchBit;
|
||||
int bit;
|
||||
|
||||
do {
|
||||
matchByte <<= 1;
|
||||
matchBit = matchByte & offset;
|
||||
bit = rc.decodeBit(probs, offset + matchBit + symbol);
|
||||
symbol = (symbol << 1) | bit;
|
||||
offset &= (0 - bit) ^ ~matchBit;
|
||||
} while (symbol < 0x100);
|
||||
}
|
||||
|
||||
lz.putByte((byte) symbol);
|
||||
state.updateLiteral();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthDecoder extends LengthCoder {
|
||||
|
||||
int decode(int posState) throws IOException {
|
||||
if (rc.decodeBit(choice, 0) == 0)
|
||||
return rc.decodeBitTree(low[posState]) + MATCH_LEN_MIN;
|
||||
|
||||
if (rc.decodeBit(choice, 1) == 0)
|
||||
return rc.decodeBitTree(mid[posState])
|
||||
+ MATCH_LEN_MIN + LOW_SYMBOLS;
|
||||
|
||||
return rc.decodeBitTree(high)
|
||||
+ MATCH_LEN_MIN + LOW_SYMBOLS + MID_SYMBOLS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,710 +0,0 @@
|
||||
/*
|
||||
* LZMAEncoder
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.lz.Matches;
|
||||
import org.tukaani.xz.rangecoder.RangeEncoder;
|
||||
|
||||
public abstract class LZMAEncoder extends LZMACoder {
|
||||
|
||||
public static final int MODE_FAST = 1;
|
||||
public static final int MODE_NORMAL = 2;
|
||||
|
||||
/**
|
||||
* LZMA2 chunk is considered full when its uncompressed size exceeds
|
||||
* <code>LZMA2_UNCOMPRESSED_LIMIT</code>.
|
||||
* <p>
|
||||
* A compressed LZMA2 chunk can hold 2 MiB of uncompressed data. A single
|
||||
* LZMA symbol may indicate up to MATCH_LEN_MAX bytes of data, so the LZMA2
|
||||
* chunk is considered full when there is less space than MATCH_LEN_MAX
|
||||
* bytes.
|
||||
*/
|
||||
private static final int LZMA2_UNCOMPRESSED_LIMIT
|
||||
= (2 << 20) - MATCH_LEN_MAX;
|
||||
|
||||
/**
|
||||
* LZMA2 chunk is considered full when its compressed size exceeds
|
||||
* <code>LZMA2_COMPRESSED_LIMIT</code>.
|
||||
* <p>
|
||||
* The maximum compressed size of a LZMA2 chunk is 64 KiB. A single LZMA
|
||||
* symbol might use 20 bytes of space even though it usually takes just one
|
||||
* byte or so. Two more bytes are needed for LZMA2 uncompressed chunks (see
|
||||
* LZMA2OutputStream.writeChunk). Leave a little safety margin and use 26
|
||||
* bytes.
|
||||
*/
|
||||
private static final int LZMA2_COMPRESSED_LIMIT = (64 << 10) - 26;
|
||||
|
||||
private static final int DIST_PRICE_UPDATE_INTERVAL = FULL_DISTANCES;
|
||||
private static final int ALIGN_PRICE_UPDATE_INTERVAL = ALIGN_SIZE;
|
||||
|
||||
private final RangeEncoder rc;
|
||||
final LZEncoder lz;
|
||||
final LiteralEncoder literalEncoder;
|
||||
final LengthEncoder matchLenEncoder;
|
||||
final LengthEncoder repLenEncoder;
|
||||
final int niceLen;
|
||||
|
||||
private int distPriceCount = 0;
|
||||
private int alignPriceCount = 0;
|
||||
|
||||
private final int distSlotPricesSize;
|
||||
private final int[][] distSlotPrices;
|
||||
private final int[][] fullDistPrices
|
||||
= new int[DIST_STATES][FULL_DISTANCES];
|
||||
private final int[] alignPrices = new int[ALIGN_SIZE];
|
||||
|
||||
int back = 0;
|
||||
int readAhead = -1;
|
||||
private int uncompressedSize = 0;
|
||||
|
||||
public static int getMemoryUsage(int mode, int dictSize,
|
||||
int extraSizeBefore, int mf) {
|
||||
int m = 80;
|
||||
|
||||
switch (mode) {
|
||||
case MODE_FAST:
|
||||
m += LZMAEncoderFast.getMemoryUsage(
|
||||
dictSize, extraSizeBefore, mf);
|
||||
break;
|
||||
|
||||
case MODE_NORMAL:
|
||||
m += LZMAEncoderNormal.getMemoryUsage(
|
||||
dictSize, extraSizeBefore, mf);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static LZMAEncoder getInstance(
|
||||
RangeEncoder rc, int lc, int lp, int pb, int mode,
|
||||
int dictSize, int extraSizeBefore,
|
||||
int niceLen, int mf, int depthLimit) {
|
||||
switch (mode) {
|
||||
case MODE_FAST:
|
||||
return new LZMAEncoderFast(rc, lc, lp, pb,
|
||||
dictSize, extraSizeBefore,
|
||||
niceLen, mf, depthLimit);
|
||||
|
||||
case MODE_NORMAL:
|
||||
return new LZMAEncoderNormal(rc, lc, lp, pb,
|
||||
dictSize, extraSizeBefore,
|
||||
niceLen, mf, depthLimit);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an integer [0, 63] matching the highest two bits of an integer. This
|
||||
* is like bit scan reverse (BSR) on x86 except that this also cares about
|
||||
* the second highest bit.
|
||||
*/
|
||||
public static int getDistSlot(int dist) {
|
||||
if (dist <= DIST_MODEL_START)
|
||||
return dist;
|
||||
|
||||
int n = dist;
|
||||
int i = 31;
|
||||
|
||||
if ((n & 0xFFFF0000) == 0) {
|
||||
n <<= 16;
|
||||
i = 15;
|
||||
}
|
||||
|
||||
if ((n & 0xFF000000) == 0) {
|
||||
n <<= 8;
|
||||
i -= 8;
|
||||
}
|
||||
|
||||
if ((n & 0xF0000000) == 0) {
|
||||
n <<= 4;
|
||||
i -= 4;
|
||||
}
|
||||
|
||||
if ((n & 0xC0000000) == 0) {
|
||||
n <<= 2;
|
||||
i -= 2;
|
||||
}
|
||||
|
||||
if ((n & 0x80000000) == 0)
|
||||
--i;
|
||||
|
||||
return (i << 1) + ((dist >>> (i - 1)) & 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next LZMA symbol.
|
||||
* <p>
|
||||
* There are three types of symbols: literal (a single byte), repeated
|
||||
* match, and normal match. The symbol is indicated by the return value and
|
||||
* by the variable <code>back</code>.
|
||||
* <p>
|
||||
* Literal: <code>back == -1</code> and return value is <code>1</code>. The
|
||||
* literal itself needs to be read from <code>lz</code> separately.
|
||||
* <p>
|
||||
* Repeated match: <code>back</code> is in the range [0, 3] and the return
|
||||
* value is the length of the repeated match.
|
||||
* <p>
|
||||
* Normal match: <code>back - REPS<code> (<code>back - 4</code>) is the
|
||||
* distance of the match and the return value is the length of the match.
|
||||
*/
|
||||
abstract int getNextSymbol();
|
||||
|
||||
LZMAEncoder(RangeEncoder rc, LZEncoder lz,
|
||||
int lc, int lp, int pb, int dictSize, int niceLen) {
|
||||
super(pb);
|
||||
this.rc = rc;
|
||||
this.lz = lz;
|
||||
this.niceLen = niceLen;
|
||||
|
||||
literalEncoder = new LiteralEncoder(lc, lp);
|
||||
matchLenEncoder = new LengthEncoder(pb, niceLen);
|
||||
repLenEncoder = new LengthEncoder(pb, niceLen);
|
||||
|
||||
distSlotPricesSize = getDistSlot(dictSize - 1) + 1;
|
||||
distSlotPrices = new int[DIST_STATES][distSlotPricesSize];
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public LZEncoder getLZEncoder() {
|
||||
return lz;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
super.reset();
|
||||
literalEncoder.reset();
|
||||
matchLenEncoder.reset();
|
||||
repLenEncoder.reset();
|
||||
distPriceCount = 0;
|
||||
alignPriceCount = 0;
|
||||
|
||||
uncompressedSize += readAhead + 1;
|
||||
readAhead = -1;
|
||||
}
|
||||
|
||||
public int getUncompressedSize() {
|
||||
return uncompressedSize;
|
||||
}
|
||||
|
||||
public void resetUncompressedSize() {
|
||||
uncompressedSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses for LZMA2.
|
||||
*
|
||||
* @return true if the LZMA2 chunk became full, false otherwise
|
||||
*/
|
||||
public boolean encodeForLZMA2() {
|
||||
if (!lz.isStarted() && !encodeInit())
|
||||
return false;
|
||||
|
||||
while (uncompressedSize <= LZMA2_UNCOMPRESSED_LIMIT
|
||||
&& rc.getPendingSize() <= LZMA2_COMPRESSED_LIMIT)
|
||||
if (!encodeSymbol())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean encodeInit() {
|
||||
assert readAhead == -1;
|
||||
if (!lz.hasEnoughData(0))
|
||||
return false;
|
||||
|
||||
// The first symbol must be a literal unless using
|
||||
// a preset dictionary. This code isn't run if using
|
||||
// a preset dictionary.
|
||||
skip(1);
|
||||
rc.encodeBit(isMatch[state.get()], 0, 0);
|
||||
literalEncoder.encodeInit();
|
||||
|
||||
--readAhead;
|
||||
assert readAhead == -1;
|
||||
|
||||
++uncompressedSize;
|
||||
assert uncompressedSize == 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean encodeSymbol() {
|
||||
if (!lz.hasEnoughData(readAhead + 1))
|
||||
return false;
|
||||
|
||||
int len = getNextSymbol();
|
||||
|
||||
assert readAhead >= 0;
|
||||
int posState = (lz.getPos() - readAhead) & posMask;
|
||||
|
||||
if (back == -1) {
|
||||
// Literal i.e. eight-bit byte
|
||||
assert len == 1;
|
||||
rc.encodeBit(isMatch[state.get()], posState, 0);
|
||||
literalEncoder.encode();
|
||||
} else {
|
||||
// Some type of match
|
||||
rc.encodeBit(isMatch[state.get()], posState, 1);
|
||||
if (back < REPS) {
|
||||
// Repeated match i.e. the same distance
|
||||
// has been used earlier.
|
||||
assert lz.getMatchLen(-readAhead, reps[back], len) == len;
|
||||
rc.encodeBit(isRep, state.get(), 1);
|
||||
encodeRepMatch(back, len, posState);
|
||||
} else {
|
||||
// Normal match
|
||||
assert lz.getMatchLen(-readAhead, back - REPS, len) == len;
|
||||
rc.encodeBit(isRep, state.get(), 0);
|
||||
encodeMatch(back - REPS, len, posState);
|
||||
}
|
||||
}
|
||||
|
||||
readAhead -= len;
|
||||
uncompressedSize += len;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void encodeMatch(int dist, int len, int posState) {
|
||||
state.updateMatch();
|
||||
matchLenEncoder.encode(len, posState);
|
||||
|
||||
int distSlot = getDistSlot(dist);
|
||||
rc.encodeBitTree(distSlots[getDistState(len)], distSlot);
|
||||
|
||||
if (distSlot >= DIST_MODEL_START) {
|
||||
int footerBits = (distSlot >>> 1) - 1;
|
||||
int base = (2 | (distSlot & 1)) << footerBits;
|
||||
int distReduced = dist - base;
|
||||
|
||||
if (distSlot < DIST_MODEL_END)
|
||||
rc.encodeReverseBitTree(
|
||||
distSpecial[distSlot - DIST_MODEL_START],
|
||||
distReduced);
|
||||
else {
|
||||
rc.encodeDirectBits(distReduced >>> ALIGN_BITS,
|
||||
footerBits - ALIGN_BITS);
|
||||
rc.encodeReverseBitTree(distAlign, distReduced & ALIGN_MASK);
|
||||
--alignPriceCount;
|
||||
}
|
||||
}
|
||||
|
||||
reps[3] = reps[2];
|
||||
reps[2] = reps[1];
|
||||
reps[1] = reps[0];
|
||||
reps[0] = dist;
|
||||
|
||||
--distPriceCount;
|
||||
}
|
||||
|
||||
private void encodeRepMatch(int rep, int len, int posState) {
|
||||
if (rep == 0) {
|
||||
rc.encodeBit(isRep0, state.get(), 0);
|
||||
rc.encodeBit(isRep0Long[state.get()], posState, len == 1 ? 0 : 1);
|
||||
} else {
|
||||
int dist = reps[rep];
|
||||
rc.encodeBit(isRep0, state.get(), 1);
|
||||
|
||||
if (rep == 1)
|
||||
rc.encodeBit(isRep1, state.get(), 0);
|
||||
else {
|
||||
rc.encodeBit(isRep1, state.get(), 1);
|
||||
rc.encodeBit(isRep2, state.get(), rep - 2);
|
||||
|
||||
if (rep == 3)
|
||||
reps[3] = reps[2];
|
||||
|
||||
reps[2] = reps[1];
|
||||
}
|
||||
|
||||
reps[1] = reps[0];
|
||||
reps[0] = dist;
|
||||
}
|
||||
|
||||
if (len == 1)
|
||||
state.updateShortRep();
|
||||
else {
|
||||
repLenEncoder.encode(len, posState);
|
||||
state.updateLongRep();
|
||||
}
|
||||
}
|
||||
|
||||
Matches getMatches() {
|
||||
++readAhead;
|
||||
Matches matches = lz.getMatches();
|
||||
assert lz.verifyMatches(matches);
|
||||
return matches;
|
||||
}
|
||||
|
||||
void skip(int len) {
|
||||
readAhead += len;
|
||||
lz.skip(len);
|
||||
}
|
||||
|
||||
int getAnyMatchPrice(State state, int posState) {
|
||||
return RangeEncoder.getBitPrice(isMatch[state.get()][posState], 1);
|
||||
}
|
||||
|
||||
int getNormalMatchPrice(int anyMatchPrice, State state) {
|
||||
return anyMatchPrice
|
||||
+ RangeEncoder.getBitPrice(isRep[state.get()], 0);
|
||||
}
|
||||
|
||||
int getAnyRepPrice(int anyMatchPrice, State state) {
|
||||
return anyMatchPrice
|
||||
+ RangeEncoder.getBitPrice(isRep[state.get()], 1);
|
||||
}
|
||||
|
||||
int getShortRepPrice(int anyRepPrice, State state, int posState) {
|
||||
return anyRepPrice
|
||||
+ RangeEncoder.getBitPrice(isRep0[state.get()], 0)
|
||||
+ RangeEncoder.getBitPrice(isRep0Long[state.get()][posState],
|
||||
0);
|
||||
}
|
||||
|
||||
int getLongRepPrice(int anyRepPrice, int rep, State state, int posState) {
|
||||
int price = anyRepPrice;
|
||||
|
||||
if (rep == 0)
|
||||
price += RangeEncoder.getBitPrice(isRep0[state.get()], 0)
|
||||
+ RangeEncoder.getBitPrice(
|
||||
isRep0Long[state.get()][posState], 1);
|
||||
else {
|
||||
price += RangeEncoder.getBitPrice(isRep0[state.get()], 1);
|
||||
|
||||
if (rep == 1)
|
||||
price += RangeEncoder.getBitPrice(isRep1[state.get()], 0);
|
||||
else
|
||||
price += RangeEncoder.getBitPrice(isRep1[state.get()], 1)
|
||||
+ RangeEncoder.getBitPrice(isRep2[state.get()],
|
||||
rep - 2);
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
int getLongRepAndLenPrice(int rep, int len, State state, int posState) {
|
||||
int anyMatchPrice = getAnyMatchPrice(state, posState);
|
||||
int anyRepPrice = getAnyRepPrice(anyMatchPrice, state);
|
||||
int longRepPrice = getLongRepPrice(anyRepPrice, rep, state, posState);
|
||||
return longRepPrice + repLenEncoder.getPrice(len, posState);
|
||||
}
|
||||
|
||||
int getMatchAndLenPrice(int normalMatchPrice,
|
||||
int dist, int len, int posState) {
|
||||
int price = normalMatchPrice
|
||||
+ matchLenEncoder.getPrice(len, posState);
|
||||
int distState = getDistState(len);
|
||||
|
||||
if (dist < FULL_DISTANCES)
|
||||
price += fullDistPrices[distState][dist];
|
||||
else {
|
||||
// Note that distSlotPrices includes also
|
||||
// the price of direct bits.
|
||||
int distSlot = getDistSlot(dist);
|
||||
price += distSlotPrices[distState][distSlot]
|
||||
+ alignPrices[dist & ALIGN_MASK];
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
private void updateDistPrices() {
|
||||
distPriceCount = DIST_PRICE_UPDATE_INTERVAL;
|
||||
|
||||
for (int distState = 0; distState < DIST_STATES; ++distState) {
|
||||
for (int distSlot = 0; distSlot < distSlotPricesSize; ++distSlot)
|
||||
distSlotPrices[distState][distSlot]
|
||||
= RangeEncoder.getBitTreePrice(
|
||||
distSlots[distState], distSlot);
|
||||
|
||||
for (int distSlot = DIST_MODEL_END; distSlot < distSlotPricesSize;
|
||||
++distSlot) {
|
||||
int count = (distSlot >>> 1) - 1 - ALIGN_BITS;
|
||||
distSlotPrices[distState][distSlot]
|
||||
+= RangeEncoder.getDirectBitsPrice(count);
|
||||
}
|
||||
|
||||
for (int dist = 0; dist < DIST_MODEL_START; ++dist)
|
||||
fullDistPrices[distState][dist]
|
||||
= distSlotPrices[distState][dist];
|
||||
}
|
||||
|
||||
int dist = DIST_MODEL_START;
|
||||
for (int distSlot = DIST_MODEL_START; distSlot < DIST_MODEL_END;
|
||||
++distSlot) {
|
||||
int footerBits = (distSlot >>> 1) - 1;
|
||||
int base = (2 | (distSlot & 1)) << footerBits;
|
||||
|
||||
int limit = distSpecial[distSlot - DIST_MODEL_START].length;
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
int distReduced = dist - base;
|
||||
int price = RangeEncoder.getReverseBitTreePrice(
|
||||
distSpecial[distSlot - DIST_MODEL_START],
|
||||
distReduced);
|
||||
|
||||
for (int distState = 0; distState < DIST_STATES; ++distState)
|
||||
fullDistPrices[distState][dist]
|
||||
= distSlotPrices[distState][distSlot] + price;
|
||||
|
||||
++dist;
|
||||
}
|
||||
}
|
||||
|
||||
assert dist == FULL_DISTANCES;
|
||||
}
|
||||
|
||||
private void updateAlignPrices() {
|
||||
alignPriceCount = ALIGN_PRICE_UPDATE_INTERVAL;
|
||||
|
||||
for (int i = 0; i < ALIGN_SIZE; ++i)
|
||||
alignPrices[i] = RangeEncoder.getReverseBitTreePrice(distAlign,
|
||||
i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the lookup tables used for calculating match distance and length
|
||||
* prices. The updating is skipped for performance reasons if the tables
|
||||
* haven't changed much since the previous update.
|
||||
*/
|
||||
void updatePrices() {
|
||||
if (distPriceCount <= 0)
|
||||
updateDistPrices();
|
||||
|
||||
if (alignPriceCount <= 0)
|
||||
updateAlignPrices();
|
||||
|
||||
matchLenEncoder.updatePrices();
|
||||
repLenEncoder.updatePrices();
|
||||
}
|
||||
|
||||
class LiteralEncoder extends LiteralCoder {
|
||||
|
||||
LiteralSubencoder[] subencoders;
|
||||
|
||||
LiteralEncoder(int lc, int lp) {
|
||||
super(lc, lp);
|
||||
|
||||
subencoders = new LiteralSubencoder[1 << (lc + lp)];
|
||||
for (int i = 0; i < subencoders.length; ++i)
|
||||
subencoders[i] = new LiteralSubencoder();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
for (int i = 0; i < subencoders.length; ++i)
|
||||
subencoders[i].reset();
|
||||
}
|
||||
|
||||
void encodeInit() {
|
||||
// When encoding the first byte of the stream, there is
|
||||
// no previous byte in the dictionary so the encode function
|
||||
// wouldn't work.
|
||||
assert readAhead >= 0;
|
||||
subencoders[0].encode();
|
||||
}
|
||||
|
||||
void encode() {
|
||||
assert readAhead >= 0;
|
||||
int i = getSubcoderIndex(lz.getByte(1 + readAhead),
|
||||
lz.getPos() - readAhead);
|
||||
subencoders[i].encode();
|
||||
}
|
||||
|
||||
int getPrice(int curByte, int matchByte,
|
||||
int prevByte, int pos, State state) {
|
||||
int price = RangeEncoder.getBitPrice(
|
||||
isMatch[state.get()][pos & posMask], 0);
|
||||
|
||||
int i = getSubcoderIndex(prevByte, pos);
|
||||
price += state.isLiteral()
|
||||
? subencoders[i].getNormalPrice(curByte)
|
||||
: subencoders[i].getMatchedPrice(curByte, matchByte);
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
private class LiteralSubencoder extends LiteralSubcoder {
|
||||
|
||||
void encode() {
|
||||
int symbol = lz.getByte(readAhead) | 0x100;
|
||||
|
||||
if (state.isLiteral()) {
|
||||
int subencoderIndex;
|
||||
int bit;
|
||||
|
||||
do {
|
||||
subencoderIndex = symbol >>> 8;
|
||||
bit = (symbol >>> 7) & 1;
|
||||
rc.encodeBit(probs, subencoderIndex, bit);
|
||||
symbol <<= 1;
|
||||
} while (symbol < 0x10000);
|
||||
|
||||
} else {
|
||||
int matchByte = lz.getByte(reps[0] + 1 + readAhead);
|
||||
int offset = 0x100;
|
||||
int subencoderIndex;
|
||||
int matchBit;
|
||||
int bit;
|
||||
|
||||
do {
|
||||
matchByte <<= 1;
|
||||
matchBit = matchByte & offset;
|
||||
subencoderIndex = offset + matchBit + (symbol >>> 8);
|
||||
bit = (symbol >>> 7) & 1;
|
||||
rc.encodeBit(probs, subencoderIndex, bit);
|
||||
symbol <<= 1;
|
||||
offset &= ~(matchByte ^ symbol);
|
||||
} while (symbol < 0x10000);
|
||||
}
|
||||
|
||||
state.updateLiteral();
|
||||
}
|
||||
|
||||
int getNormalPrice(int symbol) {
|
||||
int price = 0;
|
||||
int subencoderIndex;
|
||||
int bit;
|
||||
|
||||
symbol |= 0x100;
|
||||
|
||||
do {
|
||||
subencoderIndex = symbol >>> 8;
|
||||
bit = (symbol >>> 7) & 1;
|
||||
price += RangeEncoder.getBitPrice(probs[subencoderIndex],
|
||||
bit);
|
||||
symbol <<= 1;
|
||||
} while (symbol < (0x100 << 8));
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
int getMatchedPrice(int symbol, int matchByte) {
|
||||
int price = 0;
|
||||
int offset = 0x100;
|
||||
int subencoderIndex;
|
||||
int matchBit;
|
||||
int bit;
|
||||
|
||||
symbol |= 0x100;
|
||||
|
||||
do {
|
||||
matchByte <<= 1;
|
||||
matchBit = matchByte & offset;
|
||||
subencoderIndex = offset + matchBit + (symbol >>> 8);
|
||||
bit = (symbol >>> 7) & 1;
|
||||
price += RangeEncoder.getBitPrice(probs[subencoderIndex],
|
||||
bit);
|
||||
symbol <<= 1;
|
||||
offset &= ~(matchByte ^ symbol);
|
||||
} while (symbol < (0x100 << 8));
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LengthEncoder extends LengthCoder {
|
||||
|
||||
/**
|
||||
* The prices are updated after at least
|
||||
* <code>PRICE_UPDATE_INTERVAL</code> many lengths have been encoded
|
||||
* with the same posState.
|
||||
*/
|
||||
private static final int PRICE_UPDATE_INTERVAL = 32; // FIXME?
|
||||
|
||||
private final int[] counters;
|
||||
private final int[][] prices;
|
||||
|
||||
LengthEncoder(int pb, int niceLen) {
|
||||
int posStates = 1 << pb;
|
||||
counters = new int[posStates];
|
||||
|
||||
// Always allocate at least LOW_SYMBOLS + MID_SYMBOLS because
|
||||
// it makes updatePrices slightly simpler. The prices aren't
|
||||
// usually needed anyway if niceLen < 18.
|
||||
int lenSymbols = Math.max(niceLen - MATCH_LEN_MIN + 1,
|
||||
LOW_SYMBOLS + MID_SYMBOLS);
|
||||
prices = new int[posStates][lenSymbols];
|
||||
}
|
||||
|
||||
void reset() {
|
||||
super.reset();
|
||||
|
||||
// Reset counters to zero to force price update before
|
||||
// the prices are needed.
|
||||
for (int i = 0; i < counters.length; ++i)
|
||||
counters[i] = 0;
|
||||
}
|
||||
|
||||
void encode(int len, int posState) {
|
||||
len -= MATCH_LEN_MIN;
|
||||
|
||||
if (len < LOW_SYMBOLS) {
|
||||
rc.encodeBit(choice, 0, 0);
|
||||
rc.encodeBitTree(low[posState], len);
|
||||
} else {
|
||||
rc.encodeBit(choice, 0, 1);
|
||||
len -= LOW_SYMBOLS;
|
||||
|
||||
if (len < MID_SYMBOLS) {
|
||||
rc.encodeBit(choice, 1, 0);
|
||||
rc.encodeBitTree(mid[posState], len);
|
||||
} else {
|
||||
rc.encodeBit(choice, 1, 1);
|
||||
rc.encodeBitTree(high, len - MID_SYMBOLS);
|
||||
}
|
||||
}
|
||||
|
||||
--counters[posState];
|
||||
}
|
||||
|
||||
int getPrice(int len, int posState) {
|
||||
return prices[posState][len - MATCH_LEN_MIN];
|
||||
}
|
||||
|
||||
void updatePrices() {
|
||||
for (int posState = 0; posState < counters.length; ++posState)
|
||||
if (counters[posState] <= 0) {
|
||||
counters[posState] = PRICE_UPDATE_INTERVAL;
|
||||
updatePrices(posState);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePrices(int posState) {
|
||||
int choice0Price = RangeEncoder.getBitPrice(choice[0], 0);
|
||||
|
||||
int i = 0;
|
||||
for (; i < LOW_SYMBOLS; ++i)
|
||||
prices[posState][i] = choice0Price
|
||||
+ RangeEncoder.getBitTreePrice(low[posState], i);
|
||||
|
||||
choice0Price = RangeEncoder.getBitPrice(choice[0], 1);
|
||||
int choice1Price = RangeEncoder.getBitPrice(choice[1], 0);
|
||||
|
||||
for (; i < LOW_SYMBOLS + MID_SYMBOLS; ++i)
|
||||
prices[posState][i] = choice0Price + choice1Price
|
||||
+ RangeEncoder.getBitTreePrice(mid[posState],
|
||||
i - LOW_SYMBOLS);
|
||||
|
||||
choice1Price = RangeEncoder.getBitPrice(choice[1], 1);
|
||||
|
||||
for (; i < prices[posState].length; ++i)
|
||||
prices[posState][i] = choice0Price + choice1Price
|
||||
+ RangeEncoder.getBitTreePrice(high, i - LOW_SYMBOLS
|
||||
- MID_SYMBOLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* LZMAEncoderFast
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.lz.Matches;
|
||||
import org.tukaani.xz.rangecoder.RangeEncoder;
|
||||
|
||||
final class LZMAEncoderFast extends LZMAEncoder {
|
||||
|
||||
private static int EXTRA_SIZE_BEFORE = 1;
|
||||
private static int EXTRA_SIZE_AFTER = MATCH_LEN_MAX - 1;
|
||||
|
||||
private Matches matches = null;
|
||||
|
||||
static int getMemoryUsage(int dictSize, int extraSizeBefore, int mf) {
|
||||
return LZEncoder.getMemoryUsage(
|
||||
dictSize, Math.max(extraSizeBefore, EXTRA_SIZE_BEFORE),
|
||||
EXTRA_SIZE_AFTER, MATCH_LEN_MAX, mf);
|
||||
}
|
||||
|
||||
LZMAEncoderFast(RangeEncoder rc, int lc, int lp, int pb,
|
||||
int dictSize, int extraSizeBefore,
|
||||
int niceLen, int mf, int depthLimit) {
|
||||
super(rc, LZEncoder.getInstance(dictSize,
|
||||
Math.max(extraSizeBefore,
|
||||
EXTRA_SIZE_BEFORE),
|
||||
EXTRA_SIZE_AFTER,
|
||||
niceLen, MATCH_LEN_MAX,
|
||||
mf, depthLimit),
|
||||
lc, lp, pb, dictSize, niceLen);
|
||||
}
|
||||
|
||||
private boolean changePair(int smallDist, int bigDist) {
|
||||
return smallDist < (bigDist >>> 7);
|
||||
}
|
||||
|
||||
int getNextSymbol() {
|
||||
// Get the matches for the next byte unless readAhead indicates
|
||||
// that we already got the new matches during the previous call
|
||||
// to this function.
|
||||
if (readAhead == -1)
|
||||
matches = getMatches();
|
||||
|
||||
back = -1;
|
||||
|
||||
// Get the number of bytes available in the dictionary, but
|
||||
// not more than the maximum match length. If there aren't
|
||||
// enough bytes remaining to encode a match at all, return
|
||||
// immediately to encode this byte as a literal.
|
||||
int avail = Math.min(lz.getAvail(), MATCH_LEN_MAX);
|
||||
if (avail < MATCH_LEN_MIN)
|
||||
return 1;
|
||||
|
||||
// Look for a match from the previous four match distances.
|
||||
int bestRepLen = 0;
|
||||
int bestRepIndex = 0;
|
||||
for (int rep = 0; rep < REPS; ++rep) {
|
||||
int len = lz.getMatchLen(reps[rep], avail);
|
||||
if (len < MATCH_LEN_MIN)
|
||||
continue;
|
||||
|
||||
// If it is long enough, return it.
|
||||
if (len >= niceLen) {
|
||||
back = rep;
|
||||
skip(len - 1);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Remember the index and length of the best repeated match.
|
||||
if (len > bestRepLen) {
|
||||
bestRepIndex = rep;
|
||||
bestRepLen = len;
|
||||
}
|
||||
}
|
||||
|
||||
int mainLen = 0;
|
||||
int mainDist = 0;
|
||||
|
||||
if (matches.count > 0) {
|
||||
mainLen = matches.len[matches.count - 1];
|
||||
mainDist = matches.dist[matches.count - 1];
|
||||
|
||||
if (mainLen >= niceLen) {
|
||||
back = mainDist + REPS;
|
||||
skip(mainLen - 1);
|
||||
return mainLen;
|
||||
}
|
||||
|
||||
while (matches.count > 1
|
||||
&& mainLen == matches.len[matches.count - 2] + 1) {
|
||||
if (!changePair(matches.dist[matches.count - 2], mainDist))
|
||||
break;
|
||||
|
||||
--matches.count;
|
||||
mainLen = matches.len[matches.count - 1];
|
||||
mainDist = matches.dist[matches.count - 1];
|
||||
}
|
||||
|
||||
if (mainLen == MATCH_LEN_MIN && mainDist >= 0x80)
|
||||
mainLen = 1;
|
||||
}
|
||||
|
||||
if (bestRepLen >= MATCH_LEN_MIN)
|
||||
if (bestRepLen + 1 >= mainLen
|
||||
|| (bestRepLen + 2 >= mainLen && mainDist >= (1 << 9))
|
||||
|| (bestRepLen + 3 >= mainLen && mainDist >= (1 << 15))) {
|
||||
back = bestRepIndex;
|
||||
skip(bestRepLen - 1);
|
||||
return bestRepLen;
|
||||
}
|
||||
|
||||
if (mainLen < MATCH_LEN_MIN || avail <= MATCH_LEN_MIN)
|
||||
return 1;
|
||||
|
||||
// Get the next match. Test if it is better than the current match.
|
||||
// If so, encode the current byte as a literal.
|
||||
matches = getMatches();
|
||||
|
||||
if (matches.count > 0) {
|
||||
int newLen = matches.len[matches.count - 1];
|
||||
int newDist = matches.dist[matches.count - 1];
|
||||
|
||||
if ((newLen >= mainLen && newDist < mainDist)
|
||||
|| (newLen == mainLen + 1
|
||||
&& !changePair(mainDist, newDist))
|
||||
|| newLen > mainLen + 1
|
||||
|| (newLen + 1 >= mainLen
|
||||
&& mainLen >= MATCH_LEN_MIN + 1
|
||||
&& changePair(newDist, mainDist)))
|
||||
return 1;
|
||||
}
|
||||
|
||||
int limit = Math.max(mainLen - 1, MATCH_LEN_MIN);
|
||||
for (int rep = 0; rep < REPS; ++rep)
|
||||
if (lz.getMatchLen(reps[rep], limit) == limit)
|
||||
return 1;
|
||||
|
||||
back = mainDist + REPS;
|
||||
skip(mainLen - 2);
|
||||
return mainLen;
|
||||
}
|
||||
}
|
||||
@@ -1,561 +0,0 @@
|
||||
/*
|
||||
* LZMAEncoderNormal
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.lz.Matches;
|
||||
import org.tukaani.xz.rangecoder.RangeEncoder;
|
||||
|
||||
final class LZMAEncoderNormal extends LZMAEncoder {
|
||||
|
||||
private static final int OPTS = 4096;
|
||||
|
||||
private static int EXTRA_SIZE_BEFORE = OPTS;
|
||||
private static int EXTRA_SIZE_AFTER = OPTS;
|
||||
|
||||
private final Optimum[] opts = new Optimum[OPTS];
|
||||
private int optCur = 0;
|
||||
private int optEnd = 0;
|
||||
|
||||
private Matches matches;
|
||||
|
||||
// These are fields solely to avoid allocating the objects again and
|
||||
// again on each function call.
|
||||
private final int[] repLens = new int[REPS];
|
||||
private final State nextState = new State();
|
||||
|
||||
static int getMemoryUsage(int dictSize, int extraSizeBefore, int mf) {
|
||||
return LZEncoder.getMemoryUsage(dictSize,
|
||||
Math.max(extraSizeBefore, EXTRA_SIZE_BEFORE),
|
||||
EXTRA_SIZE_AFTER, MATCH_LEN_MAX, mf)
|
||||
+ OPTS * 64 / 1024;
|
||||
}
|
||||
|
||||
LZMAEncoderNormal(RangeEncoder rc, int lc, int lp, int pb,
|
||||
int dictSize, int extraSizeBefore,
|
||||
int niceLen, int mf, int depthLimit) {
|
||||
super(rc, LZEncoder.getInstance(dictSize,
|
||||
Math.max(extraSizeBefore,
|
||||
EXTRA_SIZE_BEFORE),
|
||||
EXTRA_SIZE_AFTER,
|
||||
niceLen, MATCH_LEN_MAX,
|
||||
mf, depthLimit),
|
||||
lc, lp, pb, dictSize, niceLen);
|
||||
|
||||
for (int i = 0; i < OPTS; ++i)
|
||||
opts[i] = new Optimum();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
optCur = 0;
|
||||
optEnd = 0;
|
||||
super.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the opts array from backward indexes to forward indexes. Then it
|
||||
* will be simple to get the next symbol from the array in later calls to
|
||||
* <code>getNextSymbol()</code>.
|
||||
*/
|
||||
private int convertOpts() {
|
||||
optEnd = optCur;
|
||||
|
||||
int optPrev = opts[optCur].optPrev;
|
||||
|
||||
do {
|
||||
Optimum opt = opts[optCur];
|
||||
|
||||
if (opt.prev1IsLiteral) {
|
||||
opts[optPrev].optPrev = optCur;
|
||||
opts[optPrev].backPrev = -1;
|
||||
optCur = optPrev--;
|
||||
|
||||
if (opt.hasPrev2) {
|
||||
opts[optPrev].optPrev = optPrev + 1;
|
||||
opts[optPrev].backPrev = opt.backPrev2;
|
||||
optCur = optPrev;
|
||||
optPrev = opt.optPrev2;
|
||||
}
|
||||
}
|
||||
|
||||
int temp = opts[optPrev].optPrev;
|
||||
opts[optPrev].optPrev = optCur;
|
||||
optCur = optPrev;
|
||||
optPrev = temp;
|
||||
} while (optCur > 0);
|
||||
|
||||
optCur = opts[0].optPrev;
|
||||
back = opts[optCur].backPrev;
|
||||
return optCur;
|
||||
}
|
||||
|
||||
int getNextSymbol() {
|
||||
// If there are pending symbols from an earlier call to this
|
||||
// function, return those symbols first.
|
||||
if (optCur < optEnd) {
|
||||
int len = opts[optCur].optPrev - optCur;
|
||||
optCur = opts[optCur].optPrev;
|
||||
back = opts[optCur].backPrev;
|
||||
return len;
|
||||
}
|
||||
|
||||
assert optCur == optEnd;
|
||||
optCur = 0;
|
||||
optEnd = 0;
|
||||
back = -1;
|
||||
|
||||
if (readAhead == -1)
|
||||
matches = getMatches();
|
||||
|
||||
// Get the number of bytes available in the dictionary, but
|
||||
// not more than the maximum match length. If there aren't
|
||||
// enough bytes remaining to encode a match at all, return
|
||||
// immediately to encode this byte as a literal.
|
||||
int avail = Math.min(lz.getAvail(), MATCH_LEN_MAX);
|
||||
if (avail < MATCH_LEN_MIN)
|
||||
return 1;
|
||||
|
||||
// Get the lengths of repeated matches.
|
||||
int repBest = 0;
|
||||
for (int rep = 0; rep < REPS; ++rep) {
|
||||
repLens[rep] = lz.getMatchLen(reps[rep], avail);
|
||||
|
||||
if (repLens[rep] < MATCH_LEN_MIN) {
|
||||
repLens[rep] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repLens[rep] > repLens[repBest])
|
||||
repBest = rep;
|
||||
}
|
||||
|
||||
// Return if the best repeated match is at least niceLen bytes long.
|
||||
if (repLens[repBest] >= niceLen) {
|
||||
back = repBest;
|
||||
skip(repLens[repBest] - 1);
|
||||
return repLens[repBest];
|
||||
}
|
||||
|
||||
// Initialize mainLen and mainDist to the longest match found
|
||||
// by the match finder.
|
||||
int mainLen = 0;
|
||||
int mainDist = 0;
|
||||
if (matches.count > 0) {
|
||||
mainLen = matches.len[matches.count - 1];
|
||||
mainDist = matches.dist[matches.count - 1];
|
||||
|
||||
// Return if it is at least niceLen bytes long.
|
||||
if (mainLen >= niceLen) {
|
||||
back = mainDist + REPS;
|
||||
skip(mainLen - 1);
|
||||
return mainLen;
|
||||
}
|
||||
}
|
||||
|
||||
int curByte = lz.getByte(0);
|
||||
int matchByte = lz.getByte(reps[0] + 1);
|
||||
|
||||
// If the match finder found no matches and this byte cannot be
|
||||
// encoded as a repeated match (short or long), we must be return
|
||||
// to have the byte encoded as a literal.
|
||||
if (mainLen < MATCH_LEN_MIN && curByte != matchByte
|
||||
&& repLens[repBest] < MATCH_LEN_MIN)
|
||||
return 1;
|
||||
|
||||
int pos = lz.getPos();
|
||||
int posState = pos & posMask;
|
||||
|
||||
// Calculate the price of encoding the current byte as a literal.
|
||||
{
|
||||
int prevByte = lz.getByte(1);
|
||||
int literalPrice = literalEncoder.getPrice(curByte, matchByte,
|
||||
prevByte, pos, state);
|
||||
opts[1].set1(literalPrice, 0, -1);
|
||||
}
|
||||
|
||||
int anyMatchPrice = getAnyMatchPrice(state, posState);
|
||||
int anyRepPrice = getAnyRepPrice(anyMatchPrice, state);
|
||||
|
||||
// If it is possible to encode this byte as a short rep, see if
|
||||
// it is cheaper than encoding it as a literal.
|
||||
if (matchByte == curByte) {
|
||||
int shortRepPrice = getShortRepPrice(anyRepPrice,
|
||||
state, posState);
|
||||
if (shortRepPrice < opts[1].price)
|
||||
opts[1].set1(shortRepPrice, 0, 0);
|
||||
}
|
||||
|
||||
// Return if there is neither normal nor long repeated match. Use
|
||||
// a short match instead of a literal if is is possible and cheaper.
|
||||
optEnd = Math.max(mainLen, repLens[repBest]);
|
||||
if (optEnd < MATCH_LEN_MIN) {
|
||||
assert optEnd == 0 : optEnd;
|
||||
back = opts[1].backPrev;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Update the lookup tables for distances and lengths before using
|
||||
// those price calculation functions. (The price function above
|
||||
// don't need these tables.)
|
||||
updatePrices();
|
||||
|
||||
// Initialize the state and reps of this position in opts[].
|
||||
// updateOptStateAndReps() will need these to get the new
|
||||
// state and reps for the next byte.
|
||||
opts[0].state.set(state);
|
||||
System.arraycopy(reps, 0, opts[0].reps, 0, REPS);
|
||||
|
||||
// Initialize the prices for latter opts that will be used below.
|
||||
for (int i = optEnd; i >= MATCH_LEN_MIN; --i)
|
||||
opts[i].reset();
|
||||
|
||||
// Calculate the prices of repeated matches of all lengths.
|
||||
for (int rep = 0; rep < REPS; ++rep) {
|
||||
int repLen = repLens[rep];
|
||||
if (repLen < MATCH_LEN_MIN)
|
||||
continue;
|
||||
|
||||
int longRepPrice = getLongRepPrice(anyRepPrice, rep,
|
||||
state, posState);
|
||||
do {
|
||||
int price = longRepPrice + repLenEncoder.getPrice(repLen,
|
||||
posState);
|
||||
if (price < opts[repLen].price)
|
||||
opts[repLen].set1(price, 0, rep);
|
||||
} while (--repLen >= MATCH_LEN_MIN);
|
||||
}
|
||||
|
||||
// Calculate the prices of normal matches that are longer than rep0.
|
||||
{
|
||||
int len = Math.max(repLens[0] + 1, MATCH_LEN_MIN);
|
||||
if (len <= mainLen) {
|
||||
int normalMatchPrice = getNormalMatchPrice(anyMatchPrice,
|
||||
state);
|
||||
|
||||
// Set i to the index of the shortest match that is
|
||||
// at least len bytes long.
|
||||
int i = 0;
|
||||
while (len > matches.len[i])
|
||||
++i;
|
||||
|
||||
while (true) {
|
||||
int dist = matches.dist[i];
|
||||
int price = getMatchAndLenPrice(normalMatchPrice,
|
||||
dist, len, posState);
|
||||
if (price < opts[len].price)
|
||||
opts[len].set1(price, 0, dist + REPS);
|
||||
|
||||
if (len == matches.len[i])
|
||||
if (++i == matches.count)
|
||||
break;
|
||||
|
||||
++len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
avail = Math.min(lz.getAvail(), OPTS - 1);
|
||||
|
||||
// Get matches for later bytes and optimize the use of LZMA symbols
|
||||
// by calculating the prices and picking the cheapest symbol
|
||||
// combinations.
|
||||
while (++optCur < optEnd) {
|
||||
matches = getMatches();
|
||||
if (matches.count > 0
|
||||
&& matches.len[matches.count - 1] >= niceLen)
|
||||
break;
|
||||
|
||||
--avail;
|
||||
++pos;
|
||||
posState = pos & posMask;
|
||||
|
||||
updateOptStateAndReps();
|
||||
anyMatchPrice = opts[optCur].price
|
||||
+ getAnyMatchPrice(opts[optCur].state, posState);
|
||||
anyRepPrice = getAnyRepPrice(anyMatchPrice, opts[optCur].state);
|
||||
|
||||
calc1BytePrices(pos, posState, avail, anyRepPrice);
|
||||
|
||||
if (avail >= MATCH_LEN_MIN) {
|
||||
int startLen = calcLongRepPrices(pos, posState,
|
||||
avail, anyRepPrice);
|
||||
if (matches.count > 0)
|
||||
calcNormalMatchPrices(pos, posState, avail,
|
||||
anyMatchPrice, startLen);
|
||||
}
|
||||
}
|
||||
|
||||
return convertOpts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state and reps for the current byte in the opts array.
|
||||
*/
|
||||
private void updateOptStateAndReps() {
|
||||
int optPrev = opts[optCur].optPrev;
|
||||
assert optPrev < optCur;
|
||||
|
||||
if (opts[optCur].prev1IsLiteral) {
|
||||
--optPrev;
|
||||
|
||||
if (opts[optCur].hasPrev2) {
|
||||
opts[optCur].state.set(opts[opts[optCur].optPrev2].state);
|
||||
if (opts[optCur].backPrev2 < REPS)
|
||||
opts[optCur].state.updateLongRep();
|
||||
else
|
||||
opts[optCur].state.updateMatch();
|
||||
} else
|
||||
opts[optCur].state.set(opts[optPrev].state);
|
||||
|
||||
opts[optCur].state.updateLiteral();
|
||||
} else
|
||||
opts[optCur].state.set(opts[optPrev].state);
|
||||
|
||||
if (optPrev == optCur - 1) {
|
||||
// Must be either a short rep or a literal.
|
||||
assert opts[optCur].backPrev == 0 || opts[optCur].backPrev == -1;
|
||||
|
||||
if (opts[optCur].backPrev == 0)
|
||||
opts[optCur].state.updateShortRep();
|
||||
else
|
||||
opts[optCur].state.updateLiteral();
|
||||
|
||||
System.arraycopy(opts[optPrev].reps, 0,
|
||||
opts[optCur].reps, 0, REPS);
|
||||
} else {
|
||||
int back;
|
||||
if (opts[optCur].prev1IsLiteral && opts[optCur].hasPrev2) {
|
||||
optPrev = opts[optCur].optPrev2;
|
||||
back = opts[optCur].backPrev2;
|
||||
opts[optCur].state.updateLongRep();
|
||||
} else {
|
||||
back = opts[optCur].backPrev;
|
||||
if (back < REPS)
|
||||
opts[optCur].state.updateLongRep();
|
||||
else
|
||||
opts[optCur].state.updateMatch();
|
||||
}
|
||||
|
||||
if (back < REPS) {
|
||||
opts[optCur].reps[0] = opts[optPrev].reps[back];
|
||||
|
||||
int rep;
|
||||
for (rep = 1; rep <= back; ++rep)
|
||||
opts[optCur].reps[rep] = opts[optPrev].reps[rep - 1];
|
||||
|
||||
for (; rep < REPS; ++rep)
|
||||
opts[optCur].reps[rep] = opts[optPrev].reps[rep];
|
||||
} else {
|
||||
opts[optCur].reps[0] = back - REPS;
|
||||
System.arraycopy(opts[optPrev].reps, 0,
|
||||
opts[optCur].reps, 1, REPS - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates prices of a literal, a short rep, and literal + rep0.
|
||||
*/
|
||||
private void calc1BytePrices(int pos, int posState,
|
||||
int avail, int anyRepPrice) {
|
||||
// This will be set to true if using a literal or a short rep.
|
||||
boolean nextIsByte = false;
|
||||
|
||||
int curByte = lz.getByte(0);
|
||||
int matchByte = lz.getByte(opts[optCur].reps[0] + 1);
|
||||
|
||||
// Try a literal.
|
||||
int literalPrice = opts[optCur].price
|
||||
+ literalEncoder.getPrice(curByte, matchByte, lz.getByte(1),
|
||||
pos, opts[optCur].state);
|
||||
if (literalPrice < opts[optCur + 1].price) {
|
||||
opts[optCur + 1].set1(literalPrice, optCur, -1);
|
||||
nextIsByte = true;
|
||||
}
|
||||
|
||||
// Try a short rep.
|
||||
if (matchByte == curByte && (opts[optCur + 1].optPrev == optCur
|
||||
|| opts[optCur + 1].backPrev != 0)) {
|
||||
int shortRepPrice = getShortRepPrice(anyRepPrice,
|
||||
opts[optCur].state,
|
||||
posState);
|
||||
if (shortRepPrice <= opts[optCur + 1].price) {
|
||||
opts[optCur + 1].set1(shortRepPrice, optCur, 0);
|
||||
nextIsByte = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If neither a literal nor a short rep was the cheapest choice,
|
||||
// try literal + long rep0.
|
||||
if (!nextIsByte && matchByte != curByte && avail > MATCH_LEN_MIN) {
|
||||
int lenLimit = Math.min(niceLen, avail - 1);
|
||||
int len = lz.getMatchLen(1, opts[optCur].reps[0], lenLimit);
|
||||
|
||||
if (len >= MATCH_LEN_MIN) {
|
||||
nextState.set(opts[optCur].state);
|
||||
nextState.updateLiteral();
|
||||
int nextPosState = (pos + 1) & posMask;
|
||||
int price = literalPrice
|
||||
+ getLongRepAndLenPrice(0, len,
|
||||
nextState, nextPosState);
|
||||
|
||||
int i = optCur + 1 + len;
|
||||
while (optEnd < i)
|
||||
opts[++optEnd].reset();
|
||||
|
||||
if (price < opts[i].price)
|
||||
opts[i].set2(price, optCur, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates prices of long rep and long rep + literal + rep0.
|
||||
*/
|
||||
private int calcLongRepPrices(int pos, int posState,
|
||||
int avail, int anyRepPrice) {
|
||||
int startLen = MATCH_LEN_MIN;
|
||||
int lenLimit = Math.min(avail, niceLen);
|
||||
|
||||
for (int rep = 0; rep < REPS; ++rep) {
|
||||
int len = lz.getMatchLen(opts[optCur].reps[rep], lenLimit);
|
||||
if (len < MATCH_LEN_MIN)
|
||||
continue;
|
||||
|
||||
while (optEnd < optCur + len)
|
||||
opts[++optEnd].reset();
|
||||
|
||||
int longRepPrice = getLongRepPrice(anyRepPrice, rep,
|
||||
opts[optCur].state, posState);
|
||||
|
||||
for (int i = len; i >= MATCH_LEN_MIN; --i) {
|
||||
int price = longRepPrice
|
||||
+ repLenEncoder.getPrice(i, posState);
|
||||
if (price < opts[optCur + i].price)
|
||||
opts[optCur + i].set1(price, optCur, rep);
|
||||
}
|
||||
|
||||
if (rep == 0)
|
||||
startLen = len + 1;
|
||||
|
||||
int len2Limit = Math.min(niceLen, avail - len - 1);
|
||||
int len2 = lz.getMatchLen(len + 1, opts[optCur].reps[rep],
|
||||
len2Limit);
|
||||
|
||||
if (len2 >= MATCH_LEN_MIN) {
|
||||
// Rep
|
||||
int price = longRepPrice
|
||||
+ repLenEncoder.getPrice(len, posState);
|
||||
nextState.set(opts[optCur].state);
|
||||
nextState.updateLongRep();
|
||||
|
||||
// Literal
|
||||
int curByte = lz.getByte(len, 0);
|
||||
int matchByte = lz.getByte(0); // lz.getByte(len, len)
|
||||
int prevByte = lz.getByte(len, 1);
|
||||
price += literalEncoder.getPrice(curByte, matchByte, prevByte,
|
||||
pos + len, nextState);
|
||||
nextState.updateLiteral();
|
||||
|
||||
// Rep0
|
||||
int nextPosState = (pos + len + 1) & posMask;
|
||||
price += getLongRepAndLenPrice(0, len2,
|
||||
nextState, nextPosState);
|
||||
|
||||
int i = optCur + len + 1 + len2;
|
||||
while (optEnd < i)
|
||||
opts[++optEnd].reset();
|
||||
|
||||
if (price < opts[i].price)
|
||||
opts[i].set3(price, optCur, rep, len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return startLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates prices of a normal match and normal match + literal + rep0.
|
||||
*/
|
||||
private void calcNormalMatchPrices(int pos, int posState, int avail,
|
||||
int anyMatchPrice, int startLen) {
|
||||
// If the longest match is so long that it would not fit into
|
||||
// the opts array, shorten the matches.
|
||||
if (matches.len[matches.count - 1] > avail) {
|
||||
matches.count = 0;
|
||||
while (matches.len[matches.count] < avail)
|
||||
++matches.count;
|
||||
|
||||
matches.len[matches.count++] = avail;
|
||||
}
|
||||
|
||||
if (matches.len[matches.count - 1] < startLen)
|
||||
return;
|
||||
|
||||
while (optEnd < optCur + matches.len[matches.count - 1])
|
||||
opts[++optEnd].reset();
|
||||
|
||||
int normalMatchPrice = getNormalMatchPrice(anyMatchPrice,
|
||||
opts[optCur].state);
|
||||
|
||||
int match = 0;
|
||||
while (startLen > matches.len[match])
|
||||
++match;
|
||||
|
||||
for (int len = startLen;; ++len) {
|
||||
int dist = matches.dist[match];
|
||||
|
||||
// Calculate the price of a match of len bytes from the nearest
|
||||
// possible distance.
|
||||
int matchAndLenPrice = getMatchAndLenPrice(normalMatchPrice,
|
||||
dist, len, posState);
|
||||
if (matchAndLenPrice < opts[optCur + len].price)
|
||||
opts[optCur + len].set1(matchAndLenPrice,
|
||||
optCur, dist + REPS);
|
||||
|
||||
if (len != matches.len[match])
|
||||
continue;
|
||||
|
||||
// Try match + literal + rep0. First get the length of the rep0.
|
||||
int len2Limit = Math.min(niceLen, avail - len - 1);
|
||||
int len2 = lz.getMatchLen(len + 1, dist, len2Limit);
|
||||
|
||||
if (len2 >= MATCH_LEN_MIN) {
|
||||
nextState.set(opts[optCur].state);
|
||||
nextState.updateMatch();
|
||||
|
||||
// Literal
|
||||
int curByte = lz.getByte(len, 0);
|
||||
int matchByte = lz.getByte(0); // lz.getByte(len, len)
|
||||
int prevByte = lz.getByte(len, 1);
|
||||
int price = matchAndLenPrice
|
||||
+ literalEncoder.getPrice(curByte, matchByte,
|
||||
prevByte, pos + len,
|
||||
nextState);
|
||||
nextState.updateLiteral();
|
||||
|
||||
// Rep0
|
||||
int nextPosState = (pos + len + 1) & posMask;
|
||||
price += getLongRepAndLenPrice(0, len2,
|
||||
nextState, nextPosState);
|
||||
|
||||
int i = optCur + len + 1 + len2;
|
||||
while (optEnd < i)
|
||||
opts[++optEnd].reset();
|
||||
|
||||
if (price < opts[i].price)
|
||||
opts[i].set3(price, optCur, dist + REPS, len, 0);
|
||||
}
|
||||
|
||||
if (++match == matches.count)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Optimum
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
final class Optimum {
|
||||
|
||||
private static final int INFINITY_PRICE = 1 << 30;
|
||||
|
||||
final State state = new State();
|
||||
final int[] reps = new int[LZMACoder.REPS];
|
||||
|
||||
/**
|
||||
* Cumulative price of arriving to this byte.
|
||||
*/
|
||||
int price;
|
||||
|
||||
int optPrev;
|
||||
int backPrev;
|
||||
boolean prev1IsLiteral;
|
||||
|
||||
boolean hasPrev2;
|
||||
int optPrev2;
|
||||
int backPrev2;
|
||||
|
||||
/**
|
||||
* Resets the price.
|
||||
*/
|
||||
void reset() {
|
||||
price = INFINITY_PRICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to indicate one LZMA symbol (literal, rep, or match).
|
||||
*/
|
||||
void set1(int newPrice, int optCur, int back) {
|
||||
price = newPrice;
|
||||
optPrev = optCur;
|
||||
backPrev = back;
|
||||
prev1IsLiteral = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to indicate two LZMA symbols of which the first one is a literal.
|
||||
*/
|
||||
void set2(int newPrice, int optCur, int back) {
|
||||
price = newPrice;
|
||||
optPrev = optCur + 1;
|
||||
backPrev = back;
|
||||
prev1IsLiteral = true;
|
||||
hasPrev2 = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to indicate three LZMA symbols of which the second one is a literal.
|
||||
*/
|
||||
void set3(int newPrice, int optCur, int back2, int len2, int back) {
|
||||
price = newPrice;
|
||||
optPrev = optCur + len2 + 1;
|
||||
backPrev = back;
|
||||
prev1IsLiteral = true;
|
||||
hasPrev2 = true;
|
||||
optPrev2 = optCur;
|
||||
backPrev2 = back2;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* State
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.lzma;
|
||||
|
||||
final class State {
|
||||
|
||||
static final int STATES = 12;
|
||||
|
||||
private static final int LIT_STATES = 7;
|
||||
|
||||
private static final int LIT_LIT = 0;
|
||||
private static final int MATCH_LIT_LIT = 1;
|
||||
private static final int REP_LIT_LIT = 2;
|
||||
private static final int SHORTREP_LIT_LIT = 3;
|
||||
private static final int MATCH_LIT = 4;
|
||||
private static final int REP_LIT = 5;
|
||||
private static final int SHORTREP_LIT = 6;
|
||||
private static final int LIT_MATCH = 7;
|
||||
private static final int LIT_LONGREP = 8;
|
||||
private static final int LIT_SHORTREP = 9;
|
||||
private static final int NONLIT_MATCH = 10;
|
||||
private static final int NONLIT_REP = 11;
|
||||
|
||||
private int state;
|
||||
|
||||
State() {
|
||||
}
|
||||
|
||||
State(State other) {
|
||||
state = other.state;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = LIT_LIT;
|
||||
}
|
||||
|
||||
int get() {
|
||||
return state;
|
||||
}
|
||||
|
||||
void set(State other) {
|
||||
state = other.state;
|
||||
}
|
||||
|
||||
void updateLiteral() {
|
||||
if (state <= SHORTREP_LIT_LIT)
|
||||
state = LIT_LIT;
|
||||
else if (state <= LIT_SHORTREP)
|
||||
state -= 3;
|
||||
else
|
||||
state -= 6;
|
||||
}
|
||||
|
||||
void updateMatch() {
|
||||
state = state < LIT_STATES ? LIT_MATCH : NONLIT_MATCH;
|
||||
}
|
||||
|
||||
void updateLongRep() {
|
||||
state = state < LIT_STATES ? LIT_LONGREP : NONLIT_REP;
|
||||
}
|
||||
|
||||
void updateShortRep() {
|
||||
state = state < LIT_STATES ? LIT_SHORTREP : NONLIT_REP;
|
||||
}
|
||||
|
||||
boolean isLiteral() {
|
||||
return state < LIT_STATES;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* BCJ filter for little endian ARM instructions
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.simple;
|
||||
|
||||
public final class ARM implements SimpleFilter {
|
||||
|
||||
private final boolean isEncoder;
|
||||
private int pos;
|
||||
|
||||
public ARM(boolean isEncoder, int startPos) {
|
||||
this.isEncoder = isEncoder;
|
||||
pos = startPos + 8;
|
||||
}
|
||||
|
||||
public int code(byte[] buf, int off, int len) {
|
||||
int end = off + len - 4;
|
||||
int i;
|
||||
|
||||
for (i = off; i <= end; i += 4)
|
||||
if ((buf[i + 3] & 0xFF) == 0xEB) {
|
||||
int src = ((buf[i + 2] & 0xFF) << 16)
|
||||
| ((buf[i + 1] & 0xFF) << 8)
|
||||
| (buf[i] & 0xFF);
|
||||
src <<= 2;
|
||||
|
||||
int dest;
|
||||
if (isEncoder)
|
||||
dest = src + (pos + i - off);
|
||||
else
|
||||
dest = src - (pos + i - off);
|
||||
|
||||
dest >>>= 2;
|
||||
buf[i + 2] = (byte) (dest >>> 16);
|
||||
buf[i + 1] = (byte) (dest >>> 8);
|
||||
buf[i] = (byte) dest;
|
||||
}
|
||||
|
||||
i -= off;
|
||||
pos += i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* BCJ filter for little endian ARM-Thumb instructions
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz.simple;
|
||||
|
||||
public final class ARMThumb implements SimpleFilter {
|
||||
|
||||
private final boolean isEncoder;
|
||||
private int pos;
|
||||
|
||||
public ARMThumb(boolean isEncoder, int startPos) {
|
||||
this.isEncoder = isEncoder;
|
||||
pos = startPos + 4;
|
||||
}
|
||||
|
||||
public int code(byte[] buf, int off, int len) {
|
||||
int end = off + len - 4;
|
||||
int i;
|
||||
|
||||
for (i = off; i <= end; i += 2)
|
||||
if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) {
|
||||
int src = ((buf[i + 1] & 0x07) << 19)
|
||||
| ((buf[i] & 0xFF) << 11)
|
||||
| ((buf[i + 3] & 0x07) << 8)
|
||||
| (buf[i + 2] & 0xFF);
|
||||
src <<= 1;
|
||||
|
||||
int dest;
|
||||
if (isEncoder)
|
||||
dest = src + (pos + i - off);
|
||||
else
|
||||
dest = src - (pos + i - off);
|
||||
|
||||
dest >>>= 1;
|
||||
buf[i + 1] = (byte) (0xF0 | ((dest >>> 19) & 0x07));
|
||||
buf[i] = (byte) (dest >>> 11);
|
||||
buf[i + 3] = (byte) (0xF8 | ((dest >>> 8) & 0x07));
|
||||
buf[i + 2] = (byte) dest;
|
||||
i += 2;
|
||||
}
|
||||
|
||||
i -= off;
|
||||
pos += i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user