package org.gbif.checklistbank.neo.traverse;
import org.gbif.api.vocabulary.Rank;
import org.gbif.checklistbank.neo.NeoProperties;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.codahale.metrics.Meter;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A utility class to iterate over nodes in taxonomic order and execute any number of StartEndHandler while walking.
*/
public class TreeWalker {
private static final Logger LOG = LoggerFactory.getLogger(TreeWalker.class);
private static final int reportingSize = 10000;
public static void walkTree(GraphDatabaseService db, boolean inclProParte, StartEndHandler ... handler) {
walkTree(db, inclProParte, null, null, null, handler);
}
/**
* Walks all nodes in a taxonomic tree order in a single transaction including pro parte relations
* @param root if given starts to walk the subtree including the given node
*/
public static void walkTree(GraphDatabaseService db, boolean inclProParte, @Nullable Node root, @Nullable Rank lowestRank, @Nullable Meter meter, StartEndHandler ... handler) {
try (Transaction tx = db.beginTx()){
walkTree(TreeIterablesSorted.allPath(db, root, lowestRank, inclProParte), meter, handler);
}
}
/**
* Walks allAccepted nodes in a single transaction
*/
public static void walkAcceptedTree(GraphDatabaseService db, StartEndHandler ... handler) {
walkAcceptedTree(db, null, null, new Meter(), handler);
}
/**
* Walks allAccepted nodes in a single transaction
*/
public static void walkAcceptedTree(GraphDatabaseService db, @Nullable Node root, @Nullable Rank lowestRank, @Nullable Meter meter, StartEndHandler ... handler) {
try (Transaction tx = db.beginTx()){
walkTree(TreeIterablesSorted.acceptedPath(db, root, lowestRank), meter, handler);
}
}
private static void walkTree(ResourceIterable<Path> paths, @Nullable Meter meter, StartEndHandler ... handler) {
Path lastPath = null;
long counter = 0;
try (ResourceIterator<Path> iter = paths.iterator()){
while (iter.hasNext()) {
Path p = iter.next();
//logPath(p);
if (counter % reportingSize == 0) {
LOG.debug("Processed {}. Rate = {}", counter, meter == null ? "unknown" : meter.getMeanRate());
}
if (meter != null) {
meter.mark();
}
if (lastPath != null) {
PeekingIterator<Node> lIter = Iterators.peekingIterator(lastPath.nodes().iterator());
PeekingIterator<Node> cIter = Iterators.peekingIterator(p.nodes().iterator());
while (lIter.hasNext() && cIter.hasNext() && lIter.peek().equals(cIter.peek())) {
lIter.next();
cIter.next();
}
// only non shared nodes left.
// first close allAccepted old nodes, then open new ones
// reverse order for closing nodes...
for (Node n : ImmutableList.copyOf(lIter).reverse()) {
handleEnd(n, handler);
}
while (cIter.hasNext()) {
handleStart(cIter.next(), handler);
}
} else {
// only new nodes
for (Node n : p.nodes()) {
handleStart(n, handler);
}
}
lastPath = p;
counter++;
}
// close all remaining nodes
if (lastPath != null) {
for (Node n : ImmutableList.copyOf(lastPath.nodes()).reverse()) {
handleEnd(n, handler);
}
}
}
}
private static void handleStart(Node n, StartEndHandler ... handler) {
for (StartEndHandler h : handler) {
h.start(n);
}
}
private static void handleEnd(Node n, StartEndHandler ... handler) {
for (StartEndHandler h : handler) {
h.end(n);
}
}
private static void logPath(Path p) {
StringBuilder sb = new StringBuilder();
for (Node n : p.nodes()) {
if (sb.length() > 0) {
sb.append(" -- ");
}
sb.append(NeoProperties.getCanonicalName(n));
}
LOG.info(sb.toString());
}
}