OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 61 | 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.cc;

import org.openconcerto.utils.CompareUtils;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.jcip.annotations.ThreadSafe;

/**
 * A thread-safe map in which all mutative operations (put, remove, and so on) are implemented by
 * making a fresh copy of the underlying map. The instance of the underlying map can be customized
 * with {@link #copy(Map)}. The underlying immutable map is available with {@link #getImmutable()}.
 * 
 * @author Sylvain
 * 
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
@ThreadSafe
public class CopyOnWriteMap<K, V> extends AbstractMap<K, V> {

    private Map<K, V> immutable;
    private Set<Entry<K, V>> entrySet = null;

    public CopyOnWriteMap() {
        this.immutable = Collections.emptyMap();
    }

    public CopyOnWriteMap(final Map<K, V> m) {
        this.immutable = Collections.unmodifiableMap(copy(m));
    }

    /**
     * Create a copy of the passed map.
     * 
     * @param src the map to copy, not <code>null</code>.
     * @return a shallow copy of <code>src</code>.
     */
    public Map<K, V> copy(Map<? extends K, ? extends V> src) {
        return new HashMap<K, V>(src);
    }

    // write

    @Override
    public synchronized V put(final K key, final V value) {
        final Map<K, V> copy = copy(this.immutable);
        final V res = copy.put(key, value);
        this.immutable = Collections.unmodifiableMap(copy);
        return res;
    }

    @Override
    public synchronized V remove(final Object key) {
        final Map<K, V> copy = copy(this.immutable);
        final V res = copy.remove(key);
        this.immutable = Collections.unmodifiableMap(copy);
        return res;
    }

    @Override
    public synchronized void putAll(final Map<? extends K, ? extends V> m) {
        final Map<K, V> copy = copy(this.immutable);
        copy.putAll(m);
        this.immutable = Collections.unmodifiableMap(copy);
    }

    @Override
    public synchronized void clear() {
        this.immutable = Collections.emptyMap();
    }

    // read

    public synchronized final Map<K, V> getImmutable() {
        return this.immutable;
    }

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

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

    @Override
    public boolean containsKey(final Object key) {
        return this.getImmutable().containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
        return this.getImmutable().containsValue(value);
    }

    @Override
    public V get(final Object key) {
        return this.getImmutable().get(key);
    }

    @Override
    public synchronized Set<Entry<K, V>> entrySet() {
        if (this.entrySet == null)
            this.entrySet = new EntrySet();
        return this.entrySet;
    }

    private final class EntrySet extends AbstractSet<Entry<K, V>> {
        public Iterator<Entry<K, V>> iterator() {
            return new Iterator<Entry<K, V>>() {
                private final Iterator<Entry<K, V>> delegate = CopyOnWriteMap.this.immutable.entrySet().iterator();
                private Entry<K, V> current = null;

                @Override
                public boolean hasNext() {
                    return this.delegate.hasNext();
                }

                @Override
                public Entry<K, V> next() {
                    return (this.current = this.delegate.next());
                }

                @Override
                public void remove() {
                    if (this.current == null)
                        throw new IllegalStateException();
                    EntrySet.this.remove(this.current);
                }
            };
        }

        public boolean contains(Object o) {
            if (!(o instanceof Entry))
                return false;
            final Entry<?, ?> e = (Entry<?, ?>) o;
            return CompareUtils.equals(e.getValue(), get(e.getKey()));
        }

        public boolean remove(Object o) {
            if (!(o instanceof Entry))
                return false;
            return CopyOnWriteMap.this.remove(((Entry<?, ?>) o).getKey()) != null;
        }

        public int size() {
            return CopyOnWriteMap.this.size();
        }

        public void clear() {
            CopyOnWriteMap.this.clear();
        }
    }

    // equals

    @Override
    public boolean equals(final Object o) {
        return this.getImmutable().equals(o);
    }

    @Override
    public int hashCode() {
        return this.getImmutable().hashCode();
    }
}