• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Parameters()32     public Parameters() { }
33 
Parameters(Parameters other)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      */
setQuality(int quality)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      */
setWindow(int lgwin)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    */
Encoder(WritableByteChannel destination, Parameters params, int inputBufferSize)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 
fail(String message)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    */
pushOutput(boolean force)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    */
encode(EncoderJNI.Operation op)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 
flush()139   void flush() throws IOException {
140     encode(EncoderJNI.Operation.FLUSH);
141   }
142 
close()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    */
compress(byte[] data, Parameters params)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 
compress(byte[] data)202   public static byte[] compress(byte[] data) throws IOException {
203     return compress(data, new Parameters());
204   }
205 }
206