Repository: ontodev/robot
Pull request #510 (2019-06-14 18:36:56)

Update `repair` command to allow migration of annotation values
See #492.

Syntax: `robot repair --input xref-need-of-repair.obo --annotation-property oboInOwl:hasDbXref --output results/xref-repaired.obo`


Patch:
diff --git a/docs/examples/xref-need-of-repair.obo b/docs/examples/xref-need-of-repair.obo
new file mode 100644
index 00000000..afdbaf4f
--- /dev/null
+++ b/docs/examples/xref-need-of-repair.obo
@@ -0,0 +1,24 @@
+ontology: xref-need-of-repair
+
+[Term]
+id: GO:1
+name: leaf node
+is_a: GO:2
+
+[Term]
+id: GO:2
+name: original parent
+is_obsolete: true
+replaced_by: GO:3
+xref: FOO:1
+
+[Term]
+id: GO:3
+name: replacement parent
+
+[Term]
+id: GO:4
+name: other term
+is_obsolete: true
+replaced_by: GO:3
+xref: FOO:2
diff --git a/docs/examples/xref-repaired.obo b/docs/examples/xref-repaired.obo
new file mode 100644
index 00000000..96f4e3f8
--- /dev/null
+++ b/docs/examples/xref-repaired.obo
@@ -0,0 +1,24 @@
+ontology: xref-need-of-repair
+
+[Term]
+id: GO:1
+name: leaf node
+is_a: GO:3
+
+[Term]
+id: GO:2
+name: original parent
+is_obsolete: true
+replaced_by: GO:3
+
+[Term]
+id: GO:3
+name: replacement parent
+xref: FOO:1
+xref: FOO:2
+
+[Term]
+id: GO:4
+name: other term
+is_obsolete: true
+replaced_by: GO:3
diff --git a/docs/repair.md b/docs/repair.md
index 1e2c5c61..7be6367b 100644
--- a/docs/repair.md
+++ b/docs/repair.md
@@ -6,3 +6,10 @@ ROBOT can repair certain problems encountered in ontologies. So far, this is lim
       --input need-of-repair.owl \
       --output results/repaired.owl
       
+
+By default, annotation axioms are not migrated to replacement classes. However, this can be enabled for a list of annotation properties passed either as arguments to `--annotation-property` or in a term file `--annotation-properties-file`:
+
+    robot repair \
+      --input xref-need-of-repair.obo \
+      --annotation-property oboInOwl:hasDbXref \
+      --output results/xref-repaired.obo
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 77c636af..8c0dd4b0 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
@@ -1,8 +1,12 @@
 package org.obolibrary.robot;
 
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
 import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAnnotationProperty;
+import org.semanticweb.owlapi.model.OWLDataFactory;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,6 +36,12 @@ public class RepairCommand implements Command {
         true,
         "if true, merge axiom annotations on duplicate axioms");
     options = o;
+    o.addOption("a", "annotation-property", true, "an annotation property to migrate");
+    o.addOption(
+        "A",
+        "annotation-properties-file",
+        true,
+        "load annotation properties to migrate from a file");
   }
 
   /**
@@ -112,7 +122,15 @@ public class RepairCommand implements Command {
     boolean mergeAxiomAnnotations =
         CommandLineHelper.getBooleanValue(line, "merge-axiom-annotations", false);
 
-    RepairOperation.repair(inputOntology, ioHelper, mergeAxiomAnnotations);
+    OWLDataFactory factory = inputOntology.getOWLOntologyManager().getOWLDataFactory();
+    Set<OWLAnnotationProperty> properties =
+        CommandLineHelper.getTerms(
+                ioHelper, line, "annotation-property", "annotation-properties-file")
+            .stream()
+            .map(factory::getOWLAnnotationProperty)
+            .collect(Collectors.toSet());
+
+    RepairOperation.repair(inputOntology, ioHelper, mergeAxiomAnnotations, properties);
     outputOntology = inputOntology;
     if (outputIRI != null) {
       outputOntology.getOWLOntologyManager().setOntologyDocumentIRI(outputOntology, outputIRI);
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 8ef1ff87..148a43c7 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
@@ -1,11 +1,7 @@
 package org.obolibrary.robot;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 import org.obolibrary.robot.checks.InvalidReferenceChecker;
 import org.obolibrary.robot.checks.InvalidReferenceViolation;
 import org.obolibrary.robot.checks.InvalidReferenceViolation.Category;
@@ -52,6 +48,22 @@ public class RepairOperation {
     repair(ontology, ioHelper, getDefaultOptions(), mergeAxiomAnnotations);
   }
 
+  /**
+   * Repairs ontology
+   *
+   * @param ontology the OWLOntology to repair
+   * @param ioHelper IOHelper to work with the ontology
+   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
+   * @param migrateAnnotations set of annotation properties for which to migrate values
+   */
+  public static void repair(
+      OWLOntology ontology,
+      IOHelper ioHelper,
+      boolean mergeAxiomAnnotations,
+      Set<OWLAnnotationProperty> migrateAnnotations) {
+    repair(ontology, ioHelper, getDefaultOptions(), mergeAxiomAnnotations, migrateAnnotations);
+  }
+
   /**
    * Repairs ontology
    *
@@ -66,7 +78,7 @@ public class RepairOperation {
   /**
    * Repairs ontology
    *
-   * @param ontology the OWLOntology to reapir
+   * @param ontology the OWLOntology to repair
    * @param ioHelper IOHelper to work with the ontology
    * @param options map of repair options
    * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
@@ -76,10 +88,30 @@ public class RepairOperation {
       IOHelper ioHelper,
       Map<String, String> options,
       boolean mergeAxiomAnnotations) {
+    repair(ontology, ioHelper, options, mergeAxiomAnnotations, Collections.emptySet());
+  }
+
+  /**
+   * Repairs ontology
+   *
+   * @param ontology the OWLOntology to repair
+   * @param ioHelper IOHelper to work with the ontology
+   * @param options map of repair options
+   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
+   * @param migrateAnnotations set of annotation properties for which to migrate values
+   */
+  public static void repair(
+      OWLOntology ontology,
+      IOHelper ioHelper,
+      Map<String, String> options,
+      boolean mergeAxiomAnnotations,
+      Set<OWLAnnotationProperty> migrateAnnotations) {
     Set<InvalidReferenceViolation> violations =
         InvalidReferenceChecker.getInvalidReferenceViolations(ontology, true);
-    repairInvalidReferences(ioHelper, ontology, violations);
-    mergeAxiomAnnotations(ontology);
+    repairInvalidReferences(ioHelper, ontology, violations, migrateAnnotations);
+    if (mergeAxiomAnnotations) {
+      mergeAxiomAnnotations(ontology);
+    }
   }
 
   /**
@@ -141,6 +173,26 @@ public class RepairOperation {
    */
   public static void repairInvalidReferences(
       IOHelper iohelper, OWLOntology ontology, Set<InvalidReferenceViolation> violations) {
+    repairInvalidReferences(iohelper, ontology, violations, Collections.emptySet());
+  }
+
+  /**
+   * Repairs invalid references
+   *
+   * <p>Currently only able to repair references to deprecated classes.
+   *
+   * <p>Assumes OBO vocabulary
+   *
+   * @param iohelper IOHelper to work with the ontology
+   * @param ontology the OWLOntology to repair
+   * @param violations set of references violations
+   * @param migrateAnnotations set of annotation properties for which to migrate values
+   */
+  public static void repairInvalidReferences(
+      IOHelper iohelper,
+      OWLOntology ontology,
+      Set<InvalidReferenceViolation> violations,
+      Set<OWLAnnotationProperty> migrateAnnotations) {
     logger.info("Invalid references: " + violations.size());
     OWLOntologyManager manager = ontology.getOWLOntologyManager();
     OWLEntityRenamer renamer = new OWLEntityRenamer(manager, ontology.getImportsClosure());
@@ -188,7 +240,12 @@ public class RepairOperation {
         axiomsToPreserve.addAll(ontology.getAxioms((OWLObjectProperty) obsObj, Imports.EXCLUDED));
       }
       axiomsToPreserve.addAll(ontology.getDeclarationAxioms(obsObj));
-      axiomsToPreserve.addAll(ontology.getAnnotationAssertionAxioms(obsObj.getIRI()));
+      axiomsToPreserve.addAll(
+          ontology
+              .getAnnotationAssertionAxioms(obsObj.getIRI())
+              .stream()
+              .filter(ax -> !migrateAnnotations.contains(ax.getProperty()))
+              .collect(Collectors.toSet()));
       logger.info("Replacing: " + obsObj + " -> " + replacedBy);
     }
 
diff --git a/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
index 3cb5f34c..c400d985 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
@@ -88,7 +88,7 @@ public class InvalidReferenceChecker {
       OWLOntology ontology, Set<OWLAxiom> axioms, boolean ignoreDangling) {
     Set<InvalidReferenceViolation> violations = new HashSet<>();
     for (OWLAxiom axiom : axioms) {
-      for (OWLEntity e : axiom.getSignature()) {
+      for (OWLEntity e : expandedSignature(axiom, ontology)) {
         if (!ignoreDangling && isDangling(ontology, e)) {
           violations.add(InvalidReferenceViolation.create(axiom, e, Category.DANGLING));
         }
@@ -126,4 +126,27 @@ public class InvalidReferenceChecker {
     return getInvalidReferenceViolations(
         importModule, baseOntology.getAxioms(Imports.INCLUDED), true);
   }
+
+  /**
+   * Get the entity signature for an axiom. This expands the standard axiom signature to include
+   * entities found in the ontology which have IRIs used as the subject for an annotation assertion
+   * axiom.
+   *
+   * @param axiom the axiom for which to get the entity signature
+   * @param ontology ontology to search for entities with IRIs used in annotation assertions
+   * @return set of entities representing the signature of the axiom
+   */
+  private static Set<OWLEntity> expandedSignature(OWLAxiom axiom, OWLOntology ontology) {
+    if (axiom instanceof OWLAnnotationAssertionAxiom) {
+      Set<OWLEntity> signature = axiom.getSignature();
+      OWLAnnotationAssertionAxiom annotationAxiom = (OWLAnnotationAssertionAxiom) axiom;
+      if (annotationAxiom.getSubject().isIRI()) {
+        IRI subjectIRI = (IRI) annotationAxiom.getSubject();
+        signature.addAll(ontology.getEntitiesInSignature(subjectIRI));
+      }
+      return signature;
+    } else {
+      return axiom.getSignature();
+    }
+  }
 }


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 7ae91244..b94f3f5c 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
@@ -1,7 +1,11 @@
 package org.obolibrary.robot;
 
 import java.io.IOException;
+import java.util.Collections;
 import org.junit.Test;
+import org.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAnnotationProperty;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 
@@ -22,4 +26,16 @@ public class RepairOperationTest extends CoreTest {
     iohelper.saveOntology(ontology, "target/foo.owl");
     assertIdentical("/repaired.owl", ontology);
   }
+
+  @Test
+  public void testXrefRepair() throws IOException {
+    OWLAnnotationProperty hasDbXref =
+        OWLManager.getOWLDataFactory()
+            .getOWLAnnotationProperty(
+                IRI.create("http://www.geneontology.org/formats/oboInOwl#hasDbXref"));
+    OWLOntology ontology = loadOntology("/xref-need-of-repair.obo");
+    IOHelper iohelper = new IOHelper();
+    RepairOperation.repair(ontology, iohelper, true, Collections.singleton(hasDbXref));
+    assertIdentical("/xref-repaired.obo", ontology);
+  }
 }
diff --git a/robot-core/src/test/resources/xref-need-of-repair.obo b/robot-core/src/test/resources/xref-need-of-repair.obo
new file mode 100644
index 00000000..afdbaf4f
--- /dev/null
+++ b/robot-core/src/test/resources/xref-need-of-repair.obo
@@ -0,0 +1,24 @@
+ontology: xref-need-of-repair
+
+[Term]
+id: GO:1
+name: leaf node
+is_a: GO:2
+
+[Term]
+id: GO:2
+name: original parent
+is_obsolete: true
+replaced_by: GO:3
+xref: FOO:1
+
+[Term]
+id: GO:3
+name: replacement parent
+
+[Term]
+id: GO:4
+name: other term
+is_obsolete: true
+replaced_by: GO:3
+xref: FOO:2
diff --git a/robot-core/src/test/resources/xref-repaired.obo b/robot-core/src/test/resources/xref-repaired.obo
new file mode 100644
index 00000000..96f4e3f8
--- /dev/null
+++ b/robot-core/src/test/resources/xref-repaired.obo
@@ -0,0 +1,24 @@
+ontology: xref-need-of-repair
+
+[Term]
+id: GO:1
+name: leaf node
+is_a: GO:3
+
+[Term]
+id: GO:2
+name: original parent
+is_obsolete: true
+replaced_by: GO:3
+
+[Term]
+id: GO:3
+name: replacement parent
+xref: FOO:1
+xref: FOO:2
+
+[Term]
+id: GO:4
+name: other term
+is_obsolete: true
+replaced_by: GO:3