/* 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()); } }