/* 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; import java.util.Arrays; import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; import org.junit.Test; import org.locationtech.geogig.api.Bucket; import org.locationtech.geogig.api.CommitBuilder; import org.locationtech.geogig.api.GeoGIG; import org.locationtech.geogig.api.Node; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; import org.locationtech.geogig.api.RevCommit; import org.locationtech.geogig.api.RevFeature; import org.locationtech.geogig.api.RevFeatureBuilder; import org.locationtech.geogig.api.RevFeatureType; import org.locationtech.geogig.api.RevFeatureTypeImpl; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.RevTreeBuilder; import org.locationtech.geogig.api.RevTreeImpl; import org.locationtech.geogig.api.SymRef; import org.locationtech.geogig.api.plumbing.LsTreeOp.Strategy; import org.locationtech.geogig.api.plumbing.diff.MutableTree; import org.locationtech.geogig.repository.SpatialOps; import org.locationtech.geogig.storage.ObjectDatabase; import org.locationtech.geogig.storage.StagingDatabase; import org.locationtech.geogig.test.integration.RepositoryTestCase; import org.opengis.feature.Feature; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.io.ParseException; public class WriteTree2Test extends RepositoryTestCase { private static final String EMPTY_ID = RevTree.EMPTY_TREE_ID.toString(); private WriteTree2 command; private GeoGIG geogig; private StagingDatabase indexDb; private ObjectDatabase objectDb; private RevTree leftTree; private RevTree rightTree; @Override protected void setUpInternal() throws Exception { geogig = getGeogig(); command = geogig.command(WriteTree2.class); indexDb = geogig.getRepository().stagingDatabase(); objectDb = geogig.getRepository().objectDatabase(); } @Override public void tearDownInternal() { if (objectDb != null) { objectDb.close(); } if (indexDb != null) { indexDb.close(); } } @Test public void testEmptyRepo() { ObjectId root = command.call(); assertNotNull(root); assertEquals(RevTree.EMPTY_TREE_ID, root); } @Test public void testEmptyRepoSingleStagedTree() { rightTree = createStageHeadTree(// indexTree("roads", "a1", "d1", 10)// ); ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // print(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(1, refsByPath.size()); assertTrue(refsByPath.keySet().contains("roads")); } @Test public void testRename() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0) // ); rightTree = createStageHeadTree(// indexTree("roadsRenamed", EMPTY_ID, null, 0) // ); ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(1, refsByPath.size()); assertTrue(refsByPath.containsKey("roadsRenamed")); } @Test public void testRenameNested() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0), // repoTree("roads/highways", "a2", "d1", 2),// repoTree("roads/streets", "a3", "d2", 2) // ); rightTree = createStageHeadTree(// indexTree("roads", EMPTY_ID, null, 0), // indexTree("roads/highways", "a2", "d1", 2),// indexTree("roads/streetsRenamed", "a3", "d2", 2) // ); ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(3, refsByPath.size()); assertTrue(refsByPath.containsKey("roads")); assertTrue(refsByPath.containsKey("roads/highways")); assertTrue(refsByPath.containsKey("roads/streetsRenamed")); } @Test public void testNoChanges() { leftTree = createHeadTree(// repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", "a5", "d3", 5),// repoTree("buildings/unknown", "a6", "d4", 5),// repoTree("buildings/towers", "a7", "d5", 5)// ); rightTree = createStageHeadTree(// repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", "a5", "d3", 5),// repoTree("buildings/unknown", "a6", "d4", 5),// repoTree("buildings/towers", "a7", "d5", 5)// ); ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(4, refsByPath.size()); Set<String> expected = set("buildings", "buildings/stores", "buildings/unknown", "buildings/towers"); assertEquals(expected, refsByPath.keySet()); } @Test public void testMetadataIdChangeOnly() { leftTree = createHeadTree(// repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", "a5", "d3", 5)// old md id is d3 ); rightTree = createStageHeadTree(// indexTree("buildings", EMPTY_ID, null, 0),// indexTree("buildings/stores", "a5", "d31", 5)// new md id is d31 ); ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(set("buildings", "buildings/stores"), refsByPath.keySet()); assertEquals(id("d31"), refsByPath.get("buildings/stores").getMetadataId()); } @Test public void testDeleteAll() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0), // repoTree("roads/highways", "a2", "d1", 10),// repoTree("roads/streets", "a3", "d2", 10), // repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", "a5", "d3", 5),// repoTree("buildings/unknown", "a6", "d4", 5),// repoTree("buildings/towers", "a7", "d5", 5)// ); rightTree = createStageHeadTree(); final ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(set(), refsByPath.keySet()); } @Test public void testDeletes() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0), // repoTree("roads/highways", "a2", "d1", 10),// repoTree("roads/streets", "a3", "d2", 10), // repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", "a5", "d3", 5),// repoTree("buildings/unknown", "a6", "d4", 5),// repoTree("buildings/towers", "a7", "d5", 5)// ); rightTree = createStageHeadTree(// indexTree("roads", EMPTY_ID, null, 0), // indexTree("roads/highways", "a2", "d1", 10),// indexTree("buildings", EMPTY_ID, null, 0),// indexTree("buildings/stores", "a5", "d31", 5)// ); final ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getTreeRefsByPath(newRepoRoot); assertEquals(set("roads", "roads/highways", "buildings", "buildings/stores"), refsByPath.keySet()); } @Test public void testSimpleChanges() { leftTree = createHeadTree(// repoTree("roads", "a1", "d1", 1), // repoTree("buildings", EMPTY_ID, null, 0)// ); rightTree = createStageHeadTree(// repoTree("roads", "a11", "d1", 2), // repoTree("buildings", "a41", null, 1)// ); // print(leftTree.getId()); // print(rightTree.getId()); final ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getRefsByPath(newRepoRoot, true); Set<String> expected = set("roads", "roads/roads.0", "roads/roads.1", "buildings", "buildings/buildings.0"); ImmutableSet<String> actual = refsByPath.keySet(); assertEquals(expected, actual); } @Test public void testNestedChanges() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0), // repoTree("roads/highways", EMPTY_ID, "d1", 0),// repoTree("roads/streets", "a3", "d2", 1), // repoTree("buildings", EMPTY_ID, null, 0),// repoTree("buildings/stores", EMPTY_ID, "d3", 0),// repoTree("buildings/unknown", "a6", "d4", 1)// ); rightTree = createStageHeadTree(// indexTree("roads", EMPTY_ID, null, 0), // indexTree("roads/highways", "a21", "d1", 1),// 1 added indexTree("roads/streets", EMPTY_ID, "d2", 0), // 1 removed indexTree("buildings", EMPTY_ID, null, 0),// indexTree("buildings/stores", "a51", "d3", 2),// 2 added indexTree("buildings/unknown", "a6", "d4", 1)// not changed ); final ObjectId newRepoRoot = command.call(); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getRefsByPath(newRepoRoot, true); Set<String> expected = set("roads", "roads/highways", "roads/highways/highways.0", "roads/streets", "buildings", "buildings/stores", "buildings/stores/stores.0", "buildings/stores/stores.1", "buildings/unknown", "buildings/unknown/unknown.0"); ImmutableSet<String> actual = refsByPath.keySet(); assertEquals(expected, actual); } @Test public void testAllKindsOfChanges() { leftTree = createHeadTree(// repoTree("roads", EMPTY_ID, null, 0), // repoTree("roads/highways", "a2", "d1", 1),// repoTree("roads/streets", "a3", "d2", 1), // repoTree("buildings", "a4", null, 2),// mixed tree, contains features and subtrees repoTree("buildings/stores", "a5", "d3", 1),// repoTree("buildings/unknown", "a6", "d4", 1),// repoTree("buildings/towers", "a7", "d5", 5)// ); rightTree = createStageHeadTree(// indexTree("roads", EMPTY_ID, null, 0), // indexTree("roads/highways", "a21", "d1", 2),// 1 feature added indexTree("roads/streetsRenamed", "a3", "d2", 1), // tree renamed indexTree("buildings", "a41", null, 1),// 1 feature removed indexTree("buildings/stores", "a5", "d31", 1),// only metadata changed indexTree("buildings/knownUnknown", "a61", "d41", 2),// renamed, changed tree and // metadata // buildings/towers removed completely indexTree("admin", "c1", "d5", 2),// new mixed tree, contains features and subtrees indexTree("admin/area", "c2", "d6", 1),// indexTree("admin/line", EMPTY_ID, "d7", 0)// ); final ObjectId newRepoRoot = command.call(); // print(newRepoRoot); assertNotNull(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); ImmutableMap<String, NodeRef> refsByPath = getRefsByPath(newRepoRoot, true); Set<String> paths = Sets.newTreeSet(); paths.addAll(refsByPath.keySet()); Set<String> expected = set(// "roads",// "roads/highways",// "roads/highways/highways.0",// "roads/highways/highways.1",// "roads/streetsRenamed",// "roads/streetsRenamed/streets.0",// "buildings",// "buildings/buildings.0",// "buildings/stores",// "buildings/stores/stores.0",// "buildings/knownUnknown",// "buildings/knownUnknown/knownUnknown.0",// "buildings/knownUnknown/knownUnknown.1",// "admin",// "admin/admin.0",// "admin/admin.1",// "admin/area",// "admin/area/area.0",// "admin/line"// ); assertEquals(expected, paths); } @Test public void testPathFilteringTopLevelTree() { leftTree = createHeadTree(// repoTree("roads", "a1", null, 2), // repoTree("roads/highways", "a2", "d1", 1),// repoTree("roads/streets", "a3", "d2", 2),// repoTree("buildings", "a4", "d3", 2)// deleted tree completely ); rightTree = createStageHeadTree(// repoTree("roads", "a11", null, 1), // deleted 1 feature repoTree("roads/highways", "a21", "d1", 3),// added 2 features repoTree("roads/streets", "a31", "d2", 1) // removed 1 feature ); MapDifference<String, NodeRef> difference; Set<String> onlyOnLeft; Set<String> onlyOnRight; Set<String> entriesInCommon; difference = runWithPathFilter(leftTree, rightTree, "roads"); onlyOnLeft = difference.entriesOnlyOnLeft().keySet(); onlyOnRight = difference.entriesOnlyOnRight().keySet(); entriesInCommon = difference.entriesInCommon().keySet(); Set<String> entriesDiffering = difference.entriesDiffering().keySet(); assertEquals(set("buildings", "buildings/buildings.0", "buildings/buildings.1"), onlyOnLeft); assertEquals(set(), onlyOnRight); assertEquals( set("roads", "roads/roads.0", "roads/streets/streets.0", "roads/highways/highways.0", "roads/highways/highways.2", "roads/highways/highways.1", "roads/highways", "roads/streets"), entriesInCommon); assertEquals(set(), entriesDiffering); } @Test public void testPathFilteringSingleFeature() { leftTree = createHeadTree(// repoTree("roads", "a1", null, 2), // repoTree("roads/highways", "a2", "d1", 1)// ); rightTree = createStageHeadTree(// repoTree("roads", "a11", null, 1), // deleted 1 feature repoTree("roads/highways", "a21", "d1", 3)// added 2 features ); MapDifference<String, NodeRef> difference; Set<String> onlyOnLeft; Set<String> onlyOnRight; difference = runWithPathFilter(leftTree, rightTree, "roads/roads.1"); onlyOnLeft = difference.entriesOnlyOnLeft().keySet(); onlyOnRight = difference.entriesOnlyOnRight().keySet(); assertEquals(set(), onlyOnLeft); assertEquals(set("roads/highways/highways.1", "roads/highways/highways.2"), onlyOnRight); assertEquals(set("roads/highways/highways.0", "roads/roads.0"), difference .entriesInCommon().keySet()); } @Test public void testPathFilteringDeletedTreeButCommitSingleChange() { leftTree = createHeadTree(// repoTree("roads", "a1", null, 1), // repoTree("highways", "a2", "d1", 2)// ); rightTree = createStageHeadTree(// repoTree("roads", "a1", null, 1) // deleted highways ); MapDifference<String, NodeRef> difference; Set<String> onlyOnNewTree; Set<String> onlyStaged; difference = runWithPathFilter(leftTree, rightTree, "highways/highways.1"); onlyOnNewTree = difference.entriesOnlyOnLeft().keySet(); onlyStaged = difference.entriesOnlyOnRight().keySet(); Set<String> differing = difference.entriesDiffering().keySet(); Set<String> inCommon = difference.entriesInCommon().keySet(); assertEquals(set("roads", "roads/roads.0"), inCommon); assertEquals(set("highways", "highways/highways.0"), onlyOnNewTree); assertEquals(set(), differing); assertEquals(set(), onlyStaged); } @Test public void testFilteredAddsFirstCommit() { leftTree = createHeadTree(); rightTree = createStageHeadTree(// repoTree("points", "a1", null, 3), // repoTree("lines", "b1", null, 2) // ); MapDifference<String, NodeRef> difference; Set<String> onlyOnLeft; Set<String> onlyOnRight; Set<String> entriesDiffering; Set<String> entriesInCommon; difference = runWithPathFilter(leftTree, rightTree, "points/points.1", "points/points.2"); onlyOnLeft = difference.entriesOnlyOnLeft().keySet(); onlyOnRight = difference.entriesOnlyOnRight().keySet(); entriesDiffering = difference.entriesDiffering().keySet(); entriesInCommon = difference.entriesInCommon().keySet(); assertEquals(set(), onlyOnLeft); assertEquals(set("points"), entriesDiffering); assertEquals(set("points/points.0", "lines", "lines/lines.0", "lines/lines.1"), onlyOnRight); assertEquals(set("points/points.1", "points/points.2"), entriesInCommon); difference = runWithPathFilter(leftTree, rightTree, "lines/lines.1", "badFilter"); onlyOnLeft = difference.entriesOnlyOnLeft().keySet(); onlyOnRight = difference.entriesOnlyOnRight().keySet(); entriesDiffering = difference.entriesDiffering().keySet(); entriesInCommon = difference.entriesInCommon().keySet(); assertEquals(set(), onlyOnLeft); assertEquals( set("lines/lines.0", "points", "points/points.0", "points/points.1", "points/points.2"), onlyOnRight); assertEquals(set("lines"), entriesDiffering); assertEquals(set("lines/lines.1"), entriesInCommon); } /** * @return the differences between the given right tree(staged) and the resulting tree after * running {@link WriteTree2} with the given filters. The result's left refs are the * ones in the new tree, and the right refs the same as given in the rightTree */ private MapDifference<String, NodeRef> runWithPathFilter(RevTree leftTree, RevTree rightTree, String... filters) { // print(leftTree.getId()); // print(rightTree.getId()); final ObjectId newRepoRoot = command.setPathFilter(Arrays.asList(filters)).call(); assertNotNull(newRepoRoot); // print(newRepoRoot); // check all blobs have been moved from the index to the object database verifyRepositoryTree(NodeRef.ROOT, newRepoRoot); final boolean includeFeatures = true; ImmutableMap<String, NodeRef> stagedRefs = getRefsByPath(rightTree.getId(), includeFeatures); ImmutableMap<String, NodeRef> resultRefs = getRefsByPath(newRepoRoot, includeFeatures); MapDifference<String, NodeRef> difference = Maps.difference(resultRefs, stagedRefs); return difference; } private ImmutableMap<String, NodeRef> getTreeRefsByPath(ObjectId newRepoRoot) { Iterator<NodeRef> iterator = geogig.command(LsTreeOp.class) .setReference(newRepoRoot.toString()).setStrategy(Strategy.DEPTHFIRST_ONLY_TREES) .call(); Function<NodeRef, String> keyFunction = new Function<NodeRef, String>() { @Override public String apply(NodeRef input) { return input.path(); } }; ImmutableMap<String, NodeRef> refsByPath = Maps.uniqueIndex(iterator, keyFunction); return refsByPath; } private ImmutableMap<String, NodeRef> getRefsByPath(ObjectId repoRoot, boolean includeFeatures) { Strategy strategy = includeFeatures ? Strategy.DEPTHFIRST : Strategy.DEPTHFIRST_ONLY_TREES; Iterator<NodeRef> iterator = geogig.command(LsTreeOp.class) .setReference(repoRoot.toString()).setStrategy(strategy).call(); Function<NodeRef, String> keyFunction = new Function<NodeRef, String>() { @Override public String apply(NodeRef input) { return input.path(); } }; ImmutableMap<String, NodeRef> refsByPath = Maps.uniqueIndex(iterator, keyFunction); return refsByPath; } private void print(ObjectId treeId) { System.err.println(treeId); Iterator<NodeRef> iterator = geogig.command(LsTreeOp.class).setReference(treeId.toString()) .setStrategy(Strategy.DEPTHFIRST).call(); while (iterator.hasNext()) { print(iterator.next()); } } private void print(NodeRef ref) { System.err.printf("\t%s '%s' -> %s (%s)\n", ref.getType().toString().charAt(0), ref.path(), ref.objectId(), ref.getNode().getMetadataId()); } private void verifyRepositoryTree(String path, ObjectId repoTreeId) { ObjectDatabase objectDb = this.objectDb; verifyTree(objectDb, path, repoTreeId); } private void verifyTree(ObjectDatabase objectDb, String path, ObjectId repoTreeId) { assertTrue(String.format("tree '%s' (%s) is not present", path, repoTreeId), objectDb.exists(repoTreeId)); RevTree tree = objectDb.getTree(repoTreeId); Iterator<Node> children = tree.children(); while (children.hasNext()) { final Node node = children.next(); if (TYPE.TREE.equals(node.getType())) { path = NodeRef.appendChild(path, node.getName()); ObjectId objectId = node.getObjectId(); verifyRepositoryTree(path, objectId); } else if (TYPE.FEATURE.equals(node.getType())) { verifyFeature(node); } else { throw new IllegalStateException(node.getType().toString()); } verifyMetadata(node); } if (tree.buckets().isPresent()) { ImmutableCollection<Bucket> buckets = tree.buckets().get().values(); for (Bucket b : buckets) { ObjectId bucketTreeId = b.id(); verifyRepositoryTree(path + "/" + bucketTreeId.toString().substring(0, 8), bucketTreeId); } } } private void verifyFeature(Node node) { ObjectId objectId = node.getObjectId(); assertTrue("feature " + node.getName() + " -> " + objectId + " is not present in objectDb", objectDb.exists(objectId)); } private void verifyMetadata(Node node) { if (node.getMetadataId().isPresent()) { ObjectId mdId = node.getMetadataId().get(); String msg = "RevFeatureType " + mdId + " is not present (from " + node.getName() + ")"; assertTrue(msg, objectDb.exists(mdId)); } } private RevTree createHeadTree(NodeRef... treeRefs) { RevTree root = createFromRefs(objectDb, treeRefs); objectDb.put(root); CommitBuilder cb = new CommitBuilder(geogig.getPlatform()); ObjectId treeId = root.getId(); RevCommit commit = cb.setTreeId(treeId).setCommitter("Gabriel Roldan") .setAuthor("Gabriel Roldan").build(); objectDb.put(commit); SymRef head = (SymRef) geogig.command(RefParse.class).setName(Ref.HEAD).call().get(); final String currentBranch = head.getTarget(); geogig.command(UpdateRef.class).setName(currentBranch).setNewValue(commit.getId()).call(); verifyRepositoryTree(NodeRef.ROOT, treeId); verifyTreeStructure(treeId, treeRefs); return root; } private void verifyTreeStructure(ObjectId treeId, NodeRef... treeRefs) { Function<NodeRef, String> function = new Function<NodeRef, String>() { @Override public String apply(NodeRef input) { return input.path(); } }; Set<String> expectedPaths = ImmutableSet.copyOf(Iterables.transform( Arrays.asList(treeRefs), function)); ImmutableMap<String, NodeRef> refs = getTreeRefsByPath(treeId); assertEquals(expectedPaths, refs.keySet()); } private RevTree createStageHeadTree(NodeRef... treeRefs) { RevTree root = createFromRefs(indexDb, treeRefs); geogig.command(UpdateRef.class).setName(Ref.STAGE_HEAD).setNewValue(root.getId()).call(); return root; } private RevTree createFromRefs(ObjectDatabase targetDb, NodeRef... treeRefs) { MutableTree mutableTree = MutableTree.createFromRefs(RevTree.EMPTY_TREE_ID, treeRefs); RevTree tree = mutableTree.build(indexDb, targetDb); return tree; } private NodeRef indexTree(String path, String id, String mdId, int numFeatures) { return tree(indexDb, path, id, mdId, numFeatures); } private NodeRef repoTree(String path, String id, String mdId, int numFeatures) { return tree(objectDb, path, id, mdId, numFeatures); } /** * Creates a tree reference for testing, forcing the specified id and metadata id, and with the * specified number of features (zero or more). * <p> * Note the tree is saved to the specified database only if its a leaf tree (more than zero * features), in order for the {@link #createFromRefs} method to be able of saving the parent */ private NodeRef tree(ObjectDatabase db, String path, String id, String mdId, int numFeatures) { Preconditions.checkArgument(numFeatures != 0 || EMPTY_ID.equals(id), "for zero features trees use RevTree.EMPTY_TREE_ID"); final ObjectId treeId = id(id); final ObjectId metadataId = id(mdId); final String feturePrefix = NodeRef.nodeFromPath(path); RevTreeBuilder b = new RevTreeBuilder(db); if (numFeatures > 0) { for (int i = 0; i < numFeatures; i++) { Node fn = feature(db, feturePrefix, i); b.put(fn); } } RevTree fakenId = forceTreeId(b, treeId); if (!db.exists(fakenId.getId())) { db.put(fakenId); } if (!metadataId.isNull()) { RevFeatureType fakeType = new RevFeatureTypeImpl(metadataId, pointsType); if (!db.exists(fakeType.getId())) { db.put(fakeType); } } String name = NodeRef.nodeFromPath(path); String parent = NodeRef.parentPath(path); Envelope bounds = SpatialOps.boundsOf(fakenId); Node node = Node.create(name, treeId, metadataId, TYPE.TREE, bounds); return new NodeRef(node, parent, ObjectId.NULL); } private RevTree forceTreeId(RevTreeBuilder b, ObjectId treeId) { RevTree tree = b.build(); RevTree fakenId = RevTreeImpl.create(treeId, tree.size(), tree); return fakenId; } private String point(int i) { return "POINT(" + i + " " + i + ")"; } private Node feature(ObjectDatabase db, String idPrefix, int index) { final String id = idPrefix + "." + index; final Feature feature; try { feature = super.feature(pointsType, id, id, index, point(index)); } catch (ParseException e) { throw Throwables.propagate(e); } RevFeature revFeature = RevFeatureBuilder.build(feature); db.put(revFeature); Envelope bounds = (Envelope) feature.getBounds(); return Node.create(id, revFeature.getId(), ObjectId.NULL, TYPE.FEATURE, bounds); } private static ObjectId id(@Nullable String partialHash) { if (partialHash == null) { return ObjectId.NULL; } partialHash = Strings.padEnd(partialHash, 2 * ObjectId.NUM_BYTES, '0'); return ObjectId.valueOf(partialHash); } private ImmutableSet<String> set(String... contents) { if (contents == null) { return ImmutableSet.of(); } return ImmutableSet.copyOf(contents); } }