OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 83 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.utils;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

public class TinyMap<K, V> extends AbstractMap<K, V> {
    private static final Function<Entry<?, ?>, Object> KEY_GETTER = Entry::getKey;
    private static final Function<Entry<?, ?>, Object> VALUE_GETTER = Entry::getValue;

    private final ArrayList<Entry<K, V>> entries;
    private transient Set<Map.Entry<K, V>> entrySet;
    /**
     * The number of times this HashMap has been structurally modified Structural modifications are
     * those that change the number of mappings in the HashMap or otherwise modify its internal
     * structure (e.g., rehash). This field is used to make iterators on Collection-views of the
     * HashMap fail-fast. (See ConcurrentModificationException).
     */
    transient int modCount = 0;

    public TinyMap() {
        this(10);
    }

    public TinyMap(final int initialCapacity) {
        this.entries = new ArrayList<>(initialCapacity);
    }

    public TinyMap(final Map<? extends K, ? extends V> m) {
        this.entries = new ArrayList<>(m.size());
        // don't call putAll() to avoid indexOfKey()
        for (final Entry<? extends K, ? extends V> e : m.entrySet()) {
            this.entries.add(new SimpleEntry<>(e));
        }
        ++this.modCount;
    }

    @Override
    public int size() {
        return this.entries.size();
    }

    @Override
    public boolean isEmpty() {
        return this.entries.isEmpty();
    }

    @Override
    public boolean containsKey(final Object key) {
        return indexOfKey(key) >= 0;
    }

    @Override
    public boolean containsValue(final Object value) {
        return this.indexOf(value, VALUE_GETTER) >= 0;
    }

    private int indexOfKey(final Object key) {
        return this.indexOf(key, KEY_GETTER);
    }

    private final int indexOf(final Object o, final Function<Entry<?, ?>, Object> getter) {
        final int stop = this.entries.size();
        for (int i = 0; i < stop; i++) {
            final Entry<K, V> e = this.entries.get(i);
            if (Objects.equals(o, getter.apply(e)))
                return i;
        }
        return -1;
    }

    @Override
    public V get(final Object key) {
        final int i = indexOfKey(key);
        if (i < 0)
            return null;
        return this.entries.get(i).getValue();
    }

    @Override
    public V put(final K key, final V value) {
        final int i = this.indexOfKey(key);
        final V res;
        if (i < 0) {
            this.entries.add(new SimpleEntry<>(key, value));
            ++this.modCount;
            res = null;
        } else {
            res = this.entries.get(i).setValue(value);
        }
        return res;
    }

    @Override
    public V remove(final Object key) {
        return this.remove(indexOfKey(key));
    }

    private V remove(final int i) {
        if (i < 0)
            return null;
        final Entry<K, V> res = this.entries.remove(i);
        ++this.modCount;
        return res.getValue();
    }

    @Override
    public boolean remove(final Object key, final Object value) {
        final int i = indexOfKey(key);
        if (i < 0)
            return false;
        final boolean eqVal = Objects.equals(this.entries.get(i).getValue(), value);
        if (eqVal) {
            this.remove(i);
        }
        return eqVal;
    }

    @Override
    public void clear() {
        this.entries.clear();
        ++this.modCount;
    }

    // Views

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K, V>> es;
        return (es = this.entrySet) == null ? (this.entrySet = new EntrySet()) : es;
    }

    final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
        @Override
        public final int size() {
            return TinyMap.this.size();
        }

        @Override
        public final void clear() {
            TinyMap.this.clear();
        }

        @Override
        public final Iterator<Map.Entry<K, V>> iterator() {
            return new Iterator<Map.Entry<K, V>>() {

                private int expectedModCount = TinyMap.this.modCount;
                /**
                 * The index last returned, i.e. initial value just before first item.
                 * 
                 * <pre>
                 *    a   b   c
                 * -1   0   1   2
                 * </pre>
                 */
                private int currentPos = -1;
                private Entry<K, V> lastReturned = null;

                @Override
                public boolean hasNext() {
                    final int nextIndex = this.currentPos + 1;
                    return nextIndex < size();
                }

                @Override
                public Entry<K, V> next() {
                    checkForComodification();
                    if (!hasNext())
                        throw new NoSuchElementException();
                    this.currentPos++;
                    final Entry<K, V> res = TinyMap.this.entries.get(this.currentPos);
                    this.lastReturned = res;
                    return res;
                }

                @Override
                public void remove() {
                    checkForComodification();
                    if (this.lastReturned == null)
                        throw new IllegalStateException();
                    TinyMap.this.remove(this.currentPos);
                    this.currentPos--;
                    // per doc : cannot be called twice
                    this.lastReturned = null;
                    this.expectedModCount = TinyMap.this.modCount;
                }

                final void checkForComodification() {
                    if (TinyMap.this.modCount != this.expectedModCount)
                        throw new ConcurrentModificationException();
                }
            };
        }

        @Override
        public final boolean contains(final Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
            return TinyMap.this.entries.contains(e);
        }

        @Override
        public final boolean remove(final Object o) {
            if (o instanceof Map.Entry) {
                final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
                final Object key = e.getKey();
                final Object value = e.getValue();
                return TinyMap.this.remove(key, value);
            }
            return false;
        }
    }
}