-/* ====================================================================\r
- Licensed to the Apache Software Foundation (ASF) under one or more\r
- contributor license agreements. See the NOTICE file distributed with\r
- this work for additional information regarding copyright ownership.\r
- The ASF licenses this file to You under the Apache License, Version 2.0\r
- (the "License"); you may not use this file except in compliance with\r
- the License. You may obtain a copy of the License at\r
-\r
- http://www.apache.org/licenses/LICENSE-2.0\r
-\r
- Unless required by applicable law or agreed to in writing, software\r
- distributed under the License is distributed on an "AS IS" BASIS,\r
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- See the License for the specific language governing permissions and\r
- limitations under the License.\r
-==================================================================== */\r
-\r
-package org.apache.poi.util;\r
-\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-\r
-/**\r
- * Wrapper of InputStream which provides Run Length Encoding (RLE) \r
- * decompression on the fly. Uses MS-OVBA decompression algorithm. See\r
- * http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/[MS-OVBA].pdf\r
- */\r
-public class RLEDecompressingInputStream extends InputStream {\r
-\r
- /**\r
- * Bitmasks for performance\r
- */\r
- private static final int[] POWER2 = new int[] { 0x0001, // 0\r
- 0x0002, // 1\r
- 0x0004, // 2\r
- 0x0008, // 3\r
- 0x0010, // 4\r
- 0x0020, // 5\r
- 0x0040, // 6\r
- 0x0080, // 7\r
- 0x0100, // 8\r
- 0x0200, // 9\r
- 0x0400, // 10\r
- 0x0800, // 11\r
- 0x1000, // 12\r
- 0x2000, // 13\r
- 0x4000, // 14\r
- 0x8000 // 15\r
- };\r
-\r
- /** the wrapped inputstream */\r
- private InputStream in;\r
-\r
- /** a byte buffer with size 4096 for storing a single chunk */\r
- private byte[] buf;\r
-\r
- /** the current position in the byte buffer for reading */\r
- private int pos;\r
-\r
- /** the number of bytes in the byte buffer */\r
- private int len;\r
-\r
- /**\r
- * Creates a new wrapper RLE Decompression InputStream.\r
- * \r
- * @param in\r
- * @throws IOException\r
- */\r
- public RLEDecompressingInputStream(InputStream in) throws IOException {\r
- this.in = in;\r
- buf = new byte[4096];\r
- pos = 0;\r
- int header = in.read();\r
- if (header != 0x01) {\r
- throw new IllegalArgumentException(String.format("Header byte 0x01 expected, received 0x%02X", header & 0xFF));\r
- }\r
- len = readChunk();\r
- }\r
-\r
- @Override\r
- public int read() throws IOException {\r
- if (len == -1) {\r
- return -1;\r
- }\r
- if (pos >= len) {\r
- if ((len = readChunk()) == -1) {\r
- return -1;\r
- }\r
- }\r
- return buf[pos++];\r
- }\r
-\r
- @Override\r
- public int read(byte[] b) throws IOException {\r
- return read(b, 0, b.length);\r
- }\r
-\r
- @Override\r
- public int read(byte[] b, int off, int l) throws IOException {\r
- if (len == -1) {\r
- return -1;\r
- }\r
- int offset = off;\r
- int length = l;\r
- while (length > 0) {\r
- if (pos >= len) {\r
- if ((len = readChunk()) == -1) {\r
- return offset > off ? offset - off : -1;\r
- }\r
- }\r
- int c = Math.min(length, len - pos);\r
- System.arraycopy(buf, pos, b, offset, c);\r
- pos += c;\r
- length -= c;\r
- offset += c;\r
- }\r
- return l;\r
- }\r
-\r
- @Override\r
- public long skip(long n) throws IOException {\r
- long length = n;\r
- while (length > 0) {\r
- if (pos >= len) {\r
- if ((len = readChunk()) == -1) {\r
- return -1;\r
- }\r
- }\r
- int c = (int) Math.min(n, len - pos);\r
- pos += c;\r
- length -= c;\r
- }\r
- return n;\r
- }\r
-\r
- @Override\r
- public int available() {\r
- return (len > 0 ? len - pos : 0);\r
- }\r
-\r
- @Override\r
- public void close() throws IOException {\r
- in.close();\r
- }\r
-\r
- /**\r
- * Reads a single chunk from the underlying inputstream.\r
- * \r
- * @return\r
- * @throws IOException\r
- */\r
- private int readChunk() throws IOException {\r
- pos = 0;\r
- int w = readShort(in);\r
- if (w == -1) {\r
- return -1;\r
- }\r
- int chunkSize = (w & 0x0FFF) + 1; // plus 3 bytes minus 2 for the length\r
- if ((w & 0x7000) != 0x3000) {\r
- throw new IllegalArgumentException(String.format("Chunksize header A should be 0x3000, received 0x%04X", w & 0xE000));\r
- }\r
- boolean rawChunk = (w & 0x8000) == 0;\r
- if (rawChunk) {\r
- if (in.read(buf, 0, chunkSize) < chunkSize) {\r
- throw new IllegalStateException(String.format("Not enough bytes read, expected %d", chunkSize));\r
- }\r
- return chunkSize;\r
- } else {\r
- int inOffset = 0;\r
- int outOffset = 0;\r
- while (inOffset < chunkSize) {\r
- int tokenFlags = in.read();\r
- inOffset++;\r
- if (tokenFlags == -1) {\r
- break;\r
- }\r
- for (int n = 0; n < 8; n++) {\r
- if (inOffset >= chunkSize) {\r
- break;\r
- }\r
- if ((tokenFlags & POWER2[n]) == 0) {\r
- // literal\r
- final int b = in.read();\r
- if (b == -1) {\r
- return -1;\r
- }\r
- buf[outOffset++] = (byte) b;\r
- inOffset++;\r
- } else {\r
- // compressed token\r
- int token = readShort(in);\r
- if (token == -1) {\r
- return -1;\r
- }\r
- inOffset += 2;\r
- int copyLenBits = getCopyLenBits(outOffset - 1);\r
- int copyOffset = (token >> (copyLenBits)) + 1;\r
- int copyLen = (token & (POWER2[copyLenBits] - 1)) + 3;\r
- int startPos = outOffset - copyOffset;\r
- int endPos = startPos + copyLen;\r
- for (int i = startPos; i < endPos; i++) {\r
- buf[outOffset++] = buf[i];\r
- }\r
- }\r
- }\r
- }\r
- return outOffset;\r
- }\r
- }\r
-\r
- /**\r
- * Helper method to determine how many bits in the CopyToken are used for the CopyLength.\r
- * \r
- * @param offset\r
- * @return\r
- */\r
- static int getCopyLenBits(int offset) {\r
- for (int n = 11; n >= 4; n--) {\r
- if ((offset & POWER2[n]) != 0) {\r
- return 15 - n;\r
- }\r
- }\r
- return 12;\r
- }\r
-\r
- /**\r
- * Convenience method for read a 2-bytes short in little endian encoding.\r
- * \r
- * @return\r
- * @throws IOException\r
- */\r
- public int readShort() throws IOException {\r
- return readShort(this);\r
- }\r
-\r
- /**\r
- * Convenience method for read a 4-bytes int in little endian encoding.\r
- * \r
- * @return\r
- * @throws IOException\r
- */\r
- public int readInt() throws IOException {\r
- return readInt(this);\r
- }\r
-\r
- private int readShort(InputStream stream) throws IOException {\r
- int b0, b1;\r
- if ((b0 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- if ((b1 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- return (b0 & 0xFF) | ((b1 & 0xFF) << 8);\r
- }\r
-\r
- private int readInt(InputStream stream) throws IOException {\r
- int b0, b1, b2, b3;\r
- if ((b0 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- if ((b1 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- if ((b2 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- if ((b3 = stream.read()) == -1) {\r
- return -1;\r
- }\r
- return (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);\r
- }\r
-}\r
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Wrapper of InputStream which provides Run Length Encoding (RLE)
+ * decompression on the fly. Uses MS-OVBA decompression algorithm. See
+ * http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/[MS-OVBA].pdf
+ */
+public class RLEDecompressingInputStream extends InputStream {
+
+ /**
+ * Bitmasks for performance
+ */
+ private static final int[] POWER2 = new int[] { 0x0001, // 0
+ 0x0002, // 1
+ 0x0004, // 2
+ 0x0008, // 3
+ 0x0010, // 4
+ 0x0020, // 5
+ 0x0040, // 6
+ 0x0080, // 7
+ 0x0100, // 8
+ 0x0200, // 9
+ 0x0400, // 10
+ 0x0800, // 11
+ 0x1000, // 12
+ 0x2000, // 13
+ 0x4000, // 14
+ 0x8000 // 15
+ };
+
+ /** the wrapped inputstream */
+ private InputStream in;
+
+ /** a byte buffer with size 4096 for storing a single chunk */
+ private byte[] buf;
+
+ /** the current position in the byte buffer for reading */
+ private int pos;
+
+ /** the number of bytes in the byte buffer */
+ private int len;
+
+ /**
+ * Creates a new wrapper RLE Decompression InputStream.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public RLEDecompressingInputStream(InputStream in) throws IOException {
+ this.in = in;
+ buf = new byte[4096];
+ pos = 0;
+ int header = in.read();
+ if (header != 0x01) {
+ throw new IllegalArgumentException(String.format("Header byte 0x01 expected, received 0x%02X", header & 0xFF));
+ }
+ len = readChunk();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (len == -1) {
+ return -1;
+ }
+ if (pos >= len) {
+ if ((len = readChunk()) == -1) {
+ return -1;
+ }
+ }
+ return buf[pos++];
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int l) throws IOException {
+ if (len == -1) {
+ return -1;
+ }
+ int offset = off;
+ int length = l;
+ while (length > 0) {
+ if (pos >= len) {
+ if ((len = readChunk()) == -1) {
+ return offset > off ? offset - off : -1;
+ }
+ }
+ int c = Math.min(length, len - pos);
+ System.arraycopy(buf, pos, b, offset, c);
+ pos += c;
+ length -= c;
+ offset += c;
+ }
+ return l;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ long length = n;
+ while (length > 0) {
+ if (pos >= len) {
+ if ((len = readChunk()) == -1) {
+ return -1;
+ }
+ }
+ int c = (int) Math.min(n, len - pos);
+ pos += c;
+ length -= c;
+ }
+ return n;
+ }
+
+ @Override
+ public int available() {
+ return (len > 0 ? len - pos : 0);
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+
+ /**
+ * Reads a single chunk from the underlying inputstream.
+ *
+ * @return
+ * @throws IOException
+ */
+ private int readChunk() throws IOException {
+ pos = 0;
+ int w = readShort(in);
+ if (w == -1) {
+ return -1;
+ }
+ int chunkSize = (w & 0x0FFF) + 1; // plus 3 bytes minus 2 for the length
+ if ((w & 0x7000) != 0x3000) {
+ throw new IllegalArgumentException(String.format("Chunksize header A should be 0x3000, received 0x%04X", w & 0xE000));
+ }
+ boolean rawChunk = (w & 0x8000) == 0;
+ if (rawChunk) {
+ if (in.read(buf, 0, chunkSize) < chunkSize) {
+ throw new IllegalStateException(String.format("Not enough bytes read, expected %d", chunkSize));
+ }
+ return chunkSize;
+ } else {
+ int inOffset = 0;
+ int outOffset = 0;
+ while (inOffset < chunkSize) {
+ int tokenFlags = in.read();
+ inOffset++;
+ if (tokenFlags == -1) {
+ break;
+ }
+ for (int n = 0; n < 8; n++) {
+ if (inOffset >= chunkSize) {
+ break;
+ }
+ if ((tokenFlags & POWER2[n]) == 0) {
+ // literal
+ final int b = in.read();
+ if (b == -1) {
+ return -1;
+ }
+ buf[outOffset++] = (byte) b;
+ inOffset++;
+ } else {
+ // compressed token
+ int token = readShort(in);
+ if (token == -1) {
+ return -1;
+ }
+ inOffset += 2;
+ int copyLenBits = getCopyLenBits(outOffset - 1);
+ int copyOffset = (token >> (copyLenBits)) + 1;
+ int copyLen = (token & (POWER2[copyLenBits] - 1)) + 3;
+ int startPos = outOffset - copyOffset;
+ int endPos = startPos + copyLen;
+ for (int i = startPos; i < endPos; i++) {
+ buf[outOffset++] = buf[i];
+ }
+ }
+ }
+ }
+ return outOffset;
+ }
+ }
+
+ /**
+ * Helper method to determine how many bits in the CopyToken are used for the CopyLength.
+ *
+ * @param offset
+ * @return
+ */
+ static int getCopyLenBits(int offset) {
+ for (int n = 11; n >= 4; n--) {
+ if ((offset & POWER2[n]) != 0) {
+ return 15 - n;
+ }
+ }
+ return 12;
+ }
+
+ /**
+ * Convenience method for read a 2-bytes short in little endian encoding.
+ *
+ * @return
+ * @throws IOException
+ */
+ public int readShort() throws IOException {
+ return readShort(this);
+ }
+
+ /**
+ * Convenience method for read a 4-bytes int in little endian encoding.
+ *
+ * @return
+ * @throws IOException
+ */
+ public int readInt() throws IOException {
+ return readInt(this);
+ }
+
+ private int readShort(InputStream stream) throws IOException {
+ int b0, b1;
+ if ((b0 = stream.read()) == -1) {
+ return -1;
+ }
+ if ((b1 = stream.read()) == -1) {
+ return -1;
+ }
+ return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
+ }
+
+ private int readInt(InputStream stream) throws IOException {
+ int b0, b1, b2, b3;
+ if ((b0 = stream.read()) == -1) {
+ return -1;
+ }
+ if ((b1 = stream.read()) == -1) {
+ return -1;
+ }
+ if ((b2 = stream.read()) == -1) {
+ return -1;
+ }
+ if ((b3 = stream.read()) == -1) {
+ return -1;
+ }
+ return (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);
+ }
+}