MiniMap.java Source code

Java tutorial

Introduction

Here is the source code for MiniMap.java

Source

/*
 * 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.
 */

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 (final Iterator<? extends Entry<? extends K, ? extends V>> iterator = map.entrySet()
                .iterator(); iterator.hasNext();) {
            final Map.Entry<? extends K, ? extends V> e = iterator.next();
            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;
    }
}