OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 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 java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

import com.google.gson.reflect.TypeToken;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;

/**
 * Type-safer map (e.g. can store multiple types and collections).
 * 
 * @author Sylvain
 */
@ThreadSafe
public class Cookies {

    @GuardedBy("this")
    private final Map<Object, Object> map;
    @GuardedBy("this")
    private Map<Object, Object> collectionTypes;

    public Cookies() {
        this(8);
    }

    public Cookies(final int initialCapacity) {
        this.map = new HashMap<>(initialCapacity);
        this.collectionTypes = null;
    }

    public Cookies(final Cookies source) {
        synchronized (source) {
            this.map = new HashMap<>(source.map);
            this.collectionTypes = source.collectionTypes == null ? null : new HashMap<>(source.collectionTypes);
        }
    }

    public final void putAll(final Cookies other) {
        if (this == other)
            return;
        final Map<Object, Object> map;
        final Map<Object, Object> collectionTypes;
        synchronized (other) {
            map = new HashMap<>(other.map);
            collectionTypes = other.collectionTypes == null ? null : new HashMap<>(other.collectionTypes);
        }
        synchronized (this) {
            // don't just use putAll() since it won't remove entries from collectionTypes for
            // replaced items
            for (final Entry<Object, Object> e : map.entrySet()) {
                this.put(e.getKey(), e.getValue(), collectionTypes == null ? null : collectionTypes.get(e.getKey()));
            }
        }
    }

    public final Object put(Object k, Object v) {
        return this.put(k, v, null);
    }

    private final Object putCollectionType(Object k, Object v, Object type) {
        if (type == null)
            throw new IllegalArgumentException("Null type");
        return this.put(k, v, type);
    }

    private final synchronized Object put(Object k, Object v, Object type) {
        if (type != null) {
            if (this.collectionTypes == null)
                this.collectionTypes = new HashMap<>(4);
            this.collectionTypes.put(k, type);
        } else if (this.collectionTypes != null) {
            this.collectionTypes.remove(k);
        }
        return this.map.put(k, v);
    }

    public final <T> Object putCollection(Object k, Collection<T> v, Class<T> clazz) {
        return this.putCollectionType(k, v, clazz);
    }

    public final <K, V> Object putMap(Object k, Map<K, V> v, Class<K> keyClass, Class<V> valueClass) {
        Objects.requireNonNull(keyClass, "Key class");
        Objects.requireNonNull(valueClass, "value class");
        return this.putCollectionType(k, v, Arrays.asList(keyClass, valueClass));
    }

    public final <T> Object putGeneric(Object k, T v, TypeToken<T> clazz) {
        return this.putCollectionType(k, v, clazz);
    }

    public final synchronized boolean containsKey(Object k) {
        return this.map.containsKey(k);
    }

    public final synchronized Object getObject(Object k) {
        return this.map.get(k);
    }

    public final int getInt(Object k) {
        return getObjectAs(k, Number.class).intValue();
    }

    public final long getLong(Object k) {
        return getObjectAs(k, Number.class).longValue();
    }

    public final Boolean getBoolean(Object k) {
        return getObjectAs(k, Boolean.class);
    }

    public final boolean getBoolean(Object k, final boolean def) {
        final Boolean res = getBoolean(k);
        return res == null ? def : res.booleanValue();
    }

    public final String getString(Object k) {
        return getObjectAs(k, String.class);
    }

    public synchronized final <T> T getContainedObjectAs(Object key, Class<T> clazz) {
        if (!this.containsKey(key))
            throw new IllegalArgumentException(key + " not present in this : " + this.map.keySet());
        return this.getObjectAs(key, clazz);
    }

    public final <T> T getObjectAs(Object k, Class<T> clazz) {
        return this.getObjectAs(k, clazz, null);
    }

    public final <T> T getObjectAs(Object k, Class<T> clazz, final T defaultVal) {
        return this.getObjectAs(k, clazz, defaultVal, true);
    }

    public synchronized final <T> T getObjectAs(Object k, Class<T> clazz, final T defaultVal, final boolean useDefaultIfNullValue) {
        final Object res = this.getObject(k);
        if (res == null && (useDefaultIfNullValue || !this.containsKey(k)))
            return defaultVal;
        try {
            return clazz.cast(res);
        } catch (ClassCastException e) {
            throw new IllegalArgumentException("Couldn't access " + k + " in " + this + " as " + clazz.getSimpleName(), e);
        }
    }

    public final <T> Object checkType(Object k, Object wantedType, final String needMethodName) {
        final Object object;
        final Object type;
        synchronized (this) {
            object = this.getObject(k);
            type = this.collectionTypes == null ? null : this.collectionTypes.get(k);
        }
        // as getObject() : don't fail on null
        if (object == null)
            return null;
        if (type == null)
            throw new IllegalStateException("Wasn't stored with " + needMethodName + " : " + k);
        if (!type.equals(wantedType))
            throw new IllegalArgumentException("Wasn't stored with the passed type : " + wantedType + " != " + type);
        return object;
    }

    public final <T> Collection<T> getCollection(Object k, Class<T> clazz) {
        final Object object = checkType(k, clazz, "putCollection()");
        @SuppressWarnings("unchecked")
        final Collection<T> res = (Collection<T>) object;
        return res;
    }
    
    public final <T> Collection<T> computeCollectionIfAbsent(Object k, Class<T> clazz, Function<Object, ? extends Collection<T>> mappingFunction) {
        Collection<T> res;
        synchronized (this) {
            res = this.getCollection(k, clazz);
            // same algorithm as java.util.Map
            if (res == null) {
                res = mappingFunction.apply(k);
                if (res != null)
                    this.putCollection(k, res, clazz);
            }
        }
        return res;
    }


    public final <T> List<T> getList(Object k, Class<T> clazz) {
        return (List<T>) this.getCollection(k, clazz);
    }

    public final <T> Set<T> getSet(Object k, Class<T> clazz) {
        return (Set<T>) this.getCollection(k, clazz);
    }

    public final <K, V> Map<K, V> getMap(Object k, Class<K> keyClass, Class<V> valueClass) {
        final Object object = checkType(k, Arrays.asList(keyClass, valueClass), "putMap()");
        @SuppressWarnings("unchecked")
        final Map<K, V> res = (Map<K, V>) object;
        return res;
    }

    public final <K, V> Map<K, V> computeMapIfAbsent(Object k, Class<K> keyClass, Class<V> valueClass, Function<Object, ? extends Map<K, V>> mappingFunction) {
        Map<K, V> res;
        synchronized (this) {
            res = this.getMap(k, keyClass, valueClass);
            // same algorithm as java.util.Map
            if (res == null) {
                res = mappingFunction.apply(k);
                if (res != null)
                    this.putMap(k, res, keyClass, valueClass);
            }
        }
        return res;
    }

    public final <T> T getGeneric(Object k, TypeToken<T> clazz) {
        final Object object = checkType(k, clazz, "putGeneric()");
        @SuppressWarnings("unchecked")
        final T res = (T) object;
        return res;
    }

    public final <T> T computeIfAbsent(Object k, TypeToken<T> clazz, Function<Object, ? extends T> mappingFunction) {
        T res;
        synchronized (this) {
            res = this.getGeneric(k, clazz);
            // same algorithm as java.util.Map
            if (res == null) {
                res = mappingFunction.apply(k);
                if (res != null)
                    this.putGeneric(k, res, clazz);
            }
        }
        return res;
    }

    @Override
    public synchronized String toString() {
        return this.getClass().getSimpleName() + " of " + this.map.keySet();
    }
}