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