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; } }