/* * 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); } } }