Repository: ontodev/robot
Pull request #774 (2020-11-24 18:56:09)

Add metrics command first pass
Resolves [#627]

- [x] `docs/` have been added/updated
- [x] tests have been added/updated
- [x] `mvn verify` says all tests pass
- [x] `mvn site` says all JavaDocs correct
- [x] `CHANGELOG.md` has been updated

ROBOT jar for testing [here](https://www.dropbox.com/s/3wr8lcuz3pkopoo/robot.jar?dl=0).


For the community, the most relevant question will be which of the metrics should go in which bin (essential, extended, advanced):

### Essential metrics

#### Entity counts
- SIGNATURE_SIZE_INCL
- SIGNATURE_SIZE
- CLASS_COUNT_INCL
- CLASS_COUNT
- OBJPROPERTY_COUNT_INCL
- OBJPROPERTY_COUNT
- INDIVIDUAL_COUNT_INCL
- INDIVIDUAL_COUNT
- DATAPROPERTY_COUNT_INCL
- DATAPROPERTY_COUNT
- ANNOTATION_PROP_COUNT
- ANNOTATION_PROP_COUNT_INCL
- DATATYPE_COUNT_INCL
- DATATYPE_COUNT

#### Axiom counts
- LOGICAL_AXIOM_COUNT
- LOGICAL_AXIOM_COUNT_INCL
- AXIOM_COUNT
- AXIOM_COUNT_INCL
- TBOX_SIZE
- TBOX_SIZE_INCL
- TBOXRBOX_SIZE
- TBOXRBOX_SIZE_INCL
- RULE_CT
- RULE_CT_INCL
- RBOX_SIZE
- RBOX_SIZE_INCL
- ABOX_SIZE
- ABOX_SIZE_INCL

#### OWL Profile metrics
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_OWL
- BOOL_PROFILE_RDFS
- VIOLATION_PROFILE_OWL

#### Other metrics
- OWLAPI_VERSION
- ONTOLOGY_ID
- ONTOLOGY_ANNOTATIONS_COUNT
- VALID_IMPORTS
- VALID_IMPORTS_INCL

### Extended metrics
#### Extended entity metrics
- DATATYPE_BUILTIN_COUNT_INCL
- DATATYPE_BUILTIN_COUNT
- DATATYPE_NOTBUILTIN_COUNT_INCL
- DATATYPE_NOTBUILTIN_COUNT

#### Logical expressivity
- EXPRESSIVITY
- EXPRESSIVITY_INCL
- CONSTRUCTS_INCL
- CONSTRUCTS
- AXIOM_TYPES
- AXIOM_TYPES_INCL
- AXIOMTYPE_COUNT
- AXIOMTYPE_COUNT_INCL
- SYNTAX

#### Entity usage
- NS_USE_AXIOMS
- NS_USE_AXIOMS_INCL
- NS_USE_SIGNATURE
- NS_USE_SIGNATURE_INCL

### Advanced metrics (all)
- MAX_AXIOMLENGTH
- MAX_AXIOMLENGTH_INCL
- AVG_ASSERT_N_SUBCLASS_INCL
- AVG_ASSERT_N_SUBCLASS
- AVG_ASSERT_N_SUPERCLASS_INCL
- AVG_ASSERT_N_SUPERCLASS
- AVG_INSTANCE_PER_CLASS_INCL
- AVG_INSTANCE_PER_CLASS
- CLASS_SGL_SUBCLASS_COUNT_INCL
- CLASS_SGL_SUBCLASS_COUNT
- AXIOM_COMPLEXRHS_COUNT_INCL
- AXIOM_COMPLEXRHS_COUNT
- TBOX_CONTAINS_NOMINALS_INCL
- TBOX_CONTAINS_NOMINALS
- ABOX_CONTAINS_NOMINALS_INCL
- ABOX_CONTAINS_NOMINALS
- GCI_COUNT_INCL
- GCI_COUNT
- GCI_HIDDEN_COUNT_INCL
- GCI_HIDDEN_COUNT
- MAX_NUM_NAMED_SUPERCLASS_INCL
- MAX_NUM_NAMED_SUPERCLASS
- MULTI_INHERITANCE_COUNT_INCL
- MULTI_INHERITANCE_COUNT
- REF_CLASS_COUNT
- REF_DATAPROP_COUNT_INCL
- REF_DATAPROP_COUNT
- REF_INDIV_COUNT_INCL
- REF_INDIV_COUNT
- REF_OBJPROP_COUNT_INCL
- REF_OBJPROP_COUNT
- UNDECLARED_ENTITY_COUNT_INCL
- UNDECLARED_ENTITY_COUNT
- TAUTOLOGYCOUNT
- TAUTOLOGYCOUNT_INCL
- CYCLE
- CYCLE_INCL
- CYCLE
- CYCLE
- DATATYPE_AXIOMCOUNT
- DATATYPE_AXIOMCOUNT_INCL
- DATATYPES
- DATATYPES_INCL
- DATATYPES_NOT_BUILT_IN
- DATATYPES_NOT_BUILT_IN_INCL
- MOST_FRQUENTLY_USED_CONCEPT_INCL
- MOST_FRQUENTLY_USED_CONCEPT



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37a218d4..6f6a5759 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+- Add [`metrics`] command [#774]
 
 ## [1.7.2] - 2020-11-18
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 89dd69ca..e4fe776e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@ Before changing anything, make sure the tests pass:
 
 * [Create a new branch](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) with your topic name (e.g. `extract-bug-fix`)
 * Make your changes and write any corresponding tests
-* Test your changes (`$ mvn test -Dtest=TestName`; make sure you are in the `robot-core` sub-directory before running tests)
+* Test your changes (`$ mvn test -Dtest=TestName`)
 * Ensure ROBOT builds correctly (`$ mvn clean package`)
 * Commit and push changes to your fork, then create a [pull request](https://help.github.com/articles/about-pull-requests/)
 
diff --git a/docs/examples/metrics.tsv b/docs/examples/metrics.tsv
new file mode 100644
index 00000000..d60d28c5
--- /dev/null
+++ b/docs/examples/metrics.tsv
@@ -0,0 +1,38 @@
+metric	metric_value	metric_type
+abox_axiom_count	0	single_value
+abox_axiom_count_incl	0	single_value
+annotation_property_count	38	single_value
+annotation_property_count_incl	38	single_value
+axiom_count	5504	single_value
+axiom_count_incl	5504	single_value
+class_count	236	single_value
+class_count_incl	236	single_value
+dataproperty_count	0	single_value
+dataproperty_count_incl	0	single_value
+datatypes_count	2	single_value
+datatypes_count_incl	2	single_value
+individual_count	0	single_value
+individual_count_incl	0	single_value
+logical_axiom_count	828	single_value
+logical_axiom_count_incl	828	single_value
+obj_property_count	49	single_value
+obj_property_count_incl	49	single_value
+ontology_anno_count	0	single_value
+ontology_id	http://purl.obolibrary.org/obo/uberon.owl	single_value
+owl2	true	single_value
+owl2_dl	true	single_value
+owl2_el	false	single_value
+owl2_ql	false	single_value
+owl2_rl	false	single_value
+owlapi_version	robot.jar	single_value
+rbox_axiom_count	56	single_value
+rbox_axiom_count_incl	56	single_value
+rdfs	false	single_value
+rule_count	0	single_value
+rule_count_incl	0	single_value
+signature_entity_count	325	single_value
+signature_entity_count_incl	325	single_value
+tbox_axiom_count	772	single_value
+tbox_axiom_count_incl	772	single_value
+tboxrbox_axiom_count	828	single_value
+tboxrbox_axiom_count_incl	828	single_value
diff --git a/docs/examples/metrics_all.json b/docs/examples/metrics_all.json
new file mode 100644
index 00000000..b21520f6
--- /dev/null
+++ b/docs/examples/metrics_all.json
@@ -0,0 +1,197 @@
+{
+  "metrics": {
+    "abox_axiom_count": 0,
+    "abox_axiom_count_incl": 0,
+    "abox_nominals": false,
+    "abox_nominals_incl": false,
+    "annotation_property_count": 38,
+    "annotation_property_count_incl": 38,
+    "assert_n_subclass_avg": 1.305084745762712,
+    "assert_n_subclass_avg_incl": 1.305084745762712,
+    "assert_n_superclass_avg": 1.326271186440678,
+    "assert_n_superclass_avg_incl": 1.326271186440678,
+    "ax_complexrhs_count": 464,
+    "ax_complexrhs_count_incl": 464,
+    "axiom_count": 5504,
+    "axiom_count_incl": 5504,
+    "axiom_length_max": 8,
+    "axiom_length_max_incl": 8,
+    "certain_cycle": true,
+    "certain_cycle_incl": true,
+    "class_count": 236,
+    "class_count_incl": 236,
+    "class_sgl_subcl_count": 41,
+    "class_sgl_subcl_count_incl": 41,
+    "dataproperty_count": 0,
+    "dataproperty_count_incl": 0,
+    "datatypes_count": 2,
+    "datatypes_count_incl": 2,
+    "dt_builtin_count": 2,
+    "dt_builtin_count_incl": 2,
+    "dt_notbuiltin_count": 0,
+    "dt_notbuiltin_count_incl": 0,
+    "expressivity": "SRIQ",
+    "expressivity_incl": "SRIQ",
+    "gci_count": 0,
+    "gci_count_incl": 0,
+    "gci_hidden_count": 105,
+    "gci_hidden_count_incl": 105,
+    "individual_count": 0,
+    "individual_count_incl": 0,
+    "instance_per_class_avg": 0.0,
+    "instance_per_class_avg_incl": 0.0,
+    "logical_axiom_count": 828,
+    "logical_axiom_count_incl": 828,
+    "most_freq_concept": "UBERON:0002091",
+    "most_freq_concept_incl": "UBERON:0002091",
+    "multiple_inheritance_count": 103,
+    "multiple_inheritance_count_incl": 103,
+    "named_supercl_count_max": 1,
+    "named_supercl_count_max_incl": 1,
+    "obj_property_count": 49,
+    "obj_property_count_incl": 49,
+    "ontology_anno_count": 0,
+    "ontology_id": "http://purl.obolibrary.org/obo/uberon.owl",
+    "owl2": true,
+    "owl2_dl": true,
+    "owl2_el": false,
+    "owl2_ql": false,
+    "owl2_rl": false,
+    "owlapi_version": "robot.jar",
+    "rbox_axiom_count": 56,
+    "rbox_axiom_count_incl": 56,
+    "rdfs": false,
+    "rule_count": 0,
+    "rule_count_incl": 0,
+    "signature_entity_count": 325,
+    "signature_entity_count_incl": 325,
+    "syntax": "RDF/XML Syntax",
+    "tautology_count": 0,
+    "tautology_count_incl": 0,
+    "tbox_axiom_count": 772,
+    "tbox_axiom_count_incl": 772,
+    "tbox_nominals": false,
+    "tbox_nominals_incl": false,
+    "tboxrbox_axiom_count": 828,
+    "tboxrbox_axiom_count_incl": 828,
+    "undecl_entity_count": 5,
+    "undecl_entity_count_incl": 5,
+    "axiom_types": [
+      "AnnotationAssertion",
+      "EquivalentClasses",
+      "TransitiveObjectProperty",
+      "SubObjectPropertyOf",
+      "SymmetricObjectProperty",
+      "SubPropertyChainOf",
+      "Declaration",
+      "SubClassOf",
+      "InverseObjectProperties"
+    ],
+    "axiom_types_incl": [
+      "AnnotationAssertion",
+      "EquivalentClasses",
+      "TransitiveObjectProperty",
+      "SubObjectPropertyOf",
+      "SymmetricObjectProperty",
+      "SubPropertyChainOf",
+      "Declaration",
+      "SubClassOf",
+      "InverseObjectProperties"
+    ],
+    "constructs": [
+      "Q",
+      "R",
+      "S",
+      "I"
+    ],
+    "constructs_incl": [
+      "Q",
+      "R",
+      "S",
+      "I"
+    ],
+    "datatypes_builtin": [
+      "XSD_STRING",
+      "XSD_ANY_URI"
+    ],
+    "datatypes_builtin_incl": [
+      "XSD_STRING",
+      "XSD_ANY_URI"
+    ],
+    "datatypes_not_built_in": [],
+    "datatypes_not_built_in_incl": [],
+    "valid_imports": [],
+    "valid_imports_incl": [],
+    "axiom_type_count": {
+      "AnnotationAssertion": 4356,
+      "EquivalentClasses": 106,
+      "TransitiveObjectProperty": 12,
+      "SubObjectPropertyOf": 25,
+      "SymmetricObjectProperty": 1,
+      "SubPropertyChainOf": 11,
+      "Declaration": 320,
+      "SubClassOf": 666,
+      "InverseObjectProperties": 7
+    },
+    "axiom_type_count_incl": {
+      "AnnotationAssertion": 4356,
+      "EquivalentClasses": 106,
+      "TransitiveObjectProperty": 12,
+      "SubObjectPropertyOf": 25,
+      "SymmetricObjectProperty": 1,
+      "SubPropertyChainOf": 11,
+      "Declaration": 320,
+      "SubClassOf": 666,
+      "InverseObjectProperties": 7
+    },
+    "datatypes_axiom_count": {},
+    "datatypes_axiom_count_incl": {},
+    "namespace_axiom_count": {
+      "oboInOwl": 4819,
+      "IAO": 306,
+      "UBERON": 1903,
+      "rdfs": 308,
+      "BFO": 283,
+      "obo": 235,
+      "RO": 307,
+      "foaf": 56,
+      "BSPO": 29
+    },
+    "namespace_axiom_count_incl": {
+      "oboInOwl": 4819,
+      "IAO": 306,
+      "UBERON": 1903,
+      "rdfs": 308,
+      "BFO": 283,
+      "obo": 235,
+      "RO": 307,
+      "foaf": 56,
+      "BSPO": 29
+    },
+    "namespace_entity_count": {
+      "oboInOwl": 20,
+      "IAO": 3,
+      "UBERON": 236,
+      "xsd": 2,
+      "BFO": 4,
+      "rdfs": 3,
+      "RO": 36,
+      "obo": 13,
+      "foaf": 1,
+      "BSPO": 7
+    },
+    "namespace_entity_count_incl": {
+      "oboInOwl": 20,
+      "IAO": 3,
+      "UBERON": 236,
+      "xsd": 2,
+      "BFO": 4,
+      "rdfs": 3,
+      "RO": 36,
+      "obo": 13,
+      "foaf": 1,
+      "BSPO": 7
+    },
+    "owl2dl_profile_violation": {}
+  }
+}
\ No newline at end of file
diff --git a/docs/examples/metrics_essential.json b/docs/examples/metrics_essential.json
new file mode 100644
index 00000000..1be9e1ff
--- /dev/null
+++ b/docs/examples/metrics_essential.json
@@ -0,0 +1,44 @@
+{
+  "metrics": {
+    "abox_axiom_count": 0,
+    "abox_axiom_count_incl": 0,
+    "annotation_property_count": 38,
+    "annotation_property_count_incl": 38,
+    "axiom_count": 5504,
+    "axiom_count_incl": 5504,
+    "class_count": 236,
+    "class_count_incl": 236,
+    "dataproperty_count": 0,
+    "dataproperty_count_incl": 0,
+    "datatypes_count": 2,
+    "datatypes_count_incl": 2,
+    "individual_count": 0,
+    "individual_count_incl": 0,
+    "logical_axiom_count": 828,
+    "logical_axiom_count_incl": 828,
+    "obj_property_count": 49,
+    "obj_property_count_incl": 49,
+    "ontology_anno_count": 0,
+    "ontology_id": "http://purl.obolibrary.org/obo/uberon.owl",
+    "owl2": true,
+    "owl2_dl": true,
+    "owl2_el": false,
+    "owl2_ql": false,
+    "owl2_rl": false,
+    "owlapi_version": "robot.jar",
+    "rbox_axiom_count": 56,
+    "rbox_axiom_count_incl": 56,
+    "rdfs": false,
+    "rule_count": 0,
+    "rule_count_incl": 0,
+    "signature_entity_count": 325,
+    "signature_entity_count_incl": 325,
+    "tbox_axiom_count": 772,
+    "tbox_axiom_count_incl": 772,
+    "tboxrbox_axiom_count": 828,
+    "tboxrbox_axiom_count_incl": 828,
+    "valid_imports": [],
+    "valid_imports_incl": [],
+    "owl2dl_profile_violation": {}
+  }
+}
\ No newline at end of file
diff --git a/docs/metrics.md b/docs/metrics.md
new file mode 100644
index 00000000..21322359
--- /dev/null
+++ b/docs/metrics.md
@@ -0,0 +1,143 @@
+# Metrics
+
+## Contents
+
+1. [Overview](#overview)
+2. [Types of metrics](#types-of-metrics)
+3. [Output formats](#output-formats)
+
+## Overview
+Robot can compute a number of metrics about your ontology, such as entity and
+axiom counts, qualitative information such as OWL 2 profiles
+and more complex metrics aimed at informing ontology developers such as logical expressivity and axiom shape.
+A number of essential metrics can be generated very simply as follows:
+
+    robot metrics --input uberon_fragment.owl --output results/metrics.tsv
+
+This will generate a table with metrics such as:
+- Entity metrics (number of classes, object properties etc.)
+- Datatypes used
+- Number of axioms (logical and otherwise)
+- TBox, RBox, ABox size (size as number of axioms)
+- OWL2 profile information
+
+## Types of metrics
+There are three general modes for computing metrics:
+
+1. Essential metrics (`--metrics essential`, default)
+2. Extended metrics (`--metrics extended`)
+3. All metrics (`--metrics all`)
+
+_Essential metrics_ include basic ontology entity counts (number of classes, individuals and properties), axiom counts
+and a few qualitiative metrics such as OWL profiles, see the initial example above.
+
+    robot metrics --input uberon_fragment.owl --format json --metrics essential --output results/metrics_essential.json
+
+_Extended metrics_ contain all the essential metrics, and additional details on axiom types, logical expressivity and datatypes.
+Use: `--metrics extended`.
+
+_All metrics_ include all the essential and extended metrics, as well as a range of more complex metrics targeted at
+OWL and reasoning specialists, such as information about GCIs, shape of the class hierarchy and potential cyclicity.
+Use: `--metrics all`.
+
+A full breakdown of all metrics can be found in the following. For simplicity, all metrics ending with `_incl` are omitted -
+they simply indicate that the metric was collected over the imports closure (the ontology _including_ its imports).
+
+Some abbreviations useful to understand the following:
+- `abox`: The part of the ontology that is about individuals, see [Wikipedia](https://en.wikipedia.org/wiki/Abox)
+- `rbox`: The part of the ontology that is about object properties
+- `tbox`: The part of the ontology that is about classes, see [Wikipedia](https://en.wikipedia.org/wiki/Tbox)
+- `avg`: average
+- `max`: maximum
+- `rhs`: ride-hand-side. If used in conjunction with axioms, usually the "superclass" part of a SubClassOf axiom.
+- `dt`: datatype
+- `gci`: General concept inclusion. Formally, [all SubClassOf axioms are GCIs](https://en.wikipedia.org/wiki/Description_logic#Terminological_axioms),
+         but here we mean those that have a complex left-hand-side (subclass part).
+- OWL 2 Profiles:
+  - OWL 2 DL: Roughly the subset of OWL that conforms to description logics, plus annotations.
+  - OWL 2 EL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_EL_2).
+  - OWL 2 QL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_QL_2).
+  - OWL 2 RL: Subset of OWL 2 DL, see [W3C](https://www.w3.org/TR/owl2-profiles/#OWL_2_RL_2).
+- Logical `expressivity` and `constructs`. A breakdown of the meaning of the letters can be found [here](https://en.wikipedia.org/wiki/Description_logic#Naming_convention).
+
+
+|metric|metric_value|metric_type| explanation |
+|:---|:---|:---|:---|
+|abox_axiom_count|0|single_value| Number of axioms in the ABox |
+|annotation_property_count|24|single_value| Number of annotation properties |
+|assert_n_subclass_avg|1.0|single_value| Average number of (asserted) subclasses per class |
+|assert_n_superclass_avg|1.0416666666666667|single_value| Average number of (asserted) superclasses per class |
+|ax_complexrhs_count|15|single_value| Number of axioms with a complex right hand side |
+|axiom_count|703|single_value| Number of axioms |
+|axiom_length_max|4|single_value| Longest axiom in terms of number of entities used (including duplicate uses). |
+|axiom_type_count|SubClassOf 37|map_value| Number of axioms of a specific type, such as SubClassOf |
+|axiom_types|AnnotationAssertion|list_value| Indicates the presence of an axiom type in the ontology. |
+|certain_cycle|false|single_value| If true, there is a definite cycle in the ontology. If false, cyclicity is unknown (experimental). |
+|class_count|24|single_value| Number of classes in the ontology |
+|class_sgl_subcl_count|10|single_value| Number of super-classes which have more than one subclass. |
+|constructs|AL|list_value| Logical constructs used |
+|dataproperty_count|0|single_value| Number of distinct data properties. |
+|datatypes_builtin|XSD_BOOLEAN|list_value| Datatypes used from the built-in datatype map (like XSD schema datatypes). |
+|datatypes_count|2|single_value| Total number of distinct datatypes. |
+|dt_builtin_count|2|single_value| Total number of distinct built-in datatypes. |
+|dt_notbuiltin_count|0|single_value| Total number of distinct custom (not built-in) datatypes. |
+|expressivity|ALEH+|single_value| Logical expressivity. |
+|gci_count|0|single_value| Total count of GCI axioms.|
+|gci_hidden_count|2|single_value| Total count of hidden GCIs. |
+|individual_count|0|single_value| Number of individuals. |
+|instance_per_class_avg|0.0|single_value| Average number of individuals per class. |
+|namespace_axiom_count|http://purl.obolibrary.org/obo/BFO_ 17|map_value| Number of axioms using at least one term in a namespace. |
+|namespace_entity_count|http://purl.obolibrary.org/obo/BFO_ 17|map_value| Number of distinct entities used from a namespace. |
+|logical_axiom_count|41|single_value| Number of logical axioms in ontology |
+|most_freq_concept|UBERON:0000475|single_value| Most frequently used class. |
+|multiple_inheritance_count|2|single_value| Number of classes with multiple inheritance. |
+|named_supercl_count_max|1|single_value| Maximum number of named superclasses. |
+|obj_property_count|2|single_value| Number of object properties. |
+|ontology_anno_count|0|single_value| Number of ontology annotations. |
+|ontology_id|https://github.com/ontodev/robot/examples/edit.owl|single_value| The ontology id. |
+|owl2_dl|false|single_value| Does the ontology correspond to the OWL2 DL profile? |
+|owl2_el|false|single_value| Does the ontology correspond to the OWL2 EL profile? |
+|owl2_ql|false|single_value| Does the ontology correspond to the OWL2 QL profile? |
+|owl2_rl|false|single_value| Does the ontology correspond to the OWL2 RL profile? |
+|owl2|true|single_value| Does the ontology correspond to the OWL2 Full profile?  |
+|owl2dl_profile_violation|UseOfUndeclaredAnnotationProperty 531 |map_value| Counts of OWL2 DL profile violations. |
+|rbox_axiom_count|2|single_value| How many axioms in the RBOX? |
+|rdfs|false|single_value| Does the ontology correspond to the RDFS profile? |
+|rule_count|0|single_value| Number of SWRL rules used. |
+|signature_entity_count|52|single_value| Total number of entities in signature, including classes and individuals. |
+|syntax|RDF/XML Syntax|single_value| What serialisation is used for the ontology? |
+|tautology_count|0|single_value| Number of tautological axioms. |
+|tbox_axiom_count|39|single_value| Number of TBOX axioms (without RBOX). |
+|tbox_nominals|false|single_value| Number of TBOX axioms with nominals. |
+|tboxrbox_axiom_count|41|single_value| Number of TBOX axioms (with RBOX).|
+|undecl_entity_count|17|single_value| Number of undeclared entities. |
+
+## Output formats
+
+Currently, the following serialisation formats are supported:
+- tsv
+- csv
+- html
+- json
+- yaml
+
+To serialise the metrics as json, for example, we run:
+
+    robot metrics --input uberon_fragment.owl --format json --metrics all --output results/metrics_all.json
+
+or
+
+    robot metrics --input uberon_fragment.owl -f json --metrics all --output results/metrics_all.json
+
+Note that if no `--format` is explicitly provided, robot will try to infer the inteded format from the file extension
+instead. Failing that, it will generate a TSV file.
+
+## Error Messages
+
+### Metrics format error
+
+Only the following `--format/-f` options are currently supported: `tsv`, `json`, `yaml`, `html`.
+
+### Metrics type error
+
+Only the following `--metrics/-m` options are currently supported: `essential`, `extended`, `all`.
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 1b935c87..019ca982 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/CommandLineInterface.java
@@ -30,6 +30,7 @@ public class CommandLineInterface {
     m.addCommand("filter", new FilterCommand());
     m.addCommand("materialize", new MaterializeCommand());
     m.addCommand("merge", new MergeCommand());
+    m.addCommand("metrics", new MetricsCommand());
     m.addCommand("mirror", new MirrorCommand());
     m.addCommand("python", new PythonCommand());
     m.addCommand("query", new QueryCommand());
diff --git a/robot-command/src/main/java/org/obolibrary/robot/MetricsCommand.java b/robot-command/src/main/java/org/obolibrary/robot/MetricsCommand.java
new file mode 100644
index 00000000..18152166
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/MetricsCommand.java
@@ -0,0 +1,140 @@
+package org.obolibrary.robot;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.io.FilenameUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles inputs and outputs for the {@link MetricsOperation}.
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MetricsCommand implements Command {
+  /** Logger. */
+  private static final Logger logger = LoggerFactory.getLogger(MetricsCommand.class);
+
+  /** Namespace for error messages. */
+  private static final String NS = "metrics#";
+
+  /** Store the command-line options for the command. */
+  private Options options;
+
+  private final List<String> legal_formats = Arrays.asList("tsv", "csv", "html", "yaml", "json");
+
+  /** Initialze the command. */
+  public MetricsCommand() {
+    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(
+        "f",
+        "format",
+        true,
+        "the metrics result format: " + String.join(" ", legal_formats).trim() + ".");
+    o.addOption("o", "output", true, "save updated metrics to a file");
+    o.addOption(
+        "m",
+        "metrics",
+        true,
+        "select which set of metrics you would like to compute: essential (default), extended, all.");
+    options = o;
+  }
+
+  /**
+   * Name of the command.
+   *
+   * @return name
+   */
+  public String getName() {
+    return "metrics";
+  }
+
+  /**
+   * Brief description of the command.
+   *
+   * @return description
+   */
+  public String getDescription() {
+    return "compute the metrics of an ontology";
+  }
+
+  /**
+   * Command-line usage for the command.
+   *
+   * @return usage
+   */
+  // todo: --output?
+  public String getUsage() {
+    return "robot metrics --input <file> --output <output>";
+  }
+
+  /**
+   * Command-line options for the command.
+   *
+   * @return options
+   */
+  public Options getOptions() {
+    return options;
+  }
+
+  /**
+   * Handle the command-line and file operations for the QueryOperation.
+   *
+   * @param args strings to use as arguments
+   */
+  public void main(String[] args) {
+    try {
+      execute(null, args);
+    } catch (Exception e) {
+      CommandLineHelper.handleException(e);
+    }
+  }
+
+  /**
+   * Given an input state and command line arguments, query the ontolgy. The input ontology is not
+   * changed.
+   *
+   * @param state the state from the previous command, or null
+   * @param args the command-line arguments
+   * @return the unchanged state
+   * @throws Exception on any problem
+   */
+  public CommandState execute(CommandState state, String[] args) throws Exception {
+    CommandLine line = CommandLineHelper.getCommandLine(getUsage(), getOptions(), args);
+    if (line == null) {
+      return null;
+    }
+
+    IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+
+    String metrics_type = CommandLineHelper.getDefaultValue(line, "metrics", "essential");
+    String format = CommandLineHelper.getOptionalValue(line, "format");
+
+    String output = CommandLineHelper.getOptionalValue(line, "output");
+
+    if (output == null) {
+      throw new Exception(String.format(missingFileError, output, "metrics"));
+    }
+
+    File output_file = new File(output);
+    if (format == null) {
+      String extension = FilenameUtils.getExtension(output_file.getName()).replace("yml", "yaml");
+      if (legal_formats.contains(extension)) {
+        format = extension;
+      } else {
+        format = "tsv";
+      }
+    }
+
+    state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+
+    MetricsOperation.executeMetrics(state.getOntology(), metrics_type, format, output_file);
+
+    return state;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/MetricsOperation.java b/robot-core/src/main/java/org/obolibrary/robot/MetricsOperation.java
new file mode 100644
index 00000000..7ef5bf2f
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/MetricsOperation.java
@@ -0,0 +1,277 @@
+package org.obolibrary.robot;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import com.google.gson.*;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.obolibrary.robot.export.Cell;
+import org.obolibrary.robot.export.Column;
+import org.obolibrary.robot.export.Row;
+import org.obolibrary.robot.export.Table;
+import org.obolibrary.robot.metrics.MetricsResult;
+import org.obolibrary.robot.metrics.OntologyMetrics;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Compute metrics for the ontology.
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MetricsOperation {
+  /** Logger. */
+  private static final Logger logger = LoggerFactory.getLogger(MetricsOperation.class);
+
+  /** Namespace for error messages. */
+  private static final String NS = "metrics#";
+
+  /** Error message when metric type is illegal. Expects: metric type. */
+  private static final String metricsTypeError = NS + "METRICS TYPE ERROR unknown metrics type: %s";
+
+  /** Error message when format type is illegal. Expects: format. */
+  private static final String metricsFormatError =
+      NS + "METRICS FORMAT ERROR unknown metrics format: %s";
+
+  /**
+   * If a result set has results, write to the output stream and return true. Otherwise return
+   * false.
+   *
+   * @param result the results to write
+   * @param format the name of the file format to write the results to
+   * @param output the file to write to
+   * @return true if there were results, false otherwise
+   * @throws IOException if writing file failed
+   */
+  public static boolean maybeWriteResult(MetricsResult result, String format, File output)
+      throws IOException {
+    if (!result.isEmpty()) {
+      writeResult(result, format, output);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  public static void executeMetrics(OWLOntology o, String metrics_type, String format, File output)
+      throws IOException {
+    MetricsResult metrics = runMetrics(o, metrics_type);
+    boolean wroteData = MetricsOperation.maybeWriteResult(metrics, format, output);
+    if (!wroteData) {
+      logger.info("No metrics written.");
+    }
+  }
+
+  /**
+   * Given a dataset, a query string, a format name, and an output stream, run the SPARQL query over
+   * the named graphs and write the output to the stream.
+   *
+   * @param ontology Ontology to run metrics
+   * @param metrics_type what kind of metrics to harvest
+   * @return Metrics, if successful
+   */
+  public static MetricsResult runMetrics(OWLOntology ontology, String metrics_type) {
+    OntologyMetrics ontologyMetrics = new OntologyMetrics(ontology);
+    MetricsResult metrics;
+    switch (metrics_type) {
+      case "essential":
+        metrics = ontologyMetrics.getEssentialMetrics();
+        break;
+      case "extended":
+        metrics = ontologyMetrics.getExtendedMetrics();
+        break;
+      case "all":
+        metrics = ontologyMetrics.getAllMetrics();
+        break;
+      default:
+        throw new IllegalArgumentException(String.format(metricsTypeError, metrics_type));
+    }
+    return metrics;
+  }
+
+  /**
+   * Write a model to an output stream.
+   *
+   * @param result results of the metrics operation
+   * @param format the language to write in (if null, TTL)
+   * @param output the output stream to write to
+   */
+  public static void writeResult(MetricsResult result, String format, File output)
+      throws IOException {
+    switch (format) {
+      case "tsv":
+        writeTable(result, output, "tsv");
+        break;
+      case "csv":
+        writeTable(result, output, "csv");
+        break;
+      case "yaml":
+        writeYAML(result, output);
+        break;
+      case "json":
+        writeJSON(result, output);
+        break;
+      case "html":
+        writeHTML(result, output);
+        break;
+      default:
+        throw new IllegalArgumentException(String.format(metricsFormatError, format));
+    }
+  }
+
+  private static JsonElement resultsToJson(MetricsResult result) {
+    JsonObject root = new JsonObject();
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+
+    JsonObject metrics = new JsonObject();
+    root.add("metrics", metrics);
+
+    Map<String, Object> data = result.getData();
+    List<String> keys = new ArrayList<>(data.keySet());
+    Collections.sort(keys);
+
+    for (String key : keys) {
+      Object value = data.get(key);
+      if (value instanceof Double) {
+        metrics.addProperty(key, (Double) value);
+      } else if (value instanceof Float) {
+        metrics.addProperty(key, (Float) value);
+      } else if (value instanceof Long) {
+        metrics.addProperty(key, (Long) value);
+      } else if (value instanceof Integer) {
+        metrics.addProperty(key, (Integer) value);
+      } else if (value instanceof Boolean) {
+        metrics.addProperty(key, (Boolean) value);
+      } else {
+        metrics.addProperty(key, value.toString());
+      }
+    }
+    Map<String, List<Object>> dataList = result.getListData();
+    List<String> keysList = new ArrayList<>(dataList.keySet());
+    Collections.sort(keysList);
+
+    for (String key : keysList) {
+      List<String> stringList = new ArrayList<>();
+      dataList.get(key).forEach(s -> stringList.add(s.toString()));
+      JsonElement element = gson.toJsonTree(stringList).getAsJsonArray();
+      metrics.add(key, element);
+    }
+
+    Map<String, Map<String, Integer>> dataMap = result.getMapData();
+    List<String> keysMap = new ArrayList<>(dataMap.keySet());
+    Collections.sort(keysMap);
+    for (String key : keysMap) {
+      JsonElement element = gson.toJsonTree(dataMap.get(key)).getAsJsonObject();
+      metrics.add(key, element);
+    }
+    return root;
+  }
+
+  private static void writeJSON(MetricsResult result, File output) throws IOException {
+    JsonElement root = resultsToJson(result);
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    writeStringToFile(gson.toJson(root), output);
+  }
+
+  private static void writeStringToFile(String output, File outputPath) throws IOException {
+    try (FileWriter fw = new FileWriter(outputPath);
+        BufferedWriter bw = new BufferedWriter(fw)) {
+      logger.debug("Writing metrics to: " + outputPath);
+      bw.write(output);
+    }
+  }
+
+  private static void writeYAML(MetricsResult result, File outputPath) throws IOException {
+    JsonElement root = resultsToJson(result);
+    Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    String output = asYaml(gson.toJson(root));
+    writeStringToFile(output, outputPath);
+  }
+
+  private static String asYaml(String jsonString) throws IOException {
+    JsonNode jsonNodeTree = new ObjectMapper().readTree(jsonString);
+    ObjectMapper mapper =
+        new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
+    return mapper.writeValueAsString(jsonNodeTree);
+  }
+
+  private static String escapeTSV(String s) {
+    return s.replaceAll("\t", " ");
+  }
+
+  private static final Column cl_metric = new Column("metric");
+  private static final Column cl_metric_value = new Column("metric_value");
+  private static final Column cl_metric_type = new Column("metric_type");
+
+  private static void addRowToTable(
+      Table table, String metric, String metric_value, String metric_type) {
+    Row row = new Row();
+    row.add(new Cell(cl_metric, metric));
+    row.add(new Cell(cl_metric_value, metric_value));
+    row.add(new Cell(cl_metric_type, metric_type));
+    table.addRow(row);
+  }
+
+  private static Table resultsToTable(MetricsResult result, String format) {
+    Table table = new Table(format);
+
+    cl_metric.setSort(2);
+    cl_metric_type.setSort(1);
+    cl_metric_value.setSort(0);
+
+    table.addColumn(cl_metric);
+    table.addColumn(cl_metric_value);
+    table.addColumn(cl_metric_type);
+    table.setSortColumns();
+
+    // StringBuilder sb = new StringBuilder();
+
+    for (Map.Entry<String, Object> entry : result.getData().entrySet()) {
+      String key = escapeTSV(entry.getKey());
+      String value = escapeTSV(entry.getValue().toString());
+      addRowToTable(table, key, value, "single_value");
+      // sb.append(key).append("\t").append(value).append("\t").append("single_value").append("\n");
+    }
+
+    for (Map.Entry<String, List<Object>> entry : result.getListData().entrySet()) {
+
+      String key = escapeTSV(entry.getKey());
+
+      for (Object v : entry.getValue()) {
+        String value = escapeTSV(v.toString());
+        addRowToTable(table, key, value, "list_value");
+        // sb.append(key).append("\t").append(value).append("\t").append("list_value").append("\n");
+      }
+    }
+
+    for (Map.Entry<String, Map<String, Integer>> entry : result.getMapData().entrySet()) {
+      String key = escapeTSV(entry.getKey());
+      Map<String, Integer> v = entry.getValue();
+      for (Map.Entry<String, Integer> entryMap : v.entrySet()) {
+        String key_inner = entryMap.getKey();
+        String value_inner = escapeTSV(entryMap.getValue() + "");
+        addRowToTable(table, key, key_inner + " " + value_inner, "map_value");
+      }
+    }
+
+    table.sortRows();
+    return table;
+  }
+
+  private static void writeTable(MetricsResult result, File output, String format)
+      throws IOException {
+    Table table = resultsToTable(result, format);
+    table.write(output.getPath(), "");
+  }
+
+  private static void writeHTML(MetricsResult result, File output) throws IOException {
+    Table table = resultsToTable(result, "tsv");
+    writeStringToFile(table.toHTML(""), output);
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java
new file mode 100644
index 00000000..495dced9
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsLabels.java
@@ -0,0 +1,198 @@
+package org.obolibrary.robot.metrics;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MetricsLabels {
+
+  public static final String OWLAPI_VERSION = "owlapi_version";
+  public static final String ABOX_SIZE_INCL = "abox_axiom_count_incl";
+  public static final String ABOX_SIZE = "abox_axiom_count";
+  public static final String TBOX_SIZE_INCL = "tbox_axiom_count_incl";
+  public static final String TBOX_SIZE = "tbox_axiom_count";
+  public static final String TBOXRBOX_SIZE_INCL = "tboxrbox_axiom_count_incl";
+  public static final String TBOXRBOX_SIZE = "tboxrbox_axiom_count";
+  public static final String SIGNATURE_SIZE = "signature_entity_count";
+  public static final String SIGNATURE_SIZE_INCL = "signature_entity_count_incl";
+  public static final String ANNOTATION_PROP_COUNT = "annotation_property_count";
+  public static final String ANNOTATION_PROP_COUNT_INCL = "annotation_property_count_incl";
+  public static final String AVG_ASSERT_N_SUBCLASS = "assert_n_subclass_avg";
+  public static final String AVG_ASSERT_N_SUBCLASS_INCL = "assert_n_subclass_avg_incl";
+  public static final String AVG_ASSERT_N_SUPERCLASS = "assert_n_superclass_avg";
+  public static final String AVG_ASSERT_N_SUPERCLASS_INCL = "assert_n_superclass_avg_incl";
+  public static final String AVG_INSTANCE_PER_CLASS = "instance_per_class_avg";
+  public static final String AVG_INSTANCE_PER_CLASS_INCL = "instance_per_class_avg_incl";
+  public static final String AXIOM_COUNT = "axiom_count";
+  public static final String AXIOM_COUNT_INCL = "axiom_count_incl";
+  public static final String AXIOMTYPE_COUNT = "axiom_type_count";
+  public static final String AXIOMTYPE_COUNT_INCL = "axiom_type_count_incl";
+  public static final String CLASS_COUNT = "class_count";
+  public static final String CLASS_COUNT_INCL = "class_count_incl";
+  public static final String CLASS_SGL_SUBCLASS_COUNT = "class_sgl_subcl_count";
+  public static final String CLASS_SGL_SUBCLASS_COUNT_INCL = "class_sgl_subcl_count_incl";
+  public static final String CONSTRUCTS = "constructs";
+  public static final String CONSTRUCTS_INCL = "constructs_incl";
+  public static final String DATAPROPERTY_COUNT = "dataproperty_count";
+  public static final String DATAPROPERTY_COUNT_INCL = "dataproperty_count_incl";
+  public static final String DATATYPE_COUNT = "datatypes_count";
+  public static final String DATATYPE_COUNT_INCL = "datatypes_count_incl";
+  public static final String EXPRESSIVITY = "expressivity";
+  public static final String EXPRESSIVITY_INCL = "expressivity_incl";
+  public static final String GCI_COUNT = "gci_count";
+  public static final String GCI_COUNT_INCL = "gci_count_incl";
+  public static final String GCI_HIDDEN_COUNT = "gci_hidden_count";
+  public static final String GCI_HIDDEN_COUNT_INCL = "gci_hidden_count_incl";
+  public static final String INDIVIDUAL_COUNT = "individual_count";
+  public static final String INDIVIDUAL_COUNT_INCL = "individual_count_incl";
+  public static final String LOGICAL_AXIOM_COUNT = "logical_axiom_count";
+  public static final String LOGICAL_AXIOM_COUNT_INCL = "logical_axiom_count_incl";
+  public static final String AXIOM_COMPLEXRHS_COUNT = "ax_complexrhs_count";
+  public static final String AXIOM_COMPLEXRHS_COUNT_INCL = "ax_complexrhs_count_incl";
+  public static final String AVG_SIZE_COMPLEXRHS = "complexrhs_expression_count_avg";
+  public static final String AVG_SIZE_COMPLEXRHS_INCL = "complexrhs_expression_count_avg_incl";
+  public static final String MAX_NUM_NAMED_SUPERCLASS = "named_supercl_count_max";
+  public static final String MAX_NUM_NAMED_SUPERCLASS_INCL = "named_supercl_count_max_incl";
+  public static final String MULTI_INHERITANCE_COUNT = "multiple_inheritance_count";
+  public static final String MULTI_INHERITANCE_COUNT_INCL = "multiple_inheritance_count_incl";
+  public static final String ONTOLOGY_ID = "ontology_id";
+  public static final String OBJPROPERTY_COUNT = "obj_property_count";
+  public static final String OBJPROPERTY_COUNT_INCL = "obj_property_count_incl";
+  public static final String RBOX_SIZE = "rbox_axiom_count";
+  public static final String RBOX_SIZE_INCL = "rbox_axiom_count_incl";
+  public static final String REF_CLASS_COUNT = "ref_class_count";
+  public static final String REF_CLASS_COUNT_INCL = "ref_class_count_incl";
+  public static final String REF_DATAPROP_COUNT = "ref_dataprop_count";
+  public static final String REF_DATAPROP_COUNT_INCL = "ref_dataprop_count_incl";
+  public static final String REF_INDIV_COUNT = "ref_indiv_count";
+  public static final String REF_INDIV_COUNT_INCL = "ref_indiv_count_incl";
+  public static final String REF_OBJPROP_COUNT = "ref_objprop_count";
+  public static final String REF_OBJPROP_COUNT_INCL = "ref_objprop_count_incl";
+  public static final String SYNTAX = "syntax";
+  public static final String UNDECLARED_ENTITY_COUNT = "undecl_entity_count";
+  public static final String UNDECLARED_ENTITY_COUNT_INCL = "undecl_entity_count_incl";
+  public static final String VALID_IMPORTS = "valid_imports";
+  public static final String BOOL_PROFILE_OWL2 = "owl2";
+  public static final String BOOL_PROFILE_OWL2_DL = "owl2_dl";
+  public static final String BOOL_PROFILE_OWL2_EL = "owl2_el";
+  public static final String BOOL_PROFILE_OWL2_QL = "owl2_ql";
+  public static final String BOOL_PROFILE_OWL2_RL = "owl2_rl";
+  public static final String VIOLATION_PROFILE_OWL2_DL = "owl2dl_profile_violation";
+  public static final String BOOL_PROFILE_RDFS = "rdfs";
+
+  public static final String CONSISTENT = "consistent";
+  public static final String UNSATISFIABLECLASSES_COUNT = "unsatisfiable_class_count";
+  public static final String UNSATISFIABLECLASSES = "unsatisfiable_classes";
+  public static final String INFERRED_SUBSUMPTIONS_COUNT = "inferred_subsumption_count";
+  public static final String ERROR_MESSAGE_REASONING = "reasoner_error_message";
+  public static final String INFERRED_ONLY_ENTAILMENT_COUNT = "inferred_only_entailment_count";
+  public static final String DATATYPES = "datatypes_builtin";
+  public static final String DATATYPES_INCL = "datatypes_builtin_incl";
+  public static final String MAX_AXIOMLENGTH = "axiom_length_max";
+  public static final String MAX_AXIOMLENGTH_INCL = "axiom_length_max_incl";
+  public static final String DATATYPES_NOT_BUILT_IN = "datatypes_not_built_in";
+  public static final String DATATYPES_NOT_BUILT_IN_INCL = "datatypes_not_built_in_incl";
+  public static final String DATATYPE_AXIOMCOUNT = "datatypes_axiom_count";
+  public static final String DATATYPE_AXIOMCOUNT_INCL = "datatypes_axiom_count_incl";
+  public static final String COHERENT = "coherent";
+  public static final String REASONERNAME = "reasoner";
+  public static final String REASONERNAME_CLASS = "reasoner_class";
+  public static final String REASONER_JAR = "reasoner_jar";
+  public static final String REASONERVERSION = "reasoner_version";
+  public static final String REASONER_TIMEOUT = "reasoner_timeout";
+  public static final String DELEGATE_REASONER_CT = "delreasoner_ct";
+  public static final String EL_DELEGATE_REASONER_CT = "el_delreasoner_ct";
+  public static final String OWL_DELEGATE_REASONER_CT = "owl_delreasoner_ct";
+  public static final String FILENAME = "filename";
+  public static final String FILEPATH = "filepath";
+  public static final String FILESIZE = "filesize";
+  public static final String JAVA_EXCEPTION = "exception";
+  public static final String JAVA_EXCEPTION_CAUSE = "exception_cause";
+  public static final String EXPERIMENT_ID = "experiment_id";
+  public static final String EXPERIMENT_VERSION = "experiment_version";
+  public static final String EXPERIMENT_DATE = "experiment_date";
+  public static final String JAVA_EXCEPTION_MESSAGE = "exception_message";
+  public static final String GRAPH_TRAVERSE_MODE = "graph_traverse";
+  public static final String LOGICAL_AXIOM_COUNT_REASONER = "logical_axiom_ct_reasoner";
+  public static final String AD_CLTIME_PER_ATOM = "cltime_atoms";
+  public static final String COMP_OVERHEAD = "comp_overhead";
+  public static final String INFERRED_TAXONOMY_AXIOM_COUNT = "inf_ax_ct";
+  public static final String INCREMENTAL_REASONER_NR_CLASSIFY_CALLS = "incr_reas_clfy_ct";
+  public static final String SUBSUMPTIONS_ATOMIC_POSSIBLE = "subs_atomic_possible";
+  public static final String SUBSUMPTIONS_ATOMIC_AVOIDED = "subs_atomic_avoid_theory";
+  public static final String SUBSUMPTIONS_ATOMIC_AVOIDED_REASONER = "subs_atomic_avoid_reasoner";
+  public static final String SUBSUMPTIONS_ATOMIC_REDUNDANT = "subs_atomic_redundant";
+  public static final String SUBSUMPTIONS_ATOMIC_REASONER = "subs_atomic_reasoner";
+  public static final String SIGNATURE_NO_URIS = "signature_w_o_iris";
+  public static final String RULE_CT = "rule_count";
+  public static final String RULE_CT_INCL = "rule_count_incl";
+  public static final String INF_CLASSHIERARCHY_DEPTH = "inf_hier_depth";
+  public static final String AXIOM_TYPES = "axiom_types";
+  public static final String AXIOM_TYPES_INCL = "axiom_types_incl";
+  public static final String NS_USE_AXIOMS = "namespace_axiom_count";
+  public static final String NS_USE_AXIOMS_INCL = "namespace_axiom_count_incl";
+  public static final String NS_USE_SIGNATURE = "namespace_entity_count";
+  public static final String NS_USE_SIGNATURE_INCL = "namespace_entity_count_incl";
+  public static final String TAUTOLOGYCOUNT = "tautology_count";
+  public static final String TAUTOLOGYCOUNT_INCL = "tautology_count_incl";
+  public static final String CYCLE = "certain_cycle";
+  public static final String CYCLE_INCL = "certain_cycle_incl";
+  public static final String EXPORT_EXCEPTION = "export_exception";
+  public static final String FIXED_VIOLATIONS = "fixed_profile_violations";
+  public static final String STRIPPED_AXIOMTYPES = "stripped_axiomtypes";
+
+  public static final String STARTPROCESS_TS = "startprocess_ts";
+  public static final String OWLAPILOAD_TS = "owlapiload_ts";
+  public static final String CREATEREASONER_TS = "createreasoner_ts";
+  public static final String CLASSIFICATION_TS = "classification_ts";
+  public static final String DISPOSE_TS = "dispose_ts";
+  public static final String FINISH_TS = "finishprocess_ts";
+
+  public static final String PREPROCESSING_TS = "preprocessing_ts";
+  public static final String CONSISTENCYCHECK_TS = "consistencycheck_ts";
+  public static final String PREREASONINGOPTIMISATION_TS = "prereasoningoptimisation_ts";
+  public static final String TRAVERSAL_TS = "traversal_ts";
+  public static final String POSTPROCESSING_TS = "postprocessing_ts";
+  public static final String CLASSIFICATION_CORRECT = "classification_correct";
+  public static final String REASONING_PREPROCESSING_TIME = "reason_pp_dur";
+  public static final String REASONING_MODULARREASONING_TIME = "reason_mmcl_dur";
+  public static final String REASONING_CONSISTENCY_TIME = "reason_cc_dur";
+  public static final String REASONING_PREREASONINGOPTIMISATION_TIME = "reason_pro_dur";
+  public static final String REASONING_TRAVERSALPHASE_TIME = "reason_trav_dur";
+  public static final String REASONING_POSTROCESSING_TIME = "reason_pop_dur";
+  public static final String REASONING_DECOMPOSITION_TIME = "reason_dec_dur";
+  public static final String REASONER_ID = "reasonerid";
+  public static final String REASONER_DISPOSE_TIME = "reasoner_disposetime";
+  public static final String RUNID = "experiment_runid";
+  public static final String ONTOLOGY_ANNOTATIONS_COUNT = "ontology_anno_count";
+  public static final String DATATYPE_BUILTIN_COUNT_INCL = "dt_builtin_count_incl";
+  public static final String DATATYPE_BUILTIN_COUNT = "dt_builtin_count";
+  public static final String DATATYPE_NOTBUILTIN_COUNT_INCL = "dt_notbuiltin_count_incl";
+  public static final String DATATYPE_NOTBUILTIN_COUNT = "dt_notbuiltin_count";
+  public static final String TBOX_CONTAINS_NOMINALS = "tbox_nominals";
+  public static final String TBOX_CONTAINS_NOMINALS_INCL = "tbox_nominals_incl";
+  public static final String ABOX_CONTAINS_NOMINALS = "abox_nominals";
+  public static final String ABOX_CONTAINS_NOMINALS_INCL = "abox_nominals_incl";
+  public static final String MOST_FRQUENTLY_USED_CONCEPT = "most_freq_concept";
+  public static final String MOST_FRQUENTLY_USED_CONCEPT_INCL = "most_freq_concept_incl";
+  public static final String VALID_IMPORTS_INCL = "valid_imports_incl";
+
+  public static Map<String, String> getLabels() {
+    Map<String, String> data = new HashMap<>();
+    for (Field field : MetricsLabels.class.getDeclaredFields()) {
+
+      try {
+        String name = field.getName();
+        String value = field.get(MetricsLabels.class).toString();
+        data.put(name, value);
+
+      } catch (IllegalArgumentException e) {
+        e.printStackTrace();
+      } catch (IllegalAccessException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
+    return data;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsResult.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsResult.java
new file mode 100644
index 00000000..e1b1c06c
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsResult.java
@@ -0,0 +1,52 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.*;
+
+public class MetricsResult {
+
+  Map<String, Object> data = new HashMap<>();
+  Map<String, List<Object>> data_listvalues = new HashMap<>();
+  Map<String, Map<String, Integer>> data_mapvalues = new HashMap<>();
+
+  void put(String key, Object value) {
+    data.put(key, value);
+  }
+
+  void addData(Map<String, String> data) {
+    this.data.putAll(data);
+  }
+
+  public Map<String, Object> getData() {
+    return data;
+  }
+
+  public boolean isEmpty() {
+    return data.isEmpty();
+  }
+
+  public void putSet(String key, Set<? extends Object> set) {
+    data_listvalues.put(key, new ArrayList<>(set));
+  }
+
+  public void putMap(String key, Map<String, Integer> data) {
+    data_mapvalues.put(key, data);
+  }
+
+  public Map<String, List<Object>> getListData() {
+    return data_listvalues;
+  }
+
+  public Map<String, Map<String, Integer>> getMapData() {
+    return data_mapvalues;
+  }
+
+  public Object getSimpleMetricValue(String key) {
+    return data.get(key);
+  }
+
+  public void importMetrics(MetricsResult data) {
+    this.data.putAll(data.getData());
+    this.data_listvalues.putAll(data.getListData());
+    this.data_mapvalues.putAll(data.getMapData());
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java
new file mode 100644
index 00000000..e9a2b510
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/MetricsUtils.java
@@ -0,0 +1,217 @@
+package org.obolibrary.robot.metrics;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import org.apache.commons.io.FileUtils;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.model.parameters.Imports;
+
+public class MetricsUtils {
+
+  private static Set<OWLAxiom> stripAnnotations(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> newAxioms = new HashSet<>();
+    for (OWLAxiom ax : axioms) {
+      if (ax instanceof OWLAnnotationAssertionAxiom) {
+        continue;
+      }
+      newAxioms.add(ax.getAxiomWithoutAnnotations());
+    }
+    return newAxioms;
+  }
+
+  private static boolean isIntegerParsable(String id) {
+    try {
+      Integer.parseInt(id);
+      return true;
+    } catch (NumberFormatException e) {
+      return false;
+    }
+  }
+
+  private static boolean isLongParsable(String id) {
+    try {
+      Long.parseLong(id);
+      return true;
+    } catch (NumberFormatException e) {
+      return false;
+    }
+  }
+
+  private static boolean isDoubleParsable(String id) {
+    try {
+      Double d = Double.parseDouble(id);
+      return !d.isNaN();
+    } catch (NumberFormatException e) {
+      return false;
+    }
+  }
+
+  private static boolean isNumeric(String id) {
+    return (isIntegerParsable(id) || isLongParsable(id) || isDoubleParsable(id));
+  }
+
+  private static Set<OWLAxiom> getLogicalAxioms(
+      OWLOntology o, Imports includeImportsClosure, boolean skiprules, boolean leavedeclarations) {
+    Set<AxiomType<?>> types = new HashSet<>();
+    types.addAll(AxiomType.TBoxAxiomTypes);
+    types.addAll(AxiomType.RBoxAxiomTypes);
+    types.addAll(AxiomType.ABoxAxiomTypes);
+    if (leavedeclarations) {
+      types.add(AxiomType.DECLARATION);
+    }
+    return getLogicalAxioms(o, includeImportsClosure, skiprules, types);
+  }
+
+  private static Set<OWLAxiom> getLogicalAxioms(
+      OWLOntology o, Imports includeImportsClosure, boolean skiprules) {
+    return getLogicalAxioms(o, includeImportsClosure, skiprules, false);
+  }
+
+  public static Set<OWLAxiom> getLogicalAxioms(
+      OWLOntology o, Imports includeImportsClosure, boolean skiprules, Set<AxiomType<?>> types) {
+    return getLogicalAxioms(o, includeImportsClosure, skiprules, false, types);
+  }
+
+  private static Set<OWLAxiom> getLogicalAxioms(
+      OWLOntology o,
+      Imports includeImportsClosure,
+      boolean skiprules,
+      boolean stripaxiomanno,
+      Set<AxiomType<?>> types) {
+    Set<OWLAxiom> axioms = new HashSet<>();
+    for (OWLAxiom ax : o.getLogicalAxioms()) {
+      if (types.contains(ax.getAxiomType())) {
+        axioms.add(ax);
+      }
+    }
+    if (includeImportsClosure == Imports.INCLUDED) {
+      for (OWLOntology imp : o.getImports()) {
+        for (OWLAxiom ax : imp.getLogicalAxioms()) {
+          if (types.contains(ax.getAxiomType())) {
+            axioms.add(ax);
+          }
+        }
+      }
+    }
+    if (skiprules) {
+      stripRules(axioms);
+    }
+    if (stripaxiomanno) {
+      axioms = stripAnnotations(axioms);
+    }
+    return axioms;
+  }
+
+  private static Set<OWLAxiom> getLogicalAxioms(
+      OWLOntology o, boolean includeImportsClosure, boolean skiprules, boolean stripaxiomanno) {
+    Set<OWLAxiom> axioms = new HashSet<>(o.getLogicalAxioms());
+    if (includeImportsClosure) {
+      for (OWLOntology imp : o.getImports()) {
+        axioms.addAll(imp.getLogicalAxioms());
+      }
+    }
+    if (skiprules) {
+      stripRules(axioms);
+    }
+    if (stripaxiomanno) {
+      axioms = stripAnnotations(axioms);
+    }
+    return axioms;
+  }
+
+  private static void stripRules(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> tmp = new HashSet<>(axioms);
+    axioms.clear();
+    for (OWLAxiom ax : tmp) {
+      if (!ax.getAxiomType().toString().equals("Rule")) {
+        axioms.add(ax);
+      }
+    }
+  }
+
+  public static File getResourcePath(Object o) {
+    return new File(o.getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
+  }
+
+  private static Map<? extends String, ? extends String> getFileMetrics(File file) {
+    Map<String, String> data = new HashMap<>();
+    data.put(MetricsLabels.FILEPATH, file.getParent());
+    data.put(MetricsLabels.FILESIZE, FileUtils.sizeOf(file) + "");
+    return data;
+  }
+
+  private static boolean isNaturalNumber(String value) {
+    try {
+      Double d = Double.valueOf(value);
+      long l = d.longValue();
+      return (l >= 0 && (double) l == d);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+
+  private static long getLong(String sl) {
+    Double d = Double.valueOf(sl);
+    long s = d.longValue();
+    if (d != (double) s) {
+      throw new NumberFormatException(sl + " not valid long!");
+    }
+    return s;
+  }
+
+  private static double round(double value, int places) {
+    if (places < 0) throw new IllegalArgumentException();
+    BigDecimal bd = new BigDecimal(value);
+    bd = bd.setScale(places, RoundingMode.HALF_UP);
+    return bd.doubleValue();
+  }
+
+  private static int getNaturalNumberValue(String metrics_rec) {
+    try {
+      int i = Integer.parseInt(metrics_rec);
+      return i;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static Set<AxiomType<?>> getTBoxAxiomTypes(boolean including_rbox) {
+    Set<AxiomType<?>> axty = new HashSet<>();
+    if (including_rbox) {
+      axty.addAll(AxiomType.RBoxAxiomTypes);
+    }
+    axty.addAll(AxiomType.TBoxAxiomTypes);
+    return axty;
+  }
+
+  private static Set<AxiomType<?>> getABoxAxiomTypes() {
+    Set<AxiomType<?>> axty = new HashSet<>();
+    axty.addAll(AxiomType.ABoxAxiomTypes);
+    return axty;
+  }
+
+  public static Set<OWLAxiom> getTBoxAxioms(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> tbox = new HashSet<>();
+    Set<AxiomType<?>> types = getTBoxAxiomTypes(true);
+    for (OWLAxiom ax : axioms) {
+      if (types.contains(ax.getAxiomType())) {
+        tbox.add(ax);
+      }
+    }
+    return tbox;
+  }
+
+  public static Set<OWLAxiom> getABoxAxioms(Set<OWLAxiom> axioms) {
+    Set<OWLAxiom> abox = new HashSet<>();
+    Set<AxiomType<?>> types = getABoxAxiomTypes();
+    for (OWLAxiom ax : axioms) {
+      if (types.contains(ax.getAxiomType())) {
+        abox.add(ax);
+      }
+    }
+    return abox;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java
new file mode 100644
index 00000000..0ed84330
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyCycleDetector.java
@@ -0,0 +1,105 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.*;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.model.parameters.Imports;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OntologyCycleDetector {
+
+  /*
+   * Adapted from Dmitry Tsarkov
+   */
+
+  /*
+   * Sound, but maybe incomplete (may miss some cycles).
+   */
+
+  /** Logger. */
+  private static final Logger logger = LoggerFactory.getLogger(OntologyCycleDetector.class);
+
+  public static boolean containsCycle(OWLOntology ontology, Imports includeImports) {
+    final Set<OWLClass> Classes = ontology.getClassesInSignature(includeImports);
+    for (OWLClass Class : Classes) {
+      Queue<OWLClass> Queue = new LinkedList<>();
+      Set<OWLClass> Visited = new HashSet<>();
+      Queue.add(Class);
+      // System.out.println("DeCycler.processOntology() Class " + Class);
+      while (Queue.peek() != null) {
+        OWLClass current = Queue.poll();
+        if (current == Class && !Queue.isEmpty()) {
+          logger.warn(
+              "Cycle detector encountered a potential problem - results may be unreliable.");
+        }
+        // no need to check the same entry more than once
+        if (Visited.contains(current)) continue;
+        Visited.add(current);
+
+        Set<OWLAxiom> Defs = getReferencingAxioms(ontology, current, includeImports);
+        Set<OWLClassExpression> ces = getDefinition(current, Defs);
+
+        // no definition for a class -- fine!
+        OWLClassExpression C =
+            ontology.getOWLOntologyManager().getOWLDataFactory().getOWLObjectIntersectionOf(ces);
+
+        // check the signature of that axiom
+        Set<OWLClass> AxSig = C.getClassesInSignature();
+
+        if (AxSig.contains(Class)) {
+          return true;
+        } else {
+          AxSig.remove(current);
+          Queue.addAll(AxSig);
+        }
+      }
+    }
+    return false;
+  }
+
+  public static Set<OWLAxiom> getReferencingAxioms(
+      OWLOntology ontology, OWLClass current, Imports includeImports) {
+    Set<OWLAxiom> refs = new HashSet<>();
+    if (includeImports == Imports.INCLUDED) {
+      for (OWLOntology o : ontology.getImportsClosure()) {
+        refs.addAll(o.getReferencingAxioms(current));
+      }
+    } else {
+      refs.addAll(ontology.getReferencingAxioms(current));
+    }
+    return refs;
+  }
+
+  private static Set<OWLClassExpression> getDescription(OWLClass current, OWLAxiom ax) {
+    Set<OWLClassExpression> ces = new HashSet<>();
+    if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+      OWLSubClassOfAxiom axiom = (OWLSubClassOfAxiom) ax;
+      return Collections.singleton(axiom.getSuperClass());
+    } else if (ax.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
+      OWLEquivalentClassesAxiom axiom = (OWLEquivalentClassesAxiom) ax;
+      ces.addAll(axiom.getClassExpressions());
+      ces.remove(current);
+    }
+    return ces;
+  }
+
+  private static Set<OWLClassExpression> getDefinition(OWLClass current, Set<OWLAxiom> defs) {
+    Set<OWLClassExpression> sce = new HashSet<>();
+    for (OWLAxiom ax : defs) {
+      if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+        OWLSubClassOfAxiom axiom = (OWLSubClassOfAxiom) ax;
+        if (current.equals(axiom.getSubClass())) {
+          sce.addAll(getDescription(current, ax));
+        }
+      } else if (ax.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
+        OWLEquivalentClassesAxiom axiom = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression cl : axiom.getClassExpressions()) {
+          if (!cl.isAnonymous() && current.equals(cl.asOWLClass())) {
+            sce.addAll(getDescription(current, ax));
+          }
+        }
+      }
+    }
+    return sce;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java
new file mode 100644
index 00000000..7fd28963
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/OntologyMetrics.java
@@ -0,0 +1,1023 @@
+package org.obolibrary.robot.metrics;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.obolibrary.robot.IOHelper;
+import org.obolibrary.robot.providers.CURIEShortFormProvider;
+import org.semanticweb.owlapi.metrics.AbstractOWLMetric;
+import org.semanticweb.owlapi.metrics.AverageAssertedNamedSuperclassCount;
+import org.semanticweb.owlapi.metrics.DLExpressivity;
+import org.semanticweb.owlapi.metrics.GCICount;
+import org.semanticweb.owlapi.metrics.HiddenGCICount;
+import org.semanticweb.owlapi.metrics.MaximumNumberOfNamedSuperclasses;
+import org.semanticweb.owlapi.metrics.NumberOfClassesWithMultipleInheritance;
+import org.semanticweb.owlapi.metrics.ReferencedClassCount;
+import org.semanticweb.owlapi.metrics.ReferencedDataPropertyCount;
+import org.semanticweb.owlapi.metrics.ReferencedIndividualCount;
+import org.semanticweb.owlapi.metrics.ReferencedObjectPropertyCount;
+import org.semanticweb.owlapi.model.AxiomType;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLClassAssertionAxiom;
+import org.semanticweb.owlapi.model.OWLClassExpression;
+import org.semanticweb.owlapi.model.OWLDataProperty;
+import org.semanticweb.owlapi.model.OWLDatatype;
+import org.semanticweb.owlapi.model.OWLEntity;
+import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
+import org.semanticweb.owlapi.model.OWLImportsDeclaration;
+import org.semanticweb.owlapi.model.OWLObjectHasValue;
+import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
+import org.semanticweb.owlapi.model.OWLObjectProperty;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyID;
+import org.semanticweb.owlapi.model.OWLOntologyLoaderConfiguration;
+import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
+import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom;
+import org.semanticweb.owlapi.model.parameters.Imports;
+import org.semanticweb.owlapi.profiles.OWL2DLProfile;
+import org.semanticweb.owlapi.profiles.OWL2ELProfile;
+import org.semanticweb.owlapi.profiles.OWL2Profile;
+import org.semanticweb.owlapi.profiles.OWL2QLProfile;
+import org.semanticweb.owlapi.profiles.OWL2RLProfile;
+import org.semanticweb.owlapi.profiles.OWLProfile;
+import org.semanticweb.owlapi.profiles.OWLProfileReport;
+import org.semanticweb.owlapi.profiles.OWLProfileViolation;
+import org.semanticweb.owlapi.util.DLExpressivityChecker;
+import uk.ac.manchester.cs.owl.owlapi.OWLObjectOneOfImpl;
+
+public class OntologyMetrics {
+
+  private final OWLOntology item;
+  private final OWLOntologyManager manager;
+  private final List<String> owlprofileviolations = new ArrayList<>();
+  private final List<OWLProfileViolation> dlprofileviolation = new ArrayList<>();
+  protected OWLOntologyLoaderConfiguration config = new OWLOntologyLoaderConfiguration();
+  private CURIEShortFormProvider curieProvider;
+
+  String OBONS = "http://purl.obolibrary.org/obo/";
+
+  public OntologyMetrics(OWLOntology item) {
+    this.item = item;
+    this.manager = item.getOWLOntologyManager();
+
+    try {
+      this.curieProvider = new CURIEShortFormProvider(new IOHelper().getPrefixes());
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  // ENTITIES
+
+  private static int getLengthOfAxiom(OWLAxiom axiom) {
+    int length = 0;
+
+    String axiomstring = axiom.getAxiomWithoutAnnotations().toString();
+    for (OWLEntity e : axiom.getSignature()) {
+      length += getNumberOfOccurences(axiomstring, e.toString());
+    }
+    return length;
+  }
+
+  private static int getNumberOfOccurences(String haystack, String needle) {
+    int length = 0;
+    Pattern p = Pattern.compile(needle);
+    Matcher m = p.matcher(haystack);
+    while (m.find()) {
+      length++;
+    }
+    return length;
+  }
+
+  public int getSignatureSize(Imports includeImportsClosure) {
+    return getOntology().getSignature(includeImportsClosure).size();
+  }
+
+  public int getUndeclaredEntitiesCount(Imports includeImportsClosure) {
+    int undeclared = 0;
+    for (OWLEntity entity : getOntology().getSignature(includeImportsClosure)) {
+      if (!getOntology().isDeclared(entity)) {
+        undeclared++;
+      }
+    }
+    return undeclared;
+  }
+
+  private boolean isTBoxContainsNominals(Imports b) {
+    for (OWLAxiom ax : MetricsUtils.getTBoxAxioms(getLogicalAxioms(b, true))) {
+      for (OWLClassExpression cl : ax.getNestedClassExpressions()) {
+        if (cl instanceof OWLObjectOneOfImpl) {
+          // System.out.println(ax);
+          return true;
+        } else if (cl instanceof OWLObjectHasValue) {
+          // System.out.println(ax);
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean isABoxContainsNominals(Imports b) {
+    for (OWLAxiom ax : MetricsUtils.getABoxAxioms(getLogicalAxioms(b, true))) {
+      for (OWLClassExpression cl : ax.getNestedClassExpressions()) {
+        if (cl instanceof OWLObjectOneOfImpl) {
+          return true;
+        } else if (cl instanceof OWLObjectHasValue) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public int getClassCount(Imports includeImportsClosure) {
+    return getOntology().getClassesInSignature(includeImportsClosure).size();
+  }
+
+  public int getObjectPropertyCount(Imports includeImportsClosure) {
+    return getOntology().getObjectPropertiesInSignature(includeImportsClosure).size();
+  }
+
+  public int getDataPropertyCount(Imports included) {
+    return getOntology().getDataPropertiesInSignature(included).size();
+  }
+
+  public int getDatatypesCount(Imports included) {
+    return getOntology().getDatatypesInSignature(included).size();
+  }
+
+  public Map<String, Integer> getDatatypesWithAxiomOccurrenceCount(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+    for (OWLAxiom axiom : axioms) {
+      Set<OWLDatatype> dtypes = axiom.getDatatypesInSignature();
+      for (OWLDatatype datatype : dtypes) {
+        String dtname = datatype.toString();
+        if (datatype.isBuiltIn()) {
+          dtname = datatype.getBuiltInDatatype().toString();
+        }
+        if (map.containsKey(dtname)) {
+          Integer itemp = map.get(dtname);
+          itemp++;
+          map.put(dtname, itemp);
+        } else {
+          map.put(dtname, 1);
+        }
+      }
+    }
+    return map;
+  }
+
+  private void countKeyUpInMap(String key, Map<String, Integer> map) {
+    if (!map.containsKey(key)) {
+      map.put(key, 0);
+    }
+    map.put(key, map.get(key) + 1);
+  }
+
+  public Map<String, Integer> getEntityUsageMap(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    for (OWLEntity e : getOntology().getSignature(includeImportsClosure)) {
+      String iri_pre = extractIRIPrefixFromEntity(e);
+      countKeyUpInMap(iri_pre, map);
+    }
+    return map;
+  }
+
+  private String getShortForm(OWLEntity e) {
+    Optional<CURIEShortFormProvider> sfpo = getCurieProvider();
+    String shortform = e.getIRI().getShortForm();
+    if (sfpo.isPresent()) {
+      CURIEShortFormProvider sfp = sfpo.get();
+      shortform = sfp.getShortForm(e);
+    }
+    return shortform;
+  }
+
+  private String extractIRIPrefixFromEntity(OWLEntity e) {
+    String shortform = getShortForm(e);
+    if (shortform.contains(":")) {
+      return shortform.split(":")[0];
+    }
+
+    String iri = e.getIRI().toString();
+    String iri_pre;
+
+    if (iri.equals(OBONS + shortform) && shortform.contains("_")) {
+      iri_pre = OBONS + shortform.split("_")[0] + "_";
+    } else {
+      iri_pre = iri.replace(shortform, "");
+    }
+    return iri_pre;
+  }
+
+  public Map<String, Integer> getAxiomUsageMap(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+    for (OWLAxiom axiom : axioms) {
+      Set<OWLEntity> entities = axiom.getSignature();
+      for (OWLEntity e : entities) {
+        String iri_pre = extractIRIPrefixFromEntity(e);
+        countKeyUpInMap(iri_pre, map);
+      }
+    }
+    return map;
+  }
+
+  public Set<String> getBuiltInDatatypes(Imports included) {
+    Set<String> set = new HashSet<>();
+    Set<OWLDatatype> datatypes = getOntology().getDatatypesInSignature(included);
+    for (OWLDatatype datatype : datatypes) {
+      if (datatype.isBuiltIn()) {
+        set.add(datatype.getBuiltInDatatype().toString());
+      }
+    }
+    return set;
+  }
+
+  public Set<String> getNotBuiltInDatatypes(Imports included) {
+    Set<String> set = new HashSet<>();
+    Set<OWLDatatype> properties = getOntology().getDatatypesInSignature(included);
+    for (OWLDatatype datatype : properties) {
+      if (!datatype.isBuiltIn()) {
+        set.add(datatype.toString());
+      }
+    }
+    return set;
+  }
+
+  public int getAnnotationPropertyCount(Imports imports) {
+    return getOntology().getAnnotationPropertiesInSignature(imports).size();
+  }
+
+  // AXIOMS
+
+  public int getAnnotationsCount() {
+    return getOntology().getAnnotations().size();
+  }
+
+  public int getIndividualsCount(Imports included) {
+    return getOntology().getIndividualsInSignature(included).size();
+  }
+
+  public int getNumberOfRules(Imports includeImportsClosure) {
+    int ct = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(includeImportsClosure, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax.isLogicalAxiom()) {
+        if (ax.getAxiomType().toString().equals("Rule")) {
+          ct++;
+        }
+      }
+    }
+    return ct;
+  }
+
+  public int getAxiomsWithComplexRHS(Imports included) {
+    // complex: RHS does not only contain nested conjuctions / atomic
+    // classnames
+    int ct = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(included, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax instanceof OWLSubClassOfAxiom) {
+        OWLSubClassOfAxiom scax = (OWLSubClassOfAxiom) ax;
+        OWLClassExpression RHS = scax.getSuperClass();
+        if (isComplex(RHS)) {
+          ct++;
+        }
+      } else if (ax instanceof OWLEquivalentClassesAxiom) {
+        OWLEquivalentClassesAxiom scax = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression ex : scax.getClassExpressions()) {
+          if (isComplex(ex)) {
+            ct++;
+            break;
+          }
+        }
+      }
+    }
+    return ct;
+  }
+
+  public double getAVGSizeOfRHS(Imports included) {
+    // complex: RHS does not only contain nested conjuctions / atomic
+    // classnames
+    double ct = 0;
+    double ct_complex = 0;
+    Set<OWLAxiom> logicalaxiom = getLogicalAxioms(included, false);
+    for (OWLAxiom ax : logicalaxiom) {
+      if (ax instanceof OWLSubClassOfAxiom) {
+        OWLSubClassOfAxiom scax = (OWLSubClassOfAxiom) ax;
+        OWLClassExpression RHS = scax.getSuperClass();
+        if (isComplex(RHS)) {
+          // System.out.println(RHS);
+          ct_complex++;
+          for (OWLClassExpression ex : RHS.getNestedClassExpressions()) {
+            if (isComplex(ex)) {
+              ct++;
+            }
+          }
+        }
+      } else if (ax instanceof OWLEquivalentClassesAxiom) {
+        OWLEquivalentClassesAxiom scax = (OWLEquivalentClassesAxiom) ax;
+        for (OWLClassExpression oper : scax.getClassExpressions()) {
+          if (isComplex(oper)) {
+            ct_complex++;
+            for (OWLClassExpression ex : oper.getNestedClassExpressions()) {
+              if (isComplex(ex)) {
+                ct++;
+              }
+            }
+          }
+        }
+      }
+    }
+    return (ct / ct_complex);
+  }
+
+  private boolean isComplex(OWLClassExpression ex) {
+    for (OWLClassExpression exnested : ex.getNestedClassExpressions()) {
+      if (!exnested.isClassExpressionLiteral()) {
+        if (!(exnested instanceof OWLObjectIntersectionOf)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private Set<OWLAxiom> getLogicalAxioms(Imports includeImportsClosure, boolean skiprules) {
+    Set<AxiomType<?>> at = new HashSet<>();
+    at.addAll(AxiomType.TBoxAxiomTypes);
+    at.addAll(AxiomType.RBoxAxiomTypes);
+    at.addAll(AxiomType.ABoxAxiomTypes);
+    return MetricsUtils.getLogicalAxioms(getOntology(), includeImportsClosure, skiprules, at);
+  }
+
+  public Set<OWLAxiom> getAxioms(Imports included) {
+    return new HashSet<>(getOntology().getAxioms(included));
+  }
+
+  public int getAxiomCount(Imports includeImportsClosure) {
+    return getAxioms(includeImportsClosure).size();
+  }
+
+  public int getLogicalAxiomCount(Imports includeImportsClosure) {
+    return getLogicalAxioms(includeImportsClosure, true).size();
+  }
+
+  public int getTBoxSize(Imports useImportsClosure) {
+    return getOntology().getTBoxAxioms(useImportsClosure).size();
+  }
+
+  public int getTBoxRboxSize(Imports useImportsClosure) {
+    int i = 0;
+    Set<AxiomType<?>> axty = MetricsUtils.getTBoxAxiomTypes(true);
+    for (AxiomType<?> at : axty) {
+      i += getOntology().getAxioms(at, useImportsClosure).size();
+    }
+    return i;
+  }
+
+  public int getABoxSize(Imports useImportsClosure) {
+    return getOntology().getABoxAxioms(useImportsClosure).size();
+  }
+
+  public int getRBoxSize(Imports useImportsClosure) {
+    return getOntology().getRBoxAxioms(useImportsClosure).size();
+  }
+
+  public Map<String, Integer> getAxiomTypeCounts(Imports includeImportsClosure) {
+    Map<String, Integer> map = new HashMap<>();
+    Set<OWLAxiom> axioms = getAxioms(includeImportsClosure);
+
+    for (OWLAxiom ax : axioms) {
+      String type = ax.getAxiomType().getName();
+      if (map.containsKey(type)) {
+        Integer i = map.get(type);
+        map.put(type, (i + 1));
+      } else {
+        map.put(type, 1);
+      }
+    }
+
+    return map;
+  }
+
+  public int getTautologyCount(Imports includeImports) {
+    int tautologies = 0;
+    for (OWLAxiom ax : getLogicalAxioms(includeImports, true)) {
+      if (TautologyChecker.isTautology(ax)) {
+        tautologies++;
+      }
+    }
+    return tautologies;
+  }
+
+  public Set<AxiomType<?>> getAxiomTypes(Imports includeImportsClosure) {
+    Set<AxiomType<?>> axtypes = new HashSet<>();
+    for (OWLAxiom ax : getAxioms(includeImportsClosure)) {
+      axtypes.add(ax.getAxiomType());
+    }
+    return axtypes;
+  }
+
+  public boolean isOWL2Profile() {
+    OWLProfile profile = new OWL2Profile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  public boolean isOWL2ELProfile() {
+    OWLProfile profile = new OWL2ELProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  public boolean isOWL2DLProfile() {
+    OWLProfile profile = new OWL2DLProfile();
+    OWLProfileReport report = profile.checkOntology(getOntology());
+    for (OWLProfileViolation vio : report.getViolations()) {
+      String s = vio.getClass().getSimpleName();
+      owlprofileviolations.add(s);
+      dlprofileviolation.add(vio);
+    }
+    return report.isInProfile();
+  }
+
+  public boolean isOWL2RLProfile() {
+    OWLProfile profile = new OWL2RLProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  public boolean isOWL2QLProfile() {
+    OWLProfile profile = new OWL2QLProfile();
+    return profile.checkOntology(getOntology()).isInProfile();
+  }
+
+  // REFERENCED CLASSES AND PROPERTIES
+
+  public boolean isRDFS() {
+    // TODO: verify
+    boolean isRDFS = true;
+
+    for (OWLAxiom ax : getAxioms(Imports.INCLUDED)) {
+      if (ax.isLogicalAxiom()) {
+        if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+          OWLSubClassOfAxiom subAx = (OWLSubClassOfAxiom) ax;
+          if (subAx.getSubClass().isAnonymous() || subAx.getSuperClass().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("SubClassAx: " + ax);
+            break;
+          }
+        } else if (ax.isOfType(AxiomType.SUB_OBJECT_PROPERTY)) {
+          OWLSubObjectPropertyOfAxiom subProp = (OWLSubObjectPropertyOfAxiom) ax;
+          if (subProp.getSubProperty().isAnonymous() || subProp.getSuperProperty().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("SupPropertyAx: " + ax);
+            break;
+          }
+        } else //noinspection StatementWithEmptyBody
+        if (ax.isOfType(AxiomType.OBJECT_PROPERTY_DOMAIN)
+            || ax.isOfType(AxiomType.OBJECT_PROPERTY_RANGE)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_ASSERTION)
+            || ax.isOfType(AxiomType.OBJECT_PROPERTY_ASSERTION)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_DOMAIN)
+            || ax.isOfType(AxiomType.DATA_PROPERTY_RANGE)) {
+          // do nothing
+          // System.out.println(ax.getAxiomType().getName()+" axiom: "
+          // + ax);
+        } else if (ax.isOfType(AxiomType.CLASS_ASSERTION)) {
+          OWLClassAssertionAxiom assAx = (OWLClassAssertionAxiom) ax;
+          if (assAx.getClassExpression().isAnonymous()) {
+            isRDFS = false;
+            // System.out.println("Class Assertion: " + ax);
+            break;
+          }
+        } else {
+          // System.out.println("Some other axiom: " + ax);
+          isRDFS = false;
+          break;
+        }
+      }
+    }
+
+    return isRDFS;
+  }
+
+  public Set<String> getValidImports(boolean direct) {
+    Set<String> validImports = new HashSet<>();
+    Set<OWLOntology> closure = new HashSet<>();
+
+    if (!direct) {
+      closure.addAll(getOntology().getImportsClosure());
+    }
+    closure.add(getOntology());
+
+    for (OWLOntology o : closure) {
+      for (OWLImportsDeclaration im : o.getImportsDeclarations()) {
+        String iri = im.getIRI().toString();
+        validImports.add(iri);
+      }
+    }
+    return validImports;
+  }
+
+  public int getReferencedClassCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedClassCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getReferencedDataPropertyCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedDataPropertyCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  // RANDOM METRICS
+
+  public int getReferencedIndividualCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedIndividualCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getReferencedObjectPropertyCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new ReferencedObjectPropertyCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getMultipleInheritanceCount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new NumberOfClassesWithMultipleInheritance(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getMaximumNumberOfNamedSuperclasses(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new MaximumNumberOfNamedSuperclasses(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getGCICount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new GCICount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public int getHiddenGCICount(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Integer> metric = new HiddenGCICount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public double getAverageAssertedNamedSuperclasses(boolean useImportsClosure) {
+    // TODO verify this
+    AbstractOWLMetric<Double> metric = new AverageAssertedNamedSuperclassCount(getOntology());
+    metric.setImportsClosureUsed(useImportsClosure);
+    metric.setOntology(getOntology());
+    return metric.getValue();
+  }
+
+  public double getAverageAssertedNamedSubclasses(Imports useImportsClosure) {
+    int count = 0;
+    for (OWLClass c : getOntology().getClassesInSignature(useImportsClosure)) {
+      count += getOntology().getSubClassAxiomsForSuperClass(c).size();
+    }
+    return (((double) count) / ((double) getOntology().getClassesInSignature().size()));
+  }
+
+  public int getClassesWithSingleSubclassCount(Imports useImportsClosure) {
+    int count = 0;
+    for (OWLClass c : getOntology().getClassesInSignature(useImportsClosure)) {
+      if (getOntology().getSubClassAxiomsForSuperClass(c).size() == 1) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  public double getAverageInstancesPerClass(Imports useImportsClosure) {
+    int count = 0;
+    for (OWLClass c : getOntology().getClassesInSignature(useImportsClosure)) {
+      count += getOntology().getClassAssertionAxioms(c).size();
+    }
+    return (((double) count)
+        / ((double) getOntology().getClassesInSignature(useImportsClosure).size()));
+  }
+
+  public String getSyntax() {
+    Object format = getManager().getOntologyFormat(getOntology());
+    if (format != null) {
+      return format.toString();
+    }
+    return "unknown";
+  }
+
+  public String getOntologyId() {
+    OWLOntologyID ontologyID = getOntology().getOntologyID();
+    if (ontologyID.isAnonymous()) {
+      return "anonymousId";
+    } else {
+      return ontologyID.getOntologyIRI().or(IRI.create("no.iri")).toString();
+    }
+  }
+
+  public String getOntologyIdScheme() {
+    OWLOntologyID ontologyID = getOntology().getOntologyID();
+    if (ontologyID.isAnonymous()) {
+      return "none";
+    } else {
+      return ontologyID.getOntologyIRI().or(IRI.create("")).getScheme();
+    }
+  }
+
+  public String getExpressivity(boolean included) {
+    DLExpressivity dl = new DLExpressivity(getOntology());
+    dl.setImportsClosureUsed(included);
+    dl.setOntology(getOntology());
+    return dl.getValue();
+  }
+
+  // this is highly unpleasant and I wish we had a NoSQL DB or even just a
+  // stupid XML file where we could throw it all in individually
+  public Set<String> getConstructs(boolean includeImportsClosure) {
+    Set<OWLOntology> onts = new HashSet<>();
+    if (includeImportsClosure) {
+      onts.addAll(getOntology().getImportsClosure());
+    } else {
+      onts.add(getOntology());
+    }
+    DLExpressivityChecker checker = new DLExpressivityChecker(onts);
+    Set<String> constructs = new HashSet<>();
+    for (DLExpressivityChecker.Construct c : checker.getConstructs()) {
+      constructs.add(c.name());
+    }
+    return constructs;
+  }
+
+  public boolean surelyContainsCycle(Imports includeImports) {
+    return OntologyCycleDetector.containsCycle(getOntology(), includeImports);
+  }
+
+  public OWLOntology getOntology() {
+    return this.item;
+  }
+
+  private Optional<CURIEShortFormProvider> getCurieProvider() {
+    if (this.curieProvider != null) {
+      return Optional.of(this.curieProvider);
+    }
+    return Optional.empty();
+  }
+
+  public OWLOntologyManager getManager() {
+    return this.manager;
+  }
+
+  public Map<String, Integer> getOwlprofileviolations() {
+    Map<String, Integer> map = new HashMap<>();
+    Set<String> uniqueviolations = new HashSet<>(owlprofileviolations);
+    for (String vio : uniqueviolations) {
+      map.put(vio, Collections.frequency(owlprofileviolations, vio));
+    }
+    return map;
+  }
+
+  private String getMostFrequentlyUsedClassInLogicalAxioms(Imports includeImportsClosure) {
+    Map<String, Integer> classCountMap = new HashMap<>();
+    for (OWLAxiom eachAxiom : getLogicalAxioms(includeImportsClosure, true)) {
+      if (!(eachAxiom instanceof OWLSubClassOfAxiom)) {
+        continue;
+      }
+      OWLSubClassOfAxiom sax = (OWLSubClassOfAxiom) eachAxiom;
+      String saxrhs = sax.getSuperClass().toString();
+      for (OWLClass eachClass : eachAxiom.getClassesInSignature()) {
+        int frequency = getNumberOfOccurences(saxrhs, eachClass.toString());
+        String class_sf = getShortForm(eachClass);
+        if (classCountMap.containsKey(eachClass.toString())) {
+          Integer currentClassCount = classCountMap.get(eachClass.toString());
+          classCountMap.put(class_sf, currentClassCount + frequency);
+        } else {
+          classCountMap.put(class_sf, frequency);
+        }
+      }
+    }
+    int max = 0;
+    String maxClassString = "";
+    for (String eachKey : classCountMap.keySet()) {
+      int eachCount = classCountMap.get(eachKey);
+      if (eachCount > max) {
+        max = eachCount;
+        maxClassString = eachKey;
+      }
+    }
+    return maxClassString;
+  }
+
+  private int getLongestAxiomLength(Imports includeImportsClosure) {
+    int longestAxiomLength = 0;
+
+    for (OWLAxiom eachAxiom : getLogicalAxioms(includeImportsClosure, true)) {
+      int eachLength = getLengthOfAxiom(eachAxiom);
+      if (eachLength > longestAxiomLength) {
+        longestAxiomLength = eachLength;
+      }
+    }
+    return longestAxiomLength;
+  }
+
+  private int getDatatypesNotBuiltinCount(Imports included) {
+    return getNotBuiltInDatatypes(included).size();
+  }
+
+  private int getDatatypesBuiltinCount(Imports included) {
+    return getBuiltInDatatypes(included).size();
+  }
+
+  public String getSignatureWithoutIRIs(Imports incl) {
+    StringBuilder b = new StringBuilder();
+    for (OWLEntity e : getOntology().getSignature(incl)) {
+      if (e.isBottomEntity() || e.isTopEntity()) {
+        continue;
+      }
+      if (e instanceof OWLClass) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      } else if (e instanceof OWLObjectProperty) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      } else if (e instanceof OWLDataProperty) {
+        b.append(e.getIRI().getRemainder().or("unknown")).append("; ");
+      }
+    }
+    return b.toString().contains(";") ? b.substring(0, b.toString().lastIndexOf(";")) : "";
+  }
+
+  public List<OWLProfileViolation> getOWLDLProfileViolations() {
+    isOWL2DLProfile();
+    return dlprofileviolation;
+  }
+
+  public MetricsResult getEssentialMetrics() {
+    return getEssentialMetrics("");
+  }
+
+  public MetricsResult getExtendedMetrics() {
+    return getExtendedMetrics("");
+  }
+
+  public MetricsResult getAllMetrics() {
+    return getAllMetrics("");
+  }
+
+  public MetricsResult getEssentialMetrics(String prefix) {
+    MetricsResult csvData = new MetricsResult();
+    csvData.put(prefix + MetricsLabels.ONTOLOGY_ID, getOntologyId());
+    /*
+    Essential entity metrics
+    */
+    csvData.put(prefix + MetricsLabels.SIGNATURE_SIZE_INCL, getSignatureSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.SIGNATURE_SIZE, getSignatureSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.CLASS_COUNT_INCL, getClassCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.CLASS_COUNT, getClassCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.OBJPROPERTY_COUNT_INCL, getObjectPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.OBJPROPERTY_COUNT, getObjectPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.INDIVIDUAL_COUNT_INCL, getIndividualsCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.INDIVIDUAL_COUNT, getIndividualsCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATAPROPERTY_COUNT_INCL, getDataPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATAPROPERTY_COUNT, getDataPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ANNOTATION_PROP_COUNT, getAnnotationPropertyCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ANNOTATION_PROP_COUNT_INCL,
+        getAnnotationPropertyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATATYPE_COUNT_INCL, getDatatypesCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.DATATYPE_COUNT, getDatatypesCount(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.ONTOLOGY_ANNOTATIONS_COUNT, getAnnotationsCount());
+
+    /*
+    Essential axiom counts
+     */
+
+    csvData.put(prefix + MetricsLabels.LOGICAL_AXIOM_COUNT, getLogicalAxiomCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.LOGICAL_AXIOM_COUNT_INCL, getLogicalAxiomCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.AXIOM_COUNT, getAxiomCount(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.AXIOM_COUNT_INCL, getAxiomCount(Imports.INCLUDED));
+
+    csvData.put(prefix + MetricsLabels.TBOX_SIZE, getTBoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOX_SIZE_INCL, getTBoxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOXRBOX_SIZE, getTBoxRboxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TBOXRBOX_SIZE_INCL, getTBoxRboxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.RULE_CT, getNumberOfRules(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.RULE_CT_INCL, getNumberOfRules(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.RBOX_SIZE, getRBoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.RBOX_SIZE_INCL, getRBoxSize(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.ABOX_SIZE, getABoxSize(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.ABOX_SIZE_INCL, getABoxSize(Imports.INCLUDED));
+
+    /*
+    Essential expressivity metrics
+     */
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2, isOWL2Profile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_DL, isOWL2DLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_EL, isOWL2ELProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_QL, isOWL2QLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_OWL2_RL, isOWL2RLProfile());
+    csvData.put(prefix + MetricsLabels.BOOL_PROFILE_RDFS, isRDFS());
+    csvData.putMap(prefix + MetricsLabels.VIOLATION_PROFILE_OWL2_DL, getOwlprofileviolations());
+
+    csvData.putSet(prefix + MetricsLabels.VALID_IMPORTS, getValidImports(false));
+    csvData.putSet(prefix + MetricsLabels.VALID_IMPORTS_INCL, getValidImports(true));
+
+    return csvData;
+  }
+
+  public MetricsResult getExtendedMetrics(String prefix) {
+    MetricsResult csvData = new MetricsResult();
+    MetricsResult essentialData = getEssentialMetrics(prefix);
+    csvData.importMetrics(essentialData);
+
+    /*
+    Extended entity metrics
+     */
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_BUILTIN_COUNT_INCL,
+        getDatatypesBuiltinCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_BUILTIN_COUNT, getDatatypesBuiltinCount(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_NOTBUILTIN_COUNT_INCL,
+        getDatatypesNotBuiltinCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.DATATYPE_NOTBUILTIN_COUNT,
+        getDatatypesNotBuiltinCount(Imports.EXCLUDED));
+
+    /*
+    Extended expressivity metrics
+     */
+    csvData.put(prefix + MetricsLabels.EXPRESSIVITY, getExpressivity(false));
+    csvData.put(prefix + MetricsLabels.EXPRESSIVITY_INCL, getExpressivity(true));
+    csvData.putSet(prefix + MetricsLabels.CONSTRUCTS_INCL, getConstructs(true));
+    csvData.putSet(prefix + MetricsLabels.CONSTRUCTS, getConstructs(false));
+    csvData.putSet(prefix + MetricsLabels.AXIOM_TYPES, getAxiomTypes(Imports.EXCLUDED));
+    csvData.putSet(prefix + MetricsLabels.AXIOM_TYPES_INCL, getAxiomTypes(Imports.INCLUDED));
+    csvData.putMap(prefix + MetricsLabels.AXIOMTYPE_COUNT, getAxiomTypeCounts(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.AXIOMTYPE_COUNT_INCL, getAxiomTypeCounts(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.SYNTAX, getSyntax());
+
+    /*
+    Entity usage
+     */
+
+    csvData.putMap(prefix + MetricsLabels.NS_USE_AXIOMS, getAxiomUsageMap(Imports.EXCLUDED));
+    csvData.putMap(prefix + MetricsLabels.NS_USE_AXIOMS_INCL, getAxiomUsageMap(Imports.INCLUDED));
+    csvData.putMap(prefix + MetricsLabels.NS_USE_SIGNATURE, getEntityUsageMap(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.NS_USE_SIGNATURE_INCL, getEntityUsageMap(Imports.INCLUDED));
+    return csvData;
+  }
+
+  public MetricsResult getAllMetrics(String prefix) {
+    MetricsResult csvData = new MetricsResult();
+    MetricsResult extendedData = getExtendedMetrics(prefix);
+    csvData.importMetrics(extendedData);
+
+    csvData.put(prefix + MetricsLabels.MAX_AXIOMLENGTH, getLongestAxiomLength(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.MAX_AXIOMLENGTH_INCL, getLongestAxiomLength(Imports.INCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUBCLASS_INCL,
+        getAverageAssertedNamedSubclasses(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUBCLASS,
+        getAverageAssertedNamedSubclasses(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUPERCLASS_INCL,
+        getAverageAssertedNamedSuperclasses(true));
+    csvData.put(
+        prefix + MetricsLabels.AVG_ASSERT_N_SUPERCLASS, getAverageAssertedNamedSuperclasses(false));
+    csvData.put(
+        prefix + MetricsLabels.AVG_INSTANCE_PER_CLASS_INCL,
+        getAverageInstancesPerClass(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AVG_INSTANCE_PER_CLASS,
+        getAverageInstancesPerClass(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.CLASS_SGL_SUBCLASS_COUNT_INCL,
+        getClassesWithSingleSubclassCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.CLASS_SGL_SUBCLASS_COUNT,
+        getClassesWithSingleSubclassCount(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.AXIOM_COMPLEXRHS_COUNT_INCL,
+        getAxiomsWithComplexRHS(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.AXIOM_COMPLEXRHS_COUNT, getAxiomsWithComplexRHS(Imports.EXCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.TBOX_CONTAINS_NOMINALS_INCL,
+        isTBoxContainsNominals(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.TBOX_CONTAINS_NOMINALS, isTBoxContainsNominals(Imports.EXCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ABOX_CONTAINS_NOMINALS_INCL,
+        isABoxContainsNominals(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.ABOX_CONTAINS_NOMINALS, isABoxContainsNominals(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.GCI_COUNT_INCL, getGCICount(true));
+    csvData.put(prefix + MetricsLabels.GCI_COUNT, getGCICount(false));
+    csvData.put(prefix + MetricsLabels.GCI_HIDDEN_COUNT_INCL, getHiddenGCICount(true));
+    csvData.put(prefix + MetricsLabels.GCI_HIDDEN_COUNT, getHiddenGCICount(false));
+
+    csvData.put(
+        prefix + MetricsLabels.MAX_NUM_NAMED_SUPERCLASS_INCL,
+        getMaximumNumberOfNamedSuperclasses(true));
+    csvData.put(
+        prefix + MetricsLabels.MAX_NUM_NAMED_SUPERCLASS,
+        getMaximumNumberOfNamedSuperclasses(false));
+    csvData.put(
+        prefix + MetricsLabels.MULTI_INHERITANCE_COUNT_INCL, getMultipleInheritanceCount(true));
+    csvData.put(prefix + MetricsLabels.MULTI_INHERITANCE_COUNT, getMultipleInheritanceCount(false));
+
+    /*    csvData.put(prefix + MetricsLabels.REF_CLASS_COUNT_INCL, getReferencedClassCount(true));
+        csvData.put(prefix + MetricsLabels.REF_CLASS_COUNT, getReferencedClassCount(false));
+        csvData.put(
+            prefix + MetricsLabels.REF_DATAPROP_COUNT_INCL, getReferencedDataPropertyCount(true));
+        csvData.put(prefix + MetricsLabels.REF_DATAPROP_COUNT, getReferencedDataPropertyCount(false));
+        csvData.put(prefix + MetricsLabels.REF_INDIV_COUNT_INCL, getReferencedIndividualCount(true));
+        csvData.put(prefix + MetricsLabels.REF_INDIV_COUNT, getReferencedIndividualCount(false));
+        csvData.put(
+            prefix + MetricsLabels.REF_OBJPROP_COUNT_INCL, getReferencedObjectPropertyCount(true));
+        csvData.put(prefix + MetricsLabels.REF_OBJPROP_COUNT, getReferencedObjectPropertyCount(false));
+    */
+    csvData.put(
+        prefix + MetricsLabels.UNDECLARED_ENTITY_COUNT_INCL,
+        getUndeclaredEntitiesCount(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.UNDECLARED_ENTITY_COUNT,
+        getUndeclaredEntitiesCount(Imports.EXCLUDED));
+
+    csvData.put(prefix + MetricsLabels.TAUTOLOGYCOUNT, getTautologyCount(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.TAUTOLOGYCOUNT_INCL, getTautologyCount(Imports.INCLUDED));
+    csvData.put(prefix + MetricsLabels.CYCLE, surelyContainsCycle(Imports.EXCLUDED));
+    csvData.put(prefix + MetricsLabels.CYCLE_INCL, surelyContainsCycle(Imports.INCLUDED));
+
+    if (surelyContainsCycle(Imports.EXCLUDED)) {
+      csvData.put(prefix + MetricsLabels.CYCLE, true);
+    } else {
+      csvData.put(prefix + MetricsLabels.CYCLE, false);
+    }
+
+    csvData.putMap(
+        prefix + MetricsLabels.DATATYPE_AXIOMCOUNT,
+        getDatatypesWithAxiomOccurrenceCount(Imports.EXCLUDED));
+    csvData.putMap(
+        prefix + MetricsLabels.DATATYPE_AXIOMCOUNT_INCL,
+        getDatatypesWithAxiomOccurrenceCount(Imports.INCLUDED));
+    csvData.putSet(prefix + MetricsLabels.DATATYPES, getBuiltInDatatypes(Imports.EXCLUDED));
+    csvData.putSet(prefix + MetricsLabels.DATATYPES_INCL, getBuiltInDatatypes(Imports.INCLUDED));
+    csvData.putSet(
+        prefix + MetricsLabels.DATATYPES_NOT_BUILT_IN, getNotBuiltInDatatypes(Imports.EXCLUDED));
+    csvData.putSet(
+        prefix + MetricsLabels.DATATYPES_NOT_BUILT_IN_INCL,
+        getNotBuiltInDatatypes(Imports.INCLUDED));
+
+    csvData.put(
+        prefix + MetricsLabels.MOST_FRQUENTLY_USED_CONCEPT_INCL,
+        getMostFrequentlyUsedClassInLogicalAxioms(Imports.INCLUDED));
+    csvData.put(
+        prefix + MetricsLabels.MOST_FRQUENTLY_USED_CONCEPT,
+        getMostFrequentlyUsedClassInLogicalAxioms(Imports.EXCLUDED));
+
+    return csvData;
+  }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java b/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java
new file mode 100644
index 00000000..81d25590
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/metrics/TautologyChecker.java
@@ -0,0 +1,66 @@
+package org.obolibrary.robot.metrics;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.*;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+
+public class TautologyChecker {
+
+  public static boolean isTautology(OWLAxiom ax) {
+    OWLOntologyManager emptyman = OWLManager.createOWLOntologyManager();
+    OWLOntology emptyo;
+    try {
+      emptyo = emptyman.createOntology();
+      OWLReasonerFactory fac = new org.semanticweb.HermiT.ReasonerFactory();
+      OWLReasoner reasoner = fac.createReasoner(emptyo);
+      return reasoner.isEntailed(ax);
+    } catch (OWLOntologyCreationException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return false;
+  }
+
+  public static Set<OWLAxiom> getTautologies(Set<OWLAxiom> unionSet)
+      throws OWLOntologyCreationException {
+    Set<OWLAxiom> delSet = new HashSet<>();
+    OWLReasoner r =
+        new org.semanticweb.HermiT.ReasonerFactory()
+            .createReasoner(OWLManager.createOWLOntologyManager().createOntology());
+    for (OWLAxiom ax : unionSet) {
+      if (r.isEntailed(ax)) {
+        delSet.add(ax);
+      }
+    }
+    return delSet;
+  }
+
+  // Filtering of axioms - remove declarations and tautologies. At the moment
+  // tautologies are
+  // just defined w.r.t classification. Later versions will require something
+  // more complex.
+  public static Set<OWLAxiom> getTautologySyntactic(Set<OWLAxiom> cleanSet) {
+    Set<OWLAxiom> filter = new HashSet<>();
+    for (OWLAxiom ax : cleanSet) {
+      if (ax.isOfType(AxiomType.DECLARATION)) {
+        filter.add(ax);
+      } else {
+        // Sanity check
+        if (ax.isOfType(AxiomType.SUBCLASS_OF)) {
+          if (isTautology((OWLSubClassOfAxiom) ax)) {
+            filter.add(ax);
+          }
+        }
+      }
+    }
+    cleanSet.removeAll(filter);
+    return cleanSet;
+  }
+
+  private static Boolean isTautology(OWLSubClassOfAxiom ax) {
+    return ax.getSubClass().isOWLNothing() || ax.getSuperClass().isOWLThing();
+  }
+}


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/MetricsOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/MetricsOperationTest.java
new file mode 100644
index 00000000..953d2079
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/MetricsOperationTest.java
@@ -0,0 +1,121 @@
+package org.obolibrary.robot;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+import org.obolibrary.robot.metrics.MetricsLabels;
+import org.obolibrary.robot.metrics.MetricsResult;
+import org.semanticweb.elk.io.FileUtils;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyStorageException;
+
+/**
+ * Test metrics operation.trea
+ *
+ * @author <a href="mailto:nicolas.matentzoglu@gmail.com">Nicolas Matentzoglu</a>
+ */
+public class MetricsOperationTest extends CoreTest {
+
+  String TESTONTOLOGY = "uberon.owl";
+
+  /**
+   * Tests essential metrics generation.
+   *
+   * @throws IOException on IO error
+   * @throws OWLOntologyStorageException on ontology error
+   */
+  @Test
+  public void testEssentialMetrics() throws IOException, OWLOntologyStorageException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "essential");
+    assertEquals(313, result.getSimpleMetricValue(MetricsLabels.AXIOM_COUNT));
+  }
+
+  /**
+   * Tests extended metrics generation.
+   *
+   * @throws IOException on IO error
+   * @throws OWLOntologyStorageException on ontology error
+   */
+  @Test
+  public void testExtendedMetrics() throws IOException, OWLOntologyStorageException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "extended");
+    assertEquals("SI(D)", result.getSimpleMetricValue(MetricsLabels.EXPRESSIVITY));
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   * @throws OWLOntologyStorageException on ontology error
+   */
+  @Test
+  public void testAllMetrics() throws IOException, OWLOntologyStorageException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "all");
+    assertEquals(0, result.getSimpleMetricValue(MetricsLabels.GCI_COUNT_INCL));
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToTSV() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "all");
+    File testFile = new File("test.tsv");
+    MetricsOperation.maybeWriteResult(result, "tsv", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToCSV() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "all");
+    File testFile = new File("test.csv");
+    MetricsOperation.maybeWriteResult(result, "csv", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToJSON() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "all");
+    File testFile = new File("test.json");
+    MetricsOperation.maybeWriteResult(result, "json", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+
+  /**
+   * Tests all metrics generation.
+   *
+   * @throws IOException on IO error
+   */
+  @Test
+  public void writeAllMetricsToYAML() throws IOException {
+    OWLOntology ontology = loadOntology("/" + TESTONTOLOGY);
+    MetricsResult result = MetricsOperation.runMetrics(ontology, "all");
+    File testFile = new File("test.yml");
+    MetricsOperation.maybeWriteResult(result, "yaml", testFile);
+    assertTrue(testFile.exists());
+    FileUtils.deleteRecursively(testFile);
+  }
+}