package org.gbif.checklistbank.neo.printer;
import org.gbif.api.vocabulary.Rank;
import org.gbif.checklistbank.neo.NeoProperties;
import org.gbif.checklistbank.neo.RelType;
import org.gbif.checklistbank.neo.traverse.RankEvaluator;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import javax.annotation.Nullable;
import com.beust.jcommander.internal.Lists;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
/**
* Expects no pro parte relations in the walker!
* https://en.wikipedia.org/wiki/Graph_Modelling_Language
* See http://www.fim.uni-passau.de/fileadmin/files/lehrstuhl/brandenburg/projekte/gml/gml-technical-report.pdf
*/
public class GmlPrinter implements TreePrinter {
private final Writer writer;
private final List<Edge> edges = Lists.newArrayList();
private final Function<Node, String> getTitle;
private final boolean strictTree;
private final RankEvaluator rankEvaluator;
/**
* @param strictTree if true omit any pro parte and basionym relations to force a strict tree
*/
public GmlPrinter(Writer writer, @Nullable Rank rankThreshold, Function<Node, String> getTitle, boolean strictTree) {
this.strictTree = strictTree;
this.writer = writer;
this.getTitle = getTitle;
this.rankEvaluator = new RankEvaluator(rankThreshold);
printHeader();
}
private static class Edge {
public final long source;
public final long target;
public Edge(long source, long target) {
this.source = source;
this.target = target;
}
public static Edge create(Relationship rel) {
return new Edge(rel.getStartNode().getId(), rel.getEndNode().getId());
}
public static Edge inverse(Relationship rel) {
return new Edge(rel.getEndNode().getId(), rel.getStartNode().getId());
}
void print(Writer writer) throws IOException {
writer.append(" edge [\n")
.append(" source ")
.append(String.valueOf(source))
.append("\n target ")
.append(String.valueOf(target))
.append("\n ]\n");
}
}
private void printHeader() {
try {
writer.write("graph [\n");
} catch (IOException e) {
Throwables.propagate(e);
}
}
@Override
public void close() {
try {
for (Edge e : edges) {
e.print(writer);
}
writer.write("]\n");
} catch (IOException e) {
Throwables.propagate(e);
}
}
@Override
public void start(Node n) {
try {
String label = String.format("%s [%s]",
getTitle.apply(n),
NeoProperties.getRank(n, Rank.UNRANKED).name().toLowerCase()
);
writer.write(" node [\n");
writer.write(" id " + n.getId() + "\n");
writer.write(" label \"" + label + "\"\n");
writer.write(" ]\n");
// keep edges with minimal footprint for later writes, they need to go at the very end!
// for a strict tree we only use parent and synonym of relations
// synonym_of relations are inversed so the tree strictly points into one direction
if (strictTree) {
for (Relationship rel : n.getRelationships(RelType.PARENT_OF, Direction.OUTGOING)) {
if (rankEvaluator.evaluateNode(rel.getOtherNode(n))) {
edges.add(Edge.create(rel));
}
}
for (Relationship rel : n.getRelationships(RelType.SYNONYM_OF, Direction.OUTGOING)) {
if (rankEvaluator.evaluateNode(rel.getOtherNode(n))) {
edges.add(Edge.inverse(rel));
}
}
} else {
for (Relationship rel : n.getRelationships(Direction.OUTGOING)) {
if (rankEvaluator.evaluateNode(rel.getOtherNode(n))) {
edges.add(Edge.create(rel));
}
}
}
} catch (IOException e) {
Throwables.propagate(e);
}
}
@Override
public void end(Node n) {
}
}