| /* |
| * 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.commons.sanselan.common.mylzw; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| public class MyLzwCompressor |
| { |
| |
| // private static final int MAX_TABLE_SIZE = 1 << 12; |
| |
| private int codeSize; |
| private final int initialCodeSize; |
| private int codes = -1; |
| |
| private final int byteOrder; |
| private final boolean earlyLimit; |
| private final int clearCode; |
| private final int eoiCode; |
| private final Listener listener; |
| |
| public MyLzwCompressor(int initialCodeSize, int byteOrder, |
| boolean earlyLimit) |
| { |
| this(initialCodeSize, byteOrder, earlyLimit, null); |
| } |
| |
| public MyLzwCompressor(int initialCodeSize, int byteOrder, |
| boolean earlyLimit, Listener listener) |
| { |
| this.listener = listener; |
| this.byteOrder = byteOrder; |
| this.earlyLimit = earlyLimit; |
| |
| this.initialCodeSize = initialCodeSize; |
| |
| clearCode = 1 << initialCodeSize; |
| eoiCode = clearCode + 1; |
| |
| if (null != listener) |
| listener.init(clearCode, eoiCode); |
| |
| InitializeStringTable(); |
| } |
| |
| private final Map<Object, Integer> map = new HashMap<Object, Integer>(); |
| |
| private final void InitializeStringTable() |
| { |
| codeSize = initialCodeSize; |
| |
| int intial_entries_count = (1 << codeSize) + 2; |
| |
| map.clear(); |
| for (codes = 0; codes < intial_entries_count; codes++) |
| { |
| if ((codes != clearCode) && (codes != eoiCode)) |
| { |
| Object key = arrayToKey((byte) codes); |
| |
| map.put(key, codes); |
| } |
| } |
| } |
| |
| private final void clearTable() |
| { |
| InitializeStringTable(); |
| incrementCodeSize(); |
| } |
| |
| private final void incrementCodeSize() |
| { |
| if (codeSize != 12) |
| codeSize++; |
| } |
| |
| private final Object arrayToKey(byte b) |
| { |
| return arrayToKey(new byte[] { b, }, 0, 1); |
| } |
| |
| private final static class ByteArray |
| { |
| private final byte bytes[]; |
| private final int start; |
| private final int length; |
| private final int hash; |
| |
| public ByteArray(byte bytes[]) |
| { |
| this(bytes, 0, bytes.length); |
| } |
| |
| public ByteArray(byte bytes[], int start, int length) |
| { |
| this.bytes = bytes; |
| this.start = start; |
| this.length = length; |
| |
| int tempHash = length; |
| |
| for (int i = 0; i < length; i++) |
| { |
| int b = 0xff & bytes[i + start]; |
| tempHash = tempHash + (tempHash << 8) ^ b ^ i; |
| } |
| |
| hash = tempHash; |
| } |
| |
| public final int hashCode() |
| { |
| return hash; |
| } |
| |
| public final boolean equals(Object o) |
| { |
| if (o instanceof ByteArray) { |
| ByteArray other = (ByteArray) o; |
| if (other.hash != hash) |
| return false; |
| if (other.length != length) |
| return false; |
| |
| for (int i = 0; i < length; i++) |
| { |
| if (other.bytes[i + other.start] != bytes[i + start]) |
| return false; |
| } |
| |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| private final Object arrayToKey(byte bytes[], int start, int length) |
| { |
| return new ByteArray(bytes, start, length); |
| } |
| |
| private final void writeDataCode(MyBitOutputStream bos, int code) |
| throws IOException |
| { |
| if (null != listener) |
| listener.dataCode(code); |
| writeCode(bos, code); |
| } |
| |
| |
| private final void writeClearCode(MyBitOutputStream bos) throws IOException |
| { |
| if (null != listener) |
| listener.dataCode(clearCode); |
| writeCode(bos, clearCode); |
| } |
| |
| private final void writeEoiCode(MyBitOutputStream bos) throws IOException |
| { |
| if (null != listener) |
| listener.eoiCode(eoiCode); |
| writeCode(bos, eoiCode); |
| } |
| |
| private final void writeCode(MyBitOutputStream bos, int code) |
| throws IOException |
| { |
| bos.writeBits(code, codeSize); |
| } |
| |
| private final boolean isInTable(byte bytes[], int start, int length) |
| { |
| Object key = arrayToKey(bytes, start, length); |
| |
| return map.containsKey(key); |
| } |
| |
| private final int codeFromString(byte bytes[], int start, int length) |
| throws IOException |
| { |
| Object key = arrayToKey(bytes, start, length); |
| Object o = map.get(key); |
| if (o == null) |
| throw new IOException("CodeFromString"); |
| return ((Integer) o).intValue(); |
| } |
| |
| private final boolean addTableEntry(MyBitOutputStream bos, byte bytes[], |
| int start, int length) throws IOException |
| { |
| Object key = arrayToKey(bytes, start, length); |
| return addTableEntry(bos, key); |
| } |
| |
| private final boolean addTableEntry(MyBitOutputStream bos, Object key) |
| throws IOException |
| { |
| boolean cleared = false; |
| |
| { |
| int limit = (1 << codeSize); |
| if (earlyLimit) |
| limit--; |
| |
| if (codes == limit) |
| { |
| if (codeSize < 12) |
| incrementCodeSize(); |
| else |
| { |
| writeClearCode(bos); |
| clearTable(); |
| cleared = true; |
| } |
| } |
| } |
| |
| if (!cleared) |
| { |
| map.put(key, codes); |
| codes++; |
| } |
| |
| return cleared; |
| } |
| |
| public static interface Listener |
| { |
| public void dataCode(int code); |
| |
| public void eoiCode(int code); |
| |
| public void clearCode(int code); |
| |
| public void init(int clearCode, int eoiCode); |
| } |
| |
| public byte[] compress(byte bytes[]) throws IOException |
| { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length); |
| MyBitOutputStream bos = new MyBitOutputStream(baos, byteOrder); |
| |
| InitializeStringTable(); |
| clearTable(); |
| writeClearCode(bos); |
| boolean cleared = false; |
| |
| int w_start = 0; |
| int w_length = 0; |
| |
| for (int i = 0; i < bytes.length; i++) |
| { |
| if (isInTable(bytes, w_start, w_length + 1)) |
| { |
| w_length++; |
| |
| cleared = false; |
| } else |
| { |
| int code = codeFromString(bytes, w_start, w_length); |
| writeDataCode(bos, code); |
| cleared = addTableEntry(bos, bytes, w_start, w_length + 1); |
| |
| w_start = i; |
| w_length = 1; |
| } |
| } /* end of for loop */ |
| |
| int code = codeFromString(bytes, w_start, w_length); |
| writeDataCode(bos, code); |
| |
| writeEoiCode(bos); |
| |
| bos.flushCache(); |
| |
| return baos.toByteArray(); |
| } |
| } |