1 /* 2 * Copyright 2014 The Netty Project 3 * 4 * The Netty Project licenses this file to you under the Apache License, 5 * version 2.0 (the "License"); you may not use this file except in compliance 6 * with the License. You may obtain a copy of the License at: 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package io.netty.handler.codec.compression; 17 18 import com.ning.compress.BufferRecycler; 19 import com.ning.compress.lzf.ChunkEncoder; 20 import com.ning.compress.lzf.LZFEncoder; 21 import com.ning.compress.lzf.util.ChunkEncoderFactory; 22 import io.netty.buffer.ByteBuf; 23 import io.netty.channel.ChannelHandlerContext; 24 import io.netty.handler.codec.MessageToByteEncoder; 25 26 import static com.ning.compress.lzf.LZFChunk.*; 27 28 /** 29 * Compresses a [email protected] ByteBuf} using the LZF format. 30 * 31 * See original <a href="http://oldhome.schmorp.de/marc/liblzf.html">LZF package</a> 32 * and <a href="https://github.com/ning/compress/wiki/LZFFormat">LZF format</a> for full description. 33 */ 34 public class LzfEncoder extends MessageToByteEncoder<ByteBuf> { 35 /** 36 * Minimum block size ready for compression. Blocks with length 37 * less than [email protected] #MIN_BLOCK_TO_COMPRESS} will write as uncompressed. 38 */ 39 private static final int MIN_BLOCK_TO_COMPRESS = 16; 40 41 /** 42 * Underlying decoder in use. 43 */ 44 private final ChunkEncoder encoder; 45 46 /** 47 * Object that handles details of buffer recycling. 48 */ 49 private final BufferRecycler recycler; 50 51 /** 52 * Creates a new LZF encoder with the most optimal available methods for underlying data access. 53 * It will "unsafe" instance if one can be used on current JVM. 54 * It should be safe to call this constructor as implementations are dynamically loaded; however, on some 55 * non-standard platforms it may be necessary to use [email protected] #LzfEncoder(boolean)} with [email protected] true} param. 56 */ 57 public LzfEncoder() { 58 this(false, MAX_CHUNK_LEN); 59 } 60 61 /** 62 * Creates a new LZF encoder with specified encoding instance. 63 * 64 * @param safeInstance 65 * If [email protected] true} encoder will use [email protected] ChunkEncoder} that only uses standard JDK access methods, 66 * and should work on all Java platforms and JVMs. 67 * Otherwise encoder will try to use highly optimized [email protected] ChunkEncoder} implementation that uses 68 * Sun JDK's [email protected] sun.misc.Unsafe} class (which may be included by other JDK's as well). 69 */ 70 public LzfEncoder(boolean safeInstance) { 71 this(safeInstance, MAX_CHUNK_LEN); 72 } 73 74 /** 75 * Creates a new LZF encoder with specified total length of encoded chunk. You can configure it to encode 76 * your data flow more efficient if you know the avarage size of messages that you send. 77 * 78 * @param totalLength 79 * Expected total length of content to compress; only matters for outgoing messages that is smaller 80 * than maximum chunk size (64k), to optimize encoding hash tables. 81 */ 82 public LzfEncoder(int totalLength) { 83 this(false, totalLength); 84 } 85 86 /** 87 * Creates a new LZF encoder with specified settings. 88 * 89 * @param safeInstance 90 * If [email protected] true} encoder will use [email protected] ChunkEncoder} that only uses standard JDK access methods, 91 * and should work on all Java platforms and JVMs. 92 * Otherwise encoder will try to use highly optimized [email protected] ChunkEncoder} implementation that uses 93 * Sun JDK's [email protected] sun.misc.Unsafe} class (which may be included by other JDK's as well). 94 * @param totalLength 95 * Expected total length of content to compress; only matters for outgoing messages that is smaller 96 * than maximum chunk size (64k), to optimize encoding hash tables. 97 */ 98 public LzfEncoder(boolean safeInstance, int totalLength) { 99 super(false); 100 if (totalLength < MIN_BLOCK_TO_COMPRESS || totalLength > MAX_CHUNK_LEN) { 101 throw new IllegalArgumentException("totalLength: " + totalLength + 102 " (expected: " + MIN_BLOCK_TO_COMPRESS + '-' + MAX_CHUNK_LEN + ')'); 103 } 104 105 encoder = safeInstance ? 106 ChunkEncoderFactory.safeNonAllocatingInstance(totalLength) 107 : ChunkEncoderFactory.optimalNonAllocatingInstance(totalLength); 108 109 recycler = BufferRecycler.instance(); 110 } 111 112 @Override 113 protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception { 114 final int length = in.readableBytes(); 115 final int idx = in.readerIndex(); 116 final byte[] input; 117 final int inputPtr; 118 if (in.hasArray()) { 119 input = in.array(); 120 inputPtr = in.arrayOffset() + idx; 121 } else { 122 input = recycler.allocInputBuffer(length); 123 in.getBytes(idx, input, 0, length); 124 inputPtr = 0; 125 } 126 127 final int maxOutputLength = LZFEncoder.estimateMaxWorkspaceSize(length); 128 out.ensureWritable(maxOutputLength); 129 final byte[] output = out.array(); 130 final int outputPtr = out.arrayOffset() + out.writerIndex(); 131 final int outputLength = LZFEncoder.appendEncoded(encoder, 132 input, inputPtr, length, output, outputPtr) - outputPtr; 133 out.writerIndex(out.writerIndex() + outputLength); 134 in.skipBytes(length); 135 136 if (!in.hasArray()) { 137 recycler.releaseInputBuffer(input); 138 } 139 } 140 }