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

org.glowroot.shaded.h2.mvstore.MVMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
 * Version 1.0, and under the Eclipse Public License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.glowroot.shaded.h2.mvstore;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

import org.glowroot.shaded.h2.mvstore.type.DataType;
import org.glowroot.shaded.h2.mvstore.type.ObjectDataType;
import org.glowroot.shaded.h2.util.New;

/**
 * A stored map.
 *
 * @param  the key class
 * @param  the value class
 */
public class MVMap extends AbstractMap
        implements ConcurrentMap {

    /**
     * The store.
     */
    protected MVStore store;

    /**
     * The current root page (may not be null).
     */
    protected volatile Page root;

    /**
     * The version used for writing.
     */
    protected volatile long writeVersion;

    /**
     * This version is set during a write operation.
     */
    protected volatile long currentWriteVersion = -1;

    private int id;
    private long createVersion;
    private final DataType keyType;
    private final DataType valueType;
    private ArrayList oldRoots = new ArrayList();

    private boolean closed;
    private boolean readOnly;

    protected MVMap(DataType keyType, DataType valueType) {
        this.keyType = keyType;
        this.valueType = valueType;
        this.root = Page.createEmpty(this,  -1);
    }

    /**
     * Open this map with the given store and configuration.
     *
     * @param store the store
     * @param config the configuration
     */
    protected void init(MVStore store, HashMap config) {
        this.store = store;
        this.id = DataUtils.readHexInt(config, "id", 0);
        this.createVersion = DataUtils.readHexLong(config, "createVersion", 0);
        this.writeVersion = store.getCurrentVersion();
    }

    /**
     * Create a copy of a page, if the write version is higher than the current
     * version. If a copy is created, the old page is marked as deleted.
     *
     * @param p the page
     * @param writeVersion the write version
     * @return a page with the given write version
     */
    protected Page copyOnWrite(Page p, long writeVersion) {
        if (p.getVersion() == writeVersion) {
            return p;
        }
        return p.copy(writeVersion);
    }

    /**
     * Add or replace a key-value pair.
     *
     * @param key the key (may not be null)
     * @param value the value (may not be null)
     * @return the old value if the key existed, or null otherwise
     */
    @Override
    @SuppressWarnings("unchecked")
    public V put(K key, V value) {
        DataUtils.checkArgument(value != null, "The value may not be null");
        beforeWrite();
        try {
            long v = writeVersion;
            Page p = copyOnWrite(root, v);
            p = splitRootIfNeeded(p, v);
            Object result = put(p, v, key, value);
            newRoot(p);
            return (V) result;
        } finally {
            afterWrite();
        }
    }

    /**
     * Split the root page if necessary.
     *
     * @param p the page
     * @param writeVersion the write version
     * @return the new sibling
     */
    protected Page splitRootIfNeeded(Page p, long writeVersion) {
        if (p.getMemory() <= store.getPageSplitSize() || p.getKeyCount() <= 1) {
            return p;
        }
        int at = p.getKeyCount() / 2;
        long totalCount = p.getTotalCount();
        Object k = p.getKey(at);
        Page split = p.split(at);
        Object[] keys = { k };
        long[] children = { p.getPos(), split.getPos() };
        Page[] childrenPages = { p, split };
        long[] counts = { p.getTotalCount(), split.getTotalCount() };
        p = Page.create(this, writeVersion,
                1, keys, null,
                2, children, childrenPages, counts,
                totalCount, 0, 0);
        return p;
    }

    /**
     * Add or update a key-value pair.
     *
     * @param p the page
     * @param writeVersion the write version
     * @param key the key (may not be null)
     * @param value the value (may not be null)
     * @return the old value, or null
     */
    protected Object put(Page p, long writeVersion, Object key, Object value) {
        int index = p.binarySearch(key);
        if (p.isLeaf()) {
            if (index < 0) {
                index = -index - 1;
                p.insertLeaf(index, key, value);
                return null;
            }
            return p.setValue(index, value);
        }
        // p is a node
        if (index < 0) {
            index = -index - 1;
        } else {
            index++;
        }
        Page c = copyOnWrite(p.getChildPage(index), writeVersion);
        if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 1) {
            // split on the way down
            int at = c.getKeyCount() / 2;
            Object k = c.getKey(at);
            Page split = c.split(at);
            p.setChild(index, split);
            p.setCounts(index, split);
            p.insertNode(index, k, c);
            // now we are not sure where to add
            return put(p, writeVersion, key, value);
        }
        p.setChild(index, c);
        Object result = put(c, writeVersion, key, value);
        p.setCounts(index, c);
        return result;
    }

    /**
     * Get the first key, or null if the map is empty.
     *
     * @return the first key, or null
     */
    public K firstKey() {
        return getFirstLast(true);
    }

    /**
     * Get the last key, or null if the map is empty.
     *
     * @return the last key, or null
     */
    public K lastKey() {
        return getFirstLast(false);
    }

    /**
     * Get the key at the given index.
     * 

* This is a O(log(size)) operation. * * @param index the index * @return the key */ @SuppressWarnings("unchecked") public K getKey(long index) { if (index < 0 || index >= size()) { return null; } Page p = root; long offset = 0; while (true) { if (p.isLeaf()) { if (index >= offset + p.getKeyCount()) { return null; } return (K) p.getKey((int) (index - offset)); } int i = 0, size = p.getChildPageCount(); for (; i < size; i++) { long c = p.getCounts(i); if (index < c + offset) { break; } offset += c; } if (i == size) { return null; } p = p.getChildPage(i); } } /** * Get the key list. The list is a read-only representation of all keys. *

* The get and indexOf methods are O(log(size)) operations. The result of * indexOf is cast to an int. * * @return the key list */ public List keyList() { return new AbstractList() { @Override public K get(int index) { return getKey(index); } @Override public int size() { return MVMap.this.size(); } @Override @SuppressWarnings("unchecked") public int indexOf(Object key) { return (int) getKeyIndex((K) key); } }; } /** * Get the index of the given key in the map. *

* This is a O(log(size)) operation. *

* If the key was found, the returned value is the index in the key array. * If not found, the returned value is negative, where -1 means the provided * key is smaller than any keys. See also Arrays.binarySearch. * * @param key the key * @return the index */ public long getKeyIndex(K key) { if (size() == 0) { return -1; } Page p = root; long offset = 0; while (true) { int x = p.binarySearch(key); if (p.isLeaf()) { if (x < 0) { return -offset + x; } return offset + x; } if (x < 0) { x = -x - 1; } else { x++; } for (int i = 0; i < x; i++) { offset += p.getCounts(i); } p = p.getChildPage(x); } } /** * Get the first (lowest) or last (largest) key. * * @param first whether to retrieve the first key * @return the key, or null if the map is empty */ @SuppressWarnings("unchecked") protected K getFirstLast(boolean first) { if (size() == 0) { return null; } Page p = root; while (true) { if (p.isLeaf()) { return (K) p.getKey(first ? 0 : p.getKeyCount() - 1); } p = p.getChildPage(first ? 0 : p.getChildPageCount() - 1); } } /** * Get the smallest key that is larger than the given key, or null if no * such key exists. * * @param key the key * @return the result */ public K higherKey(K key) { return getMinMax(key, false, true); } /** * Get the smallest key that is larger or equal to this key. * * @param key the key * @return the result */ public K ceilingKey(K key) { return getMinMax(key, false, false); } /** * Get the largest key that is smaller or equal to this key. * * @param key the key * @return the result */ public K floorKey(K key) { return getMinMax(key, true, false); } /** * Get the largest key that is smaller than the given key, or null if no * such key exists. * * @param key the key * @return the result */ public K lowerKey(K key) { return getMinMax(key, true, true); } /** * Get the smallest or largest key using the given bounds. * * @param key the key * @param min whether to retrieve the smallest key * @param excluding if the given upper/lower bound is exclusive * @return the key, or null if no such key exists */ protected K getMinMax(K key, boolean min, boolean excluding) { return getMinMax(root, key, min, excluding); } @SuppressWarnings("unchecked") private K getMinMax(Page p, K key, boolean min, boolean excluding) { if (p.isLeaf()) { int x = p.binarySearch(key); if (x < 0) { x = -x - (min ? 2 : 1); } else if (excluding) { x += min ? -1 : 1; } if (x < 0 || x >= p.getKeyCount()) { return null; } return (K) p.getKey(x); } int x = p.binarySearch(key); if (x < 0) { x = -x - 1; } else { x++; } while (true) { if (x < 0 || x >= p.getChildPageCount()) { return null; } K k = getMinMax(p.getChildPage(x), key, min, excluding); if (k != null) { return k; } x += min ? -1 : 1; } } /** * Get a value. * * @param key the key * @return the value, or null if not found */ @Override @SuppressWarnings("unchecked") public V get(Object key) { return (V) binarySearch(root, key); } /** * Get the value for the given key, or null if not found. * * @param p the page * @param key the key * @return the value or null */ protected Object binarySearch(Page p, Object key) { int x = p.binarySearch(key); if (!p.isLeaf()) { if (x < 0) { x = -x - 1; } else { x++; } p = p.getChildPage(x); return binarySearch(p, key); } if (x >= 0) { return p.getValue(x); } return null; } @Override public boolean containsKey(Object key) { return get(key) != null; } /** * Get the page for the given value. * * @param key the key * @return the value, or null if not found */ protected Page getPage(K key) { return binarySearchPage(root, key); } /** * Get the value for the given key, or null if not found. * * @param p the parent page * @param key the key * @return the page or null */ protected Page binarySearchPage(Page p, Object key) { int x = p.binarySearch(key); if (!p.isLeaf()) { if (x < 0) { x = -x - 1; } else { x++; } p = p.getChildPage(x); return binarySearchPage(p, key); } if (x >= 0) { return p; } return null; } /** * Remove all entries. */ @Override public void clear() { beforeWrite(); try { root.removeAllRecursive(); newRoot(Page.createEmpty(this, writeVersion)); } finally { afterWrite(); } } /** * Close the map. Accessing the data is still possible (to allow concurrent * reads), but it is marked as closed. */ void close() { closed = true; } public boolean isClosed() { return closed; } /** * Remove a key-value pair, if the key exists. * * @param key the key (may not be null) * @return the old value if the key existed, or null otherwise */ @Override public V remove(Object key) { beforeWrite(); try { long v = writeVersion; Page p = copyOnWrite(root, v); @SuppressWarnings("unchecked") V result = (V) remove(p, v, key); if (!p.isLeaf() && p.getTotalCount() == 0) { p.removePage(); p = Page.createEmpty(this, p.getVersion()); } newRoot(p); return result; } finally { afterWrite(); } } /** * Add a key-value pair if it does not yet exist. * * @param key the key (may not be null) * @param value the new value * @return the old value if the key existed, or null otherwise */ @Override public synchronized V putIfAbsent(K key, V value) { V old = get(key); if (old == null) { put(key, value); } return old; } /** * Remove a key-value pair if the value matches the stored one. * * @param key the key (may not be null) * @param value the expected value * @return true if the item was removed */ @Override public synchronized boolean remove(Object key, Object value) { V old = get(key); if (areValuesEqual(old, value)) { remove(key); return true; } return false; } /** * Check whether the two values are equal. * * @param a the first value * @param b the second value * @return true if they are equal */ public boolean areValuesEqual(Object a, Object b) { if (a == b) { return true; } else if (a == null || b == null) { return false; } return valueType.compare(a, b) == 0; } /** * Replace a value for an existing key, if the value matches. * * @param key the key (may not be null) * @param oldValue the expected value * @param newValue the new value * @return true if the value was replaced */ @Override public synchronized boolean replace(K key, V oldValue, V newValue) { V old = get(key); if (areValuesEqual(old, oldValue)) { put(key, newValue); return true; } return false; } /** * Replace a value for an existing key. * * @param key the key (may not be null) * @param value the new value * @return the old value, if the value was replaced, or null */ @Override public synchronized V replace(K key, V value) { V old = get(key); if (old != null) { put(key, value); return old; } return null; } /** * Remove a key-value pair. * * @param p the page (may not be null) * @param writeVersion the write version * @param key the key * @return the old value, or null if the key did not exist */ protected Object remove(Page p, long writeVersion, Object key) { int index = p.binarySearch(key); Object result = null; if (p.isLeaf()) { if (index >= 0) { result = p.getValue(index); p.remove(index); } return result; } // node if (index < 0) { index = -index - 1; } else { index++; } Page cOld = p.getChildPage(index); Page c = copyOnWrite(cOld, writeVersion); result = remove(c, writeVersion, key); if (result == null || c.getTotalCount() != 0) { // no change, or // there are more nodes p.setChild(index, c); p.setCounts(index, c); } else { // this child was deleted if (p.getKeyCount() == 0) { p.setChild(index, c); p.setCounts(index, c); c.removePage(); } else { p.remove(index); } } return result; } /** * Use the new root page from now on. * * @param newRoot the new root page */ protected void newRoot(Page newRoot) { if (root != newRoot) { removeUnusedOldVersions(); if (root.getVersion() != newRoot.getVersion()) { ArrayList list = oldRoots; if (list.size() > 0) { Page last = list.get(list.size() - 1); if (last.getVersion() != root.getVersion()) { list.add(root); } } else { list.add(root); } } root = newRoot; } } /** * Compare two keys. * * @param a the first key * @param b the second key * @return -1 if the first key is smaller, 1 if bigger, 0 if equal */ int compare(Object a, Object b) { return keyType.compare(a, b); } /** * Get the key type. * * @return the key type */ public DataType getKeyType() { return keyType; } /** * Get the value type. * * @return the value type */ public DataType getValueType() { return valueType; } /** * Read a page. * * @param pos the position of the page * @return the page */ Page readPage(long pos) { return store.readPage(this, pos); } /** * Set the position of the root page. * * @param rootPos the position, 0 for empty * @param version the version of the root */ void setRootPos(long rootPos, long version) { root = rootPos == 0 ? Page.createEmpty(this, -1) : readPage(rootPos); root.setVersion(version); } /** * Iterate over a number of keys. * * @param from the first key to return * @return the iterator */ public Iterator keyIterator(K from) { return new Cursor(this, root, from); } /** * Get a cursor to iterate over a number of keys and values. * * @param from the first key to return * @return the cursor */ public Cursor cursor(K from) { return new Cursor(this, root, from); } @Override public Set> entrySet() { final MVMap map = this; final Page root = this.root; return new AbstractSet>() { @Override public Iterator> iterator() { final Cursor cursor = new Cursor(map, root, null); return new Iterator>() { @Override public boolean hasNext() { return cursor.hasNext(); } @Override public Entry next() { K k = cursor.next(); return new DataUtils.MapEntry(k, cursor.getValue()); } @Override public void remove() { throw DataUtils.newUnsupportedOperationException( "Removing is not supported"); } }; } @Override public int size() { return MVMap.this.size(); } @Override public boolean contains(Object o) { return MVMap.this.containsKey(o); } }; } @Override public Set keySet() { final MVMap map = this; final Page root = this.root; return new AbstractSet() { @Override public Iterator iterator() { return new Cursor(map, root, null); } @Override public int size() { return MVMap.this.size(); } @Override public boolean contains(Object o) { return MVMap.this.containsKey(o); } }; } /** * Get the root page. * * @return the root page */ public Page getRoot() { return root; } /** * Get the map name. * * @return the name */ public String getName() { return store.getMapName(id); } public MVStore getStore() { return store; } public int getId() { return id; } /** * Rollback to the given version. * * @param version the version */ void rollbackTo(long version) { beforeWrite(); try { removeUnusedOldVersions(); if (version <= createVersion) { // the map is removed later } else if (root.getVersion() >= version) { // iterating in descending order - // this is not terribly efficient if there are many versions ArrayList list = oldRoots; while (list.size() > 0) { int i = list.size() - 1; Page p = list.get(i); root = p; list.remove(i); if (p.getVersion() < version) { break; } } } } finally { afterWrite(); } } /** * Forget those old versions that are no longer needed. */ void removeUnusedOldVersions() { long oldest = store.getOldestVersionToKeep(); if (oldest == -1) { return; } int i = searchRoot(oldest); if (i < 0) { i = -i - 1; } i--; if (i <= 0) { return; } // create a new instance // because another thread might iterate over it int size = oldRoots.size() - i; ArrayList list = new ArrayList(size); list.addAll(oldRoots.subList(i, oldRoots.size())); oldRoots = list; } public boolean isReadOnly() { return readOnly; } /** * This method is called before writing to the map. The default * implementation checks whether writing is allowed, and tries * to detect concurrent modification. * * @throws UnsupportedOperationException if the map is read-only, * or if another thread is concurrently writing */ protected void beforeWrite() { if (closed) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_CLOSED, "This map is closed"); } if (readOnly) { throw DataUtils.newUnsupportedOperationException( "This map is read-only"); } checkConcurrentWrite(); store.beforeWrite(); currentWriteVersion = writeVersion; } /** * Check that no write operation is in progress. */ protected void checkConcurrentWrite() { if (currentWriteVersion != -1) { // try to detect concurrent modification // on a best-effort basis throw DataUtils.newConcurrentModificationException(getName()); } } /** * This method is called after writing to the map (whether or not the write * operation was successful). */ protected void afterWrite() { currentWriteVersion = -1; } /** * If there is a concurrent update to the given version, wait until it is * finished. * * @param version the read version */ protected void waitUntilWritten(long version) { if (readOnly) { throw DataUtils.newIllegalStateException( DataUtils.ERROR_INTERNAL, "Waiting for writes to a read-only map"); } while (currentWriteVersion == version) { Thread.yield(); } } @Override public int hashCode() { return id; } @Override public boolean equals(Object o) { return this == o; } /** * Get the number of entries, as a integer. Integer.MAX_VALUE is returned if * there are more than this entries. * * @return the number of entries, as an integer */ @Override public int size() { long size = sizeAsLong(); return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; } /** * Get the number of entries, as a long. * * @return the number of entries */ public long sizeAsLong() { return root.getTotalCount(); } @Override public boolean isEmpty() { // could also use (sizeAsLong() == 0) return root.isLeaf() && root.getKeyCount() == 0; } public long getCreateVersion() { return createVersion; } /** * Remove the given page (make the space available). * * @param pos the position of the page to remove * @param memory the number of bytes used for this page */ protected void removePage(long pos, int memory) { store.removePage(this, pos, memory); } /** * Open an old version for the given map. * * @param version the version * @return the map */ public MVMap openVersion(long version) { if (readOnly) { throw DataUtils.newUnsupportedOperationException( "This map is read-only; need to call " + "the method on the writable map"); } DataUtils.checkArgument(version >= createVersion, "Unknown version {0}; this map was created in version is {1}", version, createVersion); Page newest = null; // need to copy because it can change Page r = root; if (version >= r.getVersion() && (version == writeVersion || r.getVersion() >= 0 || version <= createVersion || store.getFileStore() == null)) { newest = r; } else { // find the newest page that has a getVersion() <= version int i = searchRoot(version); if (i < 0) { // not found if (i == -1) { // smaller than all in-memory versions return store.openMapVersion(version, id, this); } i = -i - 2; } newest = oldRoots.get(i); } MVMap m = openReadOnly(); m.root = newest; return m; } /** * Open a copy of the map in read-only mode. * * @return the opened map */ MVMap openReadOnly() { MVMap m = new MVMap(keyType, valueType); m.readOnly = true; HashMap config = New.hashMap(); config.put("id", id); config.put("createVersion", createVersion); m.init(store, config); m.root = root; return m; } private int searchRoot(long version) { int low = 0, high = oldRoots.size() - 1; while (low <= high) { int x = (low + high) >>> 1; long v = oldRoots.get(x).getVersion(); if (v < version) { low = x + 1; } else if (version < v) { high = x - 1; } else { return x; } } return -(low + 1); } public long getVersion() { return root.getVersion(); } /** * Get the child page count for this page. This is to allow another map * implementation to override the default, in case the last child is not to * be used. * * @param p the page * @return the number of direct children */ protected int getChildPageCount(Page p) { return p.getChildPageCount(); } /** * Get the map type. When opening an existing map, the map type must match. * * @return the map type */ public String getType() { return null; } /** * Get the map metadata as a string. * * @param name the map name (or null) * @return the string */ String asString(String name) { StringBuilder buff = new StringBuilder(); if (name != null) { DataUtils.appendMap(buff, "name", name); } if (createVersion != 0) { DataUtils.appendMap(buff, "createVersion", createVersion); } String type = getType(); if (type != null) { DataUtils.appendMap(buff, "type", type); } return buff.toString(); } void setWriteVersion(long writeVersion) { this.writeVersion = writeVersion; } @Override public String toString() { return asString(null); } /** * A builder for maps. * * @param the map type * @param the key type * @param the value type */ public interface MapBuilder, K, V> { /** * Create a new map of the given type. * * @return the map */ M create(); } /** * A builder for this class. * * @param the key type * @param the value type */ public static class Builder implements MapBuilder, K, V> { protected DataType keyType; protected DataType valueType; /** * Create a new builder with the default key and value data types. */ public Builder() { // ignore } /** * Set the key data type. * * @param keyType the key type * @return this */ public Builder keyType(DataType keyType) { this.keyType = keyType; return this; } public DataType getKeyType() { return keyType; } public DataType getValueType() { return valueType; } /** * Set the value data type. * * @param valueType the value type * @return this */ public Builder valueType(DataType valueType) { this.valueType = valueType; return this; } @Override public MVMap create() { if (keyType == null) { keyType = new ObjectDataType(); } if (valueType == null) { valueType = new ObjectDataType(); } return new MVMap(keyType, valueType); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy