/*
* LRUSet.java
*
* Created on Nov 18, 2010, 1:32:31 PM
*
* Description: A set having a maximum capacity whose excess elements are evicted according to least recently used.
*
* Copyright (C) Nov 18, 2010, Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import net.jcip.annotations.NotThreadSafe;
/** A set having a maximum capacity whose excess elements are evicted according to least recently used.
*
* @param <E> the element class
* @author reed
*/
@NotThreadSafe
public class LRUSet<E> implements Set<E> {
/** the serial version UID */
private static final long serialVersionUID = 1L;
/** the dummy map value */
private static final Object PRESENT = new Object();
/** the map */
private final LRUMap<E, Object> map;
/** Constructs a new LRUMap instance.
*
* @param initialCapacity the initial capacity of the cache
* @param maxCapacity the maximum capacity of the cache
*/
public LRUSet(final int initialCapacity, final int maxCapacity) {
//Preconditions
assert initialCapacity >= 0 : "initialCapacity must not be negative";
assert initialCapacity > 0 : "maxCapacity must be positive";
assert initialCapacity <= maxCapacity : "initialCapacity not be greater than maxCapacity";
map = new LRUMap<>(initialCapacity, maxCapacity);
}
/** Returns <tt>true</tt> if this set contains the specified element.
*
* @param obj element whose presence in this set is to be tested
* @return <tt>true</tt> if this set contains the specified element
*/
@Override
@SuppressWarnings("element-type-mismatch")
public boolean contains(final Object obj) {
return map.containsKey(obj);
}
/** Returns an iterator over the elements in this set.
*
* @return an iterator over the elements in this set
*/
@Override
public Iterator<E> iterator() {
return map.keySet().iterator();
}
/** Returns an array containing all of the elements in this set.
*
* @return an array containing all the elements in this set
*/
@Override
public Object[] toArray() {
return map.keySet().toArray();
}
/** Returns an array containing all of the elements in this set.
*
*
* @param <T> the array type
* @param a the array into which the elements of this set are to be
* stored, if it is big enough; otherwise, a new array of the same
* runtime type is allocated for this purpose.
* @return an array containing all the elements in this set
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in this
* set
* @throws NullPointerException if the specified array is null
*/
@Override
@SuppressWarnings("SuspiciousToArrayCall")
public <T> T[] toArray(final T[] a) {
//Preconditions
assert a != null;
return map.keySet().toArray(a);
}
/** Adds the specified element to this set if it is not already present
* (optional operation).
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
* @throws UnsupportedOperationException if the <tt>add</tt> operation
* is not supported by this set
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this set
* @throws NullPointerException if the specified element is null and this
* set does not permit null elements
* @throws IllegalArgumentException if some property of the specified element
* prevents it from being added to this set
*/
@Override
public boolean add(final E e) {
//Preconditions
assert e != null;
if (map.containsKey(e)) {
return true;
}
map.put(e, PRESENT);
return false;
}
/** Removes the specified element from this set if it is present.
*
* @param o object to be removed from this set, if present
* @return <tt>true</tt> if this set contained the specified element
* @throws ClassCastException if the type of the specified element
* is incompatible with this set (optional)
* @throws NullPointerException if the specified element is null and this
* set does not permit null elements (optional)
* @throws UnsupportedOperationException if the <tt>remove</tt> operation
* is not supported by this set
*/
@Override
public boolean remove(final Object o) {
//Preconditions
assert o != null;
@SuppressWarnings("element-type-mismatch")
final Object obj = map.remove(o);
return obj != null;
}
/**
* Returns <tt>true</tt> if this set contains all of the elements of the
* specified collection. If the specified collection is also a set, this
* method returns <tt>true</tt> if it is a <i>subset</i> of this set.
*
* @param c collection to be checked for containment in this set
* @return <tt>true</tt> if this set contains all of the elements of the
* specified collection
* @throws ClassCastException if the types of one or more elements
* in the specified collection are incompatible with this
* set (optional)
* @throws NullPointerException if the specified collection contains one
* or more null elements and this set does not permit null
* elements (optional), or if the specified collection is null
* @see #contains(Object)
*/
@Override
public boolean containsAll(final Collection<?> c) {
//Preconditions
assert c != null;
return map.keySet().containsAll(c);
}
/** Adds all of the elements in the specified collection to this set if
* they're not already present (optional operation). If the specified
* collection is also a set, the <tt>addAll</tt> operation effectively
* modifies this set so that its value is the <i>union</i> of the two
* sets. The behavior of this operation is undefined if the specified
* collection is modified while the operation is in progress.
*
* @param c collection containing elements to be added to this set
* @return <tt>true</tt> if this set changed as a result of the call
*
* @throws UnsupportedOperationException if the <tt>addAll</tt> operation
* is not supported by this set
* @throws ClassCastException if the class of an element of the
* specified collection prevents it from being added to this set
* @throws NullPointerException if the specified collection contains one
* or more null elements and this set does not permit null
* elements, or if the specified collection is null
* @throws IllegalArgumentException if some property of an element of the
* specified collection prevents it from being added to this set
* @see #add(Object)
*/
@Override
public boolean addAll(final Collection<? extends E> c) {
//Preconditions
assert c != null;
boolean isAdded = map.keySet().containsAll(c);
for (final E item : c) {
map.put(item, PRESENT);
}
return isAdded;
}
/** Retains only the elements in this set that are contained in the
* specified collection (optional operation). In other words, removes
* from this set all of its elements that are not contained in the
* specified collection. If the specified collection is also a set, this
* operation effectively modifies this set so that its value is the
* <i>intersection</i> of the two sets.
*
* @param c collection containing elements to be retained in this set
* @return <tt>true</tt> if this set changed as a result of the call
* @throws UnsupportedOperationException if the <tt>retainAll</tt> operation
* is not supported by this set
* @throws ClassCastException if the class of an element of this set
* is incompatible with the specified collection (optional)
* @throws NullPointerException if this set contains a null element and the
* specified collection does not permit null elements (optional),
* or if the specified collection is null
* @see #remove(Object)
*/
@Override
public boolean retainAll(final Collection<?> c) {
//Preconditions
assert c != null;
boolean isChanged = false;
final Iterator<Entry<E, Object>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
final Entry<E, Object> entry = iter.next();
if (!c.contains(entry.getKey())) {
isChanged = true;
iter.remove();
}
}
return isChanged;
}
/** Removes from this set all of its elements that are contained in the
* specified collection (optional operation). If the specified
* collection is also a set, this operation effectively modifies this
* set so that its value is the <i>asymmetric set difference</i> of
* the two sets.
*
* @param c collection containing elements to be removed from this set
* @return <tt>true</tt> if this set changed as a result of the call
* @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
* is not supported by this set
* @throws ClassCastException if the class of an element of this set
* is incompatible with the specified collection (optional)
* @throws NullPointerException if this set contains a null element and the
* specified collection does not permit null elements (optional),
* or if the specified collection is null
* @see #remove(Object)
* @see #contains(Object)
*/
@Override
public boolean removeAll(final Collection<?> c) {
//Preconditions
assert c != null;
boolean isChanged = false;
final Iterator<Entry<E, Object>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
final Entry<E, Object> entry = iter.next();
if (c.contains(entry.getKey())) {
isChanged = true;
iter.remove();
}
}
return isChanged;
}
/** Returns the number of elements in this set (its cardinality). If this
* set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of elements in this set (its cardinality)
*/
@Override
public int size() {
return map.size();
}
/** Returns <tt>true</tt> if this set contains no elements.
*
* @return <tt>true</tt> if this set contains no elements
*/
@Override
public boolean isEmpty() {
return map.isEmpty();
}
/** Removes all of the elements from this set (optional operation).
* The set will be empty after this call returns.
*
* @throws UnsupportedOperationException if the <tt>clear</tt> method
* is not supported by this set
*/
@Override
public void clear() {
map.clear();
}
/** Returns a string representation of this object.
*
* @return a string representation of this object
*/
@Override
public String toString() {
return map.keySet().toString();
}
/** Returns whether some other object equals this one.
*
* @param obj the other object
* @return whether some other object equals this one
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
final LRUSet<E> other = (LRUSet<E>) obj;
return this.map == other.map || (this.map != null && this.map.equals(other.map));
}
/** Returns a hash code for this object.
*
* @return a hash code for this object
*/
@Override
public int hashCode() {
return map.keySet().hashCode();
}
}