package org.exist.fluent; 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 <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a> */ public class NamespaceMap implements Cloneable { private static class ReservedMap extends NamespaceMap { public ReservedMap() { super(); // bootstrap root node directly to avoid checks map = new TreeMap<String, String>(); map.put("xml", "http://www.w3.org/XML/1998/namespace"); map.put("xmlns", "http://www.w3.org/2000/xmlns/"); assert parent == null; } @Override public Map<String,String> getCombinedMap() { return new TreeMap<String,String>(); } } /** * Reserved keys and URIs, always declared and not overwriteable or overrideable. * Note that the reserved map's parent will be <code>null</code>, even though * it goes through the common constructor, since at construction time the * <code>RESERVED</code> field has not yet been initialized. It is the only map * that will have a <code>null</code> parent. */ private static final NamespaceMap RESERVED = new ReservedMap(); protected Map<String, String> map; /** * The parent map from which bindings are inherited. It cannot be modified * or accessed through its children. */ protected NamespaceMap parent; /** * Return whether the given prefix is reserved by the XML spec and should not be * manually bound to namespaces. * * @param prefix the prefix to check * @return <code>true</code> if the prefix is reserved, <code>false</code> if it's available for binding */ public static boolean isReservedPrefix(String prefix) { return RESERVED.get(prefix) != null; } /** * 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 interleaving keys and their associated URIs; its length must be even */ 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. The immediate bindings are cloned, but the ineritance * chain remains unaltered. * * @return a combined clone of this map cascade */ @Override public NamespaceMap clone() { try { NamespaceMap clone = (NamespaceMap) super.clone(); if (this.map != null) clone.map = new HashMap<String,String>(this.map); return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException("unexpected exception", e); } } /** * Sever this namespace map from its parents. The contents are collapsed prior to * the map being severed, so the mappings are not affected. However, any future * changes to the previous parents will have no effect on this map. Calling {@link #clear()} * on a severed map is guaranteed to clear all bindings. */ public void sever() { map = getCombinedMap(); parent = RESERVED; } /** * Get the URI bound to the given key, either in this map or the closest inherited one. * If the key is not bound, return <code>null</code>. * * @param key the key to look up * @return the bound URI or <code>null</code> 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<String, String>(); 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<String, String>(); map.putAll(that.getCombinedMap()); } /** * Sever inheritance connections and replace all bindings with ones from the given * map. Equivalent to calling {@link #sever()}, {@link #clear()} and {@link #putAll(NamespaceMap)} * in sequence. * * @param that the map to copy bindings from */ public void replaceWith(NamespaceMap that) { parent = RESERVED; map = new HashMap<String,String>(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<String,String> getCombinedMap() { Map<String, String> all = parent.getCombinedMap(); if (map != null) all.putAll(map); return all; } /** * Return whether this namespace map contains no bindings other than inherited * ones, and inherits from the given parent. * * @param freshParent the fresh parent to compare against * @return <code>true</code> if this namespace map is empty except for possible * inherited bindings, <code>false</code> otherwise */ public boolean isFreshFrom(NamespaceMap freshParent) { return (map == null || map.isEmpty()) && parent == freshParent; } @Override public boolean equals(Object o) { if (!(o instanceof NamespaceMap)) return false; return getCombinedMap().equals(((NamespaceMap) o).getCombinedMap()); } @Override public int hashCode() { return getCombinedMap().hashCode(); } }