package models;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jena.atlas.RuntimeIOException;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.riot.Lang;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* Created by fo on 10.12.15, modified by pvb
*/
public class TripleCommit implements Commit {
private final Header mHeader;
private final Commit.Diff mDiff;
public static class Header implements Commit.Header {
public static final String AUTHOR_HEADER = "Author";
public static final String DATE_HEADER = "Date";
public static final String HEADER_SEPARATOR = ": ";
public final String author;
public final ZonedDateTime timestamp;
public Header(final String aAuthor, final ZonedDateTime aTimestamp) {
this.author = aAuthor;
this.timestamp = aTimestamp;
}
public String toString() {
return AUTHOR_HEADER.concat(HEADER_SEPARATOR).concat(author).concat("\n").concat(DATE_HEADER)
.concat(HEADER_SEPARATOR).concat(timestamp.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)).concat("\n");
}
public Map<String, String> toMap() {
Map<String, String> map = new HashMap<>();
map.put(AUTHOR_HEADER, author);
map.put(DATE_HEADER, timestamp.toString());
return map;
}
public String getAuthor() {
return this.author;
}
public ZonedDateTime getTimestamp() {
return this.timestamp;
}
public static Header fromString(String aHeaderString) {
Scanner scanner = new Scanner(aHeaderString);
String authorHeader;
try {
authorHeader = scanner.nextLine();
} catch (NoSuchElementException e) {
throw new IllegalArgumentException("Header missing author line");
}
String author = authorHeader.substring(8);
if (!authorHeader.startsWith("Author: ") || StringUtils.isEmpty(author)) {
throw new IllegalArgumentException("Header missing author");
}
String timestampHeader;
try {
timestampHeader = scanner.nextLine();
} catch (NoSuchElementException e) {
throw new IllegalArgumentException("Header missing author line");
}
ZonedDateTime timestamp = null;
try {
timestamp = ZonedDateTime.parse(timestampHeader.substring(6));
if (!timestampHeader.startsWith("Date: ")) {
throw new IllegalArgumentException("Header missing date line");
}
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Header contains invalid date");
}
return new Header(author, timestamp);
}
}
public static class Diff implements Commit.Diff {
final private static String mLang = Lang.NTRIPLES.getName();
final private List<Commit.Diff.Line> mLines;
public static class Line extends Commit.Diff.Line {
//public final boolean add;
public final Statement stmt;
public Line(Statement stmt, boolean add) {
this.add = add;
this.stmt = stmt;
}
}
public Diff() {
mLines = new ArrayList<>();
}
public Diff(ArrayList<Commit.Diff.Line> aLineList) {
mLines = aLineList;
}
public List<Commit.Diff.Line> getLines() {
return this.mLines;
}
public void addStatement(Statement stmt) {
this.mLines.add(new Line(stmt, true));
}
public void removeStatement(Statement stmt) {
this.mLines.add(new Line(stmt, false));
}
@Override
public void apply(Object model) {
apply((Model) model);
}
public void apply(Model model) {
for (Commit.Diff.Line line : this.mLines) {
if (line.add) {
model.add(((Line) line).stmt);
} else {
model.remove(((Line) line).stmt);
}
}
}
@Override
public void append(Commit.Diff diff) {
mLines.addAll(diff.getLines());
}
@Override
public void unapply(Object model) {
unapply((Model) model);
}
public void unapply(Model model) {
for (Commit.Diff.Line line : this.mLines) {
if (line.add) {
model.remove(((Line) line).stmt);
} else {
model.add(((Line) line).stmt);
}
}
}
public Diff reverse() {
TripleCommit.Diff reverse = new TripleCommit.Diff();
for (Commit.Diff.Line line : mLines) {
if (line.add) {
reverse.removeStatement(((Line) line).stmt);
} else {
reverse.addStatement(((Line) line).stmt);
}
}
return reverse;
}
public String toString() {
final Model buffer = ModelFactory.createDefaultModel();
StringBuilder diffString = new StringBuilder();
StringWriter triple = new StringWriter();
for (Commit.Diff.Line line : this.mLines) {
Statement statement = ((Line) line).stmt;
Resource subject = statement.getSubject();
Property predicate = statement.getPredicate();
RDFNode object = statement.getObject();
if (subject.isAnon()) {
subject = ResourceFactory.createResource("_:".concat(subject.toString()));
}
if (object.isAnon()) {
object = ResourceFactory.createResource("_:".concat(object.toString()));
}
Statement skolemized = ResourceFactory.createStatement(subject, predicate, object);
buffer.add(skolemized).write(triple, mLang).removeAll();
diffString.append((((Line) line).add ? "+ " : "- ").concat(triple.toString()));
triple.getBuffer().setLength(0);
}
return diffString.toString();
}
public static Diff fromString(String aDiffString) {
final Model buffer = ModelFactory.createDefaultModel();
ArrayList<Commit.Diff.Line> lines = new ArrayList<>();
Scanner scanner = new Scanner(aDiffString).useDelimiter("\\n");
String diffLine;
while (scanner.hasNext()) {
diffLine = scanner.next().trim();
String op = diffLine.substring(0, 1);
if (!op.equals("+") && !op.equals("-")) {
throw new IllegalArgumentException("Diff Line malformed: " + diffLine);
}
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(diffLine.substring(1)
.getBytes(StandardCharsets.UTF_8))) {
buffer.read(byteArrayInputStream, null, mLang);
lines.add(new Line(buffer.listStatements().nextStatement(), op.equals("+")));
buffer.removeAll();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
scanner.close();
return new Diff(lines);
}
}
public TripleCommit(Header aHeader, Commit.Diff aDiff) {
mHeader = aHeader;
mDiff = aDiff;
}
public Commit.Diff getDiff() {
return this.mDiff;
}
public Header getHeader() {
return this.mHeader;
}
public String getId() {
return DigestUtils.sha1Hex(this.toString());
}
public String toString() {
return mHeader.toString().concat("\n").concat(mDiff.toString());
}
public boolean equals(Object aOther) {
return aOther instanceof TripleCommit && this.getId().equals(((TripleCommit) aOther).getId());
}
public static TripleCommit fromString(String aCommitString) {
String[] parts = aCommitString.split("\\n\\n");
if (!(parts.length == 2)) {
throw new IllegalArgumentException("Malformed commit");
}
return new TripleCommit(Header.fromString(parts[0]), Diff.fromString(parts[1]));
}
}