/*
Copyright (c) 1996-2008 Ariba, Inc.
All rights reserved. Patents pending.
$Id: //ariba/platform/util/core/ariba/util/core/ReadOnlyMap.java#6 $
Responsible: jshultis
*/
package ariba.util.core;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import ariba.util.io.FormattingSerializer;
/**
* A Map-like object that can't be modified - not because it is immutable
* (which would raise a run-time exception), but because it has no API
* for modifying it. In a better world, it could be defined as a superinterface
* of Map.
*
* A ReadOnlyMap wraps an ordinary Map, which may contain other maps as values
* (i.e., the Map may be nested). In that case, when returning a value that
* is a Map, the ReadOnlyMap wraps that value in another ReadOnlyMap, so that
* IT can't be modified, either. The wrapping doesn't extend to values of
* other types, unfortunately, but it is at least a good start toward getting
* a declarative, statically-enforced, read-only data structure.
*
* @aribaapi ariba
*/
public class ReadOnlyMap <K,V>
{
Map<K,V> map = null;
public ReadOnlyMap (Map<K,V> aMap)
{
map = MapUtil.copyMap(aMap);
}
public static ReadOnlyMap emptyInstance ()
{
return new ReadOnlyMap(MapUtil.map());
}
// XXX JCS: Create ReadOnlyList to protect those values, too.
public V get (K key)
{
V value = null;
if (map != null) {
value = map.get(key);
if (value != null && value instanceof Map) {
value = (V)new ReadOnlyMap((Map)value);
}
}
return value;
}
public boolean containsKey (K key)
{
return map != null && map.containsKey(key);
}
public boolean containsValue (V value)
{
return map != null && map.containsValue(value);
}
public boolean equals (Object o)
{
return o != null &&
o.getClass().equals(this.getClass()) &&
((ReadOnlyMap)o).map.equals(map);
}
public boolean isEmpty ()
{
return map == null || map.isEmpty();
}
public static <K,V> boolean isNullOrEmpty (ReadOnlyMap<K,V> m)
{
return (m == null) || (m.map == null) || m.map.isEmpty();
}
public int size ()
{
return map == null ? 0 : map.size();
}
public Set<K> keySet ()
{
Set<K> keys = null;
if (map == null) {
keys = SetUtil.set();
}
else {
keys = map.keySet();
}
return keys;
}
public Collection<V> values ()
{
Collection<V> vals = null;
if (map == null) {
vals = ListUtil.list();
}
else {
vals = map.values();
}
return vals;
}
public int hashCode ()
{
return map == null ? 0 : map.hashCode();
}
public Set entrySet ()
{
Iterator entries = map.entrySet().iterator();
Set myEntries = SetUtil.set(map.size());
while (entries.hasNext()) {
Entry myEntry = new Entry((Map.Entry)entries.next());
myEntries.add(myEntry);
}
return myEntries;
}
public static final char Dot = '.';
/**
* Fetch a value from a nested map using a path of keys using
* dot notation.
* @param path The dotted path of key values
* @return The value at that path. If the value is a Map, return a
* ReadOnlyMap, so the result is equivalent to doing a sequence of
* get operations on the path elements.
*/
public Object getPath (String path)
{
Object value = map;
String[] segments = StringUtil.delimitedStringToArray(path, Dot);
for (int i=0; i<segments.length; i++) {
if (value instanceof Map) {
value = ((Map)value).get(segments[i]);
}
else {
return null;
}
}
return (value instanceof Map) ? new ReadOnlyMap((Map)value) : value;
}
public String toString ()
{
return map == null ? null : FormattingSerializer.serializeObject(map);
}
/**
* Get a copy of the underlying Map. It's not the actual
* underlying Map, so is can't be used as a 'back door' to
* modify the state of the ReadOnlyMap.
* The static method {@link #mapCopy(ReadOnlyMap)} is
* recommended unless the caller is certain that both <code>this</code>
* and the <code>Map</code> it encloses are non-null.
* @return A copy of the wrapped Map.
*/
public Map<K,V> mapCopy ()
{
return MapUtil.copyMap(map);
}
/**
* Get a copy of the underlying Map. It's not the actual
* underlying Map, so is can't be used as a 'back door' to
* modify the state of the ReadOnlyMap. If
* @param roMap The ReadOnlyMap to copy. Return null if roMap is
* null, or its enclosed Map is null or empty.
* @return A copy of the wrapped Map.
*/
public static Map mapCopy (ReadOnlyMap roMap)
{
return (roMap == null) ? null : roMap.mapCopy();
}
public Map<K,V> immutableMap ()
{
return MapUtil.immutableMap(map);
}
public static Map immutableMap(ReadOnlyMap roMap)
{
return (roMap == null || roMap.map == null) ? null : MapUtil.immutableMap(roMap.map);
}
public class Entry <K,V>
{
private Map.Entry<K,V> entry;
Entry (Map.Entry<K,V> anEntry)
{
entry = anEntry;
}
public boolean equals (Object anObject)
{
return entry.equals(anObject);
}
public int hashCode ()
{
return entry.hashCode();
}
public K getKey ()
{
return entry.getKey();
}
public Object getValue ()
{
Object value = entry.getValue();
if (value instanceof Map) {
value = new ReadOnlyMap((Map)value);
}
return value;
}
}
}