/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotools.util;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* A set with elements ordered by the amount of time they were {@linkplain #add added}.
* Less frequently added elements are first, and most frequently added ones are last.
* If some elements were added the same amount of time, then the iterator will traverse
* them in their insertion order.
* <p>
* An optional boolean argument in the constructor allows the construction of set in reversed
* order (most frequently added elements first, less frequently added last). This is similar
* but not identical to creating a defaut {@code FrequencySortedSet} and iterating through it
* in reverse order. The difference is that elements added the same amount of time will still
* be traversed in their insertion order.
* <p>
* This class is <strong>not</strong> thread-safe. Synchronizations (if wanted) are user's
* reponsability.
*
* @param <E> The type of elements in the set.
*
* @since 2.5
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*/
public class FrequencySortedSet<E> extends AbstractSet<E> implements SortedSet<E>, Comparator<E>, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 6034102231354388179L;
/**
* The frequency of occurence for each element. We must use a linked hash map instead of an
* ordinary hash map because we want to preserve insertion order for elements that occur at
* the same frequency.
*/
private final Map<E,Integer> count;
/**
* {@code +1} if the element should be sorted in the usual order, or {@code -1}
* if the elements should be sorted in reverse order (most frequent element first).
*/
private final int order;
/**
* Elements in sorted order, or {@code null} if not yet computed.
*/
private transient E[] sorted;
/**
* The frequency for each {@linkplain #sorted} element. This array is invalid
* if {@link #sorted} is null.
*/
private transient int[] frequencies;
/**
* Creates an initially empty set with less frequent elements first.
*/
public FrequencySortedSet() {
this(false);
}
/**
* Creates an initially empty set with the default initial capacity.
*
* @param reversed {@code true} if the elements should be sorted in reverse order
* (most frequent element first, less frequent last).
*/
public FrequencySortedSet(final boolean reversed) {
this(16, reversed);
}
/**
* Creates an initially empty set with the specified initial capacity.
*
* @param initialCapacity The initial capacity.
* @param reversed {@code true} if the elements should be sorted in reverse order
* (most frequent element first, less frequent last).
*/
public FrequencySortedSet(final int initialCapacity, final boolean reversed) {
count = new LinkedHashMap<E,Integer>(initialCapacity);
order = reversed ? -1 : +1;
}
/**
* Returns the number of elements in this set.
*/
public int size() {
return count.size();
}
/**
* Returns {@code true} if this set is empty.
*/
@Override
public boolean isEmpty() {
return count.isEmpty();
}
/**
* Adds the specified element to this set. Returns {@code true} if this set changed as a
* result of this operation. Changes in element order are not notified by the returned
* value.
*
* @param element The element to add.
* @param occurence The number of time to add the given elements. The default value is 1.
* @return {@code true} if this set changed as a result of this operation.
* @throws IllegalArgumentException If {@code occurence} is negative.
*/
public boolean add(final E element, int occurence) throws IllegalArgumentException {
if (occurence != 0) {
if (occurence < 0) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NOT_GREATER_THAN_ZERO_$1, occurence));
}
sorted = null;
occurence *= order;
final Integer n = count.put(element, occurence);
if (n == null) {
return true;
}
count.put(element, n + occurence);
}
return false;
}
/**
* Adds the specified element to this set. Returns {@code true} if this set changed as a
* result of this operation. Changes in element order are not notified by the returned
* value.
*
* @param element The element to add.
* @return {@code true} if this set changed as a result of this operation.
*/
@Override
public boolean add(final E element) {
return add(element, 1);
}
/**
* Returns {@code true} if this set contains the specified element.
*
* @param element The element whose presence in this set is to be tested.
* @return {@code true} if this set contains the specified element.
*/
@Override
public boolean contains(final Object element) {
return count.containsKey(element);
}
/**
* Removes the specified element from this set, no matter how many time it has been added.
* Returns {@code true} if this set changed as a result of this operation.
*
* @param element The element to remove.
* @return {@code true} if this set changed as a result of this operation.
*/
@Override
public boolean remove(final Object element) {
if (count.remove(element) != null) {
sorted = null;
return true;
} else {
return false;
}
}
/**
* Removes all elements from this set.
*/
@Override
public void clear() {
sorted = null;
count.clear();
}
/**
* Returns an iterator over the elements in this set in frequency order.
*/
public Iterator<E> iterator() {
ensureSorted();
return new Iter();
}
/**
* Iterator over sorted elements.
*/
private final class Iter implements Iterator<E> {
/**
* A copy of {@link FrequencySortedSet#sorted} at the time this iterator has been created.
* Used because the {@code sorted} array is set to {@code null} when {@link #remove()} is
* invoked.
*/
private final E[] elements;
/**
* Index of the next element to return.
*/
private int index;
/**
* Creates an new iterator.
*/
Iter() {
elements = sorted;
}
/**
* Returns {@code true} if there is more elements to return.
*/
public boolean hasNext() {
return index < elements.length;
}
/**
* Return the next element.
*/
public E next() {
if (index >= elements.length) {
throw new NoSuchElementException();
}
return elements[index++];
}
/**
* Remove the last elements returned by {@link #next}.
*/
public void remove() {
if (index == 0) {
throw new IllegalStateException();
}
if (!FrequencySortedSet.this.remove(elements[index - 1])) {
// Could also be ConcurrentModificationException - we do not differentiate.
throw new IllegalStateException();
}
}
}
/**
* @todo Not yet implemented.
*/
public SortedSet<E> headSet(E toElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* @todo Not yet implemented.
*/
public SortedSet<E> tailSet(E fromElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* @todo Not yet implemented.
*/
public SortedSet<E> subSet(E fromElement, E toElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Returns the first element in this set.
* <ul>
* <li>For sets created with the default order, this is the less frequently added element.
* If more than one element were added with the same frequency, this is the first one
* that has been {@linkplain #added} to this set at this frequency.</li>
* <li>For sets created with the reverse order, this is the most frequently added element.
* If more than one element were added with the same frequency, this is the first one
* that has been {@linkplain #added} to this set at this frequency.</li>
* </ul>
*
* @throws NoSuchElementException if this set is empty.
*/
public E first() throws NoSuchElementException {
ensureSorted();
if (sorted.length != 0) {
return sorted[0];
} else {
throw new NoSuchElementException();
}
}
/**
* Returns the last element in this set.
* <ul>
* <li>For sets created with the default order, this is the most frequently added element.
* If more than one element were added with the same frequency, this is the last one
* that has been {@linkplain #added} to this set at this frequency.</li>
* <li>For sets created with the reverse order, this is the less frequently added element.
* If more than one element were added with the same frequency, this is the last one
* that has been {@linkplain #added} to this set at this frequency.</li>
* </ul>
*
* @throws NoSuchElementException if this set is empty.
*/
public E last() throws NoSuchElementException {
ensureSorted();
final int length = sorted.length;
if (length != 0) {
return sorted[length - 1];
} else {
throw new NoSuchElementException();
}
}
/**
* Sorts the elements in frequency order, if not already done. The sorted array will contains
* all elements without duplicated values, with the less frequent element first and the most
* frequent last (or the converse if this set has been created for reverse order). If some
* elements appear at the same frequency, then their ordering will be preserved.
*/
@SuppressWarnings("unchecked")
private void ensureSorted() {
if (sorted != null) {
return;
}
final Map.Entry<E,Integer>[] entries = count.entrySet().toArray(new Map.Entry[count.size()]);
Arrays.sort(entries, COMPARATOR);
final int length = entries.length;
sorted = (E[]) new Object[length];
if (frequencies == null || frequencies.length != length) {
frequencies = new int[length];
}
for (int i=0; i<length; i++) {
final Map.Entry<E,Integer> entry = entries[i];
sorted[i] = entry.getKey();
frequencies[i] = Math.abs(entry.getValue());
}
}
/**
* The comparator used for sorting map entries. Most be consistent with
* {@link #compare} implementation.
*/
private static final Comparator<Map.Entry<?,Integer>> COMPARATOR = new Comparator<Map.Entry<?,Integer>>() {
public int compare(Map.Entry<?,Integer> o1, Map.Entry<?,Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
};
/**
* Returns the comparator used to order the elements in this set. For a
* {@code FrequencySortedSet}, the comparator is always {@code this}.
* <p>
* This method is final because the {@code FrequencySortedSet} implementation makes
* assumptions on the comparator that would not hold if this method were overrided.
*/
public final Comparator<E> comparator() {
return this;
}
/**
* Compares the specified elements for {@linkplain #frequency frequency}. For
* {@code FrequencySortedSet} with default ordering, this method returns a positive
* number if {@code o1} has been added more frequently to this set than {@code o2},
* a negative number if {@code o1} has been added less frequently than {@code o2},
* and 0 otherwise. For {@code FrequencySortedSet} with reverse ordering, this is the
* converse.
* <p>
* This method is final because the {@code FrequencySortedSet} implementation makes
* assumptions on the comparator that would not hold if this method were overrided.
*/
public final int compare(final E o1, final E o2) {
return signedFrequency(o1) - signedFrequency(o2);
}
/**
* Returns the frequency of the specified element in this set. Returns
* a negative number if this set has been created for reversed order.
*/
private int signedFrequency(final E element) {
final Integer n = count.get(element);
return (n != null) ? n : 0;
}
/**
* Returns the frequency of the specified element in this set.
*
* @param element The element whose frequency is to be obtained.
* @return The frequency of the given element, or {@code 0} if it doesn't occur in this set.
*/
public int frequency(final E element) {
return Math.abs(signedFrequency(element));
}
/**
* Returns the frequency of each element in this set, in iteration order.
*
* @return The frequency of each element in this set.
*/
public int[] frequencies() {
ensureSorted();
return frequencies.clone();
}
/**
* Returns the content of this set as an array.
*/
@Override
public Object[] toArray() {
ensureSorted();
return sorted.clone();
}
/**
* Returns the content of this set as an array.
*
* @param <T> The type of the array elements.
* @param array The array where to copy the elements.
* @return The elements in the given array, or in a new array if the given array doesn't
* have a sufficient capacity.
*/
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] array) {
ensureSorted();
if (array.length < sorted.length) {
array = (T[]) Array.newInstance(array.getClass().getComponentType(), sorted.length);
}
System.arraycopy(sorted, 0, array, 0, sorted.length);
return array;
}
}