Repository: ontodev/robot
Pull request #347 (2018-08-15 20:39:03)

Implement merge-axiom-annotations option for repair
See #214 

Adds `--merge-axiom-annotations <boolean>` to `repair`. Default behavior is `false`.


Patch:
diff --git a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
index 33a38ffc..77c636af 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
@@ -26,6 +26,11 @@ public class RepairCommand implements Command {
     o.addOption("I", "input-iri", true, "load ontology from an IRI");
     o.addOption("o", "output", true, "save ontology to a file");
     o.addOption("O", "output-iri", true, "set OntologyIRI for output");
+    o.addOption(
+        "m",
+        "merge-axiom-annotations",
+        true,
+        "if true, merge axiom annotations on duplicate axioms");
     options = o;
   }
 
@@ -88,7 +93,7 @@ public class RepairCommand implements Command {
    * @throws Exception on any problem
    */
   public CommandState execute(CommandState state, String[] args) throws Exception {
-    OWLOntology outputOntology = null;
+    OWLOntology outputOntology;
 
     CommandLine line = CommandLineHelper.getCommandLine(getUsage(), getOptions(), args);
     if (line == null) {
@@ -104,8 +109,14 @@ public class RepairCommand implements Command {
       outputIRI = inputOntology.getOntologyID().getOntologyIRI().orNull();
     }
 
-    RepairOperation.repair(inputOntology, ioHelper);
+    boolean mergeAxiomAnnotations =
+        CommandLineHelper.getBooleanValue(line, "merge-axiom-annotations", false);
+
+    RepairOperation.repair(inputOntology, ioHelper, mergeAxiomAnnotations);
     outputOntology = inputOntology;
+    if (outputIRI != null) {
+      outputOntology.getOWLOntologyManager().setOntologyDocumentIRI(outputOntology, outputIRI);
+    }
 
     CommandLineHelper.maybeSaveOutput(line, outputOntology);
 
diff --git a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
index 955300cc..56ccd1f5 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
@@ -10,17 +10,7 @@ import java.util.Set;
 import org.obolibrary.robot.checks.InvalidReferenceChecker;
 import org.obolibrary.robot.checks.InvalidReferenceViolation;
 import org.obolibrary.robot.checks.InvalidReferenceViolation.Category;
-import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
-import org.semanticweb.owlapi.model.OWLAnnotationValue;
-import org.semanticweb.owlapi.model.OWLAxiom;
-import org.semanticweb.owlapi.model.OWLClass;
-import org.semanticweb.owlapi.model.OWLEntity;
-import org.semanticweb.owlapi.model.OWLLiteral;
-import org.semanticweb.owlapi.model.OWLObjectProperty;
-import org.semanticweb.owlapi.model.OWLOntology;
-import org.semanticweb.owlapi.model.OWLOntologyChange;
-import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.*;
 import org.semanticweb.owlapi.model.parameters.Imports;
 import org.semanticweb.owlapi.util.OWLEntityRenamer;
 import org.slf4j.Logger;
@@ -37,7 +27,7 @@ public class RepairOperation {
    * @return a map with default values for all available options
    */
   public static Map<String, String> getDefaultOptions() {
-    Map<String, String> options = new HashMap<String, String>();
+    Map<String, String> options = new HashMap<>();
     // options.put("remove-redundant-subclass-axioms", "true");
 
     return options;
@@ -47,23 +37,98 @@ public class RepairOperation {
    * Repairs ontology
    *
    * @param ontology the OWLOntology to repair
-   * @param iohelper IOHelper to work with the ontology
+   * @param ioHelper IOHelper to work with the ontology
    */
-  public static void repair(OWLOntology ontology, IOHelper iohelper) {
-    repair(ontology, iohelper, getDefaultOptions());
+  public static void repair(OWLOntology ontology, IOHelper ioHelper) {
+    repair(ontology, ioHelper, getDefaultOptions());
   }
+
   /**
    * Repairs ontology
    *
    * @param ontology the OWLOntology to repair
-   * @param iohelper IOHelper to work with the ontology
+   * @param ioHelper IOHelper to work with the ontology
+   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
+   */
+  public static void repair(
+      OWLOntology ontology, IOHelper ioHelper, boolean mergeAxiomAnnotations) {
+    repair(ontology, ioHelper, getDefaultOptions(), mergeAxiomAnnotations);
+  }
+
+  /**
+   * Repairs ontology
+   *
+   * @param ontology the OWLOntology to repair
+   * @param ioHelper IOHelper to work with the ontology
    * @param options map of repair options
    */
-  public static void repair(OWLOntology ontology, IOHelper iohelper, Map<String, String> options) {
+  public static void repair(OWLOntology ontology, IOHelper ioHelper, Map<String, String> options) {
+    repair(ontology, ioHelper, options, false);
+  }
 
+  /**
+   * Repairs ontology
+   *
+   * @param ontology the OWLOntology to reapir
+   * @param ioHelper IOHelper to work with the ontology
+   * @param options map of repair options
+   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
+   */
+  public static void repair(
+      OWLOntology ontology,
+      IOHelper ioHelper,
+      Map<String, String> options,
+      boolean mergeAxiomAnnotations) {
     Set<InvalidReferenceViolation> violations =
         InvalidReferenceChecker.getInvalidReferenceViolations(ontology, true);
-    repairInvalidReferences(iohelper, ontology, violations);
+    repairInvalidReferences(ioHelper, ontology, violations);
+    mergeAxiomAnnotations(ontology);
+  }
+
+  /**
+   * Given an ontology, merge the annotations of duplicate axioms to create one axiom with all
+   * annotations.
+   *
+   * @param ontology the OWLOntology to repair
+   */
+  public static void mergeAxiomAnnotations(OWLOntology ontology) {
+    Map<OWLAxiom, Set<OWLAnnotation>> mergedAxioms = new HashMap<>();
+    Set<OWLAxiom> axiomsToMerge = new HashSet<>();
+
+    // Find duplicated axioms and collect their annotations
+    // OWLAPI should already merge non-annotated duplicates
+    for (OWLAxiom axiom : ontology.getAxioms()) {
+      if (axiom.isAnnotated()) {
+        axiomsToMerge.add(axiom);
+        OWLAxiom strippedAxiom = axiom.getAxiomWithoutAnnotations();
+        Set<OWLAnnotation> annotations = axiom.getAnnotations();
+        if (mergedAxioms.containsKey(strippedAxiom)) {
+          logger.info("Merging annotations on axiom: {}", strippedAxiom.toString());
+          Set<OWLAnnotation> mergeAnnotations = new HashSet<>();
+          mergeAnnotations.addAll(mergedAxioms.get(strippedAxiom));
+          mergeAnnotations.addAll(annotations);
+          mergedAxioms.put(strippedAxiom, mergeAnnotations);
+        } else {
+          mergedAxioms.put(strippedAxiom, annotations);
+        }
+      }
+    }
+
+    OWLOntologyManager manager = ontology.getOWLOntologyManager();
+    OWLDataFactory dataFactory = manager.getOWLDataFactory();
+    // Remove the duplicated axioms
+    manager.removeAxioms(ontology, axiomsToMerge);
+
+    // Create the axioms with new set of annotations
+    Set<OWLAxiom> newAxioms = new HashSet<>();
+    for (Map.Entry<OWLAxiom, Set<OWLAnnotation>> mergedAxiom : mergedAxioms.entrySet()) {
+      OWLAxiom axiom = mergedAxiom.getKey();
+      if (axiom.isAnnotationAxiom()) {
+        OWLAxiom newAxiom = axiom.getAnnotatedAxiom(mergedAxiom.getValue());
+        newAxioms.add(newAxiom);
+      }
+    }
+    manager.addAxioms(ontology, newAxioms);
   }
 
   /**
@@ -132,8 +197,7 @@ public class RepairOperation {
 
     logger.info("PRESERVE: " + axiomsToPreserve);
     manager.removeAxioms(ontology, axiomsToPreserve);
-    List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
-    changes.addAll(renamer.changeIRI(renameMap));
+    List<OWLOntologyChange> changes = new ArrayList<>(renamer.changeIRI(renameMap));
     manager.applyChanges(changes);
     manager.addAxioms(ontology, axiomsToPreserve);
   }


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
index 25e50f98..7ae91244 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
@@ -18,7 +18,7 @@ public class RepairOperationTest extends CoreTest {
   public void testRepair() throws IOException, OWLOntologyCreationException {
     OWLOntology ontology = loadOntology("/need-of-repair.owl");
     IOHelper iohelper = new IOHelper();
-    RepairOperation.repair(ontology, iohelper);
+    RepairOperation.repair(ontology, iohelper, true);
     iohelper.saveOntology(ontology, "target/foo.owl");
     assertIdentical("/repaired.owl", ontology);
   }
diff --git a/robot-core/src/test/resources/need-of-repair.owl b/robot-core/src/test/resources/need-of-repair.owl
index 6d5a3f99..f146b3f9 100644
--- a/robot-core/src/test/resources/need-of-repair.owl
+++ b/robot-core/src/test/resources/need-of-repair.owl
@@ -1,17 +1,33 @@
 <?xml version="1.0"?>
 <rdf:RDF xmlns="http://purl.obolibrary.org/obo/need-of-repair.owl#"
      xml:base="http://purl.obolibrary.org/obo/need-of-repair.owl"
-     xmlns:obo="http://purl.obolibrary.org/obo/"
-     xmlns:owl="http://www.w3.org/2002/07/owl#"
      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     xmlns:oboInOwl="http://www.geneontology.org/formats/oboInOwl#"
      xmlns:xml="http://www.w3.org/XML/1998/namespace"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
      xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
-     xmlns:oboInOwl="http://www.geneontology.org/formats/oboInOwl#">
+     xmlns:obo="http://purl.obolibrary.org/obo/">
     <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/need-of-repair.owl"/>
     
 
 
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Annotation properties
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/IAO_0100001 -->
+
+    <owl:AnnotationProperty rdf:about="http://purl.obolibrary.org/obo/IAO_0100001"/>
+    
+
 
     <!-- 
     ///////////////////////////////////////////////////////////////////////////////////////
@@ -28,8 +44,21 @@
 
     <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
         <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_2"/>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Something duplicated</rdfs:comment>
         <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
     </owl:Class>
+    <owl:Axiom>
+        <owl:annotatedSource rdf:resource="http://purl.obolibrary.org/obo/GO_1"/>
+        <owl:annotatedProperty rdf:resource="http://www.w3.org/2000/01/rdf-schema#comment"/>
+        <owl:annotatedTarget rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Something duplicated</owl:annotatedTarget>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Annotation 1</rdfs:comment>
+    </owl:Axiom>
+    <owl:Axiom>
+        <owl:annotatedSource rdf:resource="http://purl.obolibrary.org/obo/GO_1"/>
+        <owl:annotatedProperty rdf:resource="http://www.w3.org/2000/01/rdf-schema#comment"/>
+        <owl:annotatedTarget rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Something duplicated</owl:annotatedTarget>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Annotation 2</rdfs:comment>
+    </owl:Axiom>
     
 
 
@@ -53,5 +82,5 @@
 
 
 
-<!-- Generated by the OWL API (version 4.5.0) https://github.com/owlcs/owlapi -->
+<!-- Generated by the OWL API (version 4.2.8.20170104-2310) https://github.com/owlcs/owlapi -->
 
diff --git a/robot-core/src/test/resources/repaired.owl b/robot-core/src/test/resources/repaired.owl
index b56a49c0..165b09dd 100644
--- a/robot-core/src/test/resources/repaired.owl
+++ b/robot-core/src/test/resources/repaired.owl
@@ -20,6 +20,11 @@
      -->
 
     
+
+
+    <!-- http://purl.obolibrary.org/obo/IAO_0100001 -->
+
+    <owl:AnnotationProperty rdf:about="http://purl.obolibrary.org/obo/IAO_0100001"/>
     
 
 
@@ -38,9 +43,16 @@
 
     <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
         <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_3"/>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Something duplicated</rdfs:comment>
         <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
     </owl:Class>
-    
+    <owl:Axiom>
+        <owl:annotatedSource rdf:resource="http://purl.obolibrary.org/obo/GO_1"/>
+        <owl:annotatedProperty rdf:resource="http://www.w3.org/2000/01/rdf-schema#comment"/>
+        <owl:annotatedTarget rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Something duplicated</owl:annotatedTarget>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Annotation 1</rdfs:comment>
+        <rdfs:comment rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Annotation 2</rdfs:comment>
+    </owl:Axiom>
     
 
 
@@ -51,6 +63,7 @@
         <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">original parent</rdfs:label>
         <owl:deprecated rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">true</owl:deprecated>
     </owl:Class>
+    
 
 
     <!-- http://purl.obolibrary.org/obo/GO_3 -->
@@ -59,11 +72,9 @@
         <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_3"/>
         <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">replacement parent</rdfs:label>
     </owl:Class>
-    
-
 </rdf:RDF>
 
 
 
-<!-- Generated by the OWL API (version 4.2.6.20160910-2108) https://github.com/owlcs/owlapi -->
+<!-- Generated by the OWL API (version 4.2.6) https://github.com/owlcs/owlapi -->