All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testifyproject.netty.handler.codec.compression.Lz4FrameEncoder Maven / Gradle / Ivy

/*
 * 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 org.testifyproject.testifyprojectpliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org.testifyproject/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.testifyproject.testifyproject.netty.handler.codec.org.testifyproject.testifyprojectpression;

import org.testifyproject.testifyproject.netty.buffer.ByteBuf;
import org.testifyproject.testifyproject.netty.channel.ChannelFuture;
import org.testifyproject.testifyproject.netty.channel.ChannelFutureListener;
import org.testifyproject.testifyproject.netty.channel.ChannelHandlerContext;
import org.testifyproject.testifyproject.netty.channel.ChannelPipeline;
import org.testifyproject.testifyproject.netty.channel.ChannelPromise;
import org.testifyproject.testifyproject.netty.channel.ChannelPromiseNotifier;
import org.testifyproject.testifyproject.netty.handler.codec.MessageToByteEncoder;
import org.testifyproject.testifyproject.netty.util.concurrent.EventExecutor;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Exception;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.xxhash.XXHashFactory;

import java.util.concurrent.TimeUnit;
import java.util.zip.Checksum;

import static org.testifyproject.testifyproject.netty.handler.codec.org.testifyproject.testifyprojectpression.Lz4Constants.*;

/**
 * Compresses a {@link ByteBuf} using the LZ4 format.
 *
 * See original LZ4 website
 * and LZ4 block format
 * for full org.testifyproject.testifyprojectscription.
 *
 * Since the original LZ4 block format does not contains size of org.testifyproject.testifyprojectpressed block and size of original data
 * this encoder uses format like LZ4 Java library
 * written by Adrien Grand and approved by Yann Collet (author of original LZ4 library).
 *
 *  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *     * * * * * * * * * *
 *  * Magic * Token *  Compressed *  Decompressed *  Checksum *  +  *  LZ4 org.testifyproject.testifyprojectpressed *
 *  *       *       *    length   *     length    *           *     *      block      *
 *  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *     * * * * * * * * * *
 */
public class Lz4FrameEncoder extends MessageToByteEncoder {
    /**
     * Underlying org.testifyproject.testifyprojectpressor in use.
     */
    private LZ4Compressor org.testifyproject.testifyprojectpressor;

    /**
     * Underlying checksum calculator in use.
     */
    private Checksum checksum;

    /**
     * Compression level of current LZ4 encoder (org.testifyproject.testifyprojectpends on {@link #org.testifyproject.testifyprojectpressedBlockSize}).
     */
    private final int org.testifyproject.testifyprojectpressionLevel;

    /**
     * Inner byte buffer for outgoing data.
     */
    private byte[] buffer;

    /**
     * Current length of buffered bytes in {@link #buffer}.
     */
    private int currentBlockLength;

    /**
     * Maximum size of org.testifyproject.testifyprojectpressed block with header.
     */
    private final int org.testifyproject.testifyprojectpressedBlockSize;

    /**
     * Indicates if the org.testifyproject.testifyprojectpressed stream has been finished.
     */
    private volatile boolean finished;

    /**
     * Used to interact with its {@link ChannelPipeline} and other handlers.
     */
    private volatile ChannelHandlerContext ctx;

    /**
     * Creates the fastest LZ4 encoder with org.testifyproject.testifyprojectfault block size (64 KB)
     * and xxhash hashing for Java, based on Yann Collet's work available at
     * Google Code.
     */
    public Lz4FrameEncoder() {
        this(false);
    }

    /**
     * Creates a new LZ4 encoder with hight or fast org.testifyproject.testifyprojectpression, org.testifyproject.testifyprojectfault block size (64 KB)
     * and xxhash hashing for Java, based on Yann Collet's work available at
     * Google Code.
     *
     * @param highCompressor  if {@code true} codec will use org.testifyproject.testifyprojectpressor which requires more memory
     *                        and is slower but org.testifyproject.testifyprojectpresses more efficiently
     */
    public Lz4FrameEncoder(boolean highCompressor) {
        this(LZ4Factory.fastestInstance(), highCompressor, DEFAULT_BLOCK_SIZE,
                XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum());
    }

    /**
     * Creates a new customizable LZ4 encoder.
     *
     * @param factory         user customizable {@link net.jpountz.lz4.LZ4Factory} instance
     *                        which may be JNI bindings to the original C implementation, a pure Java implementation
     *                        or a Java implementation that uses the {@link sun.misc.Unsafe}
     * @param highCompressor  if {@code true} codec will use org.testifyproject.testifyprojectpressor which requires more memory
     *                        and is slower but org.testifyproject.testifyprojectpresses more efficiently
     * @param blockSize       the maximum number of bytes to try to org.testifyproject.testifyprojectpress at once,
     *                        must be >= 64 and <= 32 M
     * @param checksum        the {@link Checksum} instance to use to check data for integrity
     */
    public Lz4FrameEncoder(LZ4Factory factory, boolean highCompressor, int blockSize, Checksum checksum) {
        super(false);
        if (factory == null) {
            throw new NullPointerException("factory");
        }
        if (checksum == null) {
            throw new NullPointerException("checksum");
        }

        org.testifyproject.testifyprojectpressor = highCompressor ? factory.highCompressor() : factory.fastCompressor();
        this.checksum = checksum;

        org.testifyproject.testifyprojectpressionLevel = org.testifyproject.testifyprojectpressionLevel(blockSize);
        buffer = new byte[blockSize];
        currentBlockLength = 0;
        org.testifyproject.testifyprojectpressedBlockSize = HEADER_LENGTH + org.testifyproject.testifyprojectpressor.maxCompressedLength(blockSize);

        finished = false;
    }

    /**
     * Calculates org.testifyproject.testifyprojectpression level on the basis of block size.
     */
    private static int org.testifyproject.testifyprojectpressionLevel(int blockSize) {
        if (blockSize < MIN_BLOCK_SIZE || blockSize > MAX_BLOCK_SIZE) {
            throw new IllegalArgumentException(String.format(
                    "blockSize: %d (expected: %d-%d)", blockSize, MIN_BLOCK_SIZE, MAX_BLOCK_SIZE));
        }
        int org.testifyproject.testifyprojectpressionLevel = 32 - Integer.numberOfLeadingZeros(blockSize - 1); // ceil of log2
        org.testifyproject.testifyprojectpressionLevel = Math.max(0, org.testifyproject.testifyprojectpressionLevel - COMPRESSION_LEVEL_BASE);
        return org.testifyproject.testifyprojectpressionLevel;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
        if (finished) {
            out.writeBytes(in);
            return;
        }

        int length = in.readableBytes();

        final byte[] buffer = this.buffer;
        final int blockSize = buffer.length;
        while (currentBlockLength + length >= blockSize) {
            final int tail = blockSize - currentBlockLength;
            in.getBytes(in.readerIndex(), buffer, currentBlockLength, tail);
            currentBlockLength = blockSize;
            flushBufferedData(out);
            in.skipBytes(tail);
            length -= tail;
        }
        in.readBytes(buffer, currentBlockLength, length);
        currentBlockLength += length;
    }

    private void flushBufferedData(ByteBuf out) {
        int currentBlockLength = this.currentBlockLength;
        if (currentBlockLength == 0) {
            return;
        }
        checksum.reset();
        checksum.update(buffer, 0, currentBlockLength);
        final int check = (int) checksum.getValue();

        out.ensureWritable(org.testifyproject.testifyprojectpressedBlockSize);
        final int idx = out.writerIndex();
        final byte[] org.testifyproject.testifyprojectst = out.array();
        final int org.testifyproject.testifyprojectstOff = out.arrayOffset() + idx;
        int org.testifyproject.testifyprojectpressedLength;
        try {
            org.testifyproject.testifyprojectpressedLength = org.testifyproject.testifyprojectpressor.org.testifyproject.testifyprojectpress(buffer, 0, currentBlockLength, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + HEADER_LENGTH);
        } catch (LZ4Exception e) {
            throw new CompressionException(e);
        }
        final int blockType;
        if (org.testifyproject.testifyprojectpressedLength >= currentBlockLength) {
            blockType = BLOCK_TYPE_NON_COMPRESSED;
            org.testifyproject.testifyprojectpressedLength = currentBlockLength;
            System.arraycopy(buffer, 0, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + HEADER_LENGTH, currentBlockLength);
        } else {
            blockType = BLOCK_TYPE_COMPRESSED;
        }

        out.setLong(idx, MAGIC_NUMBER);
        org.testifyproject.testifyprojectst[org.testifyproject.testifyprojectstOff + TOKEN_OFFSET] = (byte) (blockType | org.testifyproject.testifyprojectpressionLevel);
        writeIntLE(org.testifyproject.testifyprojectpressedLength, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + COMPRESSED_LENGTH_OFFSET);
        writeIntLE(currentBlockLength, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + DECOMPRESSED_LENGTH_OFFSET);
        writeIntLE(check, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + CHECKSUM_OFFSET);
        out.writerIndex(idx + HEADER_LENGTH + org.testifyproject.testifyprojectpressedLength);
        currentBlockLength = 0;

        this.currentBlockLength = currentBlockLength;
    }

    private ChannelFuture finishEncode(final ChannelHandlerContext ctx, ChannelPromise promise) {
        if (finished) {
            promise.setSuccess();
            return promise;
        }
        finished = true;

        final ByteBuf footer = ctx.alloc().heapBuffer(
                org.testifyproject.testifyprojectpressor.maxCompressedLength(currentBlockLength) + HEADER_LENGTH);
        flushBufferedData(footer);

        final int idx = footer.writerIndex();
        final byte[] org.testifyproject.testifyprojectst = footer.array();
        final int org.testifyproject.testifyprojectstOff = footer.arrayOffset() + idx;
        footer.setLong(idx, MAGIC_NUMBER);
        org.testifyproject.testifyprojectst[org.testifyproject.testifyprojectstOff + TOKEN_OFFSET] = (byte) (BLOCK_TYPE_NON_COMPRESSED | org.testifyproject.testifyprojectpressionLevel);
        writeIntLE(0, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + COMPRESSED_LENGTH_OFFSET);
        writeIntLE(0, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + DECOMPRESSED_LENGTH_OFFSET);
        writeIntLE(0, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff + CHECKSUM_OFFSET);
        footer.writerIndex(idx + HEADER_LENGTH);

        org.testifyproject.testifyprojectpressor = null;
        checksum = null;
        buffer = null;

        return ctx.writeAndFlush(footer, promise);
    }

    /**
     * Writes {@code int} value into the byte buffer with little-endian format.
     */
    private static void writeIntLE(int i, byte[] buf, int off) {
        buf[off++] = (byte) i;
        buf[off++] = (byte) (i >>> 8);
        buf[off++] = (byte) (i >>> 16);
        buf[off]   = (byte) (i >>> 24);
    }

    /**
     * Returns {@code true} if and only if the org.testifyproject.testifyprojectpressed stream has been finished.
     */
    public boolean isClosed() {
        return finished;
    }

    /**
     * Close this {@link Lz4FrameEncoder} and so finish the encoding.
     *
     * The returned {@link ChannelFuture} will be notified once the operation org.testifyproject.testifyprojectpletes.
     */
    public ChannelFuture close() {
        return close(ctx().newPromise());
    }

    /**
     * Close this {@link Lz4FrameEncoder} and so finish the encoding.
     * The given {@link ChannelFuture} will be notified once the operation
     * org.testifyproject.testifyprojectpletes and will also be returned.
     */
    public ChannelFuture close(final ChannelPromise promise) {
        ChannelHandlerContext ctx = ctx();
        EventExecutor executor = ctx.executor();
        if (executor.inEventLoop()) {
            return finishEncode(ctx, promise);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    ChannelFuture f = finishEncode(ctx(), promise);
                    f.addListener(new ChannelPromiseNotifier(promise));
                }
            });
            return promise;
        }
    }

    @Override
    public void close(final ChannelHandlerContext ctx, final ChannelPromise promise) throws Exception {
        ChannelFuture f = finishEncode(ctx, ctx.newPromise());
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture f) throws Exception {
                ctx.close(promise);
            }
        });

        if (!f.isDone()) {
            // Ensure the channel is closed even if the write operation org.testifyproject.testifyprojectpletes in time.
            ctx.executor().schedule(new Runnable() {
                @Override
                public void run() {
                    ctx.close(promise);
                }
            }, 10, TimeUnit.SECONDS); // FIXME: Magic number
        }
    }

    private ChannelHandlerContext ctx() {
        ChannelHandlerContext ctx = this.ctx;
        if (ctx == null) {
            throw new IllegalStateException("not added to a pipeline");
        }
        return ctx;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy