/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; import java.util.AbstractSequentialList; import java.util.Map; import java.util.TreeMap; import java.util.SortedMap; import java.util.Collections; import java.util.NoSuchElementException; import java.io.Serializable; /** * List of elements sorted by a key which is not the element itself. * <p> * This class is <strong>not</strong> thread-safe. Synchronizations (if wanted) are user's * reponsability. * * @param <K> The type of keys in the sorted list, to be used for sorting. * @param <V> The type of elements in the list. * * @since 2.2 * * @source $URL$ * @version $Id$ * @author Simone Giannecchini * @author Martin Desruisseaux */ public class KeySortedList<K extends Comparable<K>,V> extends AbstractSequentialList<V> implements Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 6969483179756527012L; /** * The sorted map of <var>key</var>-<var>list of values</var> pairs. */ private final SortedMap<K, List<V>> map; /** * Creates a new, initially empty list. */ public KeySortedList() { map = new TreeMap<K, List<V>>(); } /** * Creates a list using the specified map of <var>key</var>-<var>list of values</var> pairs. */ private KeySortedList(final SortedMap<K, List<V>> map) { this.map = map; } /** * Removes all of the elements from this list. */ @Override public void clear() { map.clear(); } /** * Returns the number of elements in this list. */ public int size() { int count = 0; for (final List<V> list : map.values()) { count += list.size(); } return count; } /** * Inserts the specified element at a position determined by the specified key. If some * elements were already inserted for the specified key, then this method do not replaces * the old value (like what a {@link Map} would do), but instead add the new element with * the same key. * * @param key Key to be used to find the right location. * @param element Object to be inserted. */ public void add(final K key, final V element) { List<V> values = map.get(key); if (values == null) { values = new ArrayList<V>(); map.put(key, values); } values.add(element); } /** * Removes all values that were {@linkplain #add(Comparable,Object) added} with the specified * key. * * @param key The key of values to remove. * @return The number of elements removed. */ public int removeAll(final K key) { final List<V> values = map.remove(key); return (values!=null) ? values.size() : 0; } /** * Returns the number of elements {@linkplain #add(Comparable,Object) added} with the specified * key. * * @param key The key of elements to count. * @return The number of elements inserted with the given key. */ public int count(final K key) { final List<V> values = map.get(key); return (values!=null) ? values.size() : 0; } /** * Returns {@code true} if the list contains an element {@linkplain #add(Comparable,Object) * added} with the specified key. This is equivalent to testing * <code>{@linkplain #count count}(key) != 0</code>. */ public boolean containsKey(final K key) { return map.containsKey(key); } /** * Returns the first element * {@linkplain #add(Comparable,Object) added} with the specified key. * * @param key The key for the element to search for. * @return The first element added with the specified key. * @throws NoSuchElementException if there is no element for the specified key. */ public V first(final K key) throws NoSuchElementException { final List<V> values = map.get(key); if (values==null || values.isEmpty()) { throw new NoSuchElementException(); } return values.get(0); } /** * Returns the last element * {@linkplain #add(Comparable,Object) added} with the specified key. * * @param key The key for the element to search for. * @return The last element added with the specified key. * @throws NoSuchElementException if there is no element for the specified key. */ public V last(final K key) throws NoSuchElementException { final List<V> values = map.get(key); if (values==null || values.isEmpty()) { throw new NoSuchElementException(); } return values.get(values.size()-1); } /** * Returns a list iterator of the elements in this list (in proper sequence), starting * at the elements {@linkplain #add(Comparable,Object) added} with the specified key. * * @param fromKey The key of the first element to returns. * @return A list iterator of the elements in this list (in proper sequence). * @throws IndexOutOfBoundsException if the index is out of range. */ public ListIterator<V> listIterator(final K fromKey) { return new Iter(fromKey); } /** * Returns a list iterator of the elements in this list (in proper sequence), starting at the * specified position. The specified index indicates the first element that would be returned * by an initial call to the {@link ListIterator#next next()} method. * * @param index Index of first element to be returned from the list iterator. * @return A list iterator of the elements in this list (in proper sequence). * @throws IndexOutOfBoundsException if the index is out of range. */ public ListIterator<V> listIterator(final int index) { return new Iter(index); } /** * The list iterator required for {@link AbstractSequentialList} implementation. */ private final class Iter implements ListIterator<V> { /** * The iterator over <var>key</var>-<var>list of values</var> pairs. */ private Iterator<Map.Entry<K, List<V>>> entriesIter; /** * The current key, or {@code null} if we are past the last entry. */ private K key; /** * The values list for the current key. */ private List<V> values; /** * The iterator over the current values list. */ private ListIterator<V> valuesIter; /** * The base index for the current values list. */ private int base; /** * Creates an iterator initialy positioned to the first value of the specified key. */ public Iter(final K fromKey) { entriesIter = map.entrySet().iterator(); while (entriesIter.hasNext()) { final Map.Entry<K, List<V>> entry = entriesIter.next(); key = entry.getKey(); values = entry.getValue(); if (fromKey.compareTo(key) <= 0) { valuesIter = values.listIterator(); assert equals(new Iter(base)); return; } base += values.size(); } key = null; values = Collections.emptyList(); valuesIter = values.listIterator(); } /** * Creates an iterator initialy positioned to the specified index. */ public Iter(int index) { entriesIter = map.entrySet().iterator(); while (entriesIter.hasNext()) { final Map.Entry<K, List<V>> entry = entriesIter.next(); key = entry.getKey(); values = entry.getValue(); final int size = values.size(); if (index < size) { valuesIter = values.listIterator(index); return; } index -= size; base += size; } if (index != 0) { throw new IndexOutOfBoundsException(); } key = null; values = Collections.emptyList(); valuesIter = values.listIterator(); } /** * Returns {@code true} if this list iterator has more elements when traversing the list * in the forward direction. */ public boolean hasNext() { return valuesIter.hasNext() || entriesIter.hasNext(); } /** * Returns the next element in the list. */ public V next() { while (!valuesIter.hasNext()) { if (entriesIter.hasNext()) { final Map.Entry<K, List<V>> entry = entriesIter.next(); base += values.size(); // Must be before 'values' new assignement. key = entry.getKey(); values = entry.getValue(); valuesIter = values.listIterator(); } else { key = null; values = Collections.emptyList(); valuesIter = values.listIterator(); break; } } return valuesIter.next(); } /** * Returns {@code true} if this list iterator has more elements when traversing the list * in the reverse direction. */ public boolean hasPrevious() { return valuesIter.hasPrevious() || base!=0; } /** * Returns the previous element in the list. */ public V previous() { while (!valuesIter.hasPrevious() && base!=0) { /* * Gets the key from the previous entry, and recreates a new entries iterator * starting from this key (the assert statement ensure that). It should be the * only place where this iterator needs to be recreated. Hopefully it should not * happen often. */ key = map.headMap(key).lastKey(); entriesIter = map.tailMap(key).entrySet().iterator(); final Map.Entry<K, List<V>> entry = entriesIter.next(); assert key == entry.getKey() : key; /* * Updates the values list, iterator and base index. It should now reflect the * content of the list in the previous entry. */ values = entry.getValue(); final int size = values.size(); valuesIter = values.listIterator(Math.max(size-1, 0)); base -= size; // Must be after 'values' new assignement. assert base >= 0 : base; } return valuesIter.previous(); } /** * Returns the index of the element that would be returned by a subsequent * call to {@link #next}. */ public int nextIndex() { return base + valuesIter.nextIndex(); } /** * Returns the index of the element that would be returned by a subsequent * call to {@link #previous}. */ public int previousIndex() { return base + valuesIter.previousIndex(); } /** * Removes from the list the last element that was returned by * {@link #next} or {@link #previous} */ public void remove() { valuesIter.remove(); } /** * Replaces the last element returned by {@link #next} or {@link #previous} * with the specified element. */ public void set(final V o) { valuesIter.set(o); } /** * Inserts the specified element into the list. The element will have the same key than * the one from the previous call to {@link #next} or {@link #previous}. */ public void add(final V o) { valuesIter.add(o); } /** * Compares two iterators for equality, assuming that they are iterator for the same * {@link KeySortedList} (this is not verified). This method is used for assertions only. */ private boolean equals(final Iter that) { return this.key == that.key && this.values == that.values && this.base == that.base && this.valuesIter.nextIndex() == that.valuesIter.nextIndex(); } } /** * Returns a view of the portion of this list whose keys are strictly less than {@code toKey}. * The returned list is backed by this list, so changes in the returned list are reflected in * this list, and vice-versa. * * @param toKey high endpoint (exclusive) of the sub list. * @return A view of the specified initial range of this list. */ public KeySortedList<K,V> headList(final K toKey) { return new KeySortedList<K,V>(map.headMap(toKey)); } /** * Returns a view of the portion of this list whose keys are greater than or equal to * {@code fromKey}. The returned list is backed by this list, so changes in the returned * list are reflected in this list, and vice-versa. * * @param fromKey low endpoint (inclusive) of the sub list. * @return A view of the specified final range of this list. */ public KeySortedList<K,V> tailList(final K fromKey) { return new KeySortedList<K,V>(map.tailMap(fromKey)); } }