All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.pwall.util.ListMap Maven / Gradle / Ivy

The newest version!
/*
 * @(#) ListMap.java
 *
 * javautil Java Utility Library
 * Copyright (c) 2013, 2014, 2015, 2016, 2017 Peter Wall
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.util;

import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;

/**
 * An implementation of {@link Map} that uses a {@link List} to hold the entries, retaining the
 * order of insertion.  This class is useful when the order of insertion of entries is
 * significant, but it suffers from serious performance degradation when the numbers of entries
 * exceeds a handful.
 *
 * @author  Peter Wall
 * @param            the key type
 * @param            the value type
 */
public class ListMap implements Map, Serializable {

    private static final long serialVersionUID = -5594713182082941289L;

    protected List> list;

    /**
     * Construct an empty {@code ListMap}.
     */
    public ListMap() {
        list = new ArrayList<>();
    }

    /**
     * Construct an empty {@code ListMap} with a specified initial capacity.
     *
     * @param   capacity    the initial capacity
     */
    public ListMap(int capacity) {
        list = new ArrayList<>(capacity);
    }

    /**
     * Construct a {@code ListMap} with the contents of another {@link Map}.
     *
     * @param   m   the other {@link Map}
     */
    public ListMap(Map m) {
        this(m.size());
        putAll(m);
    }

    /**
     * Get a value from the {@code ListMap}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code null} if not found
     * @see     Map#get(Object)
     */
    @Override
    public V get(Object key) {
        int index = findIndex(Objects.requireNonNull(key));
        return index < 0 ? null : list.get(index).getValue();
    }

    /**
     * Test whether the {@code ListMap} contains a specified key.
     *
     * @param   key     the key to test for
     * @return          {@code true} if the key is found
     * @see     Map#containsKey(Object)
     */
    @Override
    public boolean containsKey(Object key) {
        return findIndex(Objects.requireNonNull(key)) >= 0;
    }

    /**
     * Store a value in the {@code ListMap} with the specified key.
     *
     * @param   key     the key
     * @param   value   the value
     * @return          the previous value stored with that key, or {@code null} if no previous
     *                  value
     * @see     Map#put(Object, Object)
     */
    @Override
    public V put(K key, V value) {
        int index = findIndex(Objects.requireNonNull(key));
        if (index >= 0) {
            Entry entry = list.get(index);
            V oldValue = entry.getValue();
            entry.setValue(value);
            return oldValue;
        }
        list.add(new Entry<>(key, value));
        return null;
    }

    /**
     * Remove the specified key-value mapping from the {@code ListMap}.
     *
     * @param   key     the key
     * @return          the value stored with that key, or {@code null} if key not used
     * @see     Map#remove(Object)
     */
    @Override
    public V remove(Object key) {
        int index = findIndex(Objects.requireNonNull(key));
        if (index >= 0)
            return list.remove(index).getValue();
        return null;
    }

    /**
     * Get the number of values in the {@code ListMap}.
     *
     * @return  the number of values
     * @see     Map#size()
     */
    @Override
    public int size() {
        return list.size();
    }

    /**
     * Test whether the {@code ListMap}is empty.
     *
     * @return  {@code true} if the {@code ListMap} is empty
     * @see     Map#isEmpty()
     */
    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Test whether the {@code ListMap} contains the specified value.
     *
     * @param   value   the value
     * @return          {@code true} if the {@code ListMap} contains the value
     * @see     Map#containsValue(Object)
     */
    @Override
    public boolean containsValue(Object value) {
        for (int i = 0, n = list.size(); i < n; i++)
            if (Objects.equals(list.get(i).getValue(), value))
                return true;
        return false;
    }

    /**
     * Add all the members of another {@link Map} to this {@code ListMap}.
     *
     * @param   m       the other {@link Map}
     * @see     Map#putAll(Map)
     */
    @Override
    public void putAll(Map m) {
        for (Map.Entry e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

    /**
     * Remove all members from this {@code ListMap}.
     *
     * @see     Map#clear()
     */
    @Override
    public void clear() {
        list.clear();
    }

    /**
     * Get a {@link Set} representing the keys in use in the {@code ListMap}.
     *
     * @return  the {@link Set} of keys
     * @see     Map#keySet()
     */
    @Override
    public Set keySet() {
        return new KeySet();
    }

    /**
     * Get a {@link Collection} of the values in the {@code ListMap}.
     *
     * @return  the {@link Collection} of values
     * @see     Map#values()
     */
    @Override
    public Collection values() {
        return new ValueCollection();
    }

    /**
     * Get a {@link Set} of the key-value pairs in use in the {@code ListMap}.
     *
     * @return  the {@link Set} of key-value pairs
     * @see     Map#entrySet()
     */
    @Override
    public Set> entrySet() {
        return new EntrySet();
    }

    /**
     * Get the hash code for this {@code ListMap}.
     *
     * @return  the hash code
     * @see     Object#hashCode()
     */
    @Override
    public int hashCode() {
        int result = 0;
        for (int i = 0, n = list.size(); i < n; i++)
            result ^= list.get(i).hashCode();
        return result;
    }

    /**
     * Compare this {@code ListMap} with another object for equality.
     *
     * @param   other   the other object
     * @return  {@code true} if the other object is a {@code ListMap} and is identical to
     *          this object
     * @see     Object#equals(Object)
     */
    @Override
    public boolean equals(Object other) {
        if (other == this)
            return true;
        if (!(other instanceof ListMap))
            return false;
        ListMap otherMapping = (ListMap)other;
        if (list.size() != otherMapping.list.size())
            return false;
        for (Entry entry : list)
            if (!Objects.equals(entry.getValue(), otherMapping.get(entry.getKey())))
                return false;
        return true;
    }

    /**
     * Convert the map to {@code String} (usually for diagnostic purposes).
     *
     * @return  a string in the form {key=value, ...}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('{');
        int n = list.size();
        if (n > 0) {
            int i = 0;
            while (true) {
                Entry e = list.get(i++);
                if (e.value == this)
                    sb.append(e.key).append('=').append("(this Map)");
                else
                    sb.append(e);
                if (i >= n)
                    break;
                sb.append(',').append(' ');
            }
        }
        sb.append('}');
        return sb.toString();
    }

    /**
     * Get an {@link Entry} by index.
     *
     * @param   index   the index
     * @return  the list entry
     */
    public Entry getEntry(int index) {
        return list.get(index);
    }

    /**
     * Find the index for the specified key.
     *
     * @param   key     the key
     * @return          the index for this key, or -1 if not found
     */
    protected int findIndex(Object key) {
        for (int i = 0, n = list.size(); i < n; i++)
            if (list.get(i).getKey().equals(key))
                return i;
        return -1;
    }

    /**
     * Inner class to represent a key-value pair in the {@code ListMap}.
     *
     * @param       the key type
     * @param       the value type
     */
    public static class Entry implements Map.Entry, Serializable {

        private static final long serialVersionUID = -7610378954393786210L;

        private final KK key;
        private VV value;

        /**
         * Construct an {@code Entry} with the given key and value.
         *
         * @param   key     the key
         * @param   value   the value
         */
        public Entry(KK key, VV value) {
            this.key = key;
            this.value = value;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public KK getKey() {
            return key;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public VV getValue() {
            return value;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public VV setValue(VV value) {
            VV oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object other) {
            if (other == this)
                return true;
            if (!(other instanceof Map.Entry))
                return false;
            Map.Entry otherEntry = (Map.Entry)other;
            return Objects.equals(key, otherEntry.getKey()) &&
                    Objects.equals(value, otherEntry.getValue());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return Objects.hash(key, value);
        }

        /**
         * Convert the entry to {@code String} (usually for diagnostic purposes).
         *
         * @return  a string in the form key=value
         */
        @Override
        public String toString() {
            return key.toString() + '=' + value.toString();
        }

    }

    /**
     * An {@link Iterator} over the {@link Set} of key-value pairs in the {@code ListMap}.
     */
    private class EntryIterator extends BaseIterator> {

        /**
         * {@inheritDoc}
         */
        @Override
        public Entry next() {
            return nextEntry();
        }

    }

    /**
     * An {@link Iterator} over the {@link Set} of keys in the {@code ListMap}.
     */
    private class KeyIterator extends BaseIterator {

        /**
         * {@inheritDoc}
         */
        @Override
        public K next() {
            return nextEntry().getKey();
        }

    }

    /**
     * An {@link Iterator} over the {@link Collection} of values in the {@code ListMap}.
     */
    private class ValueIterator extends BaseIterator {

        /**
         * {@inheritDoc}
         */
        @Override
        public V next() {
            return nextEntry().getValue();
        }

    }

    /**
     * Abstract base class for various iterators.
     *
     * @param        the returned type
     */
    private abstract class BaseIterator implements Iterator {

        private int index;

        /**
         * Construct a {@code BaseIterator} starting at index 0.
         */
        public BaseIterator() {
            index = 0;
        }

        /**
         * Get the next {@code Entry} from the list.  The different derived iterator types will
         * return the entry itself, or the key or value.
         *
         * @return  the next {@code Entry}
         */
        public Entry nextEntry() {
            if (!hasNext())
                throw new NoSuchElementException();
            return list.get(index++);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasNext() {
            return index < list.size();
        }

    }

    /**
     * A collection of the key-value pairs in the {@code ListMap}.
     *
     * @see     #entrySet()
     */
    private class EntrySet extends CollectionBase> {

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object o) {
            return list.contains(o);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Iterator> iterator() {
            return new EntryIterator();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean containsAll(Collection c) {
            for (Object o : c)
                if (!list.contains(o))
                    return false;
            return true;
        }

    }

    /**
     * A collection of the keys in the {@code ListMap}.
     *
     * @see     #keySet()
     */
    private class KeySet extends CollectionBase {

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object o) {
            return containsKey(o);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Iterator iterator() {
            return new KeyIterator();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean containsAll(Collection c) {
            for (Object o : c)
                if (!containsKey(o))
                    return false;
            return true;
        }

    }

    /**
     * A collection of the values in the {@code ListMap}.
     *
     * @see     #values()
     */
    private class ValueCollection extends CollectionBase {

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object o) {
            return containsValue(o);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Iterator iterator() {
            return new ValueIterator();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean containsAll(Collection c) {
            for (Object o : c)
                if (!containsValue(o))
                    return false;
            return true;
        }

    }

    /**
     * Abstract base class for various returned collections.  All modifying operations throw an
     * {@link UnsupportedOperationException}.
     *
     * @param        the returned type
     */
    private abstract class CollectionBase extends AbstractSet {

        /**
         * Return the number of elements in the set.  All returned collections are the same size
         * as the underlying collection.
         *
         * @return  the number of elements in the collection
         */
        @Override
        public int size() {
            return list.size();
        }

        /**
         * Remove an object from the collection - not supported.
         *
         * @param   o   the object
         * @return      (never returns normally)
         * @throws      UnsupportedOperationException in all cases
         */
        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        /**
         * Add all elements from another collection - not supported.
         *
         * @param   c   the other collection
         * @return      (never returns normally)
         * @throws      UnsupportedOperationException in all cases
         */
        @Override
        public boolean addAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        /**
         * Remove all elements not matching those in another collection - not supported.
         *
         * @param   c   the other collection
         * @return      (never returns normally)
         * @throws      UnsupportedOperationException in all cases
         */
        @Override
        public boolean retainAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        /**
         * Remove all elements matching those in another collection - not supported.
         *
         * @param   c   the other collection
         * @return      (never returns normally)
         * @throws      UnsupportedOperationException in all cases
         */
        @Override
        public boolean removeAll(Collection c) {
            throw new UnsupportedOperationException();
        }

        /**
         * Clear the collection - not supported.
         *
         * @throws      UnsupportedOperationException in all cases
         */
        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy