package com.ideanest.reef.db;
import java.util.*;
/**
* A map of short keys to namespace uris that can be cascaded. Use the empty string as the
* key for the default namespace.
*
* @author Piotr Kaminski
*/
public class NamespaceMap implements Cloneable {
/**
* Reserved keys and uris, always declared and not overwriteable or overrideable.
* Note that the reserved map's parent will be null
, even though
* it goes through the common constructor, since at construction time the
* RESERVED
field has not yet been initialized. It is the only map
* that will have a null
parent.
*/
private static final NamespaceMap RESERVED = new NamespaceMap(
"xml", "http://www.w3.org/XML/1998/namespace",
"xmlns", "http://www.w3.org/2000/xmlns/"
) {
public Map getCombinedMap() {
return Collections.EMPTY_MAP;
}
};
private Map map;
/**
* The parent map from which bindings are inherited. It cannot be modified
* or accessed through its children.
*/
private NamespaceMap parent;
/**
* Create a new namespace map with no inherited bindings. Immediate bindings can
* be specified as a list of key-uri pairs.
* @param args a list of key-uri pairs
*/
public NamespaceMap(String... args) {
if (args.length % 2 != 0) throw new IllegalArgumentException("incomplete pair, " + args.length + " arguments received");
for (int i = 0; i < args.length; i+=2) {
put(args[i], args[i+1]);
}
parent = RESERVED;
}
/**
* Create a namespace map inheriting from this one.
* If this new map lacks a binding, it will be looked up in the parent.
* New bindings will always be entered in this map, and may override
* the parent's bindings.
*
* @return an extension of this map
*/
public NamespaceMap extend() {
NamespaceMap extension = new NamespaceMap();
extension.parent = this;
return extension;
}
/**
* Return a clone of this map. All inherited bindings are collapsed, and operations
* on the original or any of its inherited maps do not affect the clone or vice-versa.
*
* @return a combined clone of this map cascade
*/
public NamespaceMap clone() {
NamespaceMap clone = new NamespaceMap();
clone.map = getCombinedMap();
return clone;
}
/**
* Get the uri bound to the given key, either in this map or the closest inherited one.
* If the key is not bound, return null
.
*
* @param key the key to look up
* @return the bound uri or null
if none
*/
public String get(String key) {
if (key == null) throw new NullPointerException("null key");
String result = null;
if (result == null && map != null) result = map.get(key);
if (result == null && parent != null) result = parent.get(key);
return result;
}
private static void checkKey(String key) {
if (key == null) throw new NullPointerException("null key");
if (RESERVED.get(key) != null) throw new IllegalArgumentException("reserved key '" + key + "'");
}
/**
* Bind the given key to the given uri in this map. If the key was already bound in this
* map, the binding is overwritten. If the key was bound in an inherited map, it is
* overriden.
*
* @param key the key to use
* @param uri the namespace uri to bind
*/
public void put(String key, String uri) {
checkKey(key);
if (RESERVED.map.containsValue(uri)) throw new IllegalArgumentException("reserved uri '" + uri + "'");
if (map == null) map = new TreeMap();
map.put(key, uri);
}
/**
* Remove any binding for the given key from the map. Has no effect if the key is
* not bound in this map. If the key binding is inherited, the binding is not affected.
*
* @param key the key to remove
*/
public void remove(String key) {
checkKey(key);
if (map != null) map.remove(key);
}
/**
* Clear all bindings from this map. Does not affect any inherited bindings.
*/
public void clear() {
if (map != null) map.clear();
}
/**
* Put all bindings from the given map into this one. Bindings inherited by
* the given map are included. Existing bindings may be overwritten or
* overriden, as appropriate.
*
* @param that the map to copy bindings from
*/
public void putAll(NamespaceMap that) {
if (map == null) map = new TreeMap();
map.putAll(that.getCombinedMap());
}
/**
* Return a realized map of keys to uris that combines all information inherited
* from parents. This is effectively the map that is used for lookups, but it is
* normally kept in virtual form for efficiency. The map returned is a copy and
* is safe for mutation. The map does not include reserved bindings.
*
* @return a combined map of keys to namespace uris
*/
public Map getCombinedMap() {
Map all = parent.getCombinedMap();
if (map != null) all.putAll(map);
return all;
}
}