/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 org.apache.cassandra.io.compress; import org.apache.cassandra.utils.FBUtilities; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; public class DeflateCompressor implements ICompressor { public static final DeflateCompressor instance = new DeflateCompressor(); private final ThreadLocal<Deflater> deflater; private final ThreadLocal<Inflater> inflater; public static DeflateCompressor create(Map<String, String> compressionOptions) { // no specific options supported so far return instance; } private DeflateCompressor() { deflater = new ThreadLocal<Deflater>() { @Override protected Deflater initialValue() { return new Deflater(); } }; inflater = new ThreadLocal<Inflater>() { @Override protected Inflater initialValue() { return new Inflater(); } }; } public Set<String> supportedOptions() { return Collections.emptySet(); } public int initialCompressedBufferLength(int sourceLen) { // Taken from zlib deflateBound(). See http://www.zlib.net/zlib_tech.html. return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13; } public void compress(ByteBuffer input, ByteBuffer output) { if (input.hasArray() && output.hasArray()) { int length = compressArray(input.array(), input.arrayOffset() + input.position(), input.remaining(), output.array(), output.arrayOffset() + output.position(), output.remaining()); input.position(input.limit()); output.position(output.position() + length); } else compressBuffer(input, output); } public int compressArray(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength) { Deflater def = deflater.get(); def.reset(); def.setInput(input, inputOffset, inputLength); def.finish(); if (def.needsInput()) return 0; int len = def.deflate(output, outputOffset, maxOutputLength); assert def.finished(); return len; } public void compressBuffer(ByteBuffer input, ByteBuffer output) { Deflater def = deflater.get(); def.reset(); byte[] buffer = FBUtilities.getThreadLocalScratchBuffer(); // Use half the buffer for input, half for output. int chunkLen = buffer.length / 2; while (input.remaining() > chunkLen) { input.get(buffer, 0, chunkLen); def.setInput(buffer, 0, chunkLen); while (!def.needsInput()) { int len = def.deflate(buffer, chunkLen, chunkLen); output.put(buffer, chunkLen, len); } } int inputLength = input.remaining(); input.get(buffer, 0, inputLength); def.setInput(buffer, 0, inputLength); def.finish(); while (!def.finished()) { int len = def.deflate(buffer, chunkLen, chunkLen); output.put(buffer, chunkLen, len); } } public void uncompress(ByteBuffer input, ByteBuffer output) throws IOException { if (input.hasArray() && output.hasArray()) { int length = uncompress(input.array(), input.arrayOffset() + input.position(), input.remaining(), output.array(), output.arrayOffset() + output.position(), output.remaining()); input.position(input.limit()); output.position(output.position() + length); } else uncompressBuffer(input, output); } public void uncompressBuffer(ByteBuffer input, ByteBuffer output) throws IOException { try { Inflater inf = inflater.get(); inf.reset(); byte[] buffer = FBUtilities.getThreadLocalScratchBuffer(); // Use half the buffer for input, half for output. int chunkLen = buffer.length / 2; while (input.remaining() > chunkLen) { input.get(buffer, 0, chunkLen); inf.setInput(buffer, 0, chunkLen); while (!inf.needsInput()) { int len = inf.inflate(buffer, chunkLen, chunkLen); output.put(buffer, chunkLen, len); } } int inputLength = input.remaining(); input.get(buffer, 0, inputLength); inf.setInput(buffer, 0, inputLength); while (!inf.needsInput()) { int len = inf.inflate(buffer, chunkLen, chunkLen); output.put(buffer, chunkLen, len); } } catch (DataFormatException e) { throw new IOException(e); } } public int uncompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws IOException { return uncompress(input, inputOffset, inputLength, output, outputOffset, output.length - outputOffset); } public int uncompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength) throws IOException { Inflater inf = inflater.get(); inf.reset(); inf.setInput(input, inputOffset, inputLength); if (inf.needsInput()) return 0; // We assume output is big enough try { return inf.inflate(output, outputOffset, maxOutputLength); } catch (DataFormatException e) { throw new IOException(e); } } public boolean supports(BufferType bufferType) { return true; } public BufferType preferredBufferType() { // Prefer array-backed buffers. return BufferType.ON_HEAP; } }