Home | History | Annotate | Download | only in enc
      1 /* Copyright 2017 Google Inc. All Rights Reserved.
      2 
      3    Distributed under MIT license.
      4    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
      5 */
      6 
      7 package org.brotli.wrapper.enc;
      8 
      9 import java.io.IOException;
     10 import java.nio.Buffer;
     11 import java.nio.ByteBuffer;
     12 import java.nio.channels.WritableByteChannel;
     13 import java.util.ArrayList;
     14 
     15 /**
     16  * Base class for OutputStream / Channel implementations.
     17  */
     18 public class Encoder {
     19   private final WritableByteChannel destination;
     20   private final EncoderJNI.Wrapper encoder;
     21   private ByteBuffer buffer;
     22   final ByteBuffer inputBuffer;
     23   boolean closed;
     24 
     25   /**
     26    * Brotli encoder settings.
     27    */
     28   public static final class Parameters {
     29     private int quality = -1;
     30     private int lgwin = -1;
     31 
     32     public Parameters() { }
     33 
     34     private Parameters(Parameters other) {
     35       this.quality = other.quality;
     36       this.lgwin = other.lgwin;
     37     }
     38 
     39     /**
     40      * @param quality compression quality, or -1 for default
     41      */
     42     public Parameters setQuality(int quality) {
     43       if (quality < -1 || quality > 11) {
     44         throw new IllegalArgumentException("quality should be in range [0, 11], or -1");
     45       }
     46       this.quality = quality;
     47       return this;
     48     }
     49 
     50     /**
     51      * @param lgwin log2(LZ window size), or -1 for default
     52      */
     53     public Parameters setWindow(int lgwin) {
     54       if ((lgwin != -1) && ((lgwin < 10) || (lgwin > 24))) {
     55         throw new IllegalArgumentException("lgwin should be in range [10, 24], or -1");
     56       }
     57       this.lgwin = lgwin;
     58       return this;
     59     }
     60   }
     61 
     62   /**
     63    * Creates a Encoder wrapper.
     64    *
     65    * @param destination underlying destination
     66    * @param params encoding parameters
     67    * @param inputBufferSize read buffer size
     68    */
     69   Encoder(WritableByteChannel destination, Parameters params, int inputBufferSize)
     70       throws IOException {
     71     if (inputBufferSize <= 0) {
     72       throw new IllegalArgumentException("buffer size must be positive");
     73     }
     74     if (destination == null) {
     75       throw new NullPointerException("destination can not be null");
     76     }
     77     this.destination = destination;
     78     this.encoder = new EncoderJNI.Wrapper(inputBufferSize, params.quality, params.lgwin);
     79     this.inputBuffer = this.encoder.getInputBuffer();
     80   }
     81 
     82   private void fail(String message) throws IOException {
     83     try {
     84       close();
     85     } catch (IOException ex) {
     86       /* Ignore */
     87     }
     88     throw new IOException(message);
     89   }
     90 
     91   /**
     92    * @param force repeat pushing until all output is consumed
     93    * @return true if all encoder output is consumed
     94    */
     95   boolean pushOutput(boolean force) throws IOException {
     96     while (buffer != null) {
     97       if (buffer.hasRemaining()) {
     98         destination.write(buffer);
     99       }
    100       if (!buffer.hasRemaining()) {
    101         buffer = null;
    102       } else if (!force) {
    103         return false;
    104       }
    105     }
    106     return true;
    107   }
    108 
    109   /**
    110    * @return true if there is space in inputBuffer.
    111    */
    112   boolean encode(EncoderJNI.Operation op) throws IOException {
    113     boolean force = (op != EncoderJNI.Operation.PROCESS);
    114     if (force) {
    115       ((Buffer) inputBuffer).limit(inputBuffer.position());
    116     } else if (inputBuffer.hasRemaining()) {
    117       return true;
    118     }
    119     boolean hasInput = true;
    120     while (true) {
    121       if (!encoder.isSuccess()) {
    122         fail("encoding failed");
    123       } else if (!pushOutput(force)) {
    124         return false;
    125       } else if (encoder.hasMoreOutput()) {
    126         buffer = encoder.pull();
    127       } else if (encoder.hasRemainingInput()) {
    128         encoder.push(op, 0);
    129       } else if (hasInput) {
    130         encoder.push(op, inputBuffer.limit());
    131         hasInput = false;
    132       } else {
    133         ((Buffer) inputBuffer).clear();
    134         return true;
    135       }
    136     }
    137   }
    138 
    139   void flush() throws IOException {
    140     encode(EncoderJNI.Operation.FLUSH);
    141   }
    142 
    143   void close() throws IOException {
    144     if (closed) {
    145       return;
    146     }
    147     closed = true;
    148     try {
    149       encode(EncoderJNI.Operation.FINISH);
    150     } finally {
    151       encoder.destroy();
    152       destination.close();
    153     }
    154   }
    155 
    156   /**
    157    * Encodes the given data buffer.
    158    */
    159   public static byte[] compress(byte[] data, Parameters params) throws IOException {
    160     if (data.length == 0) {
    161       byte[] empty = new byte[1];
    162       empty[0] = 6;
    163       return empty;
    164     }
    165     /* data.length > 0 */
    166     EncoderJNI.Wrapper encoder = new EncoderJNI.Wrapper(data.length, params.quality, params.lgwin);
    167     ArrayList<byte[]> output = new ArrayList<byte[]>();
    168     int totalOutputSize = 0;
    169     try {
    170       encoder.getInputBuffer().put(data);
    171       encoder.push(EncoderJNI.Operation.FINISH, data.length);
    172       while (true) {
    173         if (!encoder.isSuccess()) {
    174           throw new IOException("encoding failed");
    175         } else if (encoder.hasMoreOutput()) {
    176           ByteBuffer buffer = encoder.pull();
    177           byte[] chunk = new byte[buffer.remaining()];
    178           buffer.get(chunk);
    179           output.add(chunk);
    180           totalOutputSize += chunk.length;
    181         } else if (!encoder.isFinished()) {
    182           encoder.push(EncoderJNI.Operation.FINISH, 0);
    183         } else {
    184           break;
    185         }
    186       }
    187     } finally {
    188       encoder.destroy();
    189     }
    190     if (output.size() == 1) {
    191       return output.get(0);
    192     }
    193     byte[] result = new byte[totalOutputSize];
    194     int offset = 0;
    195     for (byte[] chunk : output) {
    196       System.arraycopy(chunk, 0, result, offset, chunk.length);
    197       offset += chunk.length;
    198     }
    199     return result;
    200   }
    201 
    202   public static byte[] compress(byte[] data) throws IOException {
    203     return compress(data, new Parameters());
    204   }
    205 }
    206