Cleanup of Bzip2 compressor files
parent
be3c78e44b
commit
a1a745abbb
|
@ -58,7 +58,7 @@ public class ParallelZipCompressor {
|
||||||
} catch (IOException | InterruptedException | ExecutionException e) {
|
} catch (IOException | InterruptedException | ExecutionException e) {
|
||||||
TextileBackup.LOGGER.error("An exception happened!", e);
|
TextileBackup.LOGGER.error("An exception happened!", e);
|
||||||
|
|
||||||
Utilities.sendError("Something went wrong while compressing files!", ctx);;
|
Utilities.sendError("Something went wrong while compressing files!", ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
long end = System.nanoTime();
|
long end = System.nanoTime();
|
||||||
|
|
|
@ -65,7 +65,7 @@ final class BZip2EncoderExecutorServiceImpl implements BZip2EncoderExecutorServi
|
||||||
|
|
||||||
BZip2EncoderExecutorServiceImpl(int noThreads, ErrorState es)
|
BZip2EncoderExecutorServiceImpl(int noThreads, ErrorState es)
|
||||||
{
|
{
|
||||||
m_executor = new ThreadPoolExecutor(noThreads, noThreads, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), new EncodingThreadFactory(es), ShoehornInJobRejectedExecutionHandler.INSTANCE);
|
m_executor = new ThreadPoolExecutor(noThreads, noThreads, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new EncodingThreadFactory(es), ShoehornInJobRejectedExecutionHandler.INSTANCE);
|
||||||
m_errorState = es;
|
m_errorState = es;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,9 +159,8 @@ public class BZip2OutputStream extends OutputStream
|
||||||
private void writeEosBlock() throws IOException
|
private void writeEosBlock() throws IOException
|
||||||
{
|
{
|
||||||
// Write the end of stream magic
|
// Write the end of stream magic
|
||||||
for (int i = 0; i < EOS_MAGIC.length; i++)
|
for (byte b : EOS_MAGIC) {
|
||||||
{
|
m_wrapped.writeBitsLittleEndian(b & 0xFF, 8);
|
||||||
m_wrapped.writeBitsLittleEndian(EOS_MAGIC[i] & 0xFF, 8);
|
|
||||||
}
|
}
|
||||||
// Write file checksum
|
// Write file checksum
|
||||||
m_wrapped.writeBitsLittleEndian(m_blockOutputStream.getFileChecksum(), 32);
|
m_wrapped.writeBitsLittleEndian(m_blockOutputStream.getFileChecksum(), 32);
|
||||||
|
@ -264,16 +263,6 @@ public class BZip2OutputStream extends OutputStream
|
||||||
return this == o;
|
return this == o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the stream if the client has been sloppy about it.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void finalize() throws Throwable
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
super.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link BZip2EncoderExecutorService} that can be shared between
|
* Create a {@link BZip2EncoderExecutorService} that can be shared between
|
||||||
* several {@link BZip2OutputStream}:s to spread the bzip2 encoding work
|
* several {@link BZip2OutputStream}:s to spread the bzip2 encoding work
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface identifying a bzip2 data block. Used by the {@link BlockDecoder}.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
interface Block
|
|
||||||
{
|
|
||||||
// Nothing
|
|
||||||
}
|
|
|
@ -1,422 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.at4j.support.comp.ByteMoveToFront;
|
|
||||||
import org.at4j.support.comp.IntMoveToFront;
|
|
||||||
import org.at4j.support.io.LittleEndianBitInputStream;
|
|
||||||
import org.at4j.support.lang.At4JException;
|
|
||||||
import org.at4j.support.lang.UnsignedInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used by the {@link BZip2InputStream} to decode data blocks.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
final class BlockDecoder
|
|
||||||
{
|
|
||||||
// The magic number identifying a block of compressed data
|
|
||||||
private static final byte[] COMPRESSED_BLOCK_MAGIC = new byte[] { (byte) 0x31, (byte) 0x41, (byte) 0x59, (byte) 0x26, (byte) 0x53, (byte) 0x59 };
|
|
||||||
// The magic number identifying the end of stream block
|
|
||||||
private static final byte[] EOS_BLOCK_MAGIC = new byte[] { (byte) 0x17, (byte) 0x72, (byte) 0x45, (byte) 0x38, (byte) 0x50, (byte) 0x90 };
|
|
||||||
|
|
||||||
// The number of symbols to read from each Huffman tree before switching
|
|
||||||
private static final int SYMBOLS_TO_READ_FROM_EACH_TREE = 50;
|
|
||||||
|
|
||||||
// The symbol value of the special RUNA symbol.
|
|
||||||
private static final int RUNA_SYMBOL = 0;
|
|
||||||
// The symbol value of the special RUNB symbol.
|
|
||||||
private static final int RUNB_SYMBOL = 1;
|
|
||||||
|
|
||||||
private static final int MAX_NO_OF_MTF_SYMBOLS = 258;
|
|
||||||
|
|
||||||
private static final byte[] INITIAL_MOVE_TO_FRONT_ALPHABET = new byte[MAX_NO_OF_MTF_SYMBOLS];
|
|
||||||
static
|
|
||||||
{
|
|
||||||
for (int i = 0; i < MAX_NO_OF_MTF_SYMBOLS; i++)
|
|
||||||
{
|
|
||||||
INITIAL_MOVE_TO_FRONT_ALPHABET[i] = (byte) i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final LittleEndianBitInputStream m_in;
|
|
||||||
private final int m_blockSize;
|
|
||||||
|
|
||||||
// Data read from the block header
|
|
||||||
|
|
||||||
// Block checksum (CRC)
|
|
||||||
private int m_readBlockChecksum;
|
|
||||||
// The pointer to the original data used in the BW transform
|
|
||||||
private int m_originalDataPointer;
|
|
||||||
// The Huffman trees used for decompression
|
|
||||||
private HighValueBranchHuffmanTree[] m_huffmanTrees;
|
|
||||||
// The EOB (End Of Block) symbol index.
|
|
||||||
private int m_endOfBlockSymbol;
|
|
||||||
// The number of times that the Huffman trees are switched in the input.
|
|
||||||
// The trees are switched every 50 bytes.
|
|
||||||
private int m_numberOfTimesHuffmanTreesAreSwitched;
|
|
||||||
private int[] m_treeUse;
|
|
||||||
// Mapping between symbol values and byte values.
|
|
||||||
private byte[] m_symbolSequenceNos;
|
|
||||||
// Frequency of each byte in the pre-BW data
|
|
||||||
private int[] m_byteFrequencies;
|
|
||||||
|
|
||||||
// State variables
|
|
||||||
|
|
||||||
// The number of the currently selected Huffman tree
|
|
||||||
private HighValueBranchHuffmanTree m_curTree;
|
|
||||||
// The number of symbols left to read from the current Huffman tree
|
|
||||||
private int m_symbolsLeftToReadFromCurTree;
|
|
||||||
// The current number of Huffman tree switches
|
|
||||||
private int m_switchNo;
|
|
||||||
// A counter for the number of bytes decoded in this block.
|
|
||||||
private int m_noBytesDecoded;
|
|
||||||
private ByteMoveToFront m_mtfTransformer;
|
|
||||||
// This will hold the decoded data (before the Burrows Wheeler decoding)
|
|
||||||
private final byte[] m_decoded;
|
|
||||||
|
|
||||||
BlockDecoder(LittleEndianBitInputStream in, int blockSize)
|
|
||||||
{
|
|
||||||
m_in = in;
|
|
||||||
m_blockSize = blockSize;
|
|
||||||
m_decoded = new byte[blockSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwIOException(String msg) throws IOException
|
|
||||||
{
|
|
||||||
throw new IOException(msg + ". Position in input stream: " + m_in.getNumberOfBytesRead());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkInterrupted() throws InterruptedException
|
|
||||||
{
|
|
||||||
if (Thread.interrupted())
|
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trace(String s)
|
|
||||||
{
|
|
||||||
System.out.println(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static HighValueBranchHuffmanTree decodeHuffmanTree(final int totalNumberOfSymbols, final LittleEndianBitInputStream in) throws IOException
|
|
||||||
{
|
|
||||||
int[] symbolLengths = new int[totalNumberOfSymbols];
|
|
||||||
|
|
||||||
// Starting bit length for Huffman deltas in this tree
|
|
||||||
int currentBitLength = in.readBits(5);
|
|
||||||
if (currentBitLength > 20)
|
|
||||||
{
|
|
||||||
throw new IOException("Invalid starting bit length for Huffman deltas: " + currentBitLength + ". Must be <= 20");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize min and max lengths per tree with values that
|
|
||||||
// will certainly be overwritten.
|
|
||||||
int minBitLengthPerTree = 20;
|
|
||||||
int maxBitLengthPerTree = 0;
|
|
||||||
|
|
||||||
for (int j = 0; j < totalNumberOfSymbols; j++)
|
|
||||||
{
|
|
||||||
while (in.readBit())
|
|
||||||
{
|
|
||||||
currentBitLength += in.readBit() ? -1 : 1;
|
|
||||||
if ((currentBitLength < 1) || (currentBitLength > 20))
|
|
||||||
{
|
|
||||||
throw new IOException("Invalid bit length " + currentBitLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
symbolLengths[j] = currentBitLength;
|
|
||||||
|
|
||||||
if (currentBitLength < minBitLengthPerTree)
|
|
||||||
{
|
|
||||||
minBitLengthPerTree = currentBitLength;
|
|
||||||
}
|
|
||||||
if (currentBitLength > maxBitLengthPerTree)
|
|
||||||
{
|
|
||||||
maxBitLengthPerTree = currentBitLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new HighValueBranchHuffmanTree(symbolLengths, minBitLengthPerTree, maxBitLengthPerTree, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readCompressedBlockHeader() throws IOException
|
|
||||||
{
|
|
||||||
byte[] barr = new byte[4];
|
|
||||||
|
|
||||||
// Block checksum
|
|
||||||
m_readBlockChecksum = (int) UnsignedInteger.fromLittleEndianByteArrayToLong(m_in.readBytes(barr, 0, 4), 0);
|
|
||||||
|
|
||||||
// Randomized block?
|
|
||||||
if (m_in.readBit())
|
|
||||||
{
|
|
||||||
throwIOException("Randomized block mode is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting pointer into BWT
|
|
||||||
m_in.readBytes(barr, 1, 3);
|
|
||||||
barr[0] = 0;
|
|
||||||
m_originalDataPointer = (int) UnsignedInteger.fromLittleEndianByteArrayToLong(barr, 0);
|
|
||||||
if (m_originalDataPointer > m_blockSize)
|
|
||||||
{
|
|
||||||
throw new IOException("Invalid starting pointer " + m_originalDataPointer + ". It must be less than the block size " + m_blockSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Huffman used codes
|
|
||||||
boolean[] usedSymbols = new boolean[256];
|
|
||||||
int numberOfUsedSymbols = 0;
|
|
||||||
|
|
||||||
boolean[] inUseBlocks = new boolean[16];
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
{
|
|
||||||
inUseBlocks[i] = m_in.readBit();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
{
|
|
||||||
if (inUseBlocks[i])
|
|
||||||
{
|
|
||||||
for (int j = 0; j < 16; j++)
|
|
||||||
{
|
|
||||||
if (m_in.readBit())
|
|
||||||
{
|
|
||||||
usedSymbols[i * 16 + j] = true;
|
|
||||||
numberOfUsedSymbols++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numberOfUsedSymbols == 0)
|
|
||||||
{
|
|
||||||
throwIOException("No symbols used in table");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a mapping for the sequence numbers of all used bytes
|
|
||||||
m_symbolSequenceNos = new byte[numberOfUsedSymbols];
|
|
||||||
int useIndex = 0;
|
|
||||||
for (int i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
if (usedSymbols[i])
|
|
||||||
{
|
|
||||||
m_symbolSequenceNos[useIndex++] = (byte) (i & 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert useIndex == numberOfUsedSymbols;
|
|
||||||
|
|
||||||
m_byteFrequencies = new int[256];
|
|
||||||
|
|
||||||
// The number of Huffman trees to use
|
|
||||||
int numberOfHuffmanTrees = m_in.readBits(3);
|
|
||||||
if (numberOfHuffmanTrees < 2 || numberOfHuffmanTrees > 6)
|
|
||||||
{
|
|
||||||
throwIOException("Invalid number of Huffman trees " + numberOfHuffmanTrees + ". Must be between 2 and 6 (inclusive)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The number of times the trees to use are swapped in the input.
|
|
||||||
// The trees are swapped each 50 bytes.
|
|
||||||
m_numberOfTimesHuffmanTreesAreSwitched = m_in.readBitsLittleEndian(15);
|
|
||||||
if (m_numberOfTimesHuffmanTreesAreSwitched < 1)
|
|
||||||
{
|
|
||||||
throwIOException("Invalid number of times the Huffman trees are switched in the input: " + m_numberOfTimesHuffmanTreesAreSwitched);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero-terminated bit runs for each tree switch
|
|
||||||
int[] treeUseMtf = new int[m_numberOfTimesHuffmanTreesAreSwitched];
|
|
||||||
for (int i = 0; i < m_numberOfTimesHuffmanTreesAreSwitched; i++)
|
|
||||||
{
|
|
||||||
treeUseMtf[i] = 0;
|
|
||||||
while (m_in.readBit())
|
|
||||||
{
|
|
||||||
treeUseMtf[i]++;
|
|
||||||
}
|
|
||||||
if (treeUseMtf[i] > numberOfHuffmanTrees)
|
|
||||||
{
|
|
||||||
throwIOException("Invalid Huffman tree use MTF " + treeUseMtf[i] + ". Must be less than the number of Huffman trees, " + numberOfHuffmanTrees);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the tree use MTF values
|
|
||||||
m_treeUse = new int[m_numberOfTimesHuffmanTreesAreSwitched];
|
|
||||||
// The "alphabet" for the MTF encoding -- the indices of the different
|
|
||||||
// tree uses.
|
|
||||||
int[] treeUseIndices = new int[numberOfHuffmanTrees];
|
|
||||||
for (int i = 0; i < numberOfHuffmanTrees; i++)
|
|
||||||
{
|
|
||||||
treeUseIndices[i] = i;
|
|
||||||
}
|
|
||||||
new IntMoveToFront(treeUseIndices).decode(treeUseMtf, m_treeUse);
|
|
||||||
|
|
||||||
// Settings for the Huffman trees
|
|
||||||
|
|
||||||
// The total number of used symbols is the value we calculated above - 1
|
|
||||||
// + RUNA, RUNB and an end of stream marker.
|
|
||||||
int totalNumberOfSymbols = numberOfUsedSymbols + 2;
|
|
||||||
m_huffmanTrees = new HighValueBranchHuffmanTree[numberOfHuffmanTrees];
|
|
||||||
for (int i = 0; i < numberOfHuffmanTrees; i++)
|
|
||||||
{
|
|
||||||
m_huffmanTrees[i] = decodeHuffmanTree(totalNumberOfSymbols, m_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The symbol value for the end of the data block.
|
|
||||||
m_endOfBlockSymbol = totalNumberOfSymbols - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectNewHuffmanTree() throws IOException
|
|
||||||
{
|
|
||||||
if (m_switchNo >= m_numberOfTimesHuffmanTreesAreSwitched)
|
|
||||||
{
|
|
||||||
throwIOException("One Huffman tree switch too many: " + m_switchNo);
|
|
||||||
}
|
|
||||||
m_symbolsLeftToReadFromCurTree = SYMBOLS_TO_READ_FROM_EACH_TREE;
|
|
||||||
m_curTree = m_huffmanTrees[m_treeUse[m_switchNo]];
|
|
||||||
m_switchNo++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readSymbol() throws IOException
|
|
||||||
{
|
|
||||||
if (m_symbolsLeftToReadFromCurTree == 0)
|
|
||||||
{
|
|
||||||
selectNewHuffmanTree();
|
|
||||||
}
|
|
||||||
final int symbol = m_curTree.readNext(m_in);
|
|
||||||
m_symbolsLeftToReadFromCurTree--;
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decodeSingleByte(final int symbolMtf) throws IOException
|
|
||||||
{
|
|
||||||
// Move To Front decode the symbol
|
|
||||||
final int byteIndex = m_mtfTransformer.decode(symbolMtf - 1) & 0xFF;
|
|
||||||
|
|
||||||
final byte value = m_symbolSequenceNos[byteIndex];
|
|
||||||
m_decoded[m_noBytesDecoded++] = value;
|
|
||||||
m_byteFrequencies[value & 0xFF]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the next symbol
|
|
||||||
private int handleRunaAndRunb(int symbol) throws IOException
|
|
||||||
{
|
|
||||||
int n = 1;
|
|
||||||
int multiplier = 0;
|
|
||||||
while (symbol == RUNA_SYMBOL || symbol == RUNB_SYMBOL)
|
|
||||||
{
|
|
||||||
if (symbol == RUNA_SYMBOL)
|
|
||||||
{
|
|
||||||
multiplier += n;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
multiplier += 2 * n;
|
|
||||||
}
|
|
||||||
// Multiply n with 2
|
|
||||||
n <<= 1;
|
|
||||||
symbol = readSymbol();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The repeated value is at the front of the MTF list
|
|
||||||
final int byteIndex = m_mtfTransformer.decode(0) & 0xFF;
|
|
||||||
final byte value = m_symbolSequenceNos[byteIndex];
|
|
||||||
if (multiplier == 1)
|
|
||||||
{
|
|
||||||
m_decoded[m_noBytesDecoded++] = value;
|
|
||||||
m_byteFrequencies[value & 0xFF]++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Arrays.fill(m_decoded, m_noBytesDecoded, m_noBytesDecoded + multiplier, value);
|
|
||||||
m_noBytesDecoded += multiplier;
|
|
||||||
m_byteFrequencies[value & 0xFF] += multiplier;
|
|
||||||
}
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompressedDataBlock readCompressedDataBlock() throws IOException, InterruptedException
|
|
||||||
{
|
|
||||||
readCompressedBlockHeader();
|
|
||||||
|
|
||||||
int symbol = readSymbol();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
checkInterrupted();
|
|
||||||
|
|
||||||
if (symbol == RUNA_SYMBOL || symbol == RUNB_SYMBOL)
|
|
||||||
{
|
|
||||||
symbol = handleRunaAndRunb(symbol);
|
|
||||||
}
|
|
||||||
else if (symbol == m_endOfBlockSymbol)
|
|
||||||
{
|
|
||||||
BurrowsWheelerDecoder bwd = new BurrowsWheelerDecoder(m_decoded, m_noBytesDecoded, m_byteFrequencies, m_originalDataPointer);
|
|
||||||
return new CompressedDataBlock(new RLEDecodingInputStream(bwd.decode(), m_readBlockChecksum), m_readBlockChecksum);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
decodeSingleByte(symbol);
|
|
||||||
symbol = readSymbol();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDecoderState()
|
|
||||||
{
|
|
||||||
// Initialize the MTF alphabet
|
|
||||||
final byte[] moveToFrontAlphabet = new byte[MAX_NO_OF_MTF_SYMBOLS];
|
|
||||||
System.arraycopy(INITIAL_MOVE_TO_FRONT_ALPHABET, 0, moveToFrontAlphabet, 0, MAX_NO_OF_MTF_SYMBOLS);
|
|
||||||
m_mtfTransformer = new ByteMoveToFront(moveToFrontAlphabet);
|
|
||||||
m_curTree = null;
|
|
||||||
m_symbolsLeftToReadFromCurTree = 0;
|
|
||||||
m_switchNo = 0;
|
|
||||||
m_noBytesDecoded = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Block getNextBlock() throws IOException
|
|
||||||
{
|
|
||||||
initDecoderState();
|
|
||||||
|
|
||||||
byte[] barr = new byte[6];
|
|
||||||
m_in.readBytes(barr, 0, 6);
|
|
||||||
if (Arrays.equals(COMPRESSED_BLOCK_MAGIC, barr))
|
|
||||||
{
|
|
||||||
trace("Found block of compressed data");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return readCompressedDataBlock();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
throw new At4JException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Arrays.equals(EOS_BLOCK_MAGIC, barr))
|
|
||||||
{
|
|
||||||
trace("Found end of stream block");
|
|
||||||
m_in.readBytes(barr, 0, 4);
|
|
||||||
int readCrc32 = (int) UnsignedInteger.fromLittleEndianByteArrayToLong(barr, 0);
|
|
||||||
return new EosBlock(readCrc32);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throwIOException("Invalid block header " + Arrays.toString(barr) + ". Expected compressed data block or end of stream block");
|
|
||||||
// Never reached
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -179,7 +179,7 @@ final class BlockEncoder
|
||||||
{
|
{
|
||||||
byte[] res = m_scratchpad.m_sequenceMap;
|
byte[] res = m_scratchpad.m_sequenceMap;
|
||||||
byte index = 0;
|
byte index = 0;
|
||||||
for (int i = 0; i < symbols.length; i++)
|
for (int i : symbols)
|
||||||
{
|
{
|
||||||
res[symbols[i] & 0xFF] = index++;
|
res[symbols[i] & 0xFF] = index++;
|
||||||
}
|
}
|
||||||
|
@ -754,9 +754,8 @@ final class BlockEncoder
|
||||||
private void writeBlockHeader(final int blockChecksum, int bwFirstPointer, boolean[] seenDifferentBytes, MTFAndRLEResult mtfrle, HuffmanTreesAndUsage htau) throws IOException
|
private void writeBlockHeader(final int blockChecksum, int bwFirstPointer, boolean[] seenDifferentBytes, MTFAndRLEResult mtfrle, HuffmanTreesAndUsage htau) throws IOException
|
||||||
{
|
{
|
||||||
// Block magic
|
// Block magic
|
||||||
for (int i = 0; i < BLOCK_MAGIC.length; i++)
|
for (int b : BLOCK_MAGIC) {
|
||||||
{
|
m_out.writeBitsLittleEndian(b & 0xFF, 8);
|
||||||
m_out.writeBitsLittleEndian(BLOCK_MAGIC[i] & 0xFF, 8);
|
|
||||||
}
|
}
|
||||||
// Checksum
|
// Checksum
|
||||||
m_out.writeBitsLittleEndian(blockChecksum, 32);
|
m_out.writeBitsLittleEndian(blockChecksum, 32);
|
||||||
|
|
|
@ -44,16 +44,7 @@ final class BlockEncoderRunnable implements Runnable
|
||||||
m_encoder.setScratchpad(((EncodingThread) Thread.currentThread()).getScratchpad());
|
m_encoder.setScratchpad(((EncodingThread) Thread.currentThread()).getScratchpad());
|
||||||
m_encoder.encode();
|
m_encoder.encode();
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException | Error | RuntimeException e)
|
||||||
{
|
|
||||||
|
|
||||||
((EncodingThread) Thread.currentThread()).getErrorState().registerError(e, m_errorOwner);
|
|
||||||
}
|
|
||||||
catch (RuntimeException e)
|
|
||||||
{
|
|
||||||
((EncodingThread) Thread.currentThread()).getErrorState().registerError(e, m_errorOwner);
|
|
||||||
}
|
|
||||||
catch (Error e)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
((EncodingThread) Thread.currentThread()).getErrorState().registerError(e, m_errorOwner);
|
((EncodingThread) Thread.currentThread()).getErrorState().registerError(e, m_errorOwner);
|
||||||
|
|
|
@ -307,9 +307,8 @@ final class BlockOutputStream extends OutputStream
|
||||||
@Override
|
@Override
|
||||||
public void write(final byte[] data) throws IOException
|
public void write(final byte[] data) throws IOException
|
||||||
{
|
{
|
||||||
for (int i = 0; i < data.length; i++)
|
for (int datum : data) {
|
||||||
{
|
write(datum & 0xFF);
|
||||||
write(data[i] & 0xFF);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode Burrows Wheeler encoded data.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
final class BurrowsWheelerDecoder
|
|
||||||
{
|
|
||||||
static class BWInputStream extends InputStream
|
|
||||||
{
|
|
||||||
private final byte[] m_decoded;
|
|
||||||
private final int[] m_ptr;
|
|
||||||
|
|
||||||
private int m_curPointer;
|
|
||||||
private boolean m_eof;
|
|
||||||
private int m_noLeftToRead;
|
|
||||||
|
|
||||||
BWInputStream(byte[] decoded, int[] ptr, int originalDataPointer)
|
|
||||||
{
|
|
||||||
m_decoded = decoded;
|
|
||||||
m_ptr = ptr;
|
|
||||||
m_curPointer = ptr[originalDataPointer];
|
|
||||||
m_noLeftToRead = ptr.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException
|
|
||||||
{
|
|
||||||
if (m_eof)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
final int res = m_decoded[m_curPointer] & 0xFF;
|
|
||||||
m_eof = --m_noLeftToRead == 0;
|
|
||||||
m_curPointer = m_ptr[m_curPointer];
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final byte[] m_decoded;
|
|
||||||
private final int m_noBytesDecoded;
|
|
||||||
private final int[] m_byteFrequencies;
|
|
||||||
private final int m_originalDataPointer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param encoded The encoded data. This array may be longer than the actual
|
|
||||||
* amount of encoded data. The {@code noBytesDecoded} parameter determines
|
|
||||||
* how much of the array that will be used.
|
|
||||||
* @param noBytesEncoded The length of the encoded data.
|
|
||||||
* @param byteFrequencies The number of times each byte occur in the data.
|
|
||||||
* @param originalDataPointer The row number of the original data in the
|
|
||||||
* Burrows Wheeler matrix.
|
|
||||||
* @throws IOException On I/O errors.
|
|
||||||
*/
|
|
||||||
BurrowsWheelerDecoder(byte[] encoded, int noBytesEncoded, int[] byteFrequencies, int originalDataPointer) throws IOException
|
|
||||||
{
|
|
||||||
if (originalDataPointer > noBytesEncoded)
|
|
||||||
{
|
|
||||||
throw new IOException("Invalid pointer to original data in block header " + originalDataPointer + ". It is larger than the size of data in the block " + noBytesEncoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_decoded = encoded;
|
|
||||||
m_noBytesDecoded = noBytesEncoded;
|
|
||||||
m_byteFrequencies = byteFrequencies;
|
|
||||||
m_originalDataPointer = originalDataPointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream decode()
|
|
||||||
{
|
|
||||||
// Calculate the transformation vector used to move from the encoded
|
|
||||||
// data to the decoded.
|
|
||||||
|
|
||||||
// The byte frequency array contains the frequency of each byte in the
|
|
||||||
// data. Create a new array tarr that, for each byte, specifies how many
|
|
||||||
// bytes of lower value that occurs in the data.
|
|
||||||
int[] tarr = new int[256];
|
|
||||||
tarr[0] = 0;
|
|
||||||
for (int i = 1; i < 256; i++)
|
|
||||||
{
|
|
||||||
tarr[i] = tarr[i - 1] + m_byteFrequencies[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ptr array will contain a chain of positions of the decoded bytes
|
|
||||||
// in the decoded array.
|
|
||||||
final int[] ptr = new int[m_noBytesDecoded];
|
|
||||||
for (int i = 0; i < m_noBytesDecoded; i++)
|
|
||||||
{
|
|
||||||
int val = m_decoded[i] & 0xFF;
|
|
||||||
// Get the position of the decoded byte position in tt. Increment
|
|
||||||
// the tt position for the given value so that next occurrence of the
|
|
||||||
// value will end up in the next position in tt.
|
|
||||||
int ttPos = tarr[val]++;
|
|
||||||
ptr[ttPos] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BWInputStream(m_decoded, ptr, m_originalDataPointer);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bzip2 block containing compressed data.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
final class CompressedDataBlock implements Block
|
|
||||||
{
|
|
||||||
private final InputStream m_stream;
|
|
||||||
private final int m_blockChecksum;
|
|
||||||
|
|
||||||
CompressedDataBlock(InputStream stream, int blockChecksum)
|
|
||||||
{
|
|
||||||
// Null check
|
|
||||||
stream.getClass();
|
|
||||||
|
|
||||||
m_stream = stream;
|
|
||||||
m_blockChecksum = blockChecksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream getStream()
|
|
||||||
{
|
|
||||||
return m_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getBlockChecksum()
|
|
||||||
{
|
|
||||||
return m_blockChecksum;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,7 @@ final class EncodedBlockWriter
|
||||||
{
|
{
|
||||||
// All variables are protected by this object's intrinsic lock
|
// All variables are protected by this object's intrinsic lock
|
||||||
private final BitOutput m_out;
|
private final BitOutput m_out;
|
||||||
private final Map<Integer, EncodedBlockData> m_savedBlocks = new HashMap<Integer, EncodedBlockData>();
|
private final Map<Integer, EncodedBlockData> m_savedBlocks = new HashMap<>();
|
||||||
// This latch is used to signal to the bzip2 output stream when this writer
|
// This latch is used to signal to the bzip2 output stream when this writer
|
||||||
// is finished.
|
// is finished.
|
||||||
private final CountDownLatch m_doneLatch = new CountDownLatch(1);
|
private final CountDownLatch m_doneLatch = new CountDownLatch(1);
|
||||||
|
@ -119,19 +119,7 @@ final class EncodedBlockWriter
|
||||||
saveBlock(blockNo, blockData);
|
saveBlock(blockNo, blockData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Error e)
|
catch (Error | RuntimeException | IOException e)
|
||||||
{
|
|
||||||
m_hasError = true;
|
|
||||||
m_doneLatch.countDown();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
catch (RuntimeException e)
|
|
||||||
{
|
|
||||||
m_hasError = true;
|
|
||||||
m_doneLatch.countDown();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
{
|
||||||
m_hasError = true;
|
m_hasError = true;
|
||||||
m_doneLatch.countDown();
|
m_doneLatch.countDown();
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bzip2 block containing end of stream information.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
final class EosBlock implements Block
|
|
||||||
{
|
|
||||||
private final long m_readCrc;
|
|
||||||
|
|
||||||
EosBlock(long readCrc)
|
|
||||||
{
|
|
||||||
m_readCrc = readCrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
long getReadCrc()
|
|
||||||
{
|
|
||||||
return m_readCrc;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -231,7 +231,7 @@ final class HighValueBranchHuffmanTree
|
||||||
final int d2 = w2 & 0xFF;
|
final int d2 = w2 & 0xFF;
|
||||||
final int ww1 = w1 & 0xFFFFFF00;
|
final int ww1 = w1 & 0xFFFFFF00;
|
||||||
final int ww2 = w2 & 0xFFFFFF00;
|
final int ww2 = w2 & 0xFFFFFF00;
|
||||||
return (ww1 + ww2) | (1 + (d1 > d2 ? d1 : d2));
|
return (ww1 + ww2) | (1 + (Math.max(d1, d2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int getMinLength()
|
int getMinLength()
|
||||||
|
|
|
@ -34,7 +34,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
*/
|
*/
|
||||||
final class MultipleObserverErrorState implements ErrorState
|
final class MultipleObserverErrorState implements ErrorState
|
||||||
{
|
{
|
||||||
private Map<Object, Throwable> m_errors = new ConcurrentHashMap<Object, Throwable>(4);
|
private Map<Object, Throwable> m_errors = new ConcurrentHashMap<>(4);
|
||||||
|
|
||||||
public void checkAndClearErrors(Object ownerToken) throws Error, RuntimeException, IOException
|
public void checkAndClearErrors(Object ownerToken) throws Error, RuntimeException, IOException
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.comp.bzip2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This stream run length decodes read data. It is used by the
|
|
||||||
* {@link BZip2InputStream}.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
final class RLEDecodingInputStream extends InputStream
|
|
||||||
{
|
|
||||||
private static enum RLEState
|
|
||||||
{
|
|
||||||
READING, REPEATING, ABOUT_TO_READ_HOW_MANY_TO_REPEAT, EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block checksum calculated while reading the block contents.
|
|
||||||
private final CRC m_blockChecksum = new CRC();
|
|
||||||
private final InputStream m_wrapped;
|
|
||||||
private final long m_readChecksum;
|
|
||||||
|
|
||||||
private RLEState m_state;
|
|
||||||
|
|
||||||
private int m_noLeftToRepeat;
|
|
||||||
private int m_last;
|
|
||||||
private int m_numberOfSimilar;
|
|
||||||
|
|
||||||
RLEDecodingInputStream(InputStream wrapped, long readChecksum)
|
|
||||||
{
|
|
||||||
m_wrapped = wrapped;
|
|
||||||
m_readChecksum = readChecksum;
|
|
||||||
m_state = RLEState.READING;
|
|
||||||
m_numberOfSimilar = 0;
|
|
||||||
m_last = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEof() throws IOException
|
|
||||||
{
|
|
||||||
if (m_blockChecksum.getValue() != m_readChecksum)
|
|
||||||
{
|
|
||||||
throw new IOException("Invalid block checksum. Was " + m_blockChecksum.getValue() + ", expected " + m_readChecksum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException
|
|
||||||
{
|
|
||||||
switch (m_state)
|
|
||||||
{
|
|
||||||
case EOF:
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
case READING:
|
|
||||||
int val = m_wrapped.read();
|
|
||||||
if (val == -1)
|
|
||||||
{
|
|
||||||
m_state = RLEState.EOF;
|
|
||||||
handleEof();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (val == m_last)
|
|
||||||
{
|
|
||||||
m_numberOfSimilar++;
|
|
||||||
if (m_numberOfSimilar == 4)
|
|
||||||
{
|
|
||||||
// Four in a row. The next value is a repeat number.
|
|
||||||
m_state = RLEState.ABOUT_TO_READ_HOW_MANY_TO_REPEAT;
|
|
||||||
m_numberOfSimilar = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_numberOfSimilar = 1;
|
|
||||||
m_last = val;
|
|
||||||
}
|
|
||||||
m_blockChecksum.update(val);
|
|
||||||
return val;
|
|
||||||
|
|
||||||
case ABOUT_TO_READ_HOW_MANY_TO_REPEAT:
|
|
||||||
m_noLeftToRepeat = m_wrapped.read();
|
|
||||||
if (m_noLeftToRepeat == -1)
|
|
||||||
{
|
|
||||||
// A rather unexpected EOF
|
|
||||||
m_state = RLEState.EOF;
|
|
||||||
handleEof();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (m_noLeftToRepeat == 0)
|
|
||||||
{
|
|
||||||
// Nothing to repeat. Go on to read the next value.
|
|
||||||
m_state = RLEState.READING;
|
|
||||||
return read();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_state = RLEState.REPEATING;
|
|
||||||
m_noLeftToRepeat--;
|
|
||||||
if (m_noLeftToRepeat == 0)
|
|
||||||
{
|
|
||||||
// Just one to repeat, which we will do in this call.
|
|
||||||
m_state = RLEState.READING;
|
|
||||||
}
|
|
||||||
m_blockChecksum.update(m_last);
|
|
||||||
return m_last;
|
|
||||||
}
|
|
||||||
|
|
||||||
case REPEATING:
|
|
||||||
m_noLeftToRepeat--;
|
|
||||||
if (m_noLeftToRepeat == 0)
|
|
||||||
{
|
|
||||||
m_state = RLEState.READING;
|
|
||||||
}
|
|
||||||
m_blockChecksum.update(m_last);
|
|
||||||
return m_last;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unknown state " + m_state + ". This is a bug");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] barr, int off, int len) throws IOException
|
|
||||||
{
|
|
||||||
// The ranges are validated by BZip2InputStream
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
int b = read();
|
|
||||||
if (b < 0)
|
|
||||||
{
|
|
||||||
// EOF
|
|
||||||
return i > 0 ? i : -1;
|
|
||||||
}
|
|
||||||
barr[off + i] = (byte) (b & 0xFF);
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
m_wrapped.close();
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
*/
|
*/
|
||||||
final class SingleObserverErrorState implements ErrorState
|
final class SingleObserverErrorState implements ErrorState
|
||||||
{
|
{
|
||||||
private final AtomicReference<Throwable> m_exception = new AtomicReference<Throwable>();
|
private final AtomicReference<Throwable> m_exception = new AtomicReference<>();
|
||||||
|
|
||||||
public void checkAndClearErrors(Object ownerToken) throws Error, RuntimeException, IOException
|
public void checkAndClearErrors(Object ownerToken) throws Error, RuntimeException, IOException
|
||||||
{
|
{
|
||||||
|
|
|
@ -606,13 +606,7 @@ final class ThreeWayRadixQuicksort
|
||||||
*/
|
*/
|
||||||
private void addRangeToStack(final int bucketStartPos, final int bucketLen, final int depth)
|
private void addRangeToStack(final int bucketStartPos, final int bucketLen, final int depth)
|
||||||
{
|
{
|
||||||
if (bucketLen < 2)
|
if(bucketLen >= 2) {
|
||||||
{
|
|
||||||
// Already sorted
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_sortStack[++m_sortStackPointer] = new QuickSortRangeInfo(bucketStartPos, bucketLen, depth);
|
m_sortStack[++m_sortStackPointer] = new QuickSortRangeInfo(bucketStartPos, bucketLen, depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,10 +138,7 @@ public class ByteMoveToFront
|
||||||
public byte decode(int index)
|
public byte decode(int index)
|
||||||
{
|
{
|
||||||
byte val = m_alphabet[index];
|
byte val = m_alphabet[index];
|
||||||
for (int j = index; j > 0; j--)
|
System.arraycopy(m_alphabet, 0, m_alphabet, 1, index);
|
||||||
{
|
|
||||||
m_alphabet[j] = m_alphabet[j - 1];
|
|
||||||
}
|
|
||||||
m_alphabet[0] = val;
|
m_alphabet[0] = val;
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,10 +135,7 @@ public class IntMoveToFront
|
||||||
public int decode(int index)
|
public int decode(int index)
|
||||||
{
|
{
|
||||||
int val = m_alphabet[index];
|
int val = m_alphabet[index];
|
||||||
for (int j = index; j > 0; j--)
|
System.arraycopy(m_alphabet, 0, m_alphabet, 1, index);
|
||||||
{
|
|
||||||
m_alphabet[j] = m_alphabet[j - 1];
|
|
||||||
}
|
|
||||||
m_alphabet[0] = val;
|
m_alphabet[0] = val;
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -165,10 +162,7 @@ public class IntMoveToFront
|
||||||
{
|
{
|
||||||
int index = in[i];
|
int index = in[i];
|
||||||
int val = m_alphabet[index];
|
int val = m_alphabet[index];
|
||||||
for (int j = index; j > 0; j--)
|
System.arraycopy(m_alphabet, 0, m_alphabet, 1, index);
|
||||||
{
|
|
||||||
m_alphabet[j] = m_alphabet[j - 1];
|
|
||||||
}
|
|
||||||
m_alphabet[0] = val;
|
m_alphabet[0] = val;
|
||||||
out[i] = val;
|
out[i] = val;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,380 +0,0 @@
|
||||||
/* AT4J -- Archive file tools for Java -- http://www.at4j.org
|
|
||||||
* Copyright (C) 2009 Karl Gustafsson
|
|
||||||
*
|
|
||||||
* This file is a part of AT4J
|
|
||||||
*
|
|
||||||
* AT4J is free software: you can redistribute it and/or modify it under the
|
|
||||||
* terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* AT4J is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.at4j.support.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an input stream that a client can use to read single or several bits
|
|
||||||
* from an underlying {@link InputStream}. The bits are read in little-endian
|
|
||||||
* bit order.
|
|
||||||
* @author Karl Gustafsson
|
|
||||||
* @since 1.1
|
|
||||||
*/
|
|
||||||
public class LittleEndianBitInputStream extends InputStream implements BitInput
|
|
||||||
{
|
|
||||||
// 2^0
|
|
||||||
private static final int POINTER_START_OF_BYTE = 0;
|
|
||||||
// 2^7
|
|
||||||
private static final int POINTER_END_OF_BYTE = 7;
|
|
||||||
|
|
||||||
private final InputStream m_in;
|
|
||||||
|
|
||||||
// The current byte
|
|
||||||
private int m_curByte;
|
|
||||||
// The pointer to the current bit location in the current byte.
|
|
||||||
private int m_pointerInByte = POINTER_START_OF_BYTE;
|
|
||||||
|
|
||||||
private long m_numberOfBytesRead = 0;
|
|
||||||
|
|
||||||
public LittleEndianBitInputStream(InputStream in) throws IOException
|
|
||||||
{
|
|
||||||
// Null check
|
|
||||||
in.getClass();
|
|
||||||
|
|
||||||
m_in = in;
|
|
||||||
m_curByte = in.read();
|
|
||||||
// Don't increment the number of read bytes counter. It is always one
|
|
||||||
// byte behind.
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readByte() throws IOException
|
|
||||||
{
|
|
||||||
int res = m_in.read();
|
|
||||||
m_numberOfBytesRead += res != -1 ? 1 : 0;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void incrementPointerPosition() throws IOException
|
|
||||||
{
|
|
||||||
if (m_pointerInByte == POINTER_END_OF_BYTE)
|
|
||||||
{
|
|
||||||
// Read a new byte
|
|
||||||
m_curByte = readByte();
|
|
||||||
m_pointerInByte = POINTER_START_OF_BYTE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Increment the pointer only if we're not at EOF
|
|
||||||
if (!isAtEof())
|
|
||||||
{
|
|
||||||
m_pointerInByte++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAtEof()
|
|
||||||
{
|
|
||||||
return m_curByte == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of whole bytes read this far.
|
|
||||||
* @return The number of bytes read this far.
|
|
||||||
*/
|
|
||||||
public long getNumberOfBytesRead()
|
|
||||||
{
|
|
||||||
return m_numberOfBytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertNotAtEOF() throws IOException
|
|
||||||
{
|
|
||||||
if (isAtEof())
|
|
||||||
{
|
|
||||||
throwIOException("At EOF");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAtByteBoundary()
|
|
||||||
{
|
|
||||||
return m_pointerInByte == POINTER_START_OF_BYTE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertAtByteBoundary() throws IOException
|
|
||||||
{
|
|
||||||
if (!isAtByteBoundary())
|
|
||||||
{
|
|
||||||
throwIOException("Not at byte boundary. Position: pos=" + m_pointerInByte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwIOException(String msg, long pos) throws IOException
|
|
||||||
{
|
|
||||||
throw new IOException(msg + ". Position in stream: " + pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwIOException(String msg) throws IOException
|
|
||||||
{
|
|
||||||
throw new IOException(msg + ". Position in stream: " + m_numberOfBytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void skipToByteBoundary() throws IOException
|
|
||||||
{
|
|
||||||
assertNotAtEOF();
|
|
||||||
if (m_pointerInByte != POINTER_START_OF_BYTE)
|
|
||||||
{
|
|
||||||
m_pointerInByte = POINTER_START_OF_BYTE;
|
|
||||||
m_curByte = readByte();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean readBit() throws IOException
|
|
||||||
{
|
|
||||||
assertNotAtEOF();
|
|
||||||
boolean res = (m_curByte & (1 << (7 - m_pointerInByte))) > 0;
|
|
||||||
incrementPointerPosition();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readBits(int no) throws IOException, IndexOutOfBoundsException
|
|
||||||
{
|
|
||||||
if (no < 0 || no > 8)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Invalid number of bits: " + no + ". Must be between 0 and 8 (inclusive)");
|
|
||||||
}
|
|
||||||
assertNotAtEOF();
|
|
||||||
|
|
||||||
if (no == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes are stored little bit endian
|
|
||||||
if (no + m_pointerInByte <= 8)
|
|
||||||
{
|
|
||||||
// All bits to read fit in the current byte
|
|
||||||
int res = (m_curByte >> (8 - no - m_pointerInByte)) & ((1 << no) - 1);
|
|
||||||
m_pointerInByte += no;
|
|
||||||
if (m_pointerInByte > POINTER_END_OF_BYTE)
|
|
||||||
{
|
|
||||||
m_curByte = readByte();
|
|
||||||
m_pointerInByte = POINTER_START_OF_BYTE;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Read remaining bits + first bits of next byte
|
|
||||||
int noToReadInByte2 = no - (8 - m_pointerInByte);
|
|
||||||
int res = (m_curByte & ((1 << (8 - m_pointerInByte)) - 1)) << noToReadInByte2;
|
|
||||||
m_curByte = readByte();
|
|
||||||
assertNotAtEOF();
|
|
||||||
m_pointerInByte = noToReadInByte2;
|
|
||||||
res += m_curByte >> (8 - noToReadInByte2);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readBitsLittleEndian(int no) throws IOException, IndexOutOfBoundsException
|
|
||||||
{
|
|
||||||
if (no < 0 || no > 32)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Invalid number of bits: " + no + ". Must be between 0 and 32 (inclusive)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (no == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int noReads = no / 8;
|
|
||||||
int mod = no % 8;
|
|
||||||
int res = 0;
|
|
||||||
if (mod != 0)
|
|
||||||
{
|
|
||||||
res = readBits(mod) << (noReads * 8);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < noReads; i++)
|
|
||||||
{
|
|
||||||
res += readBits(8) << ((noReads - i - 1) * 8);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] readBytes(byte[] barr, int off, int len) throws IOException, IndexOutOfBoundsException
|
|
||||||
{
|
|
||||||
if (off < 0)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Invalid offset " + off + ". It must be >= 0");
|
|
||||||
}
|
|
||||||
if (len < 0)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Invalid length " + len + ". It must be >= 0");
|
|
||||||
}
|
|
||||||
if (off + len > barr.length)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Invalid offset + length (" + off + " + " + len + "). It must be <= the length of the supplied array (" + barr.length + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotAtEOF();
|
|
||||||
|
|
||||||
if (len == 0)
|
|
||||||
{
|
|
||||||
return barr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAtByteBoundary())
|
|
||||||
{
|
|
||||||
// Special case: we are at the byte boundary. We just have to read
|
|
||||||
// the len next bytes and return them.
|
|
||||||
// The read method takes care of updating all internal state.
|
|
||||||
int noRead = read(barr, off, len);
|
|
||||||
if (noRead != len)
|
|
||||||
{
|
|
||||||
throwIOException("Unexpected EOF. Wanted to read " + len + " bytes. Got " + noRead, m_numberOfBytesRead - noRead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int noRead = m_in.read(barr, off, len);
|
|
||||||
m_numberOfBytesRead += noRead;
|
|
||||||
if (noRead != len)
|
|
||||||
{
|
|
||||||
m_curByte = -1;
|
|
||||||
m_pointerInByte = POINTER_START_OF_BYTE;
|
|
||||||
throwIOException("Unexpected EOF. Wanted to read " + len + " bytes. Got " + noRead, m_numberOfBytesRead - noRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift bytes in the result array. Bytes are stored little (bit-)
|
|
||||||
// endian.
|
|
||||||
int lastByte = m_curByte;
|
|
||||||
m_curByte = barr[off + len - 1] & 0xFF;
|
|
||||||
// The distance to shift the second byte to the right.
|
|
||||||
int rightShiftDistance = 8 - m_pointerInByte;
|
|
||||||
for (int i = off; i < off + len; i++)
|
|
||||||
{
|
|
||||||
int newLastByte = barr[i];
|
|
||||||
barr[i] = (byte) (((lastByte << m_pointerInByte) | ((barr[i] & 0xFF) >>> rightShiftDistance)) & 0xFF);
|
|
||||||
lastByte = newLastByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return barr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException
|
|
||||||
{
|
|
||||||
assertAtByteBoundary();
|
|
||||||
int res = m_curByte;
|
|
||||||
if (m_curByte != -1)
|
|
||||||
{
|
|
||||||
m_curByte = readByte();
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] barr) throws IOException
|
|
||||||
{
|
|
||||||
return read(barr, 0, barr.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] barr, int offset, int len) throws IndexOutOfBoundsException, IOException
|
|
||||||
{
|
|
||||||
if (offset < 0)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Illegal offset: " + offset);
|
|
||||||
}
|
|
||||||
else if (len < 0)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Illegal length: " + len);
|
|
||||||
}
|
|
||||||
else if ((offset + len) > barr.length)
|
|
||||||
{
|
|
||||||
throw new IndexOutOfBoundsException("Illegal offset + length: " + offset + " + " + len + ". Longer than the byte array: " + barr.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAtByteBoundary();
|
|
||||||
if (isAtEof())
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
barr[offset] = (byte) m_curByte;
|
|
||||||
int res = 1;
|
|
||||||
if (len > 1)
|
|
||||||
{
|
|
||||||
int noRead = m_in.read(barr, offset + 1, len - 1);
|
|
||||||
if (noRead > 0)
|
|
||||||
{
|
|
||||||
res += noRead;
|
|
||||||
m_numberOfBytesRead += noRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_curByte = readByte();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long skip(long n) throws IOException
|
|
||||||
{
|
|
||||||
assertAtByteBoundary();
|
|
||||||
if (n <= 0L)
|
|
||||||
{
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (isAtEof())
|
|
||||||
{
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n > 1L)
|
|
||||||
{
|
|
||||||
long noToSkip = n - 1L;
|
|
||||||
long noSkipped = m_in.skip(noToSkip);
|
|
||||||
m_numberOfBytesRead += noSkipped;
|
|
||||||
if (noSkipped < noToSkip)
|
|
||||||
{
|
|
||||||
// At EOF
|
|
||||||
m_curByte = -1;
|
|
||||||
return noSkipped + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_curByte = readByte();
|
|
||||||
return noSkipped + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_curByte = readByte();
|
|
||||||
return 1L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException
|
|
||||||
{
|
|
||||||
assertAtByteBoundary();
|
|
||||||
return m_in.available() + m_curByte != -1 ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
m_in.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -186,7 +186,7 @@ public class SignedInteger implements Comparable<SignedInteger>
|
||||||
|
|
||||||
public int compareTo(SignedInteger l2)
|
public int compareTo(SignedInteger l2)
|
||||||
{
|
{
|
||||||
return Integer.valueOf(m_value).compareTo(Integer.valueOf(l2.m_value));
|
return Integer.valueOf(m_value).compareTo(l2.m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -202,7 +202,7 @@ public class SignedLong implements Comparable<SignedLong>
|
||||||
|
|
||||||
public int compareTo(SignedLong l2)
|
public int compareTo(SignedLong l2)
|
||||||
{
|
{
|
||||||
return Long.valueOf(m_value).compareTo(Long.valueOf(l2.m_value));
|
return Long.valueOf(m_value).compareTo(l2.m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.at4j.support.lang;
|
package org.at4j.support.lang;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.at4j.support.lang;
|
package org.at4j.support.lang;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,7 +230,7 @@ public final class UnsignedInteger implements Serializable, Comparable<UnsignedI
|
||||||
|
|
||||||
public int compareTo(UnsignedInteger i2)
|
public int compareTo(UnsignedInteger i2)
|
||||||
{
|
{
|
||||||
return Long.valueOf(longValue()).compareTo(Long.valueOf(i2.longValue()));
|
return Long.valueOf(longValue()).compareTo(i2.longValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.at4j.support.lang;
|
package org.at4j.support.lang;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
*/
|
*/
|
||||||
package org.at4j.support.lang;
|
package org.at4j.support.lang;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue