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

overflowdb.OdbIndexManager Maven / Gradle / Ivy

There is a newer version: 1.115
Show newest version
package overflowdb;

import org.h2.mvstore.MVMap;
import overflowdb.storage.OdbStorage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.LongStream;

public final class OdbIndexManager {

  private final OdbGraph graph;
  // TODO use concurrent but memory efficient map
  protected Map>> indexes = new ConcurrentHashMap<>();
  protected Map dirtyFlags = new ConcurrentHashMap<>();

  public OdbIndexManager(OdbGraph graph) {
    this.graph = graph;
  }

  /**
   * Create an index for specified node property.
   * Whenever an element has the specified key mutated, the index is updated.
   * When the index is created, all existing elements are indexed to ensure that they are captured by the index.
   */
  public final void createNodePropertyIndex(final String propertyName) {
    checkPropertyName(propertyName);

    if (indexes.containsKey(propertyName))
      return;

    dirtyFlags.put(propertyName, true);

    graph.nodes.iterator().forEachRemaining(node -> {
      Object value = node.property2(propertyName);
      if (value != null) {
        put(propertyName, value, (NodeRef) node);
      }
    });
  }

  public boolean isIndexed(final String propertyName) {
    return indexes.containsKey(propertyName);
  }

  private void checkPropertyName(String propertyName) {
    if (propertyName == null || propertyName.isEmpty())
      throw new IllegalArgumentException("Illegal property name: " + propertyName);
  }

  public final void loadNodePropertyIndex(final String propertyName, Map valueToNodeIds) {
    dirtyFlags.put(propertyName, false);
    valueToNodeIds.entrySet().parallelStream().forEach(entry ->
        LongStream.of(entry.getValue())
          .forEach(nodeId -> put(propertyName, entry.getKey(), (NodeRef)graph.vertex(nodeId))));
  }

  public void putIfIndexed(final String key, final Object newValue, final NodeRef nodeRef) {
    dirtyFlags.put(key, true);
    if (indexes.containsKey(key)) {
      put(key, newValue, nodeRef);
    }
  }

  private final void put(final String key, final Object value, final NodeRef nodeRef) {
    Map> keyMap = indexes.get(key);
    if (null == keyMap) {
      indexes.putIfAbsent(key, new ConcurrentHashMap<>());
      keyMap = indexes.get(key);
    }
    Set objects = keyMap.get(value);
    if (null == objects) {
      keyMap.putIfAbsent(value, ConcurrentHashMap.newKeySet());
      objects = keyMap.get(value);
    }
    objects.add(nodeRef);
  }

  /**
   * Drop the index for specified node property.
   */
  public final void dropNodePropertyIndex(final String key) {
    if (indexes.containsKey(key)) {
      indexes.remove(key).clear();
      dirtyFlags.remove(key);
    }
  }

  /**
   * Return all the keys currently being indexed for nodes.
   */
  public final Set getIndexedNodeProperties() {
    return indexes.keySet();
  }

  public final int getIndexedNodeCount(String propertyName) {
    final Map> indexMap = this.indexes.get(propertyName);
    return indexMap == null ? 0 : indexMap.values().stream().mapToInt(Set::size).sum();
  }

  public final List lookup(final String key, final Object value) {
    final Map> keyMap = indexes.get(key);
    if (null == keyMap) {
      return Collections.emptyList();
    } else {
      Set set = keyMap.get(value);
      if (null == set)
        return Collections.emptyList();
      else
        return new ArrayList<>(set);
    }
  }

  public final void remove(final String key, final Object value, final NodeRef nodeRef) {
    dirtyFlags.put(key, true);
    final Map> keyMap = indexes.get(key);
    if (null != keyMap) {
      Set objects = keyMap.get(value);
      if (null != objects) {
        objects.remove(nodeRef);
        if (objects.size() == 0) {
          keyMap.remove(value);
        }
      }
    }
  }

  public final void removeElement(final NodeRef nodeRef) {
    for (String propertyName : indexes.keySet())
      dirtyFlags.put(propertyName, true);
    for (Map> map : indexes.values()) {
      for (Set set : map.values()) {
        set.remove(nodeRef);
      }
    }
  }

  public Map> getIndexMap(String propertyName) {
    return this.indexes.get(propertyName);
  }

  public void initializeStoredIndices(OdbStorage storage) {
    storage
        .getIndexNames()
        .stream()
        .forEach(indexName -> loadIndex(indexName, storage));
  }

  public void loadIndex(String indexName, OdbStorage storage) {
    final MVMap indexMVMap = storage.openIndex(indexName);
    loadNodePropertyIndex(indexName, indexMVMap);
  }

  public void storeIndexes(OdbStorage storage) {
    getIndexedNodeProperties()
        .stream()
        .forEach(propertyName ->
            saveIndex(storage, propertyName, getIndexMap(propertyName)));
  }

  private void saveIndex(OdbStorage storage, String propertyName, Map> indexMap) {
    if (dirtyFlags.get(propertyName)) {
      storage.clearIndex(propertyName);
      final MVMap indexStore = storage.openIndex(propertyName);
      indexMap.entrySet().parallelStream().forEach(entry -> {
        final Object propertyValue = entry.getKey();
        final Set nodeRefs = entry.getValue();
        indexStore.put(propertyValue, nodeRefs.stream().mapToLong(nodeRef -> nodeRef.id).toArray());
      });
      dirtyFlags.put(propertyName, false);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy