/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* David Winslow (Boundless) - initial implementation
*/
package org.locationtech.geogig.storage.memory;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.api.plumbing.ResolveGeogigDir;
import org.locationtech.geogig.storage.GraphDatabase;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
/**
* Provides an default in memory implementation of a GeoGig Graph Database.
*/
public class HeapGraphDatabase implements GraphDatabase {
static final Function<Node, ObjectId> NODE_TO_ID = new Function<Node, ObjectId>() {
@Override
public ObjectId apply(Node n) {
return n.id;
}
};
static final Map<URL, Ref> graphs = Maps.newConcurrentMap();
final Platform platform;
Graph graph;
@Inject
public HeapGraphDatabase(Platform platform) {
this.platform = platform;
}
@Override
public void open() {
if (isOpen()) {
return;
}
Optional<URL> url = new ResolveGeogigDir(platform).call();
if (url.isPresent()) {
synchronized (graphs) {
URL key = url.get();
if (!graphs.containsKey(key)) {
graphs.put(key, new Ref(new Graph()));
}
graph = graphs.get(key).acquire();
}
} else {
graph = new Graph();
}
}
@Override
public void configure() {
// No-op
}
@Override
public void checkConfig() {
// No-op
}
@Override
public boolean isOpen() {
return graph != null;
}
@Override
public void close() {
if (!isOpen()) {
return;
}
graph = null;
Optional<URL> url = new ResolveGeogigDir(platform).call();
if (url.isPresent()) {
synchronized (graphs) {
URL key = url.get();
Ref ref = graphs.get(key);
if (ref != null && ref.release() <= -1) {
ref.destroy();
graphs.remove(key);
}
}
}
}
@Override
public boolean exists(ObjectId commitId) {
return graph.get(commitId).isPresent();
}
@Override
public ImmutableList<ObjectId> getParents(ObjectId commitId) throws IllegalArgumentException {
return graph.get(commitId).transform(new Function<Node, ImmutableList<ObjectId>>() {
@Override
public ImmutableList<ObjectId> apply(Node n) {
// transform outgoing nodes to id
// filter for null to skip fake root node
return new ImmutableList.Builder<ObjectId>().addAll(
filter(transform(n.to(), NODE_TO_ID), Predicates.notNull())).build();
}
}).or(ImmutableList.<ObjectId> of());
}
@Override
public ImmutableList<ObjectId> getChildren(ObjectId commitId) throws IllegalArgumentException {
return graph.get(commitId).transform(new Function<Node, ImmutableList<ObjectId>>() {
@Override
public ImmutableList<ObjectId> apply(Node n) {
return new ImmutableList.Builder<ObjectId>()
.addAll(transform(n.from(), NODE_TO_ID)).build();
}
}).or(ImmutableList.<ObjectId> of());
}
@Override
public boolean put(ObjectId commitId, ImmutableList<ObjectId> parentIds) {
Node n = graph.getOrAdd(commitId);
if (parentIds.isEmpty()) {
// the root node, only update on first addition
if (!n.isRoot()) {
n.setRoot(true);
return true;
}
}
// has the node been attached to graph?
if (Iterables.isEmpty(n.to())) {
// nope, attach it
for (ObjectId parent : parentIds) {
Node p = graph.getOrAdd(parent);
graph.newEdge(n, p);
}
// only mark as updated if it is actually attached
boolean added = !Iterables.isEmpty(n.to());
return added;
}
return false;
}
@Override
public void map(ObjectId mapped, ObjectId original) {
graph.map(mapped, original);
}
@Override
public ObjectId getMapping(ObjectId commitId) {
return Optional.fromNullable(graph.getMapping(commitId)).or(ObjectId.NULL);
}
@Override
public int getDepth(ObjectId commitId) {
Preconditions.checkNotNull(commitId);
Optional<Node> nodeOpt = graph.get(commitId);
Preconditions.checkArgument(nodeOpt.isPresent(), "No graph entry for commit %s on %s",
commitId, this.toString());
Node node = nodeOpt.get();
PathToRootWalker walker = new PathToRootWalker(node);
int depth = 0;
O: while (walker.hasNext()) {
for (Node n : walker.next()) {
if (Iterables.size(n.to()) == 0) {
break O;
}
}
depth++;
}
return depth;
}
@Override
public void setProperty(ObjectId commitId, String propertyName, String propertyValue) {
graph.get(commitId).get().put(propertyName, propertyValue);
;
}
@Override
public void truncate() {
graph.clear();
}
static class Ref {
int count;
Graph graph;
Ref(Graph g) {
graph = g;
count = 0;
}
Graph acquire() {
count++;
return graph;
}
int release() {
return --count;
}
void destroy() {
graph = null;
}
}
protected class HeapGraphNode extends GraphNode {
Node node;
public HeapGraphNode(Node node) {
this.node = node;
}
@Override
public ObjectId getIdentifier() {
return node.id;
}
@Override
public Iterator<GraphEdge> getEdges(final Direction direction) {
Iterator<Edge> nodeEdges;
switch (direction) {
case OUT:
nodeEdges = node.out.iterator();
break;
case IN:
nodeEdges = node.in.iterator();
break;
default:
nodeEdges = Iterators.concat(node.in.iterator(), node.out.iterator());
}
List<GraphEdge> edges = new LinkedList<GraphEdge>();
while (nodeEdges.hasNext()) {
Edge nodeEdge = nodeEdges.next();
edges.add(new GraphEdge(new HeapGraphNode(nodeEdge.src), new HeapGraphNode(
nodeEdge.dst)));
}
return edges.iterator();
}
@Override
public boolean isSparse() {
return node.props != null && node.props.containsKey(SPARSE_FLAG)
&& Boolean.valueOf(node.props.get(SPARSE_FLAG));
}
}
@Override
public GraphNode getNode(ObjectId id) {
return new HeapGraphNode(graph.get(id).get());
}
}