/* 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.repository;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Platform;
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.storage.ObjectDatabase;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.geometry.BoundingBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.vividsolutions.jts.geom.Envelope;
class RevTreeBuilder2 {
private static final Logger LOGGER = LoggerFactory.getLogger(RevTreeBuilder2.class);
private final NodeIndex nodeIndex;
private final ObjectDatabase db;
private final RevTree original;
private final Map<Name, RevFeatureType> revFeatureTypes = Maps.newConcurrentMap();
private final ObjectId defaultMetadataId;
/**
* Copy constructor
*/
public RevTreeBuilder2(final ObjectDatabase db, @Nullable final RevTree origTree,
final ObjectId defaultMetadataId, final Platform platform,
final ExecutorService executorService) {
this.db = db;
this.original = origTree;
this.defaultMetadataId = defaultMetadataId;
this.nodeIndex = new FileNodeIndex(platform, executorService);
}
public ObjectId getDefaultMetadataId() {
return defaultMetadataId;
}
/**
* Adds or replaces an element in the tree with the given key.
* <p>
* <!-- Implementation detail: If the number of cached entries (entries held directly by this
* tree) reaches {@link #DEFAULT_NORMALIZATION_THRESHOLD}, this tree will {@link #normalize()}
* itself.
*
* -->
*
* @param key non null
* @param value non null
*/
public synchronized RevTreeBuilder2 put(final Node node) {
Preconditions.checkNotNull(node, "node can't be null");
nodeIndex.add(node);
return this;
}
/**
* Traverses the nodes in the {@link NodeIndex}, deletes the ones with {@link ObjectId#NULL
* NULL} ObjectIds, and adds the ones with non "NULL" ids.
*
* @return the new tree, not saved to the object database. Any bucket tree though is saved when
* this method returns.
*/
public RevTree build() {
if (nodeIndex == null) {
return original.builder(db).build();
}
Stopwatch sw = Stopwatch.createStarted();
RevTreeBuilder builder;
try {
builder = new RevTreeBuilder(db, original);
Iterator<Node> nodes = nodeIndex.nodes();
while (nodes.hasNext()) {
Node node = nodes.next();
if (node.getObjectId().isNull()) {
builder.remove(node.getName());
} else {
builder.put(node);
}
}
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
} finally {
nodeIndex.close();
}
LOGGER.debug("Index traversed in {}", sw.stop());
sw.reset().start();
RevTree namedTree = builder.build();
saveExtraFeatureTypes();
LOGGER.debug("RevTreeBuilder.build() in {}", sw.stop());
return namedTree;
}
private void saveExtraFeatureTypes() {
Collection<RevFeatureType> types = revFeatureTypes.values();
List<RevFeatureType> nonDefaults = Lists.newLinkedList();
for (RevFeatureType t : types) {
if (!t.getId().equals(defaultMetadataId)) {
nonDefaults.add(t);
}
}
if (!nonDefaults.isEmpty()) {
db.putAll(nonDefaults.iterator());
}
}
public Node putFeature(final ObjectId id, final String name,
@Nullable final BoundingBox bounds, final FeatureType type) {
Envelope bbox;
if (bounds == null) {
bbox = null;
} else if (bounds instanceof Envelope) {
bbox = (Envelope) bounds;
} else {
bbox = new Envelope(bounds.getMinimum(0), bounds.getMaximum(0), bounds.getMinimum(1),
bounds.getMaximum(1));
}
RevFeatureType revFeatureType = revFeatureTypes.get(type.getName());
if (null == revFeatureType) {
revFeatureType = RevFeatureTypeImpl.build(type);
revFeatureTypes.put(type.getName(), revFeatureType);
}
ObjectId metadataId = revFeatureType.getId().equals(defaultMetadataId) ? ObjectId.NULL
: revFeatureType.getId();
Node node = Node.create(name, id, metadataId, TYPE.FEATURE, bbox);
put(node);
return node;
}
/**
* Marks the node named after {@code fid} to be deleted by adding a Node with
* {@link ObjectId#NULL NULL} ObjectId
*/
public void removeFeature(String fid) {
Node node = Node.create(fid, ObjectId.NULL, ObjectId.NULL, TYPE.FEATURE, null);
put(node);
}
}