/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you 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 io.netty.handler.codec.compression;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

import java.util.zip.Adler32;
import java.util.zip.Checksum;

import static io.netty.handler.codec.compression.FastLz.*;

Compresses a ByteBuf using the FastLZ algorithm. See FastLZ format.
/** * Compresses a {@link ByteBuf} using the FastLZ algorithm. * * See <a href="https://github.com/netty/netty/issues/2750">FastLZ format</a>. */
public class FastLzFrameEncoder extends MessageToByteEncoder<ByteBuf> {
Compression level.
/** * Compression level. */
private final int level;
Underlying checksum calculator in use.
/** * Underlying checksum calculator in use. */
private final Checksum checksum;
Creates a FastLZ encoder without checksum calculator and with auto detection of compression level.
/** * Creates a FastLZ encoder without checksum calculator and with auto detection of compression level. */
public FastLzFrameEncoder() { this(LEVEL_AUTO, null); }
Creates a FastLZ encoder with specified compression level and without checksum calculator.
Params:
  • level – supports only these values: 0 - Encoder will choose level automatically depending on the length of the input buffer. 1 - Level 1 is the fastest compression and generally useful for short data. 2 - Level 2 is slightly slower but it gives better compression ratio.
/** * Creates a FastLZ encoder with specified compression level and without checksum calculator. * * @param level supports only these values: * 0 - Encoder will choose level automatically depending on the length of the input buffer. * 1 - Level 1 is the fastest compression and generally useful for short data. * 2 - Level 2 is slightly slower but it gives better compression ratio. */
public FastLzFrameEncoder(int level) { this(level, null); }
Creates a FastLZ encoder with auto detection of compression level and calculation of checksums as specified.
Params:
  • validateChecksums – If true, the checksum of each block will be calculated and this value will be added to the header of block. By default FastLzFrameEncoder uses Adler32 for checksum calculation.
/** * Creates a FastLZ encoder with auto detection of compression * level and calculation of checksums as specified. * * @param validateChecksums * If true, the checksum of each block will be calculated and this value * will be added to the header of block. * By default {@link FastLzFrameEncoder} uses {@link java.util.zip.Adler32} * for checksum calculation. */
public FastLzFrameEncoder(boolean validateChecksums) { this(LEVEL_AUTO, validateChecksums ? new Adler32() : null); }
Creates a FastLZ encoder with specified compression level and checksum calculator.
Params:
  • level – supports only these values: 0 - Encoder will choose level automatically depending on the length of the input buffer. 1 - Level 1 is the fastest compression and generally useful for short data. 2 - Level 2 is slightly slower but it gives better compression ratio.
  • checksum – the Checksum instance to use to check data for integrity. You may set null if you don't want to validate checksum of each block.
/** * Creates a FastLZ encoder with specified compression level and checksum calculator. * * @param level supports only these values: * 0 - Encoder will choose level automatically depending on the length of the input buffer. * 1 - Level 1 is the fastest compression and generally useful for short data. * 2 - Level 2 is slightly slower but it gives better compression ratio. * @param checksum * the {@link Checksum} instance to use to check data for integrity. * You may set {@code null} if you don't want to validate checksum of each block. */
public FastLzFrameEncoder(int level, Checksum checksum) { super(false); if (level != LEVEL_AUTO && level != LEVEL_1 && level != LEVEL_2) { throw new IllegalArgumentException(String.format( "level: %d (expected: %d or %d or %d)", level, LEVEL_AUTO, LEVEL_1, LEVEL_2)); } this.level = level; this.checksum = checksum; } @Override protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception { final Checksum checksum = this.checksum; for (;;) { if (!in.isReadable()) { return; } final int idx = in.readerIndex(); final int length = Math.min(in.readableBytes(), MAX_CHUNK_LENGTH); final int outputIdx = out.writerIndex(); out.setMedium(outputIdx, MAGIC_NUMBER); int outputOffset = outputIdx + CHECKSUM_OFFSET + (checksum != null ? 4 : 0); final byte blockType; final int chunkLength; if (length < MIN_LENGTH_TO_COMPRESSION) { blockType = BLOCK_TYPE_NON_COMPRESSED; out.ensureWritable(outputOffset + 2 + length); final byte[] output = out.array(); final int outputPtr = out.arrayOffset() + outputOffset + 2; if (checksum != null) { final byte[] input; final int inputPtr; if (in.hasArray()) { input = in.array(); inputPtr = in.arrayOffset() + idx; } else { input = new byte[length]; in.getBytes(idx, input); inputPtr = 0; } checksum.reset(); checksum.update(input, inputPtr, length); out.setInt(outputIdx + CHECKSUM_OFFSET, (int) checksum.getValue()); System.arraycopy(input, inputPtr, output, outputPtr, length); } else { in.getBytes(idx, output, outputPtr, length); } chunkLength = length; } else { // try to compress final byte[] input; final int inputPtr; if (in.hasArray()) { input = in.array(); inputPtr = in.arrayOffset() + idx; } else { input = new byte[length]; in.getBytes(idx, input); inputPtr = 0; } if (checksum != null) { checksum.reset(); checksum.update(input, inputPtr, length); out.setInt(outputIdx + CHECKSUM_OFFSET, (int) checksum.getValue()); } final int maxOutputLength = calculateOutputBufferLength(length); out.ensureWritable(outputOffset + 4 + maxOutputLength); final byte[] output = out.array(); final int outputPtr = out.arrayOffset() + outputOffset + 4; final int compressedLength = compress(input, inputPtr, length, output, outputPtr, level); if (compressedLength < length) { blockType = BLOCK_TYPE_COMPRESSED; chunkLength = compressedLength; out.setShort(outputOffset, chunkLength); outputOffset += 2; } else { blockType = BLOCK_TYPE_NON_COMPRESSED; System.arraycopy(input, inputPtr, output, outputPtr - 2, length); chunkLength = length; } } out.setShort(outputOffset, length); out.setByte(outputIdx + OPTIONS_OFFSET, blockType | (checksum != null ? BLOCK_WITH_CHECKSUM : BLOCK_WITHOUT_CHECKSUM)); out.writerIndex(outputOffset + 2 + chunkLength); in.skipBytes(length); } } }