package org.rendersnake.internal; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * A nested hash-based <code>Map</code> implementation. * If an entry is not found in the map at nesting level <em>N</em> * then retry in map <em>N</em>-1, if N >= 0; * return <code>null</code> otherwise. * * @author ernestmicklei */ public class StackedMap implements Map<String, Object> { /** * */ public static int INITIAL_MAP_CAPACITY = 7; private Deque<Map<String,Object>> stack; /** * Constructs an empty <code>StackedMap</code>. */ public StackedMap() { this.init(); } /** * Constructs a new <code>StackedMap</code> with the same mappings as * the specified <code>Map</code>. The mappings will be created at depth * 0. * * @param m * the <code>Map</code> whose mappings are to be placed in this * <code>Map</code>, cannot be <code>null</code>. * * @throws IllegalArgumentException * if <code>m == null</code>. */ public StackedMap(Map<? extends String, ? extends Object> m) { this(); this.putAll(m); } /** * Initialize the receiver with an empty map. */ @SuppressWarnings("unchecked") private void init() { this.stack = new ArrayDeque<Map<String,Object>>(); this.push(); } /** * Increases the depth of the stack of maps. From now on all * {@link #put(String,Object)} operations will store the mappings at the * higher depth. * * <p>Note that {@link #pop()} will make all mappings at the current * depth unavailable. */ public void push() { stack.addFirst(new HashMap<String,Object>(INITIAL_MAP_CAPACITY)); } /** * Decreases the depth of the stack of maps, effectively removing all * mappings at the current level. From now on all * {@link #put(String,Object)} operations will store the mappings at the * lower depth. * * <p>Note that {@link #push()} will create a higher depth. * * @throws IllegalStateException * if <code>getDepth() == 0</code>. */ public void pop() { if (stack.size() == 1) throw new IllegalStateException("getDepth() == 0"); stack.removeFirst(); } /** * Returns the depth of the stack. * * @return * the depth of the stack, always >= 0. */ public int getDepth() { return stack.size(); } /** * Retrieves the map at the top of the stack. * * @return * the {@link HashMap} at the top of the stack ; can be <code>null</code> */ private Map<String, Object> top() { return this.stack.peekFirst(); } // // Map API // public void clear() { for(Map<String,Object> each : stack) { each.clear(); } } public boolean containsKey(Object key) { for (Map<String,Object> here : stack) { if (here.containsKey(key)) { return true; } } return false; } public boolean containsValue(Object value) { for (Map<String,Object> here : stack) { if (here.containsValue(value)) { return true; } } return false; } public Set<java.util.Map.Entry<String, Object>> entrySet() { Set<java.util.Map.Entry<String, Object>> union = new HashSet<java.util.Map.Entry<String, Object>>(); for (Map<String,Object> here : stack) { union.addAll(here.entrySet()); } return union; } public boolean isEmpty() { for (Map<String,Object> here : stack) { if (!here.isEmpty()) { return false; } } return true; } public Set<String> keySet() { Set<String> union = new HashSet<String>(); for (Map<String,Object> here : stack) { union.addAll(here.keySet()); } return union; } public Object get(Object key) { if (key == null) throw new IllegalArgumentException("key == null"); if (!String.class.isAssignableFrom(key.getClass())) throw new IllegalArgumentException("key must be a string"); String sKey = (String) key; for (Map<String,Object> here : stack) { if (here != null && here.containsKey(sKey)) { return here.get(sKey); } } return null; } public Object put(String key, Object value) { if (key == null) throw new IllegalArgumentException("key == null"); return this.top().put(key, value); } public void putAll(Map<? extends String, ? extends Object> m) { if (m == null) throw new IllegalArgumentException("m == null"); this.top().putAll(m); } // note: returns the first non-null value removed public Object remove(Object key) { Object objectToReturn = null; for (Map<String,Object> here : stack) { final Object value = here.remove(key); if (objectToReturn == null) objectToReturn = value; // may still be null } return objectToReturn; } public int size() { int total = 0; for (Map<String,Object> here : stack) { total += here.size(); } return total; } public Collection<Object> values() { // might not be the most efficient implementation.... List<Object> union = new ArrayList<Object>(); for (String each : this.keySet()) { union.add(this.get(each)); } return union; } // Equals API /** * @return the hash of the receiver */ @Override public int hashCode() { int hash = 0; for (Map<String,Object> here : stack) { hash = hash | here.hashCode(); } return hash; } /** * @return whether the contents of otherMap equals to that of the receiver */ @Override public boolean equals(Object otherMap) { if (!(otherMap instanceof StackedMap)) return false; return this.entrySet().equals(((StackedMap) otherMap).entrySet()); } /** * for debugging */ @Override public String toString() { StringBuilder sb = new StringBuilder(64); sb.append('['); Set<String> keys = new TreeSet<String>(this.keySet()); for (String each : keys) { sb .append(each) .append('=') .append(this.get(each)) .append('\n'); } sb.append(']'); return sb.toString(); } }