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

swim.spatial.QTreeMap Maven / Gradle / Ivy

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.spatial;

import java.util.Comparator;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import swim.codec.Debug;
import swim.codec.Format;
import swim.codec.Output;
import swim.math.Z2Form;
import swim.util.Cursor;
import swim.util.Murmur3;

public class QTreeMap extends QTreeContext implements SpatialMap, Comparator>, Cloneable, Debug {
  final Z2Form shapeForm;
  volatile QTreePage root;

  protected QTreeMap(Z2Form shapeForm, QTreePage root) {
    this.shapeForm = shapeForm;
    this.root = root;
  }

  public QTreeMap(Z2Form shapeForm) {
    this(shapeForm, QTreePage.empty());
  }

  public Z2Form shapeForm() {
    return this.shapeForm;
  }

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

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

  @Override
  public boolean containsKey(K key, S shape) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    return this.root.containsKey(key, x, y, this);
  }

  @Override
  public boolean containsKey(Object key) {
    final Cursor> cursor = this.root.cursor();
    while (cursor.hasNext()) {
      if (key.equals(cursor.next().key)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean containsValue(Object value) {
    final Cursor> cursor = this.root.cursor();
    while (cursor.hasNext()) {
      if (value.equals(cursor.next().value)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public V get(K key, S shape) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    return this.root.get(key, x, y, this);
  }

  @Override
  public V get(Object key) {
    final Cursor> cursor = this.root.cursor();
    while (cursor.hasNext()) {
      final QTreeEntry slot = cursor.next();
      if (key.equals(slot.key)) {
        return slot.value;
      }
    }
    return null;
  }

  @Override
  public V put(K key, S shape, V newValue) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    do {
      final QTreePage oldRoot = this.root;
      final QTreePage newRoot = oldRoot.updated(key, shape, x, y, newValue, this)
          .balanced(this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return oldRoot.get(key, x, y, this);
        }
      } else {
        return null;
      }
    } while (true);
  }

  @Override
  public V move(K key, S oldShape, S newShape, V newValue) {
    final Z2Form shapeForm = this.shapeForm;
    final long oldX = BitInterval.span(shapeForm.getXMin(oldShape), shapeForm.getXMax(oldShape));
    final long oldY = BitInterval.span(shapeForm.getYMin(oldShape), shapeForm.getYMax(oldShape));
    final long newX = BitInterval.span(shapeForm.getXMin(newShape), shapeForm.getXMax(newShape));
    final long newY = BitInterval.span(shapeForm.getYMin(newShape), shapeForm.getYMax(newShape));
    do {
      final QTreePage oldRoot = this.root;
      final QTreePage newRoot = oldRoot.removed(key, oldX, oldY, this)
          .balanced(this)
          .updated(key, newShape, newX, newY, newValue, this)
          .balanced(this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return oldRoot.get(key, oldX, oldY, this);
        }
      } else {
        return null;
      }
    } while (true);
  }

  @Override
  public V remove(K key, S shape) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    do {
      final QTreePage oldRoot = this.root;
      final QTreePage newRoot = oldRoot.removed(key, x, y, this)
          .balanced(this);
      if (oldRoot != newRoot) {
        if (ROOT.compareAndSet(this, oldRoot, newRoot)) {
          return oldRoot.get(key, x, y, this);
        }
      } else {
        return null;
      }
    } while (true);
  }

  @Override
  public void clear() {
    this.root = QTreePage.empty();
  }

  public QTreeMap updated(K key, S shape, V newValue) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    final QTreePage oldRoot = this.root;
    QTreePage newRoot = oldRoot.updated(key, shape, x, y, newValue, this);
    if (oldRoot != newRoot) {
      if (newRoot.span() > oldRoot.span()) {
        newRoot = newRoot.balanced(this);
      }
      return copy(newRoot);
    } else {
      return this;
    }
  }

  public QTreeMap removed(K key, S shape) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    final QTreePage oldRoot = this.root;
    QTreePage newRoot = oldRoot.removed(key, x, y, this);
    if (oldRoot != newRoot) {
      newRoot = newRoot.balanced(this);
      return copy(newRoot);
    } else {
      return this;
    }
  }

  @Override
  public Cursor> iterator(S shape) {
    final Z2Form shapeForm = this.shapeForm;
    final long x = BitInterval.span(shapeForm.getXMin(shape), shapeForm.getXMax(shape));
    final long y = BitInterval.span(shapeForm.getYMin(shape), shapeForm.getYMax(shape));
    return new QTreeShapeCursor(this.root.cursor(x, y), shapeForm, shape);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Cursor> iterator() {
    return (Cursor>) (Cursor) this.root.cursor();
  }

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

  @Override
  public Cursor valueIterator() {
    return Cursor.values(this.root.cursor());
  }

  public SpatialMap snapshot() {
    return new QTree(this.shapeForm, this.root);
  }

  @Override
  public QTreeMap clone() {
    return copy(this.root);
  }

  protected QTreeMap copy(QTreePage root) {
    return new QTreeMap(this.shapeForm, root);
  }

  @SuppressWarnings("unchecked")
  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    } else if (other instanceof QTreeMap) {
      final QTreeMap that = (QTreeMap) other;
      if (size() == that.size()) {
        final Cursor> those = that.iterator();
        while (those.hasNext()) {
          final Entry entry = those.next();
          final V value = get(entry.getKey());
          final V v = entry.getValue();
          if (value == null ? v != null : !value.equals(v)) {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashSeed == 0) {
      hashSeed = Murmur3.seed(QTree.class);
    }
    int a = 0;
    int b = 0;
    int c = 1;
    final Cursor> these = iterator();
    while (these.hasNext()) {
      final Entry entry = these.next();
      final int h = Murmur3.mix(Murmur3.hash(entry.getKey()), Murmur3.hash(entry.getValue()));
      a ^= h;
      b += h;
      if (h != 0) {
        c *= h;
      }
    }
    return Murmur3.mash(Murmur3.mix(Murmur3.mix(Murmur3.mix(hashSeed, a), b), c));
  }

  @Override
  public void debug(Output output) {
    output = output.write("QTree").write('.').write("empty").write('(')
        .debug(this.shapeForm).write(')');
    for (Entry entry : this) {
      output = output.write('.').write("updated").write('(')
          .debug(entry.getKey()).write(", ")
          .debug(entry.getShape()).write(", ")
          .debug(entry.getValue()).write(')');
    }
  }

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

  private static int hashSeed;

  @SuppressWarnings("rawtypes")
  public static  QTreeMap empty(Z2Form shapeForm) {
    return new QTreeMap(shapeForm);
  }

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