Skip to content

Commit 104d5b9

Browse files
committed
Use Sesame NQuads parser if possible to compare blank nodes between documents
Some of the blank nodes are in the predicate position which is disallowed by RDF (excluding the unstandardised Generalised RDF), which makes it still difficult to compare In addition, need to have a non-standard comparator to ensure that blank nodes in context position are not ignored in equality comparison
1 parent 6bd6900 commit 104d5b9

3 files changed

Lines changed: 199 additions & 6 deletions

File tree

core/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@
4747
<artifactId>mockito-core</artifactId>
4848
<scope>test</scope>
4949
</dependency>
50+
<dependency>
51+
<groupId>org.openrdf.sesame</groupId>
52+
<artifactId>sesame-rio-api</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.openrdf.sesame</groupId>
57+
<artifactId>sesame-rio-nquads</artifactId>
58+
<scope>test</scope>
59+
</dependency>
5060
</dependencies>
5161
</project>
5262

core/src/test/java/com/github/jsonldjava/core/JSONLDProcessorTest.java

Lines changed: 183 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,41 @@
1313
import java.io.InputStream;
1414
import java.io.InputStreamReader;
1515
import java.io.OutputStreamWriter;
16+
import java.io.StringReader;
1617
import java.net.URISyntaxException;
1718
import java.text.SimpleDateFormat;
1819
import java.util.ArrayList;
1920
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
23+
import java.util.Comparator;
2224
import java.util.Date;
2325
import java.util.Iterator;
2426
import java.util.LinkedHashMap;
2527
import java.util.List;
2628
import java.util.Map;
29+
import java.util.Set;
30+
import java.util.TreeSet;
2731

2832
import org.junit.AfterClass;
2933
import org.junit.BeforeClass;
3034
import org.junit.Test;
3135
import org.junit.runner.RunWith;
3236
import org.junit.runners.Parameterized;
3337
import org.junit.runners.Parameterized.Parameters;
38+
import org.openrdf.model.BNode;
39+
import org.openrdf.model.Literal;
40+
import org.openrdf.model.Statement;
41+
import org.openrdf.model.URI;
42+
import org.openrdf.model.Value;
43+
import org.openrdf.model.impl.ValueFactoryImpl;
44+
import org.openrdf.model.util.ModelUtil;
45+
import org.openrdf.rio.ParserConfig;
46+
import org.openrdf.rio.RDFFormat;
47+
import org.openrdf.rio.RDFParseException;
48+
import org.openrdf.rio.Rio;
49+
import org.openrdf.rio.helpers.BasicParserSettings;
50+
import org.openrdf.rio.helpers.ParseErrorCollector;
3451

3552
import com.fasterxml.jackson.core.JsonGenerationException;
3653
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -421,8 +438,8 @@ public void runTest() throws URISyntaxException, IOException, JSONLDProcessingEr
421438
.get("useRdfType");
422439
}
423440
if (((Map<String, Object>) test.get("option")).containsKey("useNativeTypes")) {
424-
options.useNativeTypes = (Boolean) ((Map<String, Object>) test.get("option"))
425-
.get("useNativeTypes");
441+
options.useNativeTypes = (Boolean) ((Map<String, Object>) test
442+
.get("option")).get("useNativeTypes");
426443
}
427444
}
428445
result = JSONLD.fromRDF(input, options);
@@ -437,9 +454,42 @@ public void runTest() throws URISyntaxException, IOException, JSONLDProcessingEr
437454

438455
Boolean testpassed = false;
439456
try {
440-
// TODO: for tests that are supposed to fail, a more detailed check
441-
// that it failed in the right way is needed
442-
testpassed = Obj.equals(expect, result) || failure_expected;
457+
if (options.format != null && options.format.equals("application/nquads")) {
458+
if (expect instanceof String && result instanceof String) {
459+
ParseErrorCollector expectedErrors = new ParseErrorCollector();
460+
ParseErrorCollector resultErrors = new ParseErrorCollector();
461+
ParserConfig parserConfig = new ParserConfig();
462+
parserConfig.addNonFatalError(BasicParserSettings.VERIFY_DATATYPE_VALUES);
463+
parserConfig.addNonFatalError(BasicParserSettings.VERIFY_LANGUAGE_TAGS);
464+
Set<Statement> expectedModel = new TreeSet<Statement>(
465+
new ContextInsensitiveStatementComparator());
466+
Set<Statement> resultModel = new TreeSet<Statement>(
467+
new ContextInsensitiveStatementComparator());
468+
try {
469+
expectedModel.addAll(Rio.parse(new StringReader((String) expect),
470+
options.base, RDFFormat.NQUADS, parserConfig,
471+
ValueFactoryImpl.getInstance(), expectedErrors));
472+
resultModel.addAll(Rio.parse(new StringReader((String) result),
473+
options.base, RDFFormat.NQUADS, parserConfig,
474+
ValueFactoryImpl.getInstance(), resultErrors));
475+
testpassed = ModelUtil.equals(expectedModel, resultModel);
476+
} catch (RDFParseException e) {
477+
// Sesame cannot handle blank node predicates, so try to
478+
// do a check using our N-Quads parser that can
479+
testpassed = Obj.equals(expect, result) || failure_expected;
480+
if (!testpassed) {
481+
e.printStackTrace();
482+
}
483+
}
484+
} else {
485+
testpassed = Obj.equals(expect, result) || failure_expected;
486+
}
487+
} else {
488+
// TODO: for tests that are supposed to fail, a more detailed
489+
// check
490+
// that it failed in the right way is needed
491+
testpassed = Obj.equals(expect, result) || failure_expected;
492+
}
443493
if (testpassed == false) {
444494
// System.out.println("failed test!!! details:");
445495
// jsonDiff("/", expect, result);
@@ -563,4 +613,132 @@ else if (expect instanceof List && result instanceof List) {
563613
}
564614
}
565615
}
616+
617+
private static class ContextInsensitiveStatementComparator implements Comparator<Statement> {
618+
public final static int BEFORE = -1;
619+
public final static int EQUALS = 0;
620+
public final static int AFTER = 1;
621+
622+
@Override
623+
public int compare(Statement first, Statement second) {
624+
if (first == second) {
625+
return EQUALS;
626+
}
627+
628+
if (first.getSubject().equals(second.getSubject())) {
629+
if (first.getPredicate().equals(second.getPredicate())) {
630+
if (first.getObject().equals(second.getObject())) {
631+
return EQUALS;
632+
} else {
633+
return new ValueComparator().compare(first.getObject(), second.getObject());
634+
}
635+
} else {
636+
return new ValueComparator().compare(first.getPredicate(),
637+
second.getPredicate());
638+
}
639+
} else {
640+
return new ValueComparator().compare(first.getSubject(), second.getSubject());
641+
}
642+
}
643+
644+
}
645+
646+
private static class ValueComparator implements Comparator<Value> {
647+
public final static int BEFORE = -1;
648+
public final static int EQUALS = 0;
649+
public final static int AFTER = 1;
650+
651+
/**
652+
* Sorts in the order nulls>BNodes>URIs>Literals
653+
* <p/>
654+
* This is due to the fact that nulls are only applicable to contexts,
655+
* and according to the OpenRDF documentation, the type of the null
656+
* cannot be sufficiently distinguished from any other Value to make an
657+
* intelligent comparison to other Values
658+
* <p/>
659+
* http://www.openrdf.org/doc/sesame2/api/org/openrdf/OpenRDFUtil.html#
660+
* verifyContextNotNull(org.openrdf.model.Resource...)
661+
* <p/>
662+
* BNodes are sorted according to the lexical compare of their
663+
* identifiers, which provides a way to sort statements with the same
664+
* BNodes in the same positions, near each other
665+
* <p/>
666+
* BNode sorting is not specified across sessions
667+
*/
668+
@Override
669+
public int compare(Value first, Value second) {
670+
if (first == null) {
671+
if (second == null) {
672+
return EQUALS;
673+
} else {
674+
return BEFORE;
675+
}
676+
} else if (second == null) {
677+
// always sort null Values before others, so if the second is
678+
// null, but the first wasn't, sort the first after the second
679+
return AFTER;
680+
}
681+
682+
if (first == second || first.equals(second)) {
683+
return EQUALS;
684+
}
685+
686+
if (first instanceof BNode) {
687+
if (second instanceof BNode) {
688+
// if both are BNodes, sort based on the lexical value of
689+
// the internal ID
690+
// Although this sorting is not guaranteed to be consistent
691+
// across sessions,
692+
// it provides a consistent sorting of statements in every
693+
// case
694+
// so that statements with the same BNode are sorted near
695+
// each other
696+
return ((BNode) first).getID().compareTo(((BNode) second).getID());
697+
} else {
698+
return BEFORE;
699+
}
700+
} else if (second instanceof BNode) {
701+
// sort BNodes before other things, and first was not a BNode
702+
return AFTER;
703+
} else if (first instanceof URI) {
704+
if (second instanceof URI) {
705+
return ((URI) first).stringValue().compareTo(((URI) second).stringValue());
706+
} else {
707+
return BEFORE;
708+
}
709+
} else if (second instanceof URI) {
710+
// sort URIs before Literals
711+
return AFTER;
712+
}
713+
// they must both be Literal's, so sort based on the lexical value
714+
// of the Literal
715+
else {
716+
int cmp = first.stringValue().compareTo(second.stringValue());
717+
718+
if (EQUALS == cmp) {
719+
URI firstType = ((Literal) first).getDatatype();
720+
URI secondType = ((Literal) second).getDatatype();
721+
if (null == firstType) {
722+
if (null == secondType) {
723+
String firstLang = ((Literal) first).getLanguage();
724+
String secondLang = ((Literal) second).getLanguage();
725+
726+
return null == firstLang ? (null == secondLang ? EQUALS : BEFORE)
727+
: (null == secondLang ? AFTER : firstLang.compareTo(secondLang));
728+
} else {
729+
return BEFORE;
730+
}
731+
} else {
732+
if (null == secondType) {
733+
return AFTER;
734+
} else {
735+
return firstType.stringValue().compareTo(secondType.stringValue());
736+
}
737+
}
738+
} else {
739+
return cmp;
740+
}
741+
}
742+
}
743+
}
566744
}

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<jena.version>2.11.0</jena.version>
5151
<junit.version>4.11</junit.version>
5252
<rdf2go.version>4.7.4</rdf2go.version>
53-
<sesame.version>2.7.6</sesame.version>
53+
<sesame.version>2.7.7</sesame.version>
5454
<slf4j.version>1.7.5</slf4j.version>
5555
</properties>
5656
<prerequisites>
@@ -83,6 +83,11 @@
8383
<artifactId>sesame-rio-api</artifactId>
8484
<version>${sesame.version}</version>
8585
</dependency>
86+
<dependency>
87+
<groupId>org.openrdf.sesame</groupId>
88+
<artifactId>sesame-rio-nquads</artifactId>
89+
<version>${sesame.version}</version>
90+
</dependency>
8691
<dependency>
8792
<groupId>org.openrdf.sesame</groupId>
8893
<artifactId>sesame-rio-testsuite</artifactId>

0 commit comments

Comments
 (0)