Dépôt officiel du code source de l'ERP OpenConcerto
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();
}
}