package org.yajul.collections;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;
/**
* Basic set of entities by their id. Generally entities do not use
* the surrogate key for equals and hashCode so using Set with natural equality test is
* not always practical.
* <br>User: Joshua Davis
* Date: Nov 9, 2007
* Time: 10:27:46 PM
*/
public class IdMap<K, V extends EntityWithId<K>> implements Externalizable, Map<K, V> {
private Map<K, V> map;
private static <K, V extends EntityWithId<K>> LinkedHashMap<K, V> createMap() {
return CollectionUtil.newLinkedHashMap();
}
private static <K, V extends EntityWithId<K>> LinkedHashMap<K, V> createMap(int size) {
return CollectionUtil.newLinkedHashMap(size);
}
private void newMapWithCapacity(int size) {
if (map != null)
map.clear();
map = createMap(size);
}
/**
* Makes an empty IdMap with the default map implementation.
*/
public IdMap() {
this(IdMap.<K,V>createMap(), null);
}
/**
* Makes an IdMap using the map provided as the backing store, and adds all the 'items'
* to it.
*
* @param map the backing map
* @param items the entities to add
*/
public IdMap(Map<K, V> map, Collection<V> items) {
this.map = map;
if (items != null)
putAll(items);
}
/**
* Creates the IdMap with the specified backing map.
*
* @param map the map implementation
*/
public IdMap(Map<K, V> map) {
this(map, null);
}
/**
* Makes an IdMap using the default backing store, and adds all the 'items'
* to it.
*
* @param items the entities to add
*/
public IdMap(Collection<V> items) {
this(IdMap.<K,V>createMap(items == null ? 0 : items.size()), items);
}
/**
* Creates a map as a subset of another map.
*
* @param superSet the superset map
* @param ids the subset of ids for this map
*/
public IdMap(IdMap<K, V> superSet, Collection<K> ids) {
addSubset(superSet, ids);
}
/**
* Adds a subset of the reference map.
*
* @param reference the reference map (superset)
* @param ids the ids in the subset
*/
public void addSubset(IdMap<K, V> reference, Collection<K> ids) {
final int size = ids.size();
if (size > 0) {
newMapWithCapacity(size);
for (K id : ids)
put(reference.get(id));
}
else
this.map = Collections.emptyMap();
}
/**
* Adds the entity to the map by it's id.
*
* @param thing the entity to add
* @see EntityWithId<K>.getId()
*/
public void put(V thing) {
if (thing == null)
return;
K id = thing.getId();
if (id == null)
return;
map.put(id, thing);
}
/**
* Objects are added if their ids don't exist, replaced if the id exists.
*
* @param objects the objects to add or replace
*/
public void aggregate(Iterable<V> objects) {
if (objects == null)
return;
for (V thing : objects)
put(thing);
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public V get(Object key) {
return map.get(key);
}
public V put(K key, V value) {
return map.put(key, value);
}
public V remove(Object key) {
return map.remove(key);
}
@SuppressWarnings("NullableProblems")
public void putAll(Map<? extends K, ? extends V> t) {
map.putAll(t);
}
public void putAll(Collection<V> items) {
for (V v : items)
put(v.getId(), v);
}
public void clear() {
map.clear();
}
@SuppressWarnings("NullableProblems")
public Set<K> keySet() {
return map.keySet();
}
@SuppressWarnings("NullableProblems")
public Collection<V> values() {
return map.values();
}
@SuppressWarnings("NullableProblems")
public Set<Entry<K, V>> entrySet() {
return map.entrySet();
}
public boolean equals(Object o) {
return o instanceof Map && map.equals(o);
}
public int hashCode() {
return map.hashCode();
}
/**
* @return the values in the id map, same as Map.values()
* @see Map#values()
*/
public Collection<V> getCollection() {
return values();
}
/**
* @param id the id to look for
* @return true if the id exists in the map
* @see Map#containsKey(Object)
*/
public boolean containsId(K id) {
return containsKey(id);
}
public V getOne() {
assert map.size() == 1;
return map.values().iterator().next();
}
/**
* @return the unique ids, same as Map.keySet()
* @see Map#keySet()
*/
public Collection<K> getIds() {
return map.keySet();
}
public String toString() {
if (map.size() == 0)
return "{}";
else
return getClass().getSimpleName() + "{" + map.values() + '}';
}
public void writeExternal(ObjectOutput out) throws IOException {
// We don't need to store the keys, just the values.
out.writeInt(map.size());
for (V v : map.values()) {
out.writeObject(v);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int size = in.readInt();
newMapWithCapacity(size);
for (int i = 0; i < size; i++) {
@SuppressWarnings({"unchecked"})
V v = (V) in.readObject();
put(v); // Get the key from the object.
}
}
/**
* Gets the set of unique ids from a bunch of entities.
*
* @param things the entities
* @param <K> the key type
* @param <E> the entity type
* @return a set of unique ids
*/
public static <K, E extends EntityWithId<K>> Set<K> idSet(Iterable<E> things) {
LinkedHashSet<K> set = CollectionUtil.newLinkedHashSet();
for (E thing : things)
set.add(thing.getId());
return set;
}
}