/*
* Copyright 2001-2009 Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.utils;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* <p>
* An implementation of <code>Map</code> that wraps another <code>Map</code>
* and flags itself 'dirty' when it is modified.
* </p>
*
* @author James House
*/
public class DirtyFlagMap<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Data members.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
private static final long serialVersionUID = 1433884852607126222L;
private boolean dirty = false;
private Map<K,V> map;
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constructors.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
* <p>
* Create a DirtyFlagMap that 'wraps' a <code>HashMap</code>.
* </p>
*
* @see java.util.HashMap
*/
public DirtyFlagMap() {
map = new HashMap<K,V>();
}
/**
* <p>
* Create a DirtyFlagMap that 'wraps' a <code>HashMap</code> that has the
* given initial capacity.
* </p>
*
* @see java.util.HashMap
*/
public DirtyFlagMap(final int initialCapacity) {
map = new HashMap<K,V>(initialCapacity);
}
/**
* <p>
* Create a DirtyFlagMap that 'wraps' a <code>HashMap</code> that has the
* given initial capacity and load factor.
* </p>
*
* @see java.util.HashMap
*/
public DirtyFlagMap(final int initialCapacity, final float loadFactor) {
map = new HashMap<K,V>(initialCapacity, loadFactor);
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
* <p>
* Clear the 'dirty' flag (set dirty flag to <code>false</code>).
* </p>
*/
public void clearDirtyFlag() {
dirty = false;
}
/**
* <p>
* Determine whether the <code>Map</code> is flagged dirty.
* </p>
*/
public boolean isDirty() {
return dirty;
}
/**
* <p>
* Get a direct handle to the underlying Map.
* </p>
*/
public Map<K,V> getWrappedMap() {
return map;
}
public void clear() {
if (!map.isEmpty()) {
dirty = true;
}
map.clear();
}
public boolean containsKey(final Object key) {
return map.containsKey(key);
}
public boolean containsValue(final Object val) {
return map.containsValue(val);
}
public Set<Entry<K,V>> entrySet() {
return new DirtyFlagMapEntrySet(map.entrySet());
}
@Override
public boolean equals(final Object obj) {
if (obj == null || !(obj instanceof DirtyFlagMap)) {
return false;
}
return map.equals(((DirtyFlagMap<?,?>) obj).getWrappedMap());
}
@Override
public int hashCode()
{
return map.hashCode();
}
public V get(final Object key) {
return map.get(key);
}
public boolean isEmpty() {
return map.isEmpty();
}
public Set<K> keySet() {
return new DirtyFlagSet<K>(map.keySet());
}
public V put(final K key, final V val) {
dirty = true;
return map.put(key, val);
}
public void putAll(final Map<? extends K, ? extends V> t) {
if (!t.isEmpty()) {
dirty = true;
}
map.putAll(t);
}
public V remove(final Object key) {
V obj = map.remove(key);
if (obj != null) {
dirty = true;
}
return obj;
}
public int size() {
return map.size();
}
public Collection<V> values() {
return new DirtyFlagCollection<V>(map.values());
}
@Override
@SuppressWarnings("unchecked") // suppress warnings on generic cast of super.clone() and map.clone() lines.
public Object clone() {
DirtyFlagMap<K,V> copy;
try {
copy = (DirtyFlagMap<K,V>) super.clone();
if (map instanceof HashMap) {
copy.map = (Map<K,V>)((HashMap<K,V>)map).clone();
}
} catch (CloneNotSupportedException ex) {
throw new IncompatibleClassChangeError("Not Cloneable.");
}
return copy;
}
/**
* Wrap a Collection so we can mark the DirtyFlagMap as dirty if
* the underlying Collection is modified.
*/
private class DirtyFlagCollection<T> implements Collection<T> {
private Collection<T> collection;
public DirtyFlagCollection(final Collection<T> c) {
collection = c;
}
protected Collection<T> getWrappedCollection() {
return collection;
}
public Iterator<T> iterator() {
return new DirtyFlagIterator<T>(collection.iterator());
}
public boolean remove(final Object o) {
boolean removed = collection.remove(o);
if (removed) {
dirty = true;
}
return removed;
}
public boolean removeAll(final Collection<?> c) {
boolean changed = collection.removeAll(c);
if (changed) {
dirty = true;
}
return changed;
}
public boolean retainAll(final Collection<?> c) {
boolean changed = collection.retainAll(c);
if (changed) {
dirty = true;
}
return changed;
}
public void clear() {
if (collection.isEmpty() == false) {
dirty = true;
}
collection.clear();
}
// Pure wrapper methods
public int size() { return collection.size(); }
public boolean isEmpty() { return collection.isEmpty(); }
public boolean contains(final Object o) { return collection.contains(o); }
public boolean add(final T o) { return collection.add(o); } // Not supported
public boolean addAll(final Collection<? extends T> c) { return collection.addAll(c); } // Not supported
public boolean containsAll(final Collection<?> c) { return collection.containsAll(c); }
public Object[] toArray() { return collection.toArray(); }
public <U> U[] toArray(final U[] array) { return collection.toArray(array); }
}
/**
* Wrap a Set so we can mark the DirtyFlagMap as dirty if
* the underlying Collection is modified.
*/
private class DirtyFlagSet<T> extends DirtyFlagCollection<T> implements Set<T> {
public DirtyFlagSet(final Set<T> set) {
super(set);
}
protected Set<T> getWrappedSet() {
return (Set<T>)getWrappedCollection();
}
}
/**
* Wrap an Iterator so that we can mark the DirtyFlagMap as dirty if an
* element is removed.
*/
private class DirtyFlagIterator<T> implements Iterator<T> {
private Iterator<T> iterator;
public DirtyFlagIterator(final Iterator<T> iterator) {
this.iterator = iterator;
}
public void remove() {
dirty = true;
iterator.remove();
}
// Pure wrapper methods
public boolean hasNext() { return iterator.hasNext(); }
public T next() { return iterator.next(); }
}
/**
* Wrap a Map.Entry Set so we can mark the Map as dirty if
* the Set is modified, and return Map.Entry objects
* wrapped in the <code>DirtyFlagMapEntry</code> class.
*/
private class DirtyFlagMapEntrySet extends DirtyFlagSet<Map.Entry<K,V>> {
public DirtyFlagMapEntrySet(final Set<Map.Entry<K,V>> set) {
super(set);
}
@Override
public Iterator<Map.Entry<K,V>> iterator() {
return new DirtyFlagMapEntryIterator(getWrappedSet().iterator());
}
@Override
public Object[] toArray() {
return toArray(new Object[super.size()]);
}
@SuppressWarnings("unchecked") // suppress warnings on both U[] and U casting.
@Override
public <U> U[] toArray(final U[] array) {
if (array.getClass().getComponentType().isAssignableFrom(Map.Entry.class) == false) {
throw new IllegalArgumentException("Array must be of type assignable from Map.Entry");
}
int size = super.size();
U[] result =
array.length < size ?
(U[])Array.newInstance(array.getClass().getComponentType(), size) : array;
Iterator<Map.Entry<K,V>> entryIter = iterator(); // Will return DirtyFlagMapEntry objects
for (int i = 0; i < size; i++) {
result[i] = ( U ) entryIter.next();
}
if (result.length > size) {
result[size] = null;
}
return result;
}
}
/**
* Wrap an Iterator over Map.Entry objects so that we can
* mark the Map as dirty if an element is removed or modified.
*/
private class DirtyFlagMapEntryIterator extends DirtyFlagIterator<Map.Entry<K,V>> {
public DirtyFlagMapEntryIterator(final Iterator<Map.Entry<K,V>> iterator) {
super(iterator);
}
@Override
public DirtyFlagMapEntry next() {
return new DirtyFlagMapEntry(super.next());
}
}
/**
* Wrap a Map.Entry so we can mark the Map as dirty if
* a value is set.
*/
private class DirtyFlagMapEntry implements Map.Entry<K,V> {
private Map.Entry<K,V> entry;
public DirtyFlagMapEntry(final Map.Entry<K,V> entry) {
this.entry = entry;
}
public V setValue(final V o) {
dirty = true;
return entry.setValue(o);
}
// Pure wrapper methods
public K getKey() { return entry.getKey(); }
public V getValue() { return entry.getValue(); }
public boolean equals(Object o) { return entry.equals(o); }
}
}