Skip to content

Commit

Permalink
Fix sameAs failure when more than 2 entities included #994
Browse files Browse the repository at this point in the history
Same bug exists for equivalent classes, data properties and object
properties.

When rendering an entity a1, a RDFGraph object is created. This holds
all the triples that need to be stored, and contains all triples
required for one of these axioms, but they look like:

(a1 sameAs a2)
(a2 sameAs a3)...
Once the graph is created, standard procedure is to render starting from
the a1 entity, then rendering all anonymous roots inside the element for
a1 (on account of anonymous nodes having to be included here). This
makes the rendering work for annotated axioms (because annotated axioms
force a blank node to be created, and that acts as a hook to pull in all
the required triples.
But when no annotations are there, rendering a1 only pulls in the first
triple (IRIs used in multiple places, like a2, are not followed in the
graph - if they did, other axioms would be rendered in unexpected ways).

Only these four axiom types generate disjoint graphs of this sort - so,
for these, the easiest fix is to store the information about other
entities that need rendering and render them after a1 is completed.
  • Loading branch information
ignazio1977 committed Apr 10, 2021
1 parent 429b50a commit 76b9ef6
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
@@ -0,0 +1,116 @@
/* This file is part of the OWL API.
* The contents of this file are subject to the LGPL License, Version 3.0.
* Copyright 2014, The University of Manchester
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
*
* Alternatively, the contents of this file may be used under the terms of the Apache License, Version 2.0 in which case, the provisions of the Apache License Version 2.0 are applicable instead of those above.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package org.semanticweb.owlapi.api.test.individuals;

import static org.junit.Assert.assertTrue;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.Class;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.DataProperty;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.EquivalentClasses;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.EquivalentDataProperties;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.EquivalentObjectProperties;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.NamedIndividual;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.ObjectProperty;
import static org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.SameIndividual;

import java.util.Arrays;
import java.util.HashSet;

import org.semanticweb.owlapi.api.test.baseclasses.AxiomsRoundTrippingBase;
import org.semanticweb.owlapi.formats.FunctionalSyntaxDocumentFormat;
import org.semanticweb.owlapi.formats.RDFXMLDocumentFormat;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
import org.semanticweb.owlapi.model.OWLEquivalentDataPropertiesAxiom;
import org.semanticweb.owlapi.model.OWLEquivalentObjectPropertiesAxiom;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.semanticweb.owlapi.model.OWLSameIndividualAxiom;

/**
* @author Matthew Horridge, The University of Manchester, Information Management Group
* @since 3.0.0
*/
public class SameIndividualsQuadrupletTestCase extends AxiomsRoundTrippingBase {

private static final OWLClass a = Class(iri("a"));
private static final OWLClass b = Class(iri("b"));
private static final OWLClass c = Class(iri("c"));
private static final OWLClass d = Class(iri("d"));

private static final OWLDataProperty p = DataProperty(iri("p"));
private static final OWLDataProperty q = DataProperty(iri("q"));
private static final OWLDataProperty r = DataProperty(iri("r"));
private static final OWLDataProperty s = DataProperty(iri("s"));

private static final OWLObjectProperty t = ObjectProperty(iri("t"));
private static final OWLObjectProperty u = ObjectProperty(iri("u"));
private static final OWLObjectProperty w = ObjectProperty(iri("w"));
private static final OWLObjectProperty z = ObjectProperty(iri("z"));

private static final OWLNamedIndividual i = NamedIndividual(iri("i"));
private static final OWLNamedIndividual j = NamedIndividual(iri("j"));
private static final OWLNamedIndividual k = NamedIndividual(iri("k"));
private static final OWLNamedIndividual l = NamedIndividual(iri("l"));
private static final OWLSameIndividualAxiom axiom1 = SameIndividual(i, j, k, l);
private static final OWLEquivalentClassesAxiom axiom2 = EquivalentClasses(a, b, c, d);
private static final OWLEquivalentObjectPropertiesAxiom axiom3 =
EquivalentObjectProperties(t, u, w, z);
private static final OWLEquivalentDataPropertiesAxiom axiom4 =
EquivalentDataProperties(p, q, r, s);

public SameIndividualsQuadrupletTestCase() {
super((AxiomBuilder) () -> new HashSet<>(Arrays.asList(axiom1, axiom2, axiom3, axiom4)));
}

@Override
public boolean equal(OWLOntology ont1, OWLOntology ont2) {
// More than two individuals in a sameAs cannot be roundtripped traditionally in RDF based
// languages
assertTrue(ont1.containsAxiom(axiom1));
assertTrue(ont1.containsAxiom(axiom2));
assertTrue(ont1.containsAxiom(axiom3));
assertTrue(ont1.containsAxiom(axiom4));
if (!ont2.containsAxiom(axiom1)) {
axiom1.splitToAnnotatedPairs().forEach(ax -> assertAxiomContained(ax, ont2));
}
if (!ont2.containsAxiom(axiom2)) {
axiom2.splitToAnnotatedPairs().forEach(ax -> assertAxiomContained(ax, ont2));
}
if (!ont2.containsAxiom(axiom3)) {
axiom3.splitToAnnotatedPairs().forEach(ax -> assertAxiomContained(ax, ont2));
}
if (!ont2.containsAxiom(axiom4)) {
axiom4.splitToAnnotatedPairs().forEach(ax -> assertAxiomContained(ax, ont2));
}
return true;
}

protected void assertAxiomContained(OWLAxiom ax, OWLOntology ont2) {
assertTrue(ax + "\t" + ont2.toString(), ont2.containsAxiom(ax));
}

@Override
public void roundTripRDFXMLAndFunctionalShouldBeSame()
throws OWLOntologyCreationException, OWLOntologyStorageException {
OWLOntology ont = createOntology();
OWLOntology o1 = roundTrip(ont, new RDFXMLDocumentFormat());
OWLOntology o2 = roundTrip(ont, new FunctionalSyntaxDocumentFormat());
equal(ont, o1);
equal(o2, o1);
}
}
Expand Up @@ -50,6 +50,7 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -300,6 +301,10 @@ private void render(OWLEntity entity, AtomicBoolean firstRendering, String banne
writeBanner(bannerText);
}
renderEntity(entity);
Iterator<OWLEntity> it = graph.getRootIRIs(entity).iterator();
while (it.hasNext()) {
renderEntity(it.next());
}
}

private void renderEntity(OWLEntity entity) {
Expand Down
Expand Up @@ -141,6 +141,7 @@
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDataPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLDataPropertyDomainAxiom;
import org.semanticweb.owlapi.model.OWLDataPropertyExpression;
import org.semanticweb.owlapi.model.OWLDataPropertyRangeAxiom;
import org.semanticweb.owlapi.model.OWLDataRange;
import org.semanticweb.owlapi.model.OWLDataSomeValuesFrom;
Expand Down Expand Up @@ -185,6 +186,7 @@
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLObjectPropertyDomainAxiom;
import org.semanticweb.owlapi.model.OWLObjectPropertyExpression;
import org.semanticweb.owlapi.model.OWLObjectPropertyRangeAxiom;
import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom;
import org.semanticweb.owlapi.model.OWLObjectUnionOf;
Expand Down Expand Up @@ -536,6 +538,8 @@ public void visit(OWLEquivalentClassesAxiom axiom) {
addPairwise(axiom, axiom.classExpressions(), OWL_EQUIVALENT_CLASS.getIRI());
} else {
axiom.splitToAnnotatedPairs().stream().sorted().forEach(ax -> ax.accept(this));
processIfNotAnonymous(axiom.getOperandsAsList().stream()
.filter(OWLClassExpression::isNamed).map(OWLClassExpression::asOWLClass));
}
}

Expand Down Expand Up @@ -575,6 +579,9 @@ public void visit(OWLEquivalentObjectPropertiesAxiom axiom) {
addPairwise(axiom, axiom.properties(), OWL_EQUIVALENT_PROPERTY.getIRI());
} else {
axiom.splitToAnnotatedPairs().stream().sorted().forEach(ax -> ax.accept(this));
processIfNotAnonymous(
axiom.getOperandsAsList().stream().filter(OWLObjectPropertyExpression::isNamed)
.map(OWLObjectPropertyExpression::asOWLObjectProperty));
}
}

Expand Down Expand Up @@ -660,6 +667,9 @@ public void visit(OWLEquivalentDataPropertiesAxiom axiom) {
addPairwise(axiom, axiom.properties(), OWL_EQUIVALENT_PROPERTY.getIRI());
} else {
axiom.splitToAnnotatedPairs().stream().sorted().forEach(ax -> ax.accept(this));
processIfNotAnonymous(
axiom.getOperandsAsList().stream().filter(OWLDataPropertyExpression::isNamed)
.map(OWLDataPropertyExpression::asOWLDataProperty));
}
}

Expand Down Expand Up @@ -709,6 +719,8 @@ public void visit(OWLSameIndividualAxiom axiom) {
.forEach(a -> addSingleTripleAxiom(a, a.getIndividualsAsList().get(0),
OWL_SAME_AS.getIRI(), a.getIndividualsAsList().get(1)));
processIfAnonymous(axiom.individuals(), axiom);
processIfNotAnonymous(axiom.getIndividualsAsList().stream().filter(OWLIndividual::isNamed)
.map(OWLIndividual::asOWLNamedIndividual));
}

@Override
Expand Down Expand Up @@ -1251,6 +1263,10 @@ private void processIfAnonymous(Stream<? extends OWLIndividual> inds, @Nullable
inds.sorted().forEach(i -> processIfAnonymous(i, root));
}

private void processIfNotAnonymous(Stream<OWLEntity> inds) {
inds.forEach(graph::addRootIRIs);
}

private void processIfAnonymous(OWLIndividual ind, @Nullable OWLAxiom root) {
process(ind, x -> !Objects.equals(x, root));
}
Expand Down
Expand Up @@ -29,6 +29,7 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nullable;

Expand All @@ -37,6 +38,7 @@
import org.semanticweb.owlapi.io.RDFResourceBlankNode;
import org.semanticweb.owlapi.io.RDFTriple;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.vocab.OWLRDFVocabulary;

/**
Expand All @@ -50,6 +52,7 @@ public class RDFGraph implements Serializable {
Collections.singleton(OWLRDFVocabulary.OWL_ANNOTATED_TARGET.getIRI());
private final Map<RDFResource, Set<RDFTriple>> triplesBySubject = createMap();
private final Set<RDFResourceBlankNode> rootAnonymousNodes = createLinkedSet();
private final Set<OWLEntity> rootIRIs = new HashSet<>();
private final Set<RDFTriple> triples = createLinkedSet();
private final Map<RDFNode, RDFNode> remappedNodes = createMap();
@Nullable
Expand Down Expand Up @@ -231,4 +234,23 @@ public void setOntology(@Nullable RDFResource mappedNode) {
public RDFResource getOntology() {
return ontology;
}

/**
* Some graphs have multiple root entities, i.e., sameAs(a1, a2, a3) axioms are turned to
* {@code (a1 sameAs a2), (a2 sameAs a3)} because of RDF limitations. To render all triples, a1
* and a2 must be rendered; usual process would render only a1, and so a3 would be lost.
*
* @param i root IRI for this graph
*/
public void addRootIRIs(OWLEntity i) {
rootIRIs.add(i);
}

/**
* @param toSkip entity to skip
* @return root entities, minus the one to be skipped, sorted
*/
public Stream<OWLEntity> getRootIRIs(OWLEntity toSkip) {
return rootIRIs.stream().filter(x -> !toSkip.equals(x)).sorted();
}
}

0 comments on commit 76b9ef6

Please sign in to comment.