de690daccc
Motivation: We should close encoder when `LzfEncoder` was removed from pipeline. Modification: call `encoder.close` when `handlerRemoved` triggered. Result: Close encoder to release internal buffer.
147 lines
5.8 KiB
Java
147 lines
5.8 KiB
Java
/*
|
|
* 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 com.ning.compress.BufferRecycler;
|
|
import com.ning.compress.lzf.ChunkEncoder;
|
|
import com.ning.compress.lzf.LZFEncoder;
|
|
import com.ning.compress.lzf.util.ChunkEncoderFactory;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.handler.codec.MessageToByteEncoder;
|
|
|
|
import static com.ning.compress.lzf.LZFChunk.*;
|
|
|
|
/**
|
|
* Compresses a {@link ByteBuf} using the LZF format.
|
|
*
|
|
* See original <a href="http://oldhome.schmorp.de/marc/liblzf.html">LZF package</a>
|
|
* and <a href="https://github.com/ning/compress/wiki/LZFFormat">LZF format</a> for full description.
|
|
*/
|
|
public class LzfEncoder extends MessageToByteEncoder<ByteBuf> {
|
|
/**
|
|
* Minimum block size ready for compression. Blocks with length
|
|
* less than {@link #MIN_BLOCK_TO_COMPRESS} will write as uncompressed.
|
|
*/
|
|
private static final int MIN_BLOCK_TO_COMPRESS = 16;
|
|
|
|
/**
|
|
* Underlying decoder in use.
|
|
*/
|
|
private final ChunkEncoder encoder;
|
|
|
|
/**
|
|
* Object that handles details of buffer recycling.
|
|
*/
|
|
private final BufferRecycler recycler;
|
|
|
|
/**
|
|
* Creates a new LZF encoder with the most optimal available methods for underlying data access.
|
|
* It will "unsafe" instance if one can be used on current JVM.
|
|
* It should be safe to call this constructor as implementations are dynamically loaded; however, on some
|
|
* non-standard platforms it may be necessary to use {@link #LzfEncoder(boolean)} with {@code true} param.
|
|
*/
|
|
public LzfEncoder() {
|
|
this(false, MAX_CHUNK_LEN);
|
|
}
|
|
|
|
/**
|
|
* Creates a new LZF encoder with specified encoding instance.
|
|
*
|
|
* @param safeInstance
|
|
* If {@code true} encoder will use {@link ChunkEncoder} that only uses standard JDK access methods,
|
|
* and should work on all Java platforms and JVMs.
|
|
* Otherwise encoder will try to use highly optimized {@link ChunkEncoder} implementation that uses
|
|
* Sun JDK's {@link sun.misc.Unsafe} class (which may be included by other JDK's as well).
|
|
*/
|
|
public LzfEncoder(boolean safeInstance) {
|
|
this(safeInstance, MAX_CHUNK_LEN);
|
|
}
|
|
|
|
/**
|
|
* Creates a new LZF encoder with specified total length of encoded chunk. You can configure it to encode
|
|
* your data flow more efficient if you know the average size of messages that you send.
|
|
*
|
|
* @param totalLength
|
|
* Expected total length of content to compress; only matters for outgoing messages that is smaller
|
|
* than maximum chunk size (64k), to optimize encoding hash tables.
|
|
*/
|
|
public LzfEncoder(int totalLength) {
|
|
this(false, totalLength);
|
|
}
|
|
|
|
/**
|
|
* Creates a new LZF encoder with specified settings.
|
|
*
|
|
* @param safeInstance
|
|
* If {@code true} encoder will use {@link ChunkEncoder} that only uses standard JDK access methods,
|
|
* and should work on all Java platforms and JVMs.
|
|
* Otherwise encoder will try to use highly optimized {@link ChunkEncoder} implementation that uses
|
|
* Sun JDK's {@link sun.misc.Unsafe} class (which may be included by other JDK's as well).
|
|
* @param totalLength
|
|
* Expected total length of content to compress; only matters for outgoing messages that is smaller
|
|
* than maximum chunk size (64k), to optimize encoding hash tables.
|
|
*/
|
|
public LzfEncoder(boolean safeInstance, int totalLength) {
|
|
super(false);
|
|
if (totalLength < MIN_BLOCK_TO_COMPRESS || totalLength > MAX_CHUNK_LEN) {
|
|
throw new IllegalArgumentException("totalLength: " + totalLength +
|
|
" (expected: " + MIN_BLOCK_TO_COMPRESS + '-' + MAX_CHUNK_LEN + ')');
|
|
}
|
|
|
|
encoder = safeInstance ?
|
|
ChunkEncoderFactory.safeNonAllocatingInstance(totalLength)
|
|
: ChunkEncoderFactory.optimalNonAllocatingInstance(totalLength);
|
|
|
|
recycler = BufferRecycler.instance();
|
|
}
|
|
|
|
@Override
|
|
protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
|
|
final int length = in.readableBytes();
|
|
final int idx = in.readerIndex();
|
|
final byte[] input;
|
|
final int inputPtr;
|
|
if (in.hasArray()) {
|
|
input = in.array();
|
|
inputPtr = in.arrayOffset() + idx;
|
|
} else {
|
|
input = recycler.allocInputBuffer(length);
|
|
in.getBytes(idx, input, 0, length);
|
|
inputPtr = 0;
|
|
}
|
|
|
|
final int maxOutputLength = LZFEncoder.estimateMaxWorkspaceSize(length);
|
|
out.ensureWritable(maxOutputLength);
|
|
final byte[] output = out.array();
|
|
final int outputPtr = out.arrayOffset() + out.writerIndex();
|
|
final int outputLength = LZFEncoder.appendEncoded(encoder,
|
|
input, inputPtr, length, output, outputPtr) - outputPtr;
|
|
out.writerIndex(out.writerIndex() + outputLength);
|
|
in.skipBytes(length);
|
|
|
|
if (!in.hasArray()) {
|
|
recycler.releaseInputBuffer(input);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
encoder.close();
|
|
super.handlerRemoved(ctx);
|
|
}
|
|
}
|