/*
* JBoss, Home of Professional Open Source.
* Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.cache.util;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Wraps an existing map, which is not modified, reflecting modifications
* in an internal modification set.
*
* This is to minimize the amount of data copying, for instance in the
* case few changes are applied.
*
* Example usage:
*
* HashMap<String, String> hm = new HashMap<String, String>();
* hm.put("a", "apple");
* DeltaMap dm = DeltaMap.create(hm);
* dm.remove("a");
* assert hm.containsKey("a");
* assert !dm.containsKey("a");
* dm.commit();
* assert !hm.containsKey("a");
*
*
* @author Elias Ross
* @param key type
* @param value type
*/
public class DeltaMap extends AbstractMap
{
/**
* Wrapped instance.
*/
private Map original;
/**
* Keys excluded.
*/
private Set exclude;
/**
* Keys changed.
* This may contain new entries or entries modified.
*/
private Map changed = new HashMap();
/**
* Constructs a new DeltaMap.
*/
private DeltaMap(Map original, Set exclude)
{
if (original == null)
throw new NullPointerException("original");
if (exclude == null)
throw new NullPointerException("exclude");
this.original = original;
this.exclude = exclude;
}
/**
* Creates and returns a DeltaMap for an original map.
*
* @param original will not be modified, except by {@link #commit()}
* @return a new instance
*/
public static DeltaMap create(Map original)
{
return new DeltaMap(original, new HashSet());
}
/**
* Creates and returns a DeltaMap for an empty map.
*
* @return a new instance
*/
public static DeltaMap create()
{
return create(new HashMap(0));
}
/**
* Creates and returns a DeltaMap for an original map, excluding some key mappings.
*
* @param original will not be modified, except by {@link #commit()}
* @param exclude entries not to include
* @return a new instance
*/
public static DeltaMap excludeKeys(Map original, Set exclude)
{
return new DeltaMap(original, exclude);
}
/**
* Creates and returns a DeltaMap for an original map, excluding some key mappings.
*/
public static DeltaMap excludeKeys(Map original, K... exclude)
{
return excludeKeys(original, new HashSet(Arrays.asList(exclude)));
}
@Override
public Set> entrySet()
{
return new AbstractSet>()
{
@Override
public Iterator> iterator()
{
return new WrappedIterator();
}
@Override
public int size()
{
return DeltaMap.this.size();
}
};
}
@Override
public int size()
{
int size = original.size();
for (Object o : changed.keySet())
{
if (!original.containsKey(o))
size++;
}
for (Object o : exclude)
{
if (original.containsKey(o))
size--;
}
return size;
}
@Override
public boolean containsKey(Object key)
{
if (exclude.contains(key))
return false;
if (changed.containsKey(key))
return true;
return original.containsKey(key);
}
@Override
public V get(Object key)
{
if (exclude.contains(key))
return null;
if (changed.containsKey(key))
return changed.get(key);
return original.get(key);
}
@Override
public V put(K key, V value)
{
V old;
if (changed.containsKey(key))
old = changed.get(key);
else
old = original.get(key);
changed.put(key, value);
if (exclude.contains(key))
{
exclude.remove(key);
return null;
}
return old;
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object key)
{
if (changed.containsKey(key))
{
if (original.containsKey(key))
exclude.add((K) key);
return changed.remove(key);
}
if (exclude.contains(key))
{
return null;
}
if (original.containsKey(key))
{
exclude.add((K) key);
return original.get(key);
}
return null;
}
/**
* Commits the changes to the original map.
* Clears the list of removed keys.
*/
public void commit()
{
original.keySet().removeAll(exclude);
original.putAll(changed);
exclude.clear();
changed.clear();
}
/**
* Returns true if the internal map was modified.
*/
public boolean isModified()
{
return !changed.isEmpty() || !exclude.isEmpty();
}
/**
* Iterator that skips over removed entries.
*
* @author Elias Ross
*/
private class WrappedIterator implements Iterator>
{
private boolean orig = true;
private boolean nextSet = false;
private Entry next;
private Iterator> i = original.entrySet().iterator();
private boolean redef(Entry e)
{
K key = e.getKey();
return exclude.contains(key) || changed.containsKey(key);
}
public boolean hasNext()
{
if (nextSet)
return true;
if (orig)
{
while (true)
{
if (!i.hasNext())
{
orig = false;
i = changed.entrySet().iterator();
return hasNext();
}
next = i.next();
if (!redef(next))
{
nextSet = true;
return true;
}
}
}
if (!i.hasNext())
return false;
next = i.next();
nextSet = true;
return true;
}
public java.util.Map.Entry next()
{
if (!hasNext())
throw new NoSuchElementException();
try
{
return next;
}
finally
{
nextSet = false;
}
}
public void remove()
{
DeltaMap.this.remove(next.getKey());
}
}
/**
* Returns a debug string.
*/
public String toDebugString()
{
return "DeltaMap original=" + original + " exclude=" + exclude + " changed=" + changed;
}
@Override
public void clear()
{
exclude.addAll(original.keySet());
changed.clear();
}
/**
* Returns the original wrapped Map.
*/
public Map getOriginal()
{
return original;
}
/**
* Sets the original values of this delta map.
*/
public void setOriginal(Map original)
{
if (original == null)
throw new NullPointerException("original");
this.original = original;
}
/**
* Returns a Map of the entries changed, not including those removed.
*/
public Map getChanged()
{
return changed;
}
/**
* Returns the entries removed, including entries excluded by the constructor.
*/
public Set getRemoved()
{
return exclude;
}
}