/* Copyright 2004-2014 Jim Voris * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qumasoft.qvcslib; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; /** * The default compressor implementation. This is based on an algorithm I discovered a long time ago (in C++), and ported to Java. * @author Jim Voris * @deprecated Use the {@link com.qumasoft.qvcslib.ZlibCompressor} instead. */ public class DefaultCompressor implements Compressor { // Create our logger object private static final Logger LOGGER = Logger.getLogger("com.qumasoft.qvcslib"); private static final int ITEMMAX = 16; private byte[] inputBuffer; private byte[] outputBuffer; private int inputIndex; private int outputIndex; private int walkerIndexA; private int walkerIndexB; private static final int ONE_NIBBLE_BIT_COUNT = 4; private static final int TWO_NIBBLE_BIT_COUNT = 8; private static final int FOUR_NIBBLE_BIT_COUNT = 16; private static final int FOUR_K = 4096; private static final int INCREMENTOR_SHIFT_COUNT = 13; private static final int LO_ONE_NIBBLE_BITS = 0x000F; private static final int LO_SECOND_NIBBLE_BITS = 0x00F0; private static final int LO_TWO_NIBBLE_BITS = 0x00FF; private static final int LO_THIRD_NIBBLE_BITS = 0x0F00; private static final int LO_THREE_NIBBLE_BITS = 0x0FFF; private static final int MOST_SIGNIFICANT_BIT = 0x08000; private static final int MAGIC_PRIME_NUMBER = 40543; private byte[] unCompressedBuffer; private byte[] compressedBuffer; private boolean bufferIsCompressedFlag; /** * Default constructor. */ public DefaultCompressor() { } /** * Compress the input buffer. Return true if we compressed the data, false otherwise. If compression is successful, the outputBuffer will contain the compressed result. The * compressed result includes a RevisionCompressionHeader as the first 10 bytes. * * Note that this method is not re-entrant, so we have to synchronize it -- it uses some object level variables that make it unworkable for it to be called again (on the same * object). * @param inBuffer to buffer that we want to compress. * @return true if we were able to compress the input buffer; false if not. */ @Override public synchronized boolean compress(byte[] inBuffer) { throw new QVCSRuntimeException("Default compression is no longer used."); } /** * The input buffer has the RevisionCompressionHeader at the beginning. * * @param inputBufferLocal compressed input buffer that has a RevisionCompressionHeader at its beginning. * @return the expanded buffer. */ @Override public byte[] expand(byte[] inputBufferLocal) { byte[] retVal = null; ByteArrayInputStream inStream = new ByteArrayInputStream(inputBufferLocal); DataInputStream dataInStream = new DataInputStream(inStream); RevisionCompressionHeader compressionHeader = new RevisionCompressionHeader(); try { compressionHeader.read(dataInStream); retVal = expand(compressionHeader, inputBufferLocal); dataInStream.close(); inStream.close(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Caught IOException in expand: " + e.getLocalizedMessage()); } return retVal; } @Override public byte[] expand(RevisionCompressionHeader compressionHeader, byte[] inputBufferLocal) { int controlbits = 0; int control = 0; int srcIndex; int dstIndex; int junk; byte[] outputBufferLocal; /* * Allocate memory for the output (decompressed buffer) */ outputBufferLocal = new byte[(int) compressionHeader.getInputSize()]; dstIndex = 0; srcIndex = RevisionCompressionHeader.getHeaderSize(); while (srcIndex < inputBufferLocal.length) { if (controlbits == 0) { junk = inputBufferLocal[srcIndex++]; junk &= LO_TWO_NIBBLE_BITS; control = junk; junk = inputBufferLocal[srcIndex++]; junk &= LO_TWO_NIBBLE_BITS; control |= junk << TWO_NIBBLE_BIT_COUNT; controlbits = FOUR_NIBBLE_BIT_COUNT; } if ((control & 1) == 1) { int offset; int len; int p; junk = inputBufferLocal[srcIndex]; junk &= LO_TWO_NIBBLE_BITS; offset = (junk & LO_SECOND_NIBBLE_BITS) << ONE_NIBBLE_BIT_COUNT; junk = inputBufferLocal[srcIndex++]; junk &= LO_TWO_NIBBLE_BITS; len = 1 + (junk & LO_ONE_NIBBLE_BITS); junk = inputBufferLocal[srcIndex++]; junk &= LO_TWO_NIBBLE_BITS; offset += junk & LO_TWO_NIBBLE_BITS; p = dstIndex - offset; while (len-- > 0) { outputBufferLocal[dstIndex++] = outputBufferLocal[p++]; } } else { outputBufferLocal[dstIndex++] = inputBufferLocal[srcIndex++]; } control >>>= 1; controlbits--; } if (dstIndex != (int) compressionHeader.getInputSize()) { throw new QVCSRuntimeException("Decompression failed in DefaultCompressor"); } return outputBufferLocal; } private boolean walkAndMatch() { boolean retVal = inputBuffer[walkerIndexA++] != inputBuffer[walkerIndexB++]; return retVal; } private void incrementor() { boolean bResult = false; for (int i = 0; i < INCREMENTOR_SHIFT_COUNT; i++) { if (walkAndMatch()) { bResult = true; break; } } if (!bResult) { walkerIndexA++; } } @Override public void setUncompressedBuffer(byte[] uncompressedBuffer) { this.unCompressedBuffer = uncompressedBuffer; } @Override public byte[] getUncompressedBuffer() { return this.unCompressedBuffer; } @Override public byte[] getCompressedBuffer() { return this.compressedBuffer; } @Override public ByteArrayInputStream getCompressedStream() { ByteArrayInputStream byteArrayInputStream; if (bufferIsCompressedFlag) { byteArrayInputStream = new ByteArrayInputStream(compressedBuffer); } else { byteArrayInputStream = new ByteArrayInputStream(unCompressedBuffer); } return byteArrayInputStream; } @Override public boolean getBufferIsCompressedFlag() { return this.bufferIsCompressedFlag; } }