// Copyright 2012 Google Inc. All Rights Reserved. // // 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 com.google.collide.client.util.collections; import com.google.common.annotations.VisibleForTesting; import com.google.gwt.core.client.JavaScriptObject; import java.util.Iterator; import java.util.NoSuchElementException; import javax.annotation.Nonnull; /** * <a href="http://en.wikipedia.org/wiki/Skip_list">Skip List</a> * implementation that holds Strings. * * <p>Actually this class provides some services similar to * {@link java.util.SortedSet}, i.e. items can be placed / removed and * sequentially accessed. * * <p>If some value is added two or more times, only one value is placed in set. * So, when such value is removed (even one time), it will not appear in search * results (until it is added again). Also, removing value that do not exist * has no effect. * * <p>{@code null} values are not allowed for adding / removing / search. */ public final class SkipListStringSet { private final LevelGenerator levelGenerator; private final int maxLevel; /** * Object that generates integer values with semi-geometric * distribution in range [0 .. maxLevel). * * <p>Mostly, this interface is used for testing purpose. */ @VisibleForTesting interface LevelGenerator { int generate(); } private static class RandomLevelGenerator implements LevelGenerator { private final int maxValue; private final double promotionProbability; private RandomLevelGenerator(int maxLevel, double promotionProbability) { this.maxValue = maxLevel - 1; this.promotionProbability = promotionProbability; } @Override public int generate() { int result = 0; while (Math.random() < promotionProbability && result < maxValue) { result++; } return result; } } /** * Skip list node. */ private static class Node extends JavaScriptObject { private static native Node createHead(int maxLevel) /*-{ return new Array(maxLevel); }-*/; private static native Node create(String value, int maxLevel) /*-{ var result = new Array(maxLevel); result.value = value; return result; }-*/; protected Node() { } private Node getNext() { return get(0); } private native Node get(int level) /*-{ return this[level]; }-*/; private native void set(int level, Node node) /*-{ this[level] = node; }-*/; private native String getValue() /*-{ return this.value; }-*/; } private static class SkipListIterator implements Iterator<String> { private Node nextItem; public SkipListIterator(Node nextItem) { this.nextItem = nextItem; } @Override public boolean hasNext() { return nextItem.getNext() != null; } @Override public String next() { Node next = nextItem.getNext(); if (next == null) { throw new NoSuchElementException(); } nextItem = next; return nextItem.getValue(); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * Height of the most "tall" item. */ private int currentLevel; /** * Special item that placed before all other items, and do not hold value, */ private final Node head; public static SkipListStringSet create() { return new SkipListStringSet(8, new RandomLevelGenerator(8, 0.25)); } @VisibleForTesting SkipListStringSet(int maxLevel, LevelGenerator levelGenerator) { this.levelGenerator = levelGenerator; this.maxLevel = maxLevel; this.currentLevel = 0; this.head = Node.createHead(maxLevel); } /** * Adds value to "set". * * @param item non-{@code null} value to add. */ public void add(@Nonnull String item) { Node backtrace = doSearch(item); Node cursor = backtrace.get(0).getNext(); // If node with specified value exists: do nothing. if (cursor != null && cursor.getValue().equals(item)) { return; } int nodeLevel = levelGenerator.generate(); if (nodeLevel > currentLevel) { for (int i = currentLevel + 1; i <= nodeLevel; i++) { backtrace.set(i, head); } currentLevel = nodeLevel; } Node newNode = Node.create(item, nodeLevel + 1); for (int i = 0; i <= nodeLevel; ++i) { newNode.set(i, backtrace.get(i).get(i)); backtrace.get(i).set(i, newNode); } } /** * Searches the given node and returns "backtrace". */ private Node doSearch(@Nonnull String item) { Node backtrace = Node.createHead(maxLevel); Node cursor = head; for (int i = currentLevel; i >= 0; --i) { Node next = cursor.get(i); while (next != null && next.getValue().compareTo(item) < 0) { cursor = next; next = cursor.get(i); } backtrace.set(i, cursor); } return backtrace; } /** * Searches for the earliest (in sorting order) item greater or equal to * the given one. * * @param item non-{@code null} value to search */ public Iterable<String> search(@Nonnull final String item) { return new Iterable<String>() { @Override public Iterator<String> iterator() { return new SkipListIterator(doSearch(item).get(0)); } }; } /** * Returns {@link Iterable} whose iterators first value is the * first item in this collection, so all items could be traversed. */ public Iterable<String> first() { return new Iterable<String>() { @Override public Iterator<String> iterator() { return new SkipListIterator(head); } }; } /** * Removes value from "set". * * @param item non-{@code null} value to search */ public void remove(@Nonnull String item) { Node backtrace = doSearch(item); Node cursor = backtrace.get(0).getNext(); // If node with specified do not exist: do nothing. if (cursor == null || !cursor.getValue().equals(item)) { return; } for (int i = 0; i <= currentLevel; i++) { if (backtrace.get(i).get(i) == cursor) { backtrace.get(i).set(i, cursor.get(i)); } } while (currentLevel > 0 && head.get(currentLevel) == null) { currentLevel--; } } }