package com.servoy.j2db.server.ngclient.utils; import java.io.Serializable; import java.util.AbstractList; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * A fixed size map implementation. Holds an array of keys and array of values which correspond by * index. Null key entries are available for use. This means that null is not a valid key. * * @author Jonathan Locke * @param <K> * Key type * @param <V> * Value type */ public class MiniMap<K, V> implements Map<K, V>, Serializable { private static final long serialVersionUID = 1L; /** The array of keys. Keys that are null are not used. */ private final K[] keys; /** The array of values which correspond by index with the keys array. */ private final V[] values; /** The number of valid entries */ private int size; /** The last search index. This makes putting and getting more efficient. */ private int lastSearchIndex; /** * Constructor * * @param maxEntries * The maximum number of entries this map can hold */ @SuppressWarnings("unchecked") public MiniMap(final int maxEntries) { keys = (K[])new Object[maxEntries]; values = (V[])new Object[maxEntries]; } /** * Constructor * * @param map * The map * @param maxEntries * The maximum number of entries this map can hold */ public MiniMap(final Map< ? extends K, ? extends V> map, final int maxEntries) { this(maxEntries); putAll(map); } /** * @return True if this MicroMap is full */ public boolean isFull() { return size == keys.length; } /** * @see java.util.Map#size() */ public int size() { return size; } /** * @see java.util.Map#isEmpty() */ public boolean isEmpty() { return size == 0; } /** * @see java.util.Map#containsKey(java.lang.Object) */ public boolean containsKey(final Object key) { return findKey(0, key) != -1; } /** * @see java.util.Map#containsValue(java.lang.Object) */ public boolean containsValue(final Object value) { return findValue(0, value) != -1; } /** * @see java.util.Map#get(java.lang.Object) */ public V get(final Object key) { // Search for key final int index = findKey(key); if (index != -1) { // Return value return values[index]; } // Failed to find key return null; } /** * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ public V put(final K key, final V value) { // Search for key final int index = findKey(key); if (index != -1) { // Replace existing value final V oldValue = values[index]; values[index] = value; return oldValue; } // Is there room for a new entry? if (size < keys.length) { // Store at first null index and continue searching after null index // next time final int nullIndex = nextNullKey(lastSearchIndex); lastSearchIndex = nextIndex(nullIndex); keys[nullIndex] = key; values[nullIndex] = value; size++; return null; } else { throw new IllegalStateException("Map full"); } } /** * @see java.util.Map#remove(java.lang.Object) */ public V remove(final Object key) { // Search for key final int index = findKey(key); if (index != -1) { // Store value final V oldValue = values[index]; keys[index] = null; values[index] = null; size--; return oldValue; } return null; } /** * @see java.util.Map#putAll(java.util.Map) */ public void putAll(Map< ? extends K, ? extends V> map) { for (java.util.Map.Entry< ? extends K, ? extends V> e : map.entrySet()) { put(e.getKey(), e.getValue()); } } /** * @see java.util.Map#clear() */ public void clear() { for (int i = 0; i < keys.length; i++) { keys[i] = null; values[i] = null; } size = 0; } /** * @see java.util.Map#keySet() */ public Set<K> keySet() { return new AbstractSet<K>() { @Override public Iterator<K> iterator() { return new Iterator<K>() { public boolean hasNext() { return i < size - 1; } public K next() { // Just in case... (WICKET-428) if (!hasNext()) { throw new NoSuchElementException(); } // Find next key i = nextKey(nextIndex(i)); // Get key return keys[i]; } public void remove() { keys[i] = null; values[i] = null; size--; } int i = -1; }; } @Override public int size() { return size; } }; } /** * @see java.util.Map#values() */ public Collection<V> values() { return new AbstractList<V>() { @Override public V get(final int index) { if (index > size - 1) { throw new IndexOutOfBoundsException(); } int keyIndex = nextKey(0); for (int i = 0; i < index; i++) { keyIndex = nextKey(keyIndex + 1); } return values[keyIndex]; } @Override public int size() { return size; } }; } /** * @see java.util.Map#entrySet() */ public Set<Entry<K, V>> entrySet() { return new AbstractSet<Entry<K, V>>() { @Override public Iterator<Entry<K, V>> iterator() { return new Iterator<Entry<K, V>>() { public boolean hasNext() { return index < size; } public Entry<K, V> next() { if (!hasNext()) { throw new NoSuchElementException(); } keyIndex = nextKey(nextIndex(keyIndex)); index++; return new Map.Entry<K, V>() { public K getKey() { return keys[keyIndex]; } public V getValue() { return values[keyIndex]; } public V setValue(final V value) { final V oldValue = values[keyIndex]; values[keyIndex] = value; return oldValue; } }; } public void remove() { keys[keyIndex] = null; values[keyIndex] = null; } int keyIndex = -1; int index = 0; }; } @Override public int size() { return size; } }; } /** * Computes the next index in the key or value array (both are the same length) * * @param index * The index * @return The next index, taking into account wraparound */ private int nextIndex(final int index) { return (index + 1) % keys.length; } /** * Finds the index of the next non-null key. If the map is empty, -1 will be returned. * * @param start * Index to start at * @return Index of next non-null key */ private int nextKey(final int start) { int i = start; do { if (keys[i] != null) { return i; } i = nextIndex(i); } while (i != start); return -1; } /** * Finds the index of the next null key. If no null key can be found, the map is full and -1 * will be returned. * * @param start * Index to start at * @return Index of next null key */ private int nextNullKey(final int start) { int i = start; do { if (keys[i] == null) { return i; } i = nextIndex(i); } while (i != start); return -1; } /** * Finds a key by starting at lastSearchIndex and searching from there. If the key is found, * lastSearchIndex is advanced so the next key search can find the next key in the array, which * is the most likely to be retrieved. * * @param key * Key to find in map * @return Index of matching key or -1 if not found */ private int findKey(final Object key) { if (size > 0) { // Find key starting at search index final int index = findKey(lastSearchIndex, key); // Found match? if (index != -1) { // Start search at the next index next time lastSearchIndex = nextIndex(index); // Return index of key return index; } } return -1; } /** * Searches for a key from a given starting index. * * @param key * The key to find in this map * @param start * Index to start at * @return Index of matching key or -1 if not found */ private int findKey(final int start, final Object key) { int i = start; do { if (key.equals(keys[i])) { return i; } i = nextIndex(i); } while (i != start); return -1; } /** * Searches for a value from a given starting index. * * @param start * Index to start at * @param value * The value to find in this map * @return Index of matching value or -1 if not found */ private int findValue(final int start, final Object value) { int i = start; do { if (value.equals(values[i])) { return i; } i = nextIndex(i); } while (i != start); return -1; } @Override public String toString() { StringBuilder sb = new StringBuilder("MiniMap[size:"); sb.append(size); sb.append(",values:{"); for (int i = 0; i < size; i++) { sb.append(keys[i]); sb.append(':'); sb.append(values[i]); if (i < size - 1) sb.append(','); } sb.append("}]"); return sb.toString(); } }