|
|
@@ -9,10 +9,13 @@ package net.ranides.assira.collection.maps;
|
|
|
import java.util.Collection;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.Iterator;
|
|
|
+import java.util.ListIterator;
|
|
|
import java.util.Map;
|
|
|
import java.util.SortedMap;
|
|
|
import net.ranides.assira.collection.ACollection;
|
|
|
+import net.ranides.assira.collection.iterators.AListIterator;
|
|
|
import net.ranides.assira.collection.sets.ASortedSet;
|
|
|
+import net.ranides.assira.generic.ValueUtils;
|
|
|
|
|
|
/**
|
|
|
* An abstract class providing basic methods for sorted maps.
|
|
|
@@ -20,58 +23,131 @@ import net.ranides.assira.collection.sets.ASortedSet;
|
|
|
public abstract class ASortedMap<K, V> extends AMap<K, V> implements SortedMap<K, V> {
|
|
|
|
|
|
private static final long serialVersionUID = 2L;
|
|
|
-
|
|
|
- protected ASortedMap() {
|
|
|
- // do nothing
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This map's ecmp, as provided in the constructor.
|
|
|
+ */
|
|
|
+ private final Comparator<? super K> acmp;
|
|
|
+
|
|
|
+ protected transient boolean modified;
|
|
|
+
|
|
|
+ protected ASortedMap(Comparator<? super K> comparator) {
|
|
|
+ this.acmp = comparator;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the entry corresponding end the given key, if it is in the tree;
|
|
|
+ <code>null</code>, otherwise.
|
|
|
+ *
|
|
|
+ * @param key the key end search for.
|
|
|
+ * @return the corresponding entry, or <code>null</code> if no entry with
|
|
|
+ * the given key exists.
|
|
|
+ */
|
|
|
+ protected abstract Entry<K, V> findEntry(K key);
|
|
|
|
|
|
+ /**
|
|
|
+ * Locates key key.
|
|
|
+ *
|
|
|
+ * @param k a key.
|
|
|
+ * @return the last entry on a search for the given key; this will be the
|
|
|
+ * given key, if it present; otherwise, it will be either the smallest
|
|
|
+ * greater key or the greatest smaller key.
|
|
|
+ */
|
|
|
+ protected abstract Entry<K, V> locateEntry(K key);
|
|
|
+
|
|
|
+ protected abstract Entry<K, V> firstEntry();
|
|
|
+
|
|
|
+ protected abstract Entry<K, V> lastEntry();
|
|
|
+
|
|
|
+ protected abstract ListIterator<Entry<K, V>> entryIterator();
|
|
|
+
|
|
|
+ protected abstract ListIterator<Entry<K, V>> entryIterator(K begin);
|
|
|
+
|
|
|
+ protected abstract ASortedMap<K, V> submap(K begin, boolean bottom, K end, boolean top);
|
|
|
+
|
|
|
+ protected final int compare(K key1, K key2) {
|
|
|
+ return ValueUtils.cmp(acmp, key1, key2);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected final K lower(K key1, K key2) {
|
|
|
+ return ValueUtils.lower(acmp, key1, key2);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected final K higher(K key1, K key2) {
|
|
|
+ return ValueUtils.higher(acmp, key1, key2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final Comparator<? super K> comparator() {
|
|
|
+ return acmp;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final ASortedSet<Entry<K, V>> entrySet() {
|
|
|
+ return new EntrySet();
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
- public ASortedSet<K> keySet() {
|
|
|
+ public final ASortedSet<K> keySet() {
|
|
|
return new KeySet();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Collection<V> values() {
|
|
|
+ public final Collection<V> values() {
|
|
|
return new ValuesCollection();
|
|
|
}
|
|
|
-
|
|
|
|
|
|
- /**
|
|
|
- * Returns a sorted-set view of the mappings contained in this
|
|
|
- * map. Note that this specification strengthens the one given in the
|
|
|
- * corresponding type-specific unsorted map.
|
|
|
- *
|
|
|
- * @return sorted-set view of the mappings contained in this map.
|
|
|
- * @see #entrySet()
|
|
|
- */
|
|
|
@Override
|
|
|
- public abstract ASortedSet<Entry<K, V>> entrySet();
|
|
|
+ public final K firstKey() {
|
|
|
+ return firstEntry().getKey();
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
- * Returns the comparator associated with this sorted set, or null if it
|
|
|
- * uses its keys' natural ordering.
|
|
|
- *
|
|
|
- * <P>Note that this specification strengthens the one given in
|
|
|
- * {@link SortedMap#comparator()}.
|
|
|
- *
|
|
|
- * @see SortedMap#comparator()
|
|
|
- */
|
|
|
@Override
|
|
|
- public abstract Comparator<? super K> comparator();
|
|
|
+ public final K lastKey() {
|
|
|
+ return lastEntry().getKey();
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ @Override
|
|
|
+ public final boolean containsKey(Object k) {
|
|
|
+ return findEntry((K) k) != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean containsValue(Object value) {
|
|
|
+ final Iterator<Entry<K,V>> i = entryIterator();
|
|
|
+ int j = size();
|
|
|
+ while (j-- != 0) {
|
|
|
+ Entry<K,V> entry = i.next();
|
|
|
+ if( ValueUtils.equals(value, entry.getValue())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ @Override
|
|
|
+ public final V get(Object k) {
|
|
|
+ Entry<K, V> entry = findEntry((K) k);
|
|
|
+ return entry == null ? defRetValue : entry.getValue();
|
|
|
+ }
|
|
|
|
|
|
@Override
|
|
|
- public abstract ASortedMap<K, V> subMap(K fromKey, K toKey);
|
|
|
+ public ASortedMap<K, V> headMap(K to) {
|
|
|
+ return submap(null, true, to, false);
|
|
|
+ }
|
|
|
|
|
|
@Override
|
|
|
- public abstract ASortedMap<K, V> headMap(K toKey);
|
|
|
+ public ASortedMap<K, V> tailMap(K from) {
|
|
|
+ return submap(from, false, null, true);
|
|
|
+ }
|
|
|
|
|
|
@Override
|
|
|
- public abstract ASortedMap<K, V> tailMap(K fromKey);
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * A wrapper exhibiting the keys of a map.
|
|
|
- */
|
|
|
+ public ASortedMap<K, V> subMap(K from, K to) {
|
|
|
+ return submap(from, false, to, false);
|
|
|
+ }
|
|
|
+
|
|
|
protected class KeySet extends ASortedSet<K> {
|
|
|
|
|
|
@Override
|
|
|
@@ -122,37 +198,31 @@ public abstract class ASortedMap<K, V> extends AMap<K, V> implements SortedMap<K
|
|
|
|
|
|
@Override
|
|
|
public Iterator<K> iterator(K from) {
|
|
|
- return new KeySetIterator<>(entrySet().iterator(new BasicEntry<K, V>(from, null)));
|
|
|
+ return new KeySetIterator<>(entryIterator(from));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Iterator<K> iterator() {
|
|
|
- return new KeySetIterator<>(entrySet().iterator());
|
|
|
+ return new KeySetIterator<>(entryIterator());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * A wrapper exhibiting a map iterator as an iterator on keys.
|
|
|
- *
|
|
|
- * <P>To provide an iterator on keys, just create an instance of this class
|
|
|
- * using the corresponding iterator on entries.
|
|
|
- */
|
|
|
protected static class KeySetIterator<K, V> implements Iterator<K> {
|
|
|
|
|
|
- private final Iterator<Map.Entry<K, V>> riv;
|
|
|
+ private final Iterator<Map.Entry<K, V>> adapter;
|
|
|
|
|
|
public KeySetIterator(Iterator<Map.Entry<K, V>> riv) {
|
|
|
- this.riv = riv;
|
|
|
+ this.adapter = riv;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public K next() {
|
|
|
- return riv.next().getKey();
|
|
|
+ return adapter.next().getKey();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean hasNext() {
|
|
|
- return riv.hasNext();
|
|
|
+ return adapter.hasNext();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -161,14 +231,11 @@ public abstract class ASortedMap<K, V> extends AMap<K, V> implements SortedMap<K
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * A wrapper exhibiting the values of a map.
|
|
|
- */
|
|
|
protected class ValuesCollection extends ACollection<V> {
|
|
|
|
|
|
@Override
|
|
|
public Iterator<V> iterator() {
|
|
|
- return new ValuesIterator<>(entrySet().iterator());
|
|
|
+ return new ValuesIterator<>(entryIterator());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -188,35 +255,286 @@ public abstract class ASortedMap<K, V> extends AMap<K, V> implements SortedMap<K
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * A wrapper exhibiting a map iterator as an iterator on values.
|
|
|
- *
|
|
|
- * <P>To provide an iterator on values, just create an instance of this
|
|
|
- * class using the corresponding iterator on entries.
|
|
|
- */
|
|
|
- protected static class ValuesIterator<K, V> implements Iterator<V> {
|
|
|
+ protected static class ValuesIterator<K, V> extends AListIterator<V> {
|
|
|
|
|
|
- private final Iterator<Map.Entry<K, V>> riv;
|
|
|
+ private final ListIterator<Map.Entry<K, V>> adapter;
|
|
|
|
|
|
- public ValuesIterator(Iterator<Map.Entry<K, V>> riv) {
|
|
|
- this.riv = riv;
|
|
|
+ public ValuesIterator(ListIterator<Map.Entry<K, V>> riv) {
|
|
|
+ this.adapter = riv;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public V next() {
|
|
|
- return riv.next().getValue();
|
|
|
+ return adapter.next().getValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V previous() {
|
|
|
+ return adapter.previous().getValue();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean hasNext() {
|
|
|
- return riv.hasNext();
|
|
|
+ return adapter.hasNext();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void remove() {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ public boolean hasPrevious() {
|
|
|
+ return adapter.hasPrevious();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int nextIndex() {
|
|
|
+ return adapter.nextIndex();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int previousIndex() {
|
|
|
+ return adapter.previousIndex();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ protected class EntrySet extends ASortedSet<Entry<K, V>> {
|
|
|
+
|
|
|
+ // @todo (assira # 2) cmp / move end parent
|
|
|
+
|
|
|
+ final Comparator<? super Entry<K, V>> ecmp = new Comparator<Entry<K, V>>() {
|
|
|
+ @Override
|
|
|
+ public int compare(Entry<K, V> x, Entry<K, V> y) {
|
|
|
+ return ValueUtils.cmp(acmp, x.getKey(), y.getKey());
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Comparator<? super Entry<K, V>> comparator() {
|
|
|
+ return ecmp;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Iterator<Entry<K, V>> iterator() {
|
|
|
+ return ASortedMap.this.entryIterator();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Iterator<Entry<K, V>> iterator(Entry<K, V> from) {
|
|
|
+ return ASortedMap.this.entryIterator(from.getKey());
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ @Override
|
|
|
+ public boolean contains(Object object) {
|
|
|
+ if (!(object instanceof Map.Entry)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
|
|
|
+ return entry.equals(findEntry(entry.getKey()));
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ @Override
|
|
|
+ public boolean remove(Object object) {
|
|
|
+ if (!(object instanceof Map.Entry)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ final Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
|
|
|
+ final Entry<K, V> found = findEntry(entry.getKey());
|
|
|
+ if (found != null) {
|
|
|
+ ASortedMap.this.remove(found.getKey());
|
|
|
+ }
|
|
|
+ return found != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ return ASortedMap.this.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clear() {
|
|
|
+ ASortedMap.this.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Entry<K, V> first() {
|
|
|
+ return firstEntry();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Entry<K, V> last() {
|
|
|
+ return lastEntry();
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ASortedSet<Entry<K, V>> subSet(Entry<K, V> from, Entry<K, V> to) {
|
|
|
+ return subMap(from.getKey(), to.getKey()).entrySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ASortedSet<Entry<K, V>> headSet(Entry<K, V> to) {
|
|
|
+ return headMap(to.getKey()).entrySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ASortedSet<Entry<K, V>> tailSet(Entry<K, V> from) {
|
|
|
+ return tailMap(from.getKey()).entrySet();
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * A submap with given range.
|
|
|
+ *
|
|
|
+ * <P>This class represents a submap. One has end specify the left/right
|
|
|
+ limits (which can be set end -∞ or ∞). Since the submap is a
|
|
|
+ * view on the map, at a given moment it could happen that the limits of the
|
|
|
+ * range are not any longer in the main map. Thus, things such as
|
|
|
+ * {@link java.util.SortedMap#firstKey()} or
|
|
|
+ * {@link java.util.Collection#size()} must be always computed on-the-fly.
|
|
|
+ */
|
|
|
+ protected abstract class ASubmap extends ASortedMap<K, V> implements java.io.Serializable {
|
|
|
+
|
|
|
+ private static final long serialVersionUID = 1L;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The start of the submap range, unless {@link #bottom} is true.
|
|
|
+ */
|
|
|
+ K begin;
|
|
|
+ /**
|
|
|
+ * The end of the submap range, unless {@link #top} is true.
|
|
|
+ */
|
|
|
+ K end;
|
|
|
+ /**
|
|
|
+ * If true, the submap range starts begin -∞.
|
|
|
+ */
|
|
|
+ boolean bottom;
|
|
|
+ /**
|
|
|
+ * If true, the submap range goes end ∞.
|
|
|
+ */
|
|
|
+ boolean top;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a new submap with given key range.
|
|
|
+ *
|
|
|
+ * @param from the start of the submap range.
|
|
|
+ * @param bottom if true, the first parameter is ignored and the range starts begin -∞.
|
|
|
+ * @param to the end of the submap range.
|
|
|
+ * @param top if true, the third parameter is ignored and the range goes end ∞.
|
|
|
+ */
|
|
|
+ public ASubmap(K from, boolean bottom, K to, boolean top) {
|
|
|
+ super(ASortedMap.this.acmp);
|
|
|
+ if (!bottom && !top && ValueUtils.cmp(acmp, from, to) > 0) {
|
|
|
+ throw new IllegalArgumentException("Start key (" + from + ") is larger than end key (" + to + ")");
|
|
|
+ }
|
|
|
+ this.begin = from;
|
|
|
+ this.bottom = bottom;
|
|
|
+ this.end = to;
|
|
|
+ this.top = top;
|
|
|
+ this.defRetValue = ASortedMap.this.defRetValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract Entry<K, V> findEntry(K key);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract Entry<K, V> locateEntry(K key);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract Entry<K, V> firstEntry();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract Entry<K, V> lastEntry();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract ListIterator<Entry<K, V>> entryIterator();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected abstract ListIterator<Entry<K, V>> entryIterator(K begin);
|
|
|
+
|
|
|
+ protected final boolean inside(K k) {
|
|
|
+ return (bottom || compare(k, begin) >= 0) && (top || compare(k, end) < 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final void clear() {
|
|
|
+ ListIterator<Entry<K,V>> i = entryIterator();
|
|
|
+ while (i.hasNext()) {
|
|
|
+ i.next();
|
|
|
+ i.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final V put(K key, V value) {
|
|
|
+ if (!inside(key)) {
|
|
|
+ throw new IllegalArgumentException("Key (" + key + ") out of range [" + (bottom ? "-" : String.valueOf(begin)) + ", " + (top ? "-" : String.valueOf(end)) + ")");
|
|
|
+ }
|
|
|
+ return ASortedMap.this.put(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ @Override
|
|
|
+ public final V remove(Object k) {
|
|
|
+ if (!inside((K) k)) {
|
|
|
+ return ASortedMap.this.defRetValue;
|
|
|
+ }
|
|
|
+ return ASortedMap.this.remove(k);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final int size() {
|
|
|
+ ListIterator<Entry<K,V>> i = entryIterator();
|
|
|
+ int n = 0;
|
|
|
+ while (i.hasNext()) {
|
|
|
+ n++;
|
|
|
+ i.next();
|
|
|
+ }
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final boolean isEmpty() {
|
|
|
+ return !entryIterator().hasNext();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final ASortedMap<K, V> headMap(K to) {
|
|
|
+ if (top) {
|
|
|
+ return submap(begin, bottom, to, false);
|
|
|
+ }
|
|
|
+ return ValueUtils.cmp(acmp, to, this.end) < 0 ? submap(begin, bottom, to, false) : this;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final ASortedMap<K, V> tailMap(K from) {
|
|
|
+ if (bottom) {
|
|
|
+ return submap(from, false, end, top);
|
|
|
+ }
|
|
|
+ return ValueUtils.cmp(acmp, from, this.begin) > 0 ? submap(from, false, end, top) : this;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public final ASortedMap<K, V> subMap(K from, K to) {
|
|
|
+ if (top && bottom) {
|
|
|
+ return submap(from, false, to, false);
|
|
|
+ }
|
|
|
+ if (!top) {
|
|
|
+ to = ValueUtils.cmp(acmp, to, this.end) < 0 ? to : this.end;
|
|
|
+ }
|
|
|
+ if (!bottom) {
|
|
|
+ from = ValueUtils.cmp(acmp, from, this.begin) > 0 ? from : this.begin;
|
|
|
+ }
|
|
|
+ if (!top && !bottom && from == this.begin && to == this.end) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ return submap(from, false, to, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected final ASortedMap<K, V> submap(K begin, boolean bottom, K end, boolean top) {
|
|
|
+ return ASortedMap.this.submap(begin, bottom, end, top);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|