HuffmanCompressor.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * 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 org.waarp.compress.zstdsafe;

import static org.waarp.compress.zstdsafe.Constants.*;
import static org.waarp.compress.zstdsafe.UnsafeUtil.*;

class HuffmanCompressor {
  private HuffmanCompressor() {
  }

  public static int compress4streams(final byte[] outputBase,
                                     final int outputAddress,
                                     final int outputSize,
                                     final byte[] inputBase,
                                     final int inputAddress,
                                     final int inputSize,
                                     final HuffmanCompressionTable table) {
    int input = inputAddress;
    final int inputLimit = inputAddress + inputSize;
    int output = outputAddress;
    final int outputLimit = outputAddress + outputSize;

    final int segmentSize = (inputSize + 3) / 4;

    if (outputSize <
        6 /* jump table */ + 1 /* first stream */ + 1 /* second stream */ +
        1 /* third stream */ +
        8 /* 8 bytes minimum needed by the bitstream encoder */) {
      return 0; // minimum space to compress successfully
    }

    if (inputSize <= 6 + 1 + 1 + 1) { // jump table + one byte per stream
      return 0;  // no saving possible: input too small
    }

    output += SIZE_OF_SHORT + SIZE_OF_SHORT + SIZE_OF_SHORT; // jump table

    int compressedSize;

    // first segment
    compressedSize =
        compressSingleStream(outputBase, output, outputLimit - output,
                             inputBase, input, segmentSize, table);
    if (compressedSize == 0) {
      return 0;
    }
    putShort(outputBase, outputAddress, (short) compressedSize);
    output += compressedSize;
    input += segmentSize;

    // second segment
    compressedSize =
        compressSingleStream(outputBase, output, outputLimit - output,
                             inputBase, input, segmentSize, table);
    if (compressedSize == 0) {
      return 0;
    }
    putShort(outputBase, outputAddress + SIZE_OF_SHORT, (short) compressedSize);
    output += compressedSize;
    input += segmentSize;

    // third segment
    compressedSize =
        compressSingleStream(outputBase, output, outputLimit - output,
                             inputBase, input, segmentSize, table);
    if (compressedSize == 0) {
      return 0;
    }
    putShort(outputBase, outputAddress + SIZE_OF_SHORT + SIZE_OF_SHORT,
             (short) compressedSize);
    output += compressedSize;
    input += segmentSize;

    // fourth segment
    compressedSize =
        compressSingleStream(outputBase, output, outputLimit - output,
                             inputBase, input, inputLimit - input, table);
    if (compressedSize == 0) {
      return 0;
    }
    output += compressedSize;

    return output - outputAddress;
  }

  public static int compressSingleStream(final byte[] outputBase,
                                         final int outputAddress,
                                         final int outputSize,
                                         final byte[] inputBase,
                                         final int inputAddress,
                                         final int inputSize,
                                         final HuffmanCompressionTable table) {
    if (outputSize < SIZE_OF_LONG) {
      return 0;
    }
    final BitOutputStream bitstream =
        new BitOutputStream(outputBase, outputAddress, outputSize);

    int n = inputSize & ~3; // join to mod 4

    switch (inputSize & 3) {
      case 3:
        table.encodeSymbol(bitstream, inputBase[inputAddress + n + 2] & 0xFF);
        // fall-through
      case 2:
        table.encodeSymbol(bitstream, inputBase[inputAddress + n + 1] & 0xFF);
        // fall-through
      case 1:
        table.encodeSymbol(bitstream, inputBase[inputAddress + n] & 0xFF);
        bitstream.flush();
        // fall-through
      case 0: /* fall-through */
      default:
        break;
    }

    for (; n > 0; n -= 4) {  // note: n & 3 == 0 at this stage
      table.encodeSymbol(bitstream, inputBase[inputAddress + n - 1] & 0xFF);
      table.encodeSymbol(bitstream, inputBase[inputAddress + n - 2] & 0xFF);
      table.encodeSymbol(bitstream, inputBase[inputAddress + n - 3] & 0xFF);
      table.encodeSymbol(bitstream, inputBase[inputAddress + n - 4] & 0xFF);
      bitstream.flush();
    }

    return bitstream.close();
  }
}