package org.obolibrary.oboformat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.semanticweb.owlapi.util.OWLAPIStreamUtils.asList;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.obolibrary.obo2owl.OWLAPIObo2Owl;
import org.obolibrary.obo2owl.OWLAPIOwl2Obo;
import org.obolibrary.obo2owl.Obo2OWLConstants;
import org.obolibrary.obo2owl.Obo2OWLConstants.Obo2OWLVocabulary;
import org.obolibrary.oboformat.diff.Diff;
import org.obolibrary.oboformat.diff.OBODocDiffer;
import org.obolibrary.oboformat.model.Clause;
import org.obolibrary.oboformat.model.Frame;
import org.obolibrary.oboformat.model.OBODoc;
import org.obolibrary.oboformat.model.QualifierValue;
import org.obolibrary.oboformat.parser.OBOFormatConstants.OboFormatTag;
import org.obolibrary.oboformat.parser.OBOFormatParser;
import org.obolibrary.oboformat.writer.OBOFormatWriter;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.formats.OWLXMLDocumentFormat;
import org.semanticweb.owlapi.io.StringDocumentTarget;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLObjectExactCardinality;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectPropertyExpression;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
import org.semanticweb.owlapi.model.OWLSubPropertyChainOfAxiom;
@SuppressWarnings("javadoc")
public class RoundTripTestCase extends RoundTripTestBasics {
private static void checkAsAltId(IRI iri, OWLOntology ont, String replacedBy) {
assertTrue(
ont.annotationAssertionAxioms(iri).anyMatch(ax -> ax.getProperty().isDeprecated()));
assertTrue(ont.annotationAssertionAxioms(iri)
.filter(ax -> ax.getProperty().getIRI().equals(Obo2OWLConstants.IRI_IAO_0000231))
.map(ax -> ax.getValue().asIRI()).filter(Optional::isPresent)
.anyMatch(p -> Obo2OWLConstants.IRI_IAO_0000227.equals(p.get())));
String altId = ont.annotationAssertionAxioms(iri)
.filter(
ax -> ax.getProperty().getIRI().equals(Obo2OWLVocabulary.IRI_IAO_0100001.getIRI()))
.map(ax -> ax.getValue().asIRI()).filter(Optional::isPresent)
.map(p -> OWLAPIOwl2Obo.getIdentifier(p.get())).findAny().orElse(null);
assertEquals(replacedBy, altId);
}
@Test
public void testAltIds() throws Exception {
OBODoc input = parseOBOFile("alt_id_test.obo");
OWLOntology owl = convert(input);
// check round trip
OBODoc output = convert(owl);
String outObo = renderOboToString(output);
assertEquals(readResource("alt_id_test.obo").trim(), outObo.trim());
// check owl
// check that both alt_id is declared as deprecated class and has
// appropriate annotations
IRI alt_id_t1 = IRI.create("http://purl.obolibrary.org/obo/", "TEST_1000");
IRI alt_id_r1 = IRI.create("http://purl.obolibrary.org/obo/", "TEST_REL_1000");
checkAsAltId(alt_id_t1, owl, "TEST:0001");
checkAsAltId(alt_id_r1, owl, "TEST_REL:0001");
}
@Test
public void testRoundTripCardinality() throws Exception {
// create minimal ontology
OBODoc oboDocSource = parseOBOFile("roundtrip_cardinality.obo");
// convert to OWL and retrieve def
OWLAPIObo2Owl bridge = new OWLAPIObo2Owl(m1);
OWLOntology owlOntology = bridge.convert(oboDocSource);
OWLDataFactory factory = owlOntology.getOWLOntologyManager().getOWLDataFactory();
OWLClass c = factory.getOWLClass(bridge.oboIdToIRI("PR:000027136"));
// Relations
boolean foundRel1 = false;
boolean foundRel2 = false;
List<OWLSubClassOfAxiom> axioms = asList(owlOntology.subClassAxiomsForSubClass(c));
assertEquals(3, axioms.size());
for (OWLSubClassOfAxiom axiom : axioms) {
OWLClassExpression superClass = axiom.getSuperClass();
if (superClass instanceof OWLObjectExactCardinality) {
OWLObjectExactCardinality cardinality = (OWLObjectExactCardinality) superClass;
OWLClassExpression filler = cardinality.getFiller();
assertFalse(filler.isAnonymous());
IRI iri = filler.asOWLClass().getIRI();
if (iri.equals(bridge.oboIdToIRI("PR:000005116"))) {
foundRel1 = true;
assertEquals(1, cardinality.getCardinality());
} else if (iri.equals(bridge.oboIdToIRI("PR:000027122"))) {
foundRel2 = true;
assertEquals(2, cardinality.getCardinality());
}
}
}
assertTrue(foundRel1);
assertTrue(foundRel2);
// convert back to OBO
OWLAPIOwl2Obo owl2Obo = new OWLAPIOwl2Obo(OWLManager.createOWLOntologyManager());
OBODoc convertedOboDoc = owl2Obo.convert(owlOntology, storerParameters);
Frame convertedFrame = convertedOboDoc.getTermFrame("PR:000027136");
assert convertedFrame != null;
Collection<Clause> clauses = convertedFrame.getClauses(OboFormatTag.TAG_RELATIONSHIP);
// check that round trip still contains relationships
assertEquals(2, clauses.size());
for (Clause clause : clauses) {
Collection<QualifierValue> qualifierValues = clause.getQualifierValues();
assertEquals(1, qualifierValues.size());
QualifierValue value = qualifierValues.iterator().next();
assertEquals("cardinality", value.getQualifier());
if (clause.getValue2().equals("PR:000005116")) {
assertEquals("1", value.getValue());
} else if (clause.getValue2().equals("PR:000027122")) {
assertEquals("2", value.getValue());
}
}
}
@Test
public void testRoundTripLabeledXrefs() throws Exception {
OBODoc source = parseOBOFile("labeled_xrefs.obo");
String written = renderOboToString(source);
OBODoc parsed = parseOboToString(written);
List<Diff> diffs = OBODocDiffer.getDiffs(source, parsed);
assertEquals(0, diffs.size());
}
@Test
public void testDefinitionsMultipleDefXref() {
OWLAnnotationProperty hasDbXref = df
.getOWLAnnotationProperty("http://www.geneontology.org/formats/oboInOwl#", "hasDbXref");
OWLOntology owlOnt = convertOBOFile("multiple_def_xref_test.obo");
AtomicInteger n = new AtomicInteger(0);
owlOnt.axioms().forEach(ax -> ax.annotations(hasDbXref).forEach(a -> {
OWLLiteral v = (OWLLiteral) a.getValue();
// expect this twice, as we have annotations on synonyms
if (v.getLiteral().equals("BTO:0001750")) {
n.incrementAndGet();
}
if (v.getLiteral().equals("Wikipedia:Mandibular_condyle")) {
n.incrementAndGet();
}
}));
assertEquals(3, n.intValue());
}
@Test
public void testWriteNamespaceIdRule() throws Exception {
OBODoc oboDoc = parseOBOFile("namespace-id-rule.obo");
String oboString = renderOboToString(oboDoc);
assertTrue(oboString.contains("\nnamespace-id-rule: * test:$sequence(7,0,9999999)$\n"));
}
@Test
public void testWriteReadConvertedOWLNamespaceIdRule() throws Exception {
OBODoc oboDoc = parseOBOFile("namespace-id-rule.obo");
OWLOntology owlOntology = convert(oboDoc);
OWLOntologyManager manager = owlOntology.getOWLOntologyManager();
StringDocumentTarget documentTarget = new StringDocumentTarget();
manager.saveOntology(owlOntology, new OWLXMLDocumentFormat(), documentTarget);
String owlString = documentTarget.toString();
OWLOntology reloadedOwl = loadOntologyFromString(owlString, new OWLXMLDocumentFormat());
assertEquals(owlOntology.getAxiomCount(), reloadedOwl.getAxiomCount());
}
/**
* Test that the converted RO from OWL to OBO can be written and parsed back into OBO, and also
* round-trip back into OWL.
*/
@Test
public void testRoundTripOWLRO() throws Exception {
OWLOntology oo1 = parseOWLFile("ro.owl");
OBODoc oboDoc1 = convert(oo1);
// write OBO
String oboString = renderOboToString(oboDoc1);
// parse OBO
OBOFormatParser p = new OBOFormatParser();
OBODoc oboDoc2 = p.parse(new BufferedReader(new StringReader(oboString)));
// check that the annotations are pre-served on the property values
Frame typedefFrame = oboDoc2.getTypedefFrame("RO:0002224");
assert typedefFrame != null;
Collection<Clause> propertyValues =
typedefFrame.getClauses(OboFormatTag.TAG_PROPERTY_VALUE);
boolean found = false;
for (Clause clause : propertyValues) {
if ("IAO:0000118".equals(clause.getValue())
&& "started by".equals(clause.getValue2())) {
Collection<QualifierValue> values = clause.getQualifierValues();
assertEquals(1, values.size());
QualifierValue value = values.iterator().next();
assertEquals("http://purl.obolibrary.org/obo/IAO_0000116", value.getQualifier());
assertEquals("From Allen terminology", value.getValue());
found = true;
}
}
assertTrue("The expected annotations on the property value are missing.", found);
// convert back into OWL
convert(oboDoc2);
// check that the two oboDocs are equal
List<Diff> diffs = OBODocDiffer.getDiffs(oboDoc1, oboDoc2);
assertEquals("Expected one diff, the oboformat diff is missing from the conversion", 1,
diffs.size());
}
@Test
public void testOBOIsInferredAnnotation() throws Exception {
OBODoc input = parseOBOFile("is_inferred_annotation.obo");
OWLOntology owl = convert(input);
// check round trip
OBODoc output = convert(owl);
String outObo = renderOboToString(output);
assertEquals(readResource("is_inferred_annotation.obo"), outObo);
// check owl
IRI t1 = IRI.create("http://purl.obolibrary.org/obo/", "TEST_0001");
IRI t3 = IRI.create("http://purl.obolibrary.org/obo/", "TEST_0003");
IRI isInferredIRI = IRI.create(Obo2OWLConstants.OIOVOCAB_IRI_PREFIX, "is_inferred");
AtomicBoolean hasAnnotation = new AtomicBoolean(false);
OWLAnnotationProperty infIRI = df.getOWLAnnotationProperty(isInferredIRI);
owl.axioms(AxiomType.SUBCLASS_OF).forEach(axiom -> {
OWLClassExpression superClassCE = axiom.getSuperClass();
OWLClassExpression subClassCE = axiom.getSubClass();
if (!superClassCE.isAnonymous() && !subClassCE.isAnonymous()) {
OWLClass superClass = (OWLClass) superClassCE;
OWLClass subClass = (OWLClass) subClassCE;
if (superClass.getIRI().equals(t1) && subClass.getIRI().equals(t3)) {
axiom.annotations(infIRI).map(OWLAnnotation::getValue).forEach(v -> {
if (v instanceof OWLLiteral) {
assertEquals("true", ((OWLLiteral) v).getLiteral());
} else {
fail("The value is not the expected type, expected OWLiteral but was: "
+ v.getClass().getName());
}
hasAnnotation.set(true);
});
}
}
});
assertTrue(
"The sub class reation between t3 and t1 should have an is_inferred=true annotation",
hasAnnotation.get());
}
@Test
public void testRequireEmptyXrefList() throws Exception {
OBODoc obo = parseOBOFile("synonym_test.obo");
// Get synonym clause with an empty xref list
Frame frame = obo.getTermFrame("GO:0009579");
assertNotNull(frame);
// write frame
StringWriter stringWriter = new StringWriter();
BufferedWriter bufferedWriter = new BufferedWriter(stringWriter);
OBOFormatWriter oboWriter = new OBOFormatWriter();
oboWriter.write(frame, bufferedWriter, null);
bufferedWriter.flush();
// get written frame
String line = stringWriter.getBuffer().toString();
// check that written frame has line:
// synonym: "photosynthetic membrane" RELATED []
assertTrue(line.contains("\nsynonym: \"photosynthetic membrane\" RELATED []\n"));
}
@Test
public void testConvertVersionIRI() {
OWLOntology owlOnt = convertOBOFile("version_iri_test.obo");
assertNotNull(owlOnt);
IRI v = owlOnt.getOntologyID().getVersionIRI().get();
assertEquals("http://purl.obolibrary.org/obo/go/2012-01-01/go.owl", v.toString());
}
@Test
public void shouldContainExpectedAnnotationXrefescapecolon() {
OBODoc oboFile = parseOBOFile("xref_escapecolon.obo");
OWLOntology o = convert(oboFile);
IRI expected = IRI.create("http://purl.obolibrary.org/obo/", "GO_0042062%3A");
assertEquals(18, o.annotationAssertionAxioms(expected).count());
}
@Test
public void testConvertTransitiveOver() {
// PARSE TEST FILE, CONVERT TO OWL
OWLOntology ontology = convert(parseOBOFile("relation_shorthand_test.obo"));
// TEST CONTENTS OF OWL ONTOLOGY
IRI regulatesIRI = getIriByLabel(ontology, "regulates");
assertNotNull(regulatesIRI);
boolean ok = false;
// test that transitive over is translated to a property chain
List<OWLSubPropertyChainOfAxiom> axioms =
asList(ontology.axioms(AxiomType.SUB_PROPERTY_CHAIN_OF));
for (OWLSubPropertyChainOfAxiom axiom : axioms) {
OWLObjectProperty p = (OWLObjectProperty) axiom.getSuperProperty();
if (regulatesIRI.equals(p.getIRI())) {
List<OWLObjectPropertyExpression> chain = axiom.getPropertyChain();
assertEquals(2, chain.size());
assertEquals(p, chain.get(0));
assertEquals("http://purl.obolibrary.org/obo/BFO_0000050",
((OWLObjectProperty) chain.get(1)).getIRI().toString());
ok = true;
}
}
assertTrue(ok);
// CONVERT BACK TO OBO
OBODoc obodoc = convert(ontology);
// test that transitive over is converted back
Frame tf = obodoc.getTypedefFrame("regulates");
assert tf != null;
assertEquals(3, tf.getClauses().size());
assertEquals("regulates", tf.getTagValue(OboFormatTag.TAG_ID));
assertEquals("regulates", tf.getTagValue(OboFormatTag.TAG_NAME));
Clause clause = tf.getClause(OboFormatTag.TAG_TRANSITIVE_OVER);
assert clause != null;
assertEquals(1, clause.getValues().size());
assertEquals("part_of", clause.getValue());
assertTrue(clause.getQualifierValues().isEmpty());
}
}