Repository: ontodev/robot
Pull request #215 (2017-12-12 01:58:06)

Repair and Report operations and commands
Adds

 - report #205 
 - repair #214 

Both are quite slim at the moment. The idea is that we can just keep adding new checks, and give the user leeway in what is considered failures vs warnings. Some may be simply lint, others may be things that OBO decides are of importance for ontology quality. 


Patch:
diff --git a/examples/README.md b/examples/README.md
index 6e6ba786..f4faefc4 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -220,15 +220,6 @@ Ontologies are shared in different formats. The default format used by ROBOT is
 
     robot convert --input annotated.owl --output results/annotated.obo
 
-The file format is determined by the extension of the output file (e.g. `.obo`), but it can also be declared with the `--format` option. Valid file formats are:
-  - json - [OBO Graphs JSON](https://github.com/geneontology/obographs/)
-  - obo - [OBO Format](http://purl.obolibrary.org/obo/oboformat)
-  - ofn - [OWL Functional](http://www.w3.org/TR/owl2-syntax/)
-  - omn - [Manchester](https://www.w3.org/TR/owl2-manchester-syntax/)
-  - owl - [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/)
-  - owx - [OWL/XML](https://www.w3.org/TR/owl2-xml-serialization/)
-  - ttl - [Turtle](https://www.w3.org/TR/turtle/)
-
 ## Mirroring
 
 Many ontologies make use of `owl:imports` to bring in other ontologies, or portions of other ontologies. Large import chains involving multiple large ontologies are more prone to run-time failure due to network errors or latency. It can therefore be beneficial to mirror or cache an external ontology's import chain locally. This can be though of as analogous to what happens with standard dependency management tools for software development.
@@ -257,6 +248,14 @@ OWL 2 has a number of [profiles](https://www.w3.org/TR/owl2-profiles/) that stri
       --input merged.owl \
       --output results/merged-validation.txt
 
+## Repairing Ontologies
+
+ROBOT can repair certain problems encountered in ontologies. So far this is limited to updating axioms pointing to deprecated classes with their replacement class
+
+    robot repair \
+      --input need-of-repair.owl \
+      --output results/repaired.owl
+
 
 ## Chaining
 
diff --git a/examples/need-of-repair.owl b/examples/need-of-repair.owl
new file mode 100644
index 00000000..6d5a3f99
--- /dev/null
+++ b/examples/need-of-repair.owl
@@ -0,0 +1,57 @@
+<?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: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#">
+    <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/need-of-repair.owl"/>
+    
+
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_1 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_2"/>
+        <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_2 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_2">
+        <obo:IAO_0100001 rdf:datatype="http://www.w3.org/2001/XMLSchema#string">GO:3</obo:IAO_0100001>
+        <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 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_3">
+        <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.5.0) https://github.com/owlcs/owlapi -->
+
diff --git a/examples/repaired.owl b/examples/repaired.owl
new file mode 100644
index 00000000..01af01c3
--- /dev/null
+++ b/examples/repaired.owl
@@ -0,0 +1,72 @@
+<?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:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     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: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"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_1 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_3"/>
+        <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_2 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_2">
+        <obo:IAO_0100001 rdf:datatype="http://www.w3.org/2001/XMLSchema#string">GO:3</obo:IAO_0100001>
+        <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 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_3">
+        <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) https://github.com/owlcs/owlapi -->
+
diff --git a/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java b/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
index a171cf44..d52aca90 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
@@ -34,6 +34,8 @@ public class CommandLineInterface {
         m.addCommand("reason",           new ReasonCommand());
         m.addCommand("reduce",           new ReduceCommand());
         m.addCommand("relax",            new RelaxCommand());
+        m.addCommand("repair",           new RepairCommand());
+        m.addCommand("report",           new ReportCommand());
         m.addCommand("template",         new TemplateCommand());
         m.addCommand("unmerge",          new UnmergeCommand());
         m.addCommand("validate-profile", new ValidateProfileCommand());
diff --git a/robot-command/src/main/java/org/obolibrary/robot/ConvertCommand.java b/robot-command/src/main/java/org/obolibrary/robot/ConvertCommand.java
index cffbda09..be48ccfa 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/ConvertCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/ConvertCommand.java
@@ -35,8 +35,7 @@ public class ConvertCommand implements Command {
         o.addOption("i", "input",     true, "convert ontology from a file");
         o.addOption("I", "input-iri", true, "convert ontology from an IRI");
         o.addOption("o", "output",    true, "save ontology to a file");
-        o.addOption("f", "format",    
-        		true, "the format: obo, owl, ttl, owx, omn, ofn, json");
+        o.addOption("f", "format",    true, "the format: RDFXML, OBO, etc.");
         options = o;
     }
 
diff --git a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
new file mode 100644
index 00000000..ed3ea352
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
@@ -0,0 +1,135 @@
+package org.obolibrary.robot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLOntology;
+import uk.ac.manchester.cs.owlapi.modularity.ModuleType;
+
+/**
+ * Handles inputs and outputs for the {@link RepairOperation}.
+ *
+ * @author cjm
+ */
+public class RepairCommand implements Command {
+    /**
+     * Logger.
+     */
+    private static final Logger logger =
+        LoggerFactory.getLogger(RepairCommand.class);
+
+    /**
+     * Store the command-line options for the command.
+     */
+    private Options options;
+
+    /**
+     * Initialze the command.
+     */
+    public RepairCommand() {
+        Options o = CommandLineHelper.getCommonOptions();
+        o.addOption("i", "input",     true, "load ontology from a file");
+        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");
+        options = o;
+    }
+
+    /**
+     * Name of the command.
+     *
+     * @return name
+     */
+    public String getName() {
+        return "repair";
+    }
+
+    /**
+     * Brief description of the command.
+     *
+     * @return description
+     */
+    public String getDescription() {
+        return "repair terms from an ontology";
+    }
+
+    /**
+     * Command-line usage for the command.
+     *
+     * @return usage
+     */
+    public String getUsage() {
+        return "robot repair --input <file> "
+             + "--output <file> "
+             + "--output-iri <iri>";
+    }
+
+    /**
+     * Command-line options for the command.
+     *
+     * @return options
+     */
+    public Options getOptions() {
+        return options;
+    }
+
+    /**
+     * Handle the command-line and file operations for the RepairOperation.
+     *
+     * @param args strings to use as arguments
+     */
+    public void main(String[] args) {
+        try {
+            execute(null, args);
+        } catch (Exception e) {
+            CommandLineHelper.handleException(getUsage(), getOptions(), e);
+        }
+    }
+
+    /**
+     * Given an input state and command line arguments,
+     * repair a new ontology and return an new state.
+     * The input ontology is not changed.
+     *
+     * @param state the state from the previous command, or null
+     * @param args the command-line arguments
+     * @return a new state with the repaired ontology
+     * @throws Exception on any problem
+     */
+    public CommandState execute(CommandState state, String[] args)
+            throws Exception {
+        OWLOntology outputOntology = null;
+
+        CommandLine line = CommandLineHelper
+            .getCommandLine(getUsage(), getOptions(), args);
+        if (line == null) {
+            return null;
+        }
+
+        IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+        state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+        OWLOntology inputOntology = state.getOntology();
+
+        IRI outputIRI = CommandLineHelper.getOutputIRI(line);
+        if (outputIRI == null) {
+            outputIRI = inputOntology.getOntologyID().getOntologyIRI().orNull();
+        }
+
+        RepairOperation.repair(
+                    inputOntology,
+                    ioHelper);
+        outputOntology = inputOntology;
+
+        CommandLineHelper.maybeSaveOutput(line, outputOntology);
+
+        state.setOntology(outputOntology);
+        return state;
+    }
+}
diff --git a/robot-command/src/main/java/org/obolibrary/robot/ReportCommand.java b/robot-command/src/main/java/org/obolibrary/robot/ReportCommand.java
new file mode 100644
index 00000000..2c1b5575
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/ReportCommand.java
@@ -0,0 +1,125 @@
+package org.obolibrary.robot;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles inputs and outputs for the {@link ReportOperation}.
+ *
+ * @author cjm
+ */
+public class ReportCommand implements Command {
+    /**
+     * Logger.
+     */
+    private static final Logger logger =
+        LoggerFactory.getLogger(ReportCommand.class);
+
+    /**
+     * Store the command-line options for the command.
+     */
+    private Options options;
+
+    /**
+     * Initialze the command.
+     */
+    public ReportCommand() {
+        Options o = CommandLineHelper.getCommonOptions();
+        o.addOption("i", "input",     true, "load ontology from a file");
+        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");
+        options = o;
+    }
+
+    /**
+     * Name of the command.
+     *
+     * @return name
+     */
+    public String getName() {
+        return "report";
+    }
+
+    /**
+     * Brief description of the command.
+     *
+     * @return description
+     */
+    public String getDescription() {
+        return "report terms from an ontology";
+    }
+
+    /**
+     * Command-line usage for the command.
+     *
+     * @return usage
+     */
+    public String getUsage() {
+        return "robot report --input <file> "
+             + "--output <file> "
+             + "--output-iri <iri>";
+    }
+
+    /**
+     * Command-line options for the command.
+     *
+     * @return options
+     */
+    public Options getOptions() {
+        return options;
+    }
+
+    /**
+     * Handle the command-line and file operations for the reportOperation.
+     *
+     * @param args strings to use as arguments
+     */
+    public void main(String[] args) {
+        try {
+            execute(null, args);
+        } catch (Exception e) {
+            CommandLineHelper.handleException(getUsage(), getOptions(), e);
+        }
+    }
+
+    /**
+     * Given an input state and command line arguments,
+     * report a new ontology and return an new state.
+     * The input ontology is not changed.
+     *
+     * @param state the state from the previous command, or null
+     * @param args the command-line arguments
+     * @return a new state with the reported ontology
+     * @throws Exception on any problem
+     */
+    public CommandState execute(CommandState state, String[] args)
+            throws Exception {
+        OWLOntology outputOntology = null;
+
+        CommandLine line = CommandLineHelper
+            .getCommandLine(getUsage(), getOptions(), args);
+        if (line == null) {
+            return null;
+        }
+
+        IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+        state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+        OWLOntology inputOntology = state.getOntology();
+
+        IRI outputIRI = CommandLineHelper.getOutputIRI(line);
+        if (outputIRI == null) {
+            outputIRI = inputOntology.getOntologyID().getOntologyIRI().orNull();
+        }
+
+        // TODO save results
+        ReportOperation.report(
+                    inputOntology,
+                    ioHelper);
+        return state;
+    }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
new file mode 100644
index 00000000..f16ecc49
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
@@ -0,0 +1,152 @@
+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 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.parameters.Imports;
+import org.semanticweb.owlapi.util.OWLEntityRenamer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+/**
+ * Repair an ontology
+ */
+public class RepairOperation {
+    /**
+     * Logger.
+     */
+    private static final Logger logger =
+            LoggerFactory.getLogger(RepairOperation.class);
+
+
+    /**
+     * Return a map from option name to default option value,
+     * for all the available repair options.
+     *
+     * @return a map with default values for all available options
+     */
+    public static Map<String, String> getDefaultOptions() {
+        Map<String, String> options = new HashMap<String, String>();
+        //options.put("remove-redundant-subclass-axioms", "true");
+ 
+        return options;
+    }
+
+ 
+    /**
+     * Repairs ontology
+     * 
+     * @param ontology
+     * @param iohelper
+     */
+    public static void repair(OWLOntology ontology, IOHelper iohelper) {
+        repair(ontology,  iohelper, getDefaultOptions());
+
+    }
+    /**
+     * Repairs ontology
+     * 
+     * @param ontology
+     * @param iohelper
+     * @param options
+     */
+    public static void repair(OWLOntology ontology,
+            IOHelper iohelper,
+            Map<String, String> options) {
+        
+        Set<InvalidReferenceViolation> violations = InvalidReferenceChecker.getInvalidReferenceViolations(ontology, true);
+        repairInvalidReferences(iohelper, ontology, violations);
+    }
+
+    /**
+     * Repairs invalid references
+     * 
+     * Currently only able to repair references to deprecated classes.
+     * 
+     * Assumes OBO vocabulary
+     * 
+     * @param iohelper
+     * @param ontology
+     * @param violations
+     */
+    public static void repairInvalidReferences(IOHelper iohelper, OWLOntology ontology, Set<InvalidReferenceViolation> violations) {
+        logger.info("Invalid references: "+violations.size());
+        OWLOntologyManager manager = ontology.getOWLOntologyManager();
+        OWLEntityRenamer renamer = new OWLEntityRenamer(manager, ontology.getImportsClosure());
+        Map<OWLEntity, IRI> renameMap = new HashMap<>();
+        Set<OWLAxiom> axiomsToPreserve = new HashSet<>();
+        
+        for (InvalidReferenceViolation v : violations) {
+            if (v.getCategory().equals(Category.DEPRECATED)) {
+                OWLEntity obsObj = v.getReferencedObject();
+                logger.info("Finding replacements for: "+v);
+                IRI replacedBy = null;               
+                for (OWLOntology o : ontology.getImportsClosure()) {
+                    for (OWLAnnotationAssertionAxiom aaa : o.getAnnotationAssertionAxioms(obsObj.getIRI())) {
+                        // TODO: use a vocabulary class
+                        if (aaa.getProperty().getIRI().equals(IRI.create("http://purl.obolibrary.org/obo/IAO_0100001"))) {
+                            OWLAnnotationValue val = aaa.getValue();
+                            Optional<IRI> valIRI = val.asIRI();
+                            if (valIRI.isPresent()) {
+                                logger.info("Using URI replacement: "+valIRI);
+                                replacedBy = valIRI.get();
+                            }
+                            else {
+                                Optional<OWLLiteral> valLit = val.asLiteral();
+                                if (valLit.isPresent()) {
+                                    logger.info("Using CURIE replacement: "+valLit);
+                                    replacedBy = iohelper.createIRI(valLit.get().getLiteral());
+                                }
+                            }
+                        }
+                    }
+                }
+                if (replacedBy != null) {
+                    renameMap.put(obsObj, replacedBy);
+                  }
+            }
+        } 
+        
+        for (OWLEntity obsObj : renameMap.keySet()) {
+            IRI replacedBy = renameMap.get(obsObj);
+            if (obsObj instanceof OWLClass) {
+                axiomsToPreserve.addAll(ontology.getAxioms((OWLClass) obsObj, Imports.EXCLUDED));
+            }
+            if (obsObj instanceof OWLObjectProperty) {
+                axiomsToPreserve.addAll(ontology.getAxioms((OWLObjectProperty) obsObj, Imports.EXCLUDED));
+            }
+            axiomsToPreserve.addAll(ontology.getDeclarationAxioms(obsObj));
+            axiomsToPreserve.addAll(ontology.getAnnotationAssertionAxioms(obsObj.getIRI()));
+            logger.info("Replacing: "+obsObj+" -> "+replacedBy);
+            
+        }
+        
+        logger.info("PRESERVE: "+axiomsToPreserve);
+        manager.removeAxioms(ontology, axiomsToPreserve);
+        List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange> ();
+        changes.addAll(renamer.changeIRI(renameMap));
+        manager.applyChanges(changes);
+        manager.addAxioms(ontology, axiomsToPreserve);
+    }
+
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java b/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java
new file mode 100644
index 00000000..0e48a4e7
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReportOperation.java
@@ -0,0 +1,92 @@
+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 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.parameters.Imports;
+import org.semanticweb.owlapi.util.OWLEntityRenamer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+/**
+ * Report issues with an ontology.
+ * 
+ * Currently this is minimal but we imagine later creating an extensive 'report card'
+ * for an ontology, describing ways to make the ontology conform to OBO conventions
+ * 
+ * TODO: decide on report structure. Perhaps JSON-LD? Create vocabulary for report violations?
+ * 
+ */
+public class ReportOperation {
+    /**
+     * Logger.
+     */
+    private static final Logger logger =
+            LoggerFactory.getLogger(ReportOperation.class);
+
+
+    /**
+     * Return a map from option name to default option value,
+     * for all the available report options.
+     *
+     * @return a map with default values for all available options
+     */
+    public static Map<String, String> getDefaultOptions() {
+        Map<String, String> options = new HashMap<String, String>();
+ 
+        return options;
+    }
+
+ 
+    /**
+     * reports ontology
+     * 
+     * @param ontology
+     * @param iohelper
+     */
+    public static void report(OWLOntology ontology, IOHelper iohelper) {
+        report(ontology,  iohelper, getDefaultOptions());
+
+    }
+    /**
+     * reports ontology
+     * 
+     * @param ontology
+     * @param iohelper
+     * @param options
+     */
+    public static void report(OWLOntology ontology,
+            IOHelper iohelper,
+            Map<String, String> options) {
+        
+        Set<InvalidReferenceViolation> refViolations = InvalidReferenceChecker.getInvalidReferenceViolations(ontology, true);
+        for (InvalidReferenceViolation v : refViolations) {
+            System.err.println("REFERENCE VIOLATION: "+v);
+        }
+        int totalViolations = refViolations.size();
+        if (totalViolations > 0) {
+            System.err.println("REPORT FAILED! Violations: "+totalViolations);
+        }
+    }
+
+}
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 101be271..d5d73100 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
@@ -7,12 +7,17 @@ import org.obolibrary.robot.checks.InvalidReferenceViolation.Category;
 import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
 import org.semanticweb.owlapi.model.OWLAxiom;
 import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
 import org.semanticweb.owlapi.model.OWLEntity;
 import org.semanticweb.owlapi.model.OWLObjectProperty;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.parameters.Imports;
 
 /**
+ * Checks to determine if axioms contain invalid references: to dangling classes or deprecated classes.
+ * 
+ * This is a structural check. To determine the *logical* validity of an axiom reference,
+ * robot uses OWL reasoning.
  * 
  * See: https://github.com/ontodev/robot/issues/1
  * 
@@ -53,10 +58,12 @@ public class InvalidReferenceChecker {
      * @return true if entity is deprecated
      */
     public static boolean isDeprecated(OWLOntology ontology, OWLEntity entity)  {
-        for (OWLAnnotationAssertionAxiom a : ontology.getAnnotationAssertionAxioms(entity.getIRI())) {
-            if (a.isDeprecatedIRIAssertion()) {
-                if (a.getValue().asLiteral().get().parseBoolean()) {
-                    return true;
+        for (OWLOntology o : ontology.getImportsClosure()) {
+            for (OWLAnnotationAssertionAxiom a : o.getAnnotationAssertionAxioms(entity.getIRI())) {
+                if (a.isDeprecatedIRIAssertion()) {
+                    if (a.getValue().asLiteral().get().parseBoolean()) {
+                        return true;
+                    }
                 }
             }
         }
@@ -65,6 +72,13 @@ public class InvalidReferenceChecker {
    
     
     /**
+     * Finds axioms that reference a deprecated or dangling entity.
+     * 
+     * Declaration axioms that reference a deprecated class do not count.
+     * 
+     * Note that this does not count the value field of an annotation assertion, since these
+     * reference IRIs and not entities
+     * 
      * @param ontology
      * @param axioms
      * @return all violations
@@ -77,7 +91,9 @@ public class InvalidReferenceChecker {
                     violations.add(InvalidReferenceViolation.create(axiom, e, Category.DANGLING));
                 }
                 if (isDeprecated(ontology, e)) {
-                    violations.add(InvalidReferenceViolation.create(axiom, e, Category.DEPRECATED));
+                    if (!(axiom instanceof OWLDeclarationAxiom)) {
+                        violations.add(InvalidReferenceViolation.create(axiom, e, Category.DEPRECATED));
+                    }
                 }
 
             }


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
new file mode 100644
index 00000000..07102488
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
@@ -0,0 +1,33 @@
+package org.obolibrary.robot;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyCreationException;
+
+/**
+ * Tests for MergeOperation.
+ */
+public class RepairOperationTest extends CoreTest {
+    
+    /**
+     * Test repair an ontology
+     *
+     * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
+     */
+    @Test
+    public void testRepair()
+            throws IOException, OWLOntologyCreationException {
+        OWLOntology ontology = loadOntology("/need-of-repair.owl");
+        IOHelper iohelper = new IOHelper();
+        RepairOperation.repair(ontology, iohelper);
+        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
new file mode 100644
index 00000000..6d5a3f99
--- /dev/null
+++ b/robot-core/src/test/resources/need-of-repair.owl
@@ -0,0 +1,57 @@
+<?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: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#">
+    <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/need-of-repair.owl"/>
+    
+
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_1 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_2"/>
+        <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_2 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_2">
+        <obo:IAO_0100001 rdf:datatype="http://www.w3.org/2001/XMLSchema#string">GO:3</obo:IAO_0100001>
+        <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 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_3">
+        <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.5.0) https://github.com/owlcs/owlapi -->
+
diff --git a/robot-core/src/test/resources/repaired.owl b/robot-core/src/test/resources/repaired.owl
new file mode 100644
index 00000000..b56a49c0
--- /dev/null
+++ b/robot-core/src/test/resources/repaired.owl
@@ -0,0 +1,69 @@
+<?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:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     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:obo="http://purl.obolibrary.org/obo/">
+    <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/need-of-repair.owl"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Annotation properties
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_1 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_3"/>
+        <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">leaf node</rdfs:label>
+    </owl:Class>
+    
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_2 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_2">
+        <obo:IAO_0100001 rdf:datatype="http://www.w3.org/2001/XMLSchema#string">GO:3</obo:IAO_0100001>
+        <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 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_3">
+        <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 -->
+