/* 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: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api.plumbing.diff; import static org.locationtech.geogig.api.NodeRef.ROOT; import static org.locationtech.geogig.api.NodeRef.depth; import static org.locationtech.geogig.api.NodeRef.split; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.annotation.Nullable; import org.locationtech.geogig.api.Node; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.RevTreeBuilder; import org.locationtech.geogig.repository.SpatialOps; import org.locationtech.geogig.storage.ObjectDatabase; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.vividsolutions.jts.geom.Envelope; /** * A mutable data structure representing the state of a tree and its subtrees */ public class MutableTree implements Cloneable { private Node node; private Map<String, MutableTree> childTrees; public static final Ordering<NodeRef> DEEPEST_LAST_COMPARATOR = new Ordering<NodeRef>() { @Override public int compare(NodeRef o1, NodeRef o2) { int depth = Integer.valueOf(depth(o1.path())).compareTo( Integer.valueOf(depth(o2.path()))); if (depth != 0) { return depth; } return o1.path().compareTo(o2.path()); } }; public static final Ordering<NodeRef> DEEPEST_FIRST_COMPARATOR = DEEPEST_LAST_COMPARATOR .reverse(); private MutableTree(String name) { this(Node.tree(name, RevTree.EMPTY_TREE_ID, ObjectId.NULL)); } private MutableTree(Node node) { this.node = node; this.childTrees = Maps.newTreeMap(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); toString(this, sb, 0); return sb.toString(); } private void toString(MutableTree tree, StringBuilder sb, int indent) { Node node = tree.getNode(); append(sb, node, indent); for (MutableTree c : tree.childTrees.values()) { toString(c, sb, indent + 1); } } private void append(StringBuilder sb, Node node, int indent) { sb.append(Strings.repeat(" ", indent)).append(node.getName()).append("->") .append(node.getObjectId()).append(" (").append(node.getMetadataId()).append(")\n"); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof MutableTree)) { return false; } MutableTree other = (MutableTree) o; return node.equals(other.node) && node.getMetadataId().equals(other.node.getMetadataId()) && childTrees.equals(other.childTrees); } public static MutableTree createFromRefs(final ObjectId rootId, final Supplier<Iterator<NodeRef>> refs) { return createFromRefs(rootId, refs.get()); } public static MutableTree createFromRefs(final ObjectId rootId, @Nullable final NodeRef... treeRefs) { Iterator<NodeRef> refs = Iterators.emptyIterator(); if (treeRefs != null) { refs = Lists.newArrayList(treeRefs).iterator(); } return createFromRefs(rootId, refs); } public static MutableTree createFromRefs(final ObjectId rootId, final Iterator<NodeRef> treeRefs) { Function<NodeRef, String> keyFunction = new Function<NodeRef, String>() { @Override public String apply(@Nullable NodeRef input) { return input.path(); } }; ImmutableMap<String, NodeRef> treesByPath = Maps.uniqueIndex(treeRefs, keyFunction); return createFromPaths(rootId, treesByPath); } public static MutableTree createFromPaths(final ObjectId rootId, final Map<String, NodeRef> entries) { List<NodeRef> refsByDepth = Lists.newArrayList(entries.values()); Collections.sort(refsByDepth, DEEPEST_LAST_COMPARATOR); Node rootNode = Node.create(ROOT, rootId, ObjectId.NULL, TYPE.TREE, null); MutableTree root = new MutableTree(rootNode); Envelope bounds = new Envelope(); for (NodeRef entry : refsByDepth) { Node node = entry.getNode(); node.expand(bounds); String parentPath = entry.getParentPath(); root.setChild(parentPath, node); } // recreate root node with the appropriate bounds rootNode = Node.create(ROOT, rootId, ObjectId.NULL, TYPE.TREE, bounds); root.setNode(rootNode); return root; } public Node getNode() { return node; } public void forceChild(final String parentPath, final Node treeNode) { ImmutableList<String> parentSteps = NodeRef.split(parentPath); MutableTree parent = this; for (String name : parentSteps) { MutableTree child = parent.childTrees.get(name); if (child == null) { child = new MutableTree(name); parent.childTrees.put(name, child); } parent = child; } MutableTree tree = parent.childTrees.get(treeNode.getName()); if (tree == null) { tree = new MutableTree(treeNode); parent.childTrees.put(treeNode.getName(), tree); } else { tree.setNode(treeNode); } } public void setChild(String parentPath, Node node) { List<String> parentSteps = split(parentPath); setChild(parentSteps, node); } public void setChild(final List<String> parentPath, final Node node) { MutableTree parent; MutableTree child; if (parentPath.isEmpty()) { parent = this; } else { parent = getChild(parentPath); } child = parent.childTrees.get(node.getName()); if (child == null) { child = new MutableTree(node); parent.childTrees.put(node.getName(), child); } else { child.setNode(node); } } public MutableTree getChild(String path) throws IllegalArgumentException { return getChild(NodeRef.split(path)); } public MutableTree getChild(final List<String> path) throws IllegalArgumentException { Preconditions.checkArgument(!path.isEmpty()); String directChildName = path.get(0); MutableTree child = childTrees.get(directChildName); if (child == null) { throw new IllegalArgumentException(String.format("No child named %s exists: %s", directChildName, childTrees.keySet())); } if (path.size() == 1) { return child; } return child.getChild(path.subList(1, path.size())); } public SortedMap<String, MutableTree> getChildrenAsMap() { TreeMap<String, MutableTree> map = Maps.newTreeMap(); asMap("", map); return map; } private void asMap(String parentPath, TreeMap<String, MutableTree> target) { for (MutableTree childTree : this.childTrees.values()) { String childTreePath = NodeRef.appendChild(parentPath, childTree.getNode().getName()); target.put(childTreePath, childTree); childTree.asMap(childTreePath, target); } } @Nullable public MutableTree removeChild(String path) { ImmutableList<String> steps = NodeRef.split(path); MutableTree tree = this; for (Iterator<String> childNames = steps.iterator(); childNames.hasNext();) { String childName = childNames.next(); MutableTree child = tree.childTrees.get(childName); if (child == null) { return null; } if (!childNames.hasNext()) { MutableTree removed = tree.childTrees.remove(childName); return removed; } else { tree = child; } } return null; } public void setNode(final Node newNode) { this.node = newNode; } public RevTree build(ObjectDatabase origin, ObjectDatabase target) { final ObjectId nodeId = node.getObjectId(); final RevTree tree = origin.getTree(nodeId); RevTreeBuilder builder = tree.builder(target).clearSubtrees(); for (MutableTree childTree : this.childTrees.values()) { String name; ObjectId newObjectId; ObjectId metadataId; Envelope bounds; { RevTree newChild = childTree.build(origin, target); target.put(newChild); Node oldNode = childTree.getNode(); name = oldNode.getName(); newObjectId = newChild.getId(); metadataId = oldNode.getMetadataId().or(ObjectId.NULL); bounds = SpatialOps.boundsOf(newChild); } Node newNode = Node.create(name, newObjectId, metadataId, TYPE.TREE, bounds); builder.put(newNode); } RevTree newTree = builder.build(); if (!this.node.getObjectId().equals(newTree.getId())) { target.put(newTree); Envelope bounds = SpatialOps.boundsOf(newTree); this.node = Node.create(node.getName(), newTree.getId(), node.getMetadataId().or(ObjectId.NULL), TYPE.TREE, bounds); } return newTree; } @Override public MutableTree clone() { MutableTree clone = new MutableTree(node); for (MutableTree child : this.childTrees.values()) { clone.childTrees.put(child.getNode().getName(), child.clone()); } return clone; } }