Skip to content

Commit

Permalink
Allow arbitrary annotation properties as qualifier tags in OBO #1099
Browse files Browse the repository at this point in the history
Allowing arbitrary defined annotation properties as qualifier tags.

Use oboInOWL as the default namespace when looking up tag IRIs.
This helps with backwards compatibility for undeclared annotation
properties.

Enforce oio namespace for created_by and creation_date.
  • Loading branch information
balhoff authored and ignazio committed Jan 13, 2024
1 parent b683b03 commit 8dda5c8
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.semanticweb.owlapi.vocab.OWL2Datatype.XSD_STRING;

import java.util.Set;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;
import org.semanticweb.owlapi.api.test.baseclasses.TestBase;
Expand Down Expand Up @@ -94,7 +95,10 @@ void shouldLoad() {
AnnotationAssertion(Annotation(hasDbXref, literal), iao0000115, uo0.getIRI(),
literal("A unit of measurement is a standardized quantity of a physical quality.")),
AnnotationAssertion(Annotation(hasDbXref, literal), iao0000115, uo1.getIRI(),
literal("A unit which is a standard measure of the distance between two points.")));
assertEquals(expected, asUnorderedSet(ontology.axioms()));
literal("A unit which is a standard measure of the distance between two points.")),
AnnotationAssertion(RDFSLabel(), createdBy.getIRI(), literal("created by")),
AnnotationAssertion(RDFSLabel(), id.getIRI(), literal("id")));

assertEquals(expected.stream().map(Object::toString).sorted().collect(Collectors.joining("\n")), ontology.axioms().map(Object::toString).sorted().collect(Collectors.joining("\n")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.obolibrary.oboformat.diff.OBODocDiffer;
import org.obolibrary.oboformat.model.FrameStructureException;
import org.obolibrary.oboformat.model.OBODoc;
import org.semanticweb.owlapi.formats.OBODocumentFormat;
import org.semanticweb.owlapi.io.StringDocumentTarget;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
Expand Down Expand Up @@ -43,14 +44,19 @@ public List<Diff> roundTripOBODoc(OBODoc obodoc, boolean isExpectRoundtrip)
OWLOntology oo = convert(obodoc);
StringDocumentTarget oo1 = saveOntology(oo);
OBODoc obodoc2 = convert(oo);
StringDocumentTarget oo2 = saveOntology(convert(obodoc2));
OWLOntology o2 = convert(obodoc2);
StringDocumentTarget oo2 = saveOntology(o2);
String s1 = oo1.toString();
String s2 = oo2.toString();
assertEquals(s1, s2);
obodoc2.check();
List<Diff> diffs = OBODocDiffer.getDiffs(obodoc, obodoc2);
if (isExpectRoundtrip) {
assertEquals(0, diffs.size(), "Expected no diffs but " + diffs);
if (diffs.size() > 0) {
StringDocumentTarget out1 = saveOntology(oo, new OBODocumentFormat());
StringDocumentTarget out2 = saveOntology(o2, new OBODocumentFormat());
assertEquals(out1.toString(), out2.toString());
}
}
return diffs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ void testRoundTripOWLRO() {
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("IAO:0000116", value.getQualifier());
assertEquals("From Allen terminology", value.getValue());
found = true;
}
Expand Down
69 changes: 69 additions & 0 deletions contract/src/test/java/org/obolibrary/oboformat/TagIRIsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.obolibrary.oboformat;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.semanticweb.owlapi.api.test.baseclasses.TestBase;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.vocab.OWL2Datatype;

class TagIRIsTest extends TestBase {

@Test
void testTagIRIMapping() {
OWLAnnotationProperty definition =
df.getOWLAnnotationProperty(IRI.create("http://purl.obolibrary.org/obo/IAO_0000115"));
OWLAnnotationProperty oioCreatedBy = df.getOWLAnnotationProperty(
IRI.create("http://www.geneontology.org/formats/oboInOwl#created_by"));
OWLAnnotationProperty oioInventedBy = df.getOWLAnnotationProperty(
IRI.create("http://www.geneontology.org/formats/oboInOwl#invented_by"));
OWLAnnotationProperty source =
df.getOWLAnnotationProperty(IRI.create("http://purl.obolibrary.org/obo/MYONT_20"));
OWLOntology ont = load("obo/tag_iris.obo", m);
Set<OWLAxiom> axioms = ont.getAxioms();
OWLClass term1 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/MYONT_1"));
OWLClass term2 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/MYONT_2"));
OWLClass term3 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/MYONT_3"));
OWLClass term4 = df.getOWLClass(IRI.create("http://purl.obolibrary.org/obo/MYONT_4"));
Set<OWLAnnotation> annotations = Stream
.of(df.getOWLAnnotation(df.getRDFSComment(), string("Here is a sub-annotation.")),
df.getOWLAnnotation(df.getRDFSSeeAlso(), string("A nested see also value.")))
.collect(Collectors.toSet());
assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term1.getIRI(),
string("Definition of term one."), annotations)));
assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(df.getRDFSSeeAlso(),
term1.getIRI(), string("See also value."))));
assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term2.getIRI(),
string("Definition of term two."), Collections
.singleton(df.getOWLAnnotation(source, string("A nested annotation value."))))));
assertTrue(axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term3.getIRI(),
string("Definition of term three."), Collections
.singleton(df.getOWLAnnotation(source, string("A definition source value."))))));
assertTrue(
axioms.contains(
df.getOWLAnnotationAssertionAxiom(oioCreatedBy, term3.getIRI(), string("goc:bro"))),
"created_by is built in and should not be overridden by a typedef");
assertTrue(
axioms.contains(df.getOWLAnnotationAssertionAxiom(definition, term4.getIRI(),
string("Definition of term four."),
Collections
.singleton(df.getOWLAnnotation(oioInventedBy, string("An inventor value."))))),
"An undeclared tag should have oio namespace");

}

protected OWLLiteral string(String l) {
return df.getOWLLiteral(l, OWL2Datatype.XSD_STRING);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.semanticweb.owlapi.api.test.baseclasses.TestBase;
import org.semanticweb.owlapi.formats.*;
import org.semanticweb.owlapi.io.ReaderDocumentSource;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLDocumentFormat;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;

class BOMSafeInputStreamAndParseTestCase extends TestBase {
Expand All @@ -39,13 +42,20 @@ static Collection<Arguments> data() {
"<rdf:RDF xml:base=\"" + nextISA() + "\" " + PREFIX_XML + " ><owl:Ontology rdf:about=\"#\" /><owl:Class rdf:about=\"" + ISA14 + RESEARCHER + "\"/></rdf:RDF>"
//@formatter:on
);
List<OWLDocumentFormat> formats = Arrays.asList(new OWLXMLDocumentFormat(),
new ManchesterSyntaxDocumentFormat(),
new FunctionalSyntaxDocumentFormat(),
new RioTurtleDocumentFormat(),
new RDFXMLDocumentFormat());

List<int[]> prefixes =
l(new int[] {0x00, 0x00, 0xFE, 0xFF}, new int[] {0xFF, 0xFE, 0x00, 0x00},
new int[] {0xFF, 0xFE}, new int[] {0xFE, 0xFF}, new int[] {0xEF, 0xBB, 0xBF});
for (int[] p : prefixes) {
for (String onto : list) {
toReturn.add(Arguments.of(p, onto));
}
for (int i = 0; i < list.size(); i++) {
toReturn.add(Arguments.of(p, list.get(i), formats.get(i)));

}
}
return toReturn;
}
Expand All @@ -72,18 +82,9 @@ private static InputStream in(int[] b, String content) throws IOException {
// EF BB BF |UTF-8
@ParameterizedTest
@MethodSource("data")
void testBOMError32big(int[] b, String input) throws IOException {
void testBOMError32big(int[] b, String input, OWLDocumentFormat format) throws IOException {
try (InputStream in = in(b, input)) {
loadFrom(in);
}
}

@ParameterizedTest
@MethodSource("data")
void testBOMError32bigReader(int[] b, String input)
throws OWLOntologyCreationException, IOException {
try (InputStream in = in(b, input); InputStreamReader r = new InputStreamReader(in)) {
m.loadOntologyFromOntologyDocument(new ReaderDocumentSource(r));
}
}
}
56 changes: 56 additions & 0 deletions contract/src/test/resources/obo/tag_iris.obo
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
format-version: 1.2
ontology: myont

[Term]
id: MYONT:1
name: term one
def: "Definition of term one." [] {comment="Here is a sub-annotation.", seeAlso="A nested see also value."}
property_value: seeAlso "See also value." xsd:string

[Term]
id: MYONT:2
name: term two
def: "Definition of term two." [] {MYONT:20="A nested annotation value."}
property_value: MYONT:21 "A top level annotation value." xsd:string

[Term]
id: MYONT:3
name: term three
def: "Definition of term three." [] {source="A definition source value."}
intersection_of: MYONT:2 ! term two
intersection_of: results_in_transport_across GO:0005739 ! mitochondrion
created_by: goc:bro

[Term]
id: MYONT:4
name: term four
def: "Definition of term four." [] {invented_by="An inventor value."}

[Typedef]
id: source
name: source
xref: MYONT:20
is_metadata_tag: true

[Typedef]
id: MYONT:21
name: source2
is_metadata_tag: true

[Typedef]
id: seeAlso
name: see also
xref: http://www.w3.org/2000/01/rdf-schema#seeAlso
is_metadata_tag: true

[Typedef]
id: results_in_transport_across
name: results in transport across
namespace: external
xref: RO:0002342

[Typedef]
id: created_by
name: created by
namespace: external
xref: http://purl.org/dc/terms/creator
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package uk.ac.manchester.cs.owl.owlapi.concurrent;

import static org.mockito.ArgumentMatchers.anyListOf;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
Expand Down Expand Up @@ -84,7 +84,7 @@ class ConcurrentOWLOntologyImpl_TestCase {
void setUp() {
when(readWriteLock.readLock()).thenReturn(readLock);
when(readWriteLock.writeLock()).thenReturn(writeLock);
when(delegate.applyChangesAndGetDetails(anyListOf(OWLOntologyChange.class)))
when(delegate.applyChangesAndGetDetails(anyList()))
.thenReturn(new ChangeDetails(ChangeApplied.NO_OPERATION, Collections.emptyList()));
ontology = spy(new ConcurrentOWLOntologyImpl(delegate, readWriteLock));
}
Expand Down
51 changes: 42 additions & 9 deletions oboformat/src/main/java/org/obolibrary/obo2owl/OWLAPIObo2Owl.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.obolibrary.obo2owl;

import static org.obolibrary.obo2owl.Obo2OWLConstants.DEFAULT_IRI_PREFIX;
import static org.obolibrary.obo2owl.Obo2OWLConstants.OIOVOCAB_IRI_PREFIX;
import static org.semanticweb.owlapi.util.OWLAPIPreconditions.checkNotNull;
import static org.semanticweb.owlapi.util.OWLAPIPreconditions.emptyOptional;
import static org.semanticweb.owlapi.util.OWLAPIPreconditions.optional;
Expand Down Expand Up @@ -1373,21 +1374,29 @@ protected OWLIndividual trIndividual(String instId) {
return fac.getOWLNamedIndividual(iri);
}

private IRI trTagToIRIIncludingTypedefs(String tag) {
IRI iri = ANNOTATIONPROPERTYMAP.get(tag);
if (iri == null) {
iri = oboIdToIRI(tag, true);
}
return iri;
}

/**
* Translate tag to annotation prop.
*
*
* @param tag the tag
* @return the OWL annotation property
*/
protected OWLAnnotationProperty trTagToAnnotationProp(String tag) {
IRI iri = trTagToIRI(tag);
IRI iri = trTagToIRIIncludingTypedefs(tag);
OWLAnnotationProperty ap = fac.getOWLAnnotationProperty(iri);
if (!apToDeclare.contains(ap)) {
apToDeclare.add(ap);
add(fac.getOWLDeclarationAxiom(ap));
Obo2OWLVocabulary vocab = Obo2OWLConstants.getVocabularyObj(tag);
if (vocab != null) {
add(fac.getOWLAnnotationAssertionAxiom(iri, fac.getRDFSLabel(vocab.getLabel())));
add(fac.getOWLAnnotationAssertionAxiom(iri, fac.getRDFSLabel(trLiteral(vocab.getLabel()))));
}
}
return ap;
Expand Down Expand Up @@ -1469,9 +1478,19 @@ protected OWLAnnotationValue trLiteral(Object inputValue) {
* @return the iri
*/
public IRI oboIdToIRI(String id) {
return oboIdToIRI(id, false);
}

/**
* Obo id to iri.
*
* @param id the id
* @return the iri
*/
public IRI oboIdToIRI(String id, boolean oboInOwlDefault) {
IRI iri = idToIRICache.get(id);
if (iri == null) {
iri = loadOboToIRI(id);
iri = loadOboToIRI(id, oboInOwlDefault);
idToIRICache.put(id, iri);
}
return iri;
Expand All @@ -1484,6 +1503,16 @@ public IRI oboIdToIRI(String id) {
* @return the iri
*/
public IRI loadOboToIRI(String id) {
return loadOboToIRI(id, false);
}

/**
* Obo id to iri.
*
* @param id the id
* @return the iri
*/
public IRI loadOboToIRI(String id, boolean oboInOwlDefault) {
if (id.contains(" ")) {
LOG.error("id contains space: \"{}\"", id);
throw new OWLParserException("spaces not allowed: '" + id + '\'');
Expand Down Expand Up @@ -1547,9 +1576,14 @@ public IRI loadOboToIRI(String id) {
db = getDefaultIDSpace() + '#';
localId = idParts[0];
}
String uriPrefix = DEFAULT_IRI_PREFIX + db;
if (idSpaceMap.containsKey(db)) {
uriPrefix = idSpaceMap.get(db);
String uriPrefix;
if (oboInOwlDefault) {
uriPrefix = OIOVOCAB_IRI_PREFIX;
} else {
uriPrefix = DEFAULT_IRI_PREFIX + db;
if (idSpaceMap.containsKey(db)) {
uriPrefix = idSpaceMap.get(db);
}
}
String safeId;
try {
Expand All @@ -1570,10 +1604,9 @@ public IRI loadOboToIRI(String id) {
}

// 5.9.3. Special Rules for Relations

/**
* Translate shorthand id to expanded id.
*
*
* @param id the id
* @return the string
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ public static String owlObjectToTag(OWLObject obj) {
String prefix = Obo2OWLConstants.OIOVOCAB_IRI_PREFIX;
if (iri.startsWith(prefix)) {
tag = iri.substring(prefix.length());
} else {
tag = getIdentifier(iriObj);
}
}
return tag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public enum Obo2OWLVocabulary implements HasIRI {
/**IRI_IAO_0100001. */ IRI_IAO_0100001(DEFAULT_IRI_PREFIX, "IAO_0100001", "term replaced by", OboFormatTag.TAG_REPLACED_BY.getTag()),
/**IRI_OIO_shorthand. */ IRI_OIO_shorthand(OIOVOCAB_IRI_PREFIX, "shorthand", "shorthand", "shorthand"),
/**IRI_OIO_consider. */ IRI_OIO_consider(OIOVOCAB_IRI_PREFIX, "consider", "consider", OboFormatTag.TAG_CONSIDER.getTag()),
/**IRI_OIO_id*/ IRI_OIO_id(OIOVOCAB_IRI_PREFIX, "id", "id", OboFormatTag.TAG_ID.getTag()),
/**IRI_OIO_created_by*/ IRI_OIO_created_by(OIOVOCAB_IRI_PREFIX, "created_by", "created by", OboFormatTag.TAG_CREATED_BY.getTag()),
/**IRI_OIO_creation_date*/ IRI_OIO_creation_date(OIOVOCAB_IRI_PREFIX, "creation_date", "creation date", OboFormatTag.TAG_CREATION_DATE.getTag()),
/**IRI_OIO_hasOBOFormatVersion. */ IRI_OIO_hasOBOFormatVersion(OIOVOCAB_IRI_PREFIX, "hasOBOFormatVersion", "has_obo_format_version", OboFormatTag.TAG_FORMAT_VERSION.getTag()),
/**IRI_OIO_treatXrefsAsIsA. */ IRI_OIO_treatXrefsAsIsA(OIOVOCAB_IRI_PREFIX, "treat-xrefs-as-is_a", "treat-xrefs-as-is_a", OboFormatTag.TAG_TREAT_XREFS_AS_IS_A.getTag()),
/**IRI_OIO_treatXrefsAsHasSubClass. */ IRI_OIO_treatXrefsAsHasSubClass(OIOVOCAB_IRI_PREFIX, "treat-xrefs-as-has-subclass", "treat-xrefs-as-has-subclass", OboFormatTag.TAG_TREAT_XREFS_AS_HAS_SUBCLASS.getTag()),
Expand Down

0 comments on commit 8dda5c8

Please sign in to comment.