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.dec; 8 9 import java.io.IOException; 10 import java.nio.ByteBuffer; 11 import java.nio.channels.ReadableByteChannel; 12 import java.util.ArrayList; 13 14 /** 15 * Base class for InputStream / Channel implementations. 16 */ 17 public class Decoder { 18 private final ReadableByteChannel source; 19 private final DecoderJNI.Wrapper decoder; 20 ByteBuffer buffer; 21 boolean closed; 22 23 /** 24 * Creates a Decoder wrapper. 25 * 26 * @param source underlying source 27 * @param inputBufferSize read buffer size 28 */ 29 public Decoder(ReadableByteChannel source, int inputBufferSize) 30 throws IOException { 31 if (inputBufferSize <= 0) { 32 throw new IllegalArgumentException("buffer size must be positive"); 33 } 34 if (source == null) { 35 throw new NullPointerException("source can not be null"); 36 } 37 this.source = source; 38 this.decoder = new DecoderJNI.Wrapper(inputBufferSize); 39 } 40 41 private void fail(String message) throws IOException { 42 try { 43 close(); 44 } catch (IOException ex) { 45 /* Ignore */ 46 } 47 throw new IOException(message); 48 } 49 50 /** 51 * Continue decoding. 52 * 53 * @return -1 if stream is finished, or number of bytes available in read buffer (> 0) 54 */ 55 int decode() throws IOException { 56 while (true) { 57 if (buffer != null) { 58 if (!buffer.hasRemaining()) { 59 buffer = null; 60 } else { 61 return buffer.remaining(); 62 } 63 } 64 65 switch (decoder.getStatus()) { 66 case DONE: 67 return -1; 68 69 case OK: 70 decoder.push(0); 71 break; 72 73 case NEEDS_MORE_INPUT: 74 ByteBuffer inputBuffer = decoder.getInputBuffer(); 75 inputBuffer.clear(); 76 int bytesRead = source.read(inputBuffer); 77 if (bytesRead == -1) { 78 fail("unexpected end of input"); 79 } 80 decoder.push(bytesRead); 81 break; 82 83 case NEEDS_MORE_OUTPUT: 84 buffer = decoder.pull(); 85 break; 86 87 default: 88 fail("corrupted input"); 89 } 90 } 91 } 92 93 void discard(int length) { 94 buffer.position(buffer.position() + length); 95 if (!buffer.hasRemaining()) { 96 buffer = null; 97 } 98 } 99 100 int consume(ByteBuffer dst) { 101 ByteBuffer slice = buffer.slice(); 102 int limit = Math.min(slice.remaining(), dst.remaining()); 103 slice.limit(limit); 104 dst.put(slice); 105 discard(limit); 106 return limit; 107 } 108 109 void close() throws IOException { 110 if (closed) { 111 return; 112 } 113 closed = true; 114 decoder.destroy(); 115 source.close(); 116 } 117 118 /** 119 * Decodes the given data buffer. 120 */ 121 public static byte[] decompress(byte[] data) throws IOException { 122 DecoderJNI.Wrapper decoder = new DecoderJNI.Wrapper(data.length); 123 ArrayList<byte[]> output = new ArrayList<byte[]>(); 124 int totalOutputSize = 0; 125 try { 126 decoder.getInputBuffer().put(data); 127 decoder.push(data.length); 128 while (decoder.getStatus() != DecoderJNI.Status.DONE) { 129 switch (decoder.getStatus()) { 130 case OK: 131 decoder.push(0); 132 break; 133 134 case NEEDS_MORE_OUTPUT: 135 ByteBuffer buffer = decoder.pull(); 136 byte[] chunk = new byte[buffer.remaining()]; 137 buffer.get(chunk); 138 output.add(chunk); 139 totalOutputSize += chunk.length; 140 break; 141 142 default: 143 throw new IOException("corrupted input"); 144 } 145 } 146 } finally { 147 decoder.destroy(); 148 } 149 if (output.size() == 1) { 150 return output.get(0); 151 } 152 byte[] result = new byte[totalOutputSize]; 153 int offset = 0; 154 for (byte[] chunk : output) { 155 System.arraycopy(chunk, 0, result, offset, chunk.length); 156 offset += chunk.length; 157 } 158 return result; 159 } 160 } 161