org.h2.mvstore.rtree.MVRTreeMap Maven / Gradle / Ivy
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore.rtree;
import java.util.ArrayList;
import java.util.Iterator;
import org.h2.mvstore.CursorPos;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.Page;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
import org.h2.util.New;
/**
* An r-tree implementation. It supports both the linear and the quadratic split
* algorithm.
*
* @param the value class
*/
public class MVRTreeMap extends MVMap {
/**
* The spatial key type.
*/
final SpatialDataType keyType;
private boolean quadraticSplit;
public MVRTreeMap(int dimensions, DataType valueType) {
super(new SpatialDataType(dimensions), valueType);
this.keyType = (SpatialDataType) getKeyType();
}
/**
* Create a new map with the given dimensions and value type.
*
* @param the value type
* @param dimensions the number of dimensions
* @param valueType the value type
* @return the map
*/
public static MVRTreeMap create(int dimensions, DataType valueType) {
return new MVRTreeMap(dimensions, valueType);
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key) {
return (V) get(root, key);
}
/**
* Iterate over all keys that have an intersection with the given rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findIntersectingKeys(SpatialKey x) {
return new RTreeCursor(root, x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
return keyType.isOverlap(key, test);
}
};
}
/**
* Iterate over all keys that are fully contained within the given
* rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findContainedKeys(SpatialKey x) {
return new RTreeCursor(root, x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
if (leaf) {
return keyType.isInside(key, test);
}
return keyType.isOverlap(key, test);
}
};
}
private boolean contains(Page p, int index, Object key) {
return keyType.contains(p.getKey(index), key);
}
/**
* Get the object for the given key. An exact match is required.
*
* @param p the page
* @param key the key
* @return the value, or null if not found
*/
protected Object get(Page p, Object key) {
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Object o = get(p.getChildPage(i), key);
if (o != null) {
return o;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
return p.getValue(i);
}
}
}
return null;
}
@Override
protected synchronized Object remove(Page p, long writeVersion, Object key) {
Object result = null;
if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
result = p.getValue(i);
p.remove(i);
break;
}
}
return result;
}
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page cOld = p.getChildPage(i);
// this will mark the old page as deleted
// so we need to update the parent in any case
// (otherwise the old page might be deleted again)
Page c = cOld.copy(writeVersion);
long oldSize = c.getTotalCount();
result = remove(c, writeVersion, key);
p.setChild(i, c);
if (oldSize == c.getTotalCount()) {
continue;
}
if (c.getTotalCount() == 0) {
// this child was deleted
p.remove(i);
if (p.getKeyCount() == 0) {
c.removePage();
}
break;
}
Object oldBounds = p.getKey(i);
if (!keyType.isInside(key, oldBounds)) {
p.setKey(i, getBounds(c));
}
break;
}
}
return result;
}
private Object getBounds(Page x) {
Object bounds = keyType.createBoundingBox(x.getKey(0));
for (int i = 1; i < x.getKeyCount(); i++) {
keyType.increaseBounds(bounds, x.getKey(i));
}
return bounds;
}
@Override
@SuppressWarnings("unchecked")
public V put(SpatialKey key, V value) {
return (V) putOrAdd(key, value, false);
}
/**
* Add a given key-value pair. The key should not exist (if it exists, the
* result is undefined).
*
* @param key the key
* @param value the value
*/
public void add(SpatialKey key, V value) {
putOrAdd(key, value, true);
}
private synchronized Object putOrAdd(SpatialKey key, V value, boolean alwaysAdd) {
beforeWrite();
long v = writeVersion;
Page p = root.copy(v);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSplitSize() &&
p.getKeyCount() > 3) {
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, v);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
Page.PageReference[] children = {
new Page.PageReference(p, p.getPos(), p.getTotalCount()),
new Page.PageReference(split, split.getPos(), split.getTotalCount()),
new Page.PageReference(null, 0, 0)
};
p = Page.create(this, v,
keys, null,
children,
totalCount, 0);
// now p is a node; continues
}
add(p, v, key, value);
result = null;
} else {
result = set(p, v, key, value);
}
newRoot(p);
return result;
}
/**
* Update the value for the given key. The key must exist.
*
* @param p the page
* @param writeVersion the write version
* @param key the key
* @param value the new value
* @return the old value (never null)
*/
private Object set(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
p.setKey(i, key);
return p.setValue(i, value);
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
if (get(c, key) != null) {
c = c.copy(writeVersion);
Object result = set(c, writeVersion, key, value);
p.setChild(i, c);
return result;
}
}
}
}
throw DataUtils.newIllegalStateException(DataUtils.ERROR_INTERNAL,
"Not found: {0}", key);
}
private void add(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
p.insertLeaf(p.getKeyCount(), key, value);
return;
}
// p is a node
int index = -1;
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
index = i;
break;
}
}
if (index < 0) {
// a new entry, we don't know where to add yet
float min = Float.MAX_VALUE;
for (int i = 0; i < p.getKeyCount(); i++) {
Object k = p.getKey(i);
float areaIncrease = keyType.getAreaIncrease(k, key);
if (areaIncrease < min) {
index = i;
min = areaIncrease;
}
}
}
Page c = p.getChildPage(index).copy(writeVersion);
if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 4) {
// split on the way down
Page split = split(c, writeVersion);
p.setKey(index, getBounds(c));
p.setChild(index, c);
p.insertNode(index, getBounds(split), split);
// now we are not sure where to add
add(p, writeVersion, key, value);
return;
}
add(c, writeVersion, key, value);
Object bounds = p.getKey(index);
keyType.increaseBounds(bounds, key);
p.setKey(index, bounds);
p.setChild(index, c);
}
private Page split(Page p, long writeVersion) {
return quadraticSplit ?
splitQuadratic(p, writeVersion) :
splitLinear(p, writeVersion);
}
private Page splitLinear(Page p, long writeVersion) {
ArrayList
© 2015 - 2024 Weber Informatics LLC | Privacy Policy