/**
* Copyright 2008 Google 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.waveprotocol.wave.client.common.util;
import org.waveprotocol.wave.model.util.CollectionUtils;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Implements a PruningSequenceMap using a doubly-linked list.
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public final class LinkedPruningSequenceMap<K extends VolatileComparable<? super K>, V>
implements PruningSequenceMap<K, V> {
private final Comparator<K> cmp;
private Node first = null;
private Node last = null;
private Node recent = null;
private int size = 0;
private class Node implements SequenceElement<V> {
Node next;
Node prev;
K key;
V value;
private Node(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public Node getNext() {
while (next != null && !next.key.isComparable()) {
remove(next);
}
return next;
}
@Override
public Node getPrev() {
while (prev != null && !prev.key.isComparable()) {
remove(prev);
}
return prev;
}
@Override
public V value() {
return value;
}
}
/**
* Factory method for when the key type is Comparable
*
* @return instance with comparator automatically supplied
*/
public static <K extends VolatileComparable<K>, V> LinkedPruningSequenceMap<K, V> create() {
return new LinkedPruningSequenceMap<K, V>(new Comparator<K>() {
public int compare(K o1, K o2) {
return o1.compareTo(o2);
}
});
}
@Override
public void clear() {
// Use getFirst() to ensure pruning of invalid nodes
while (getFirst() != null) {
remove(first.key);
}
}
/**
* Constructor
*
* @param cmp
* Comparator to use
*/
public LinkedPruningSequenceMap(Comparator<K> cmp) {
this.cmp = cmp;
}
@Override
public V get(K key) {
SequenceElement<V> node = getElement(key);
return node == null ? null : node.value();
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public V put(K key, V value) {
V old = null;
if (size == 0) {
emptyPut(key, value);
} else {
Node prev = findBefore(key);
// findBefore might have emptied the map, so check again
if (size == 0) {
emptyPut(key, value);
} else {
if (prev == null || cmp.compare(key, prev.key) != 0) {
Node node = new Node(key, value);
// goes after last
if (prev == last) {
last = node;
}
// goes before first
if (prev == null) {
prev = last;
first = node;
}
insertAfter(prev, node);
size++;
} else {
old = prev.value;
prev.value = value;
}
}
}
return old;
}
@Override
public V remove(K key) {
V val = null;
if (!key.isComparable()) {
// Key is already gone. If it is still in the node structure, it will get
// cleaned up eventually and transparently. Logically, the entry is gone
// the instant the key becomes non-comparable.
return null;
}
Node node = findBefore(key);
if (node != null && cmp.compare(key, node.key) == 0) {
val = node.value;
remove(node);
}
return val;
}
@Override
public int size() {
return size;
}
@Override
public SequenceElement<V> getElement(K key) {
Node prev = findBefore(key);
if (prev == null) {
return first;
}
return cmp.compare(prev.key, key) == 0 ? prev : null;
}
@Override
public boolean isFirst(SequenceElement<V> elt) {
return elt == first;
}
@Override
public boolean isLast(SequenceElement<V> elt) {
return elt == last;
}
@Override
public Node getFirst() {
return getUsable(first);
}
@Override
public Node getLast() {
return getUsable(last);
}
@Override
public Iterable<K> copyKeys() {
Node first = getFirst();
if (first != null) {
List<K> keys = CollectionUtils.newArrayList();
Node node = first;
do {
keys.add(node.key);
node = node.getNext();
} while (node != first);
return keys;
} else {
return Collections.emptyList();
}
}
@Override
public Node findBefore(K key) {
if (key == null) {
throw new IllegalArgumentException("key is null");
}
Node search = getUsable(recent);
if (search == null) {
return null;
}
while (true) {
int cmpPrev = cmp.compare(search.key, key);
if (cmpPrev <= 0) {
if (last == search || cmpPrev == 0) {
return search;
}
Node next = search.getNext();
int cmpNext = cmp.compare(next.key, key);
if (cmpNext > 0) {
return search;
}
search = search.getNext();
} else {
if (first == search) {
return null;
}
search = search.getPrev();
}
}
}
/**
* put, when the map is empty.
*/
private void emptyPut(K key, V value) {
Node node = new Node(key, value);
first = last = recent = node;
recent.next = recent;
recent.prev = recent;
size++;
}
/**
* Place node in the doubly linked list after prev
*
* @param prev
* @param node
*/
private void insertAfter(Node prev, Node node) {
Node next = prev.getNext();
node.prev = prev;
node.next = next;
prev.next = node;
next.prev = node;
}
private void remove(Node node) {
Node prev = node.prev, next = node.next;
prev.next = next;
next.prev = prev;
node.next = node.prev = null; // cleanup node, in case of external use
if (recent == node) {
recent = prev;
}
if (first == node && last == node) {
first = null;
last = null;
recent = null;
} else {
if (first == node) {
first = next;
}
if (last == node) {
last = prev;
}
}
size--;
}
/**
* @param node A node that may or may not be currently comparable
* @return The input if usable, otherwise a nearby usable node, or null if none
*/
private Node getUsable(Node node) {
if (node != null && !node.key.isComparable()) {
if (isLast(node)) {
node = node.getNext();
if (node != null) {
node = node.getPrev();
}
} else {
node = node.getPrev();
if (node != null) {
node = node.getNext();
}
}
}
return node;
}
}