All Downloads are FREE. Search and download functionalities are using the official Maven repository.

swim.collections.STreeList Maven / Gradle / Ivy

Go to download

Immutable, structure sharing collections, including hash array mapped tries, finger tries, B-trees, and S-trees (sequence trees)

There is a newer version: 4.3.15
Show newest version
// Copyright 2015-2019 SWIM.AI 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 swim.collections;

import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import swim.codec.Debug;
import swim.codec.Format;
import swim.codec.Output;
import swim.util.Cursor;
import swim.util.KeyedList;
import swim.util.Murmur3;

/**
 * Mutable, thread-safe {@link KeyedList} backed by an S-Tree.
 */
public class STreeList extends STreeContext implements KeyedList, Cloneable, Debug {
  volatile STreePage root;

  protected STreeList(STreePage root) {
    this.root = root;
  }

  public STreeList() {
    this(STreePage.empty());
  }

  @Override
  public boolean isEmpty() {
    return this.root.isEmpty();
  }

  @Override
  public int size() {
    return this.root.size();
  }

  @Override
  public boolean contains(Object value) {
    return this.root.contains(value);
  }

  @Override
  public boolean containsAll(Collection values) {
    final STreePage root = this.root;
    for (Object value : values) {
      if (!root.contains(value)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public int indexOf(Object value) {
    return this.root.indexOf(value);
  }

  @Override
  public int lastIndexOf(Object value) {
    return this.root.lastIndexOf(value);
  }

  @Override
  public T get(int index) {
    return get(index, null);
  }

  @Override
  public T get(int index, Object key) {
    if (key != null) {
      index = lookup(index, key);
      if (index < 0) {
        return null;
      }
    }
    return this.root.get(index);
  }

  @Override
  public Map.Entry getEntry(int index) {
    return getEntry(index, null);
  }

  @Override
  public Map.Entry getEntry(int index, Object key) {
    if (key != null) {
      index = lookup(index, key);
      if (index < 0) {
        return null;
      }
    }
    return size() <= index ? null : this.root.getEntry(index);
  }

  @Override
  public T set(int index, T newValue) {
    return set(index, newValue, null);
  }

  @Override
  public T set(int index, T newValue, Object key) {
    if (key != null) {
      index = lookup(index, key);
      if (index < 0) {
        throw new NoSuchElementException(key.toString());
      }
    }
    do {
      final STreePage oldRoot = this.root;
      if (index < 0 || index >= oldRoot.size()) {
        throw new IndexOutOfBoundsException(Integer.toString(index));
      }
      final STreePage newRoot = oldRoot.updated(index, newValue, this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return oldRoot.get(index);
        }
      } else {
        return null;
      }
    } while (true);
  }

  @Override
  public boolean add(T newValue) {
    return add(newValue, null);
  }

  @Override
  public boolean add(T newValue, Object key) {
    do {
      final STreePage oldRoot = this.root;
      final STreePage newRoot = oldRoot.appended(newValue, key, this).balanced(this);
      if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
        return true;
      }
    } while (true);
  }

  @Override
  public boolean addAll(Collection newValues) {
    boolean modified = false;
    for (T newValue : newValues) {
      add(newValue);
      modified = true;
    }
    return modified;
  }

  @Override
  public void add(int index, T newValue) {
    add(index, newValue, null);
  }

  @Override
  public void add(int index, T newValue, Object key) {
    do {
      final STreePage oldRoot = this.root;
      if (index < 0 || index > oldRoot.size()) {
        throw new IndexOutOfBoundsException(Integer.toString(index));
      }
      final STreePage newRoot = oldRoot.inserted(index, newValue, key, this).balanced(this);
      if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
        return;
      }
    } while (true);
  }

  @Override
  public boolean addAll(int index, Collection newValues) {
    boolean modified = false;
    for (T newValue : newValues) {
      add(index, newValue);
      index += 1;
      modified = true;
    }
    return modified;
  }

  @Override
  public T remove(int index) {
    return remove(index, null);
  }

  @Override
  public T remove(int index, Object key) {
    if (key != null) {
      index = lookup(index, key);
      if (index < 0) {
        return null;
      }
    }
    do {
      final STreePage oldRoot = this.root;
      if (index < 0 || index > oldRoot.size()) {
        return null;
      }
      final STreePage newRoot = oldRoot.removed(index, this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return oldRoot.get(index);
        }
      } else {
        return null;
      }
    } while (true);
  }

  @Override
  public boolean remove(Object value) {
    do {
      final STreePage oldRoot = this.root;
      final STreePage newRoot = oldRoot.removed(value, this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return true;
        }
      } else {
        return false;
      }
    } while (true);
  }

  @Override
  public boolean removeAll(Collection values) {
    do {
      final STreePage oldRoot = this.root;
      STreePage newRoot = oldRoot;
      int n = newRoot.size();
      int i = 0;
      while (i < n) {
        final T value = newRoot.get(i);
        if (values.contains(value)) {
          newRoot = newRoot.removed(i, this);
          n -= 1;
        } else {
          i += 1;
        }
      }
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return true;
        }
      } else {
        return false;
      }
    } while (true);
  }

  @Override
  public boolean retainAll(Collection values) {
    do {
      final STreePage oldRoot = this.root;
      STreePage newRoot = oldRoot;
      int n = newRoot.size();
      int i = 0;
      while (i < n) {
        final T value = newRoot.get(i);
        if (!values.contains(value)) {
          newRoot = newRoot.removed(i, this);
          n -= 1;
        } else {
          i += 1;
        }
      }
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return true;
        }
      } else {
        return false;
      }
    } while (true);
  }

  @Override
  public void move(int fromIndex, int toIndex) {
    move(fromIndex, toIndex, null);
  }

  @Override
  public void move(int fromIndex, int toIndex, Object key) {
    if (key != null) {
      fromIndex = lookup(fromIndex, key);
      if (fromIndex < 0) {
        throw new NoSuchElementException(key.toString());
      }
    }
    do {
      final STreePage oldRoot = this.root;
      if (fromIndex < 0 || fromIndex >= oldRoot.size()) {
        throw new IndexOutOfBoundsException(Integer.toString(fromIndex));
      }
      if (toIndex < 0 || toIndex >= oldRoot.size()) {
        throw new IndexOutOfBoundsException(Integer.toString(toIndex));
      }
      if (fromIndex != toIndex) {
        final Map.Entry entry = oldRoot.getEntry(fromIndex);
        final STreePage newRoot = oldRoot.removed(fromIndex, this)
            .inserted(toIndex, entry.getValue(), entry.getKey(), this)
            .balanced(this);
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void drop(int lower) {
    do {
      final STreePage oldRoot = this.root;
      if (lower > 0 && oldRoot.size() > 0) {
        final STreePage newRoot;
        if (lower < oldRoot.size()) {
          newRoot = oldRoot.drop(lower, this);
        } else {
          newRoot = STreePage.empty();
        }
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void take(int keep) {
    do {
      final STreePage oldRoot = this.root;
      if (keep < oldRoot.size() && oldRoot.size() > 0) {
        final STreePage newRoot;
        if (keep > 0) {
          newRoot = oldRoot.take(keep, this);
        } else {
          newRoot = STreePage.empty();
        }
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void clear() {
    STreePage oldRoot;
    do {
      oldRoot = this.root;
    } while (!ROOT.compareAndSet(this, oldRoot, STreePage.empty()));
  }

  @Override
  public Object[] toArray() {
    final STreePage root = this.root;
    final int n = root.size();
    final Object[] array = new Object[n];
    root.copyToArray(array, 0);
    return array;
  }

  @SuppressWarnings("unchecked")
  @Override
  public  U[] toArray(U[] array) {
    final STreePage root = this.root;
    final int n = root.size();
    if (array.length < n) {
      array = (U[]) Array.newInstance(array.getClass().getComponentType(), n);
    }
    root.copyToArray(array, 0);
    if (array.length > n) {
      array[n] = null;
    }
    return array;
  }

  @Override
  public Cursor iterator() {
    return this.root.iterator();
  }

  @Override
  public Cursor listIterator() {
    return this.root.iterator();
  }

  @Override
  public Cursor listIterator(int index) {
    final Cursor cursor = listIterator();
    cursor.skip(index);
    return cursor;
  }

  @Override
  public Cursor keyIterator() {
    return this.root.keyIterator();
  }

  @Override
  public Cursor> entryIterator() {
    return this.root.entryIterator();
  }

  public Cursor reverseIterator() {
    return this.root.reverseIterator();
  }

  public Cursor reverseKeyIterator() {
    return this.root.reverseKeyIterator();
  }

  public Cursor> reverseEntryIterator() {
    return this.root.reverseEntryIterator();
  }

  public STree snapshot() {
    return new STree(this.root);
  }

  @Override
  public List subList(int fromIndex, int toIndex) {
    if (fromIndex > toIndex) {
      throw new IllegalArgumentException();
    }
    return new STreeListSubList(this, fromIndex, toIndex);
  }

  public STree clone() {
    return copy(this.root);
  }

  protected STree copy(STreePage root) {
    return new STree(root);
  }

  protected int lookup(int start, Object key) {
    final STreePage root = this.root;
    start = Math.min(Math.max(0, start), root.size() - 1);
    if (start > -1) { // when root.size() is 0
      int index = start;
      do {
        final Map.Entry entry = root.getEntry(index);
        if (entry != null && compare(entry.getKey(), key) == 0) {
          return index;
        }
        index = (index + 1) % root.size();
      } while (index != start);
    }
    return -1;
  }

  @SuppressWarnings("unchecked")
  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    } else if (other instanceof STree) {
      final STree that = (STree) other;
      if (this.size() == that.size()) {
        final Cursor these = iterator();
        final Cursor those = that.iterator();
        while (these.hasNext() && those.hasNext()) {
          final T x = these.next();
          final T y = those.next();
          if (x == null ? y != null : !x.equals(y)) {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashSeed == 0) {
      hashSeed = Murmur3.seed(STree.class);
    }
    int h = hashSeed;
    final Cursor these = iterator();
    while (these.hasNext()) {
      h = Murmur3.mix(h, Murmur3.hash(these.next()));
    }
    return Murmur3.mash(h);
  }

  @Override
  public void debug(Output output) {
    output = output.write("STreeList").write('.');
    final Cursor these = iterator();
    if (these.hasNext()) {
      output = output.write("of").write('(').debug(these.next());
      while (these.hasNext()) {
        output = output.write(", ").debug(these.next());
      }
    } else {
      output = output.write("empty").write('(');
    }
    output = output.write(')');
  }

  @Override
  public String toString() {
    return Format.debug(this);
  }

  private static int hashSeed;

  public static  STreeList empty() {
    return new STreeList();
  }

  @SuppressWarnings("unchecked")
  public static  STree of(T... values) {
    final STree tree = new STree();
    for (T value : values) {
      tree.add(value);
    }
    return tree;
  }

  @SuppressWarnings("rawtypes")
  static final AtomicReferenceFieldUpdater ROOT =
      AtomicReferenceFieldUpdater.newUpdater(STreeList.class, STreePage.class, "root");
}

final class STreeListSubList extends AbstractList {
  final STreeList inner;
  final int fromIndex;
  final int toIndex;

  STreeListSubList(STreeList inner, int fromIndex, int toIndex) {
    this.inner = inner;
    this.fromIndex = fromIndex;
    this.toIndex = toIndex;
  }

  @Override
  public int size() {
    return this.toIndex - this.fromIndex;
  }

  @Override
  public T get(int index) {
    final int i = this.fromIndex + index;
    if (i < this.fromIndex || i >= this.toIndex) {
      throw new IndexOutOfBoundsException(Integer.toString(index));
    }
    return this.inner.get(i);
  }

  @Override
  public List subList(int fromIndex, int toIndex) {
    if (fromIndex > toIndex) {
      throw new IllegalArgumentException();
    }
    fromIndex += this.fromIndex;
    toIndex += this.fromIndex;
    if (toIndex > this.toIndex) {
      throw new IndexOutOfBoundsException();
    }
    return new STreeListSubList(this.inner, fromIndex, toIndex);
  }
}