/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2016, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.image.io.plugin; import java.util.Arrays; import java.util.HashMap; import org.apache.sis.util.ArgumentChecks; /** * Map adapted for LZW compression use. * * @author Remi Marechal (Geomatys). */ class LZWMap extends HashMap<byte[], Short> { /** * {@code byte[]} array use to store key. */ private final byte[][] keys; /** * {@code short[]} array use to store LZW key associate code. */ private final short[][] values; /** * Maximum key and value array length. */ private final static int PRIME_NUMBER = 5407; /** * Default value array length. */ private final static int DEFAULTVALUES_LENGTH = 20; LZWMap() { keys = new byte[PRIME_NUMBER][]; values = new short[PRIME_NUMBER][]; } /** * Return true if container start with element byte suite else false.<br/><br/> * * @param container * @param element * @return */ private boolean containAtBegin(final byte[] container, final byte[] element) { final int eltLength = element.length; if (eltLength > container.length) return false; // throw new IllegalArgumentException("element byte array should have length lesser than container length. element length = "+eltLength+" container length = "+container.length); for (int i = 0; i < element.length; i++) { if (element[i] != container[i]) return false; } return true; } /** * Put code in relation with key at expected position. * * @param position * @param eltLength * @param code */ private void putCode(final int position, final int eltLength, final short code) { if (values[position] == null) { values[position] = new short[((eltLength + DEFAULTVALUES_LENGTH - 1) / DEFAULTVALUES_LENGTH) * DEFAULTVALUES_LENGTH]; } else if (values[position].length <= eltLength - 2) { values[position] = Arrays.copyOf(values[position], values[position].length << 1); } values[position][eltLength - 2] = code; } /** * Add * * @param key * @param value * @return */ @Override public Short put(final byte[] key, final Short value) { ArgumentChecks.ensureNonNull("key ", key); //-- hash code creation, constitute by the first pair of byte from key. --// final int hash = (((key[0] & 0xFF) << Byte.SIZE) | (key[1] & 0xFF)); int arrayPos = hash % PRIME_NUMBER; assert arrayPos >= 0; while (true) { if (keys[arrayPos] == null) { //-- add directly --// keys[arrayPos] = key; putCode(arrayPos, key.length, value); break; } else { // 2 case final byte[] precKey = keys[arrayPos]; if (containAtBegin(precKey, key)) { //-- if key is already contained --// break; } else if (containAtBegin(key, precKey)) { keys[arrayPos] = key; putCode(arrayPos, key.length, value); break; } else { arrayPos++; if (arrayPos == PRIME_NUMBER) { assert assertArrayConform() : "keys array must contain at least null value"; arrayPos = 0; } } } } return 0; } /** * Return {@code true} if key as already been put else {@code false}. * * @param key * @return {@code true} if key as already been put else {@code false}. */ @Override public boolean containsKey(Object key) { ArgumentChecks.ensureNonNull("key ", key); if (!(key instanceof byte[])) throw new IllegalArgumentException("key must be instance of byte[]"); final byte[] ki = (byte[]) key; //-- hash code creation, constitute by the first pair of byte from key. --// final int hash = (((ki[0] & 0xFF) << Byte.SIZE) | (ki[1] & 0xFF)); int arrayPos = hash % PRIME_NUMBER; assert arrayPos >= 0 : "expected : >= 0 found : "+(hash % PRIME_NUMBER)+" hash code : "+hash; while (keys[arrayPos] != null) { if (containAtBegin(keys[arrayPos], ki)) return true; arrayPos++; if (arrayPos == PRIME_NUMBER) { assert assertArrayConform() : "keys array must contain at least null value"; arrayPos = 0; } } return false; } /** * Return {@code true} if {@linkplain #keys} array contain at least one {@code null} value else return {@code false}.<br/> * Normaly should never return {@code false}. * * @return {@code true} if {@linkplain #keys} array contain at least one {@code null} value else return {@code false}. */ private boolean assertArrayConform() { final int keysLength = keys.length; for (int i = 0; i < keysLength; i++) { if (keys[i] == null) return true; } return false; } /** * Return LZW code in relation with {@code byte[]} key if it is found * else return 256 which is a reserved code. * * @param key * @return short value which is LZW code in relation with specified key. * @throws {@link NullArgumentException} if key is {@code null}. * @throws {@link IllegalArgumentException} if key is not instance of {@code byte[]}. */ @Override public Short get(Object key) { ArgumentChecks.ensureNonNull("key ", key); if (!(key instanceof byte[])) throw new IllegalArgumentException("key must be instance of byte[]"); final byte[] ki = (byte[]) key; //-- hash code creation, constitute by the first pair of byte from key. --// final int hash = (((ki[0] & 0xFF) << Byte.SIZE) | (ki[1] & 0xFF)); int arrayPos = hash % PRIME_NUMBER; assert arrayPos >= 0; while (keys[arrayPos] != null) { if (containAtBegin(keys[arrayPos], ki)) return values[arrayPos][ki.length - 2]; arrayPos++; if (arrayPos == PRIME_NUMBER) { assert assertArrayConform() : "keys array must contain at least null value"; arrayPos = 0; } } /* * In LZW compression adapted for tiff image, short value 256 is a reserved value. * If value is not find from key, return 256 to stipulate that value is not find. */ return 256; } /** * {@inheritDoc }. */ @Override public void clear() { Arrays.fill(keys, null); Arrays.fill(values, null); } }