001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.compress.brotli;
019
020import com.aayushatharva.brotli4j.Brotli4jLoader;
021import com.aayushatharva.brotli4j.decoder.Decoder;
022import com.aayushatharva.brotli4j.decoder.DirectDecompress;
023import java.io.IOException;
024import java.nio.ByteBuffer;
025import org.apache.hadoop.hbase.io.compress.CompressionUtil;
026import org.apache.hadoop.io.compress.Decompressor;
027import org.apache.yetus.audience.InterfaceAudience;
028
029/**
030 * Hadoop decompressor glue for Brotli4j
031 */
032@InterfaceAudience.Private
033public class BrotliDecompressor implements Decompressor {
034
035  protected ByteBuffer inBuf, outBuf;
036  protected int inLen;
037  protected boolean finished;
038
039  static {
040    Brotli4jLoader.ensureAvailability();
041  }
042
043  BrotliDecompressor(int bufferSize) {
044    this.inBuf = ByteBuffer.allocate(bufferSize);
045    this.outBuf = ByteBuffer.allocate(bufferSize);
046    this.outBuf.position(bufferSize);
047  }
048
049  @Override
050  public int decompress(byte[] b, int off, int len) throws IOException {
051    if (outBuf.hasRemaining()) {
052      int remaining = outBuf.remaining(), n = Math.min(remaining, len);
053      outBuf.get(b, off, n);
054      return n;
055    }
056    if (inBuf.position() > 0) {
057      inBuf.flip();
058      int remaining = inBuf.remaining();
059      inLen -= remaining;
060      outBuf.rewind();
061      outBuf.limit(outBuf.capacity());
062      // TODO: More inefficient than it could be, but it doesn't impact decompression speed
063      // terribly and the brotli4j API alternatives do not seem to work correctly.
064      // Maybe something more clever can be done as a future improvement.
065      final byte[] inb = new byte[remaining];
066      inBuf.get(inb);
067      DirectDecompress result = Decoder.decompress(inb);
068      outBuf.put(result.getDecompressedDataByteBuf().nioBuffer());
069      final int written = outBuf.position();
070      inBuf.rewind();
071      inBuf.limit(inBuf.capacity());
072      outBuf.flip();
073      int n = Math.min(written, len);
074      outBuf.get(b, off, n);
075      return n;
076    }
077    finished = true;
078    return 0;
079  }
080
081  @Override
082  public void end() {
083  }
084
085  @Override
086  public boolean finished() {
087    return finished;
088  }
089
090  @Override
091  public int getRemaining() {
092    return inLen;
093  }
094
095  @Override
096  public boolean needsDictionary() {
097    return false;
098  }
099
100  @Override
101  public void reset() {
102    inBuf.clear();
103    inLen = 0;
104    outBuf.clear();
105    outBuf.position(outBuf.capacity());
106    finished = false;
107  }
108
109  @Override
110  public boolean needsInput() {
111    return inBuf.position() == 0;
112  }
113
114  @Override
115  public void setDictionary(byte[] b, int off, int len) {
116    throw new UnsupportedOperationException("setDictionary is not supported");
117  }
118
119  @Override
120  public void setInput(byte[] b, int off, int len) {
121    if (inBuf.remaining() < len) {
122      // Get a new buffer that can accomodate the accumulated input plus the additional
123      // input that would cause a buffer overflow without reallocation.
124      // This condition should be fortunately rare, because it is expensive.
125      int needed = CompressionUtil.roundInt2(inBuf.capacity() + len);
126      ByteBuffer newBuf = ByteBuffer.allocate(needed);
127      inBuf.flip();
128      newBuf.put(inBuf);
129      inBuf = newBuf;
130    }
131    inBuf.put(b, off, len);
132    inLen += len;
133    finished = false;
134  }
135
136}