use std::path::Path;
use crate::axiom::{Axiom, AxiomId};
use crate::entity::{EntityId, EntityKind, EntityRecord, EntityRegistry};
use crate::error::{Error, Result};
use crate::graph::{AxiomIndex, AxiomStore};
use crate::iri::{validate_iri, InternPool, IriId};
use crate::parse_meta::ParseMeta;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ontology {
pub(crate) iris: InternPool,
pub(crate) entities: EntityRegistry,
pub(crate) axioms: AxiomStore,
pub(crate) index: AxiomIndex,
#[doc(hidden)]
pub parse_meta: Option<ParseMeta>,
}
impl Default for Ontology {
fn default() -> Self {
Self::new()
}
}
impl Ontology {
#[must_use]
pub fn new() -> Self {
Self {
iris: InternPool::new(),
entities: EntityRegistry::new(),
axioms: AxiomStore::new(),
index: AxiomIndex::new(),
parse_meta: None,
}
}
#[must_use]
pub fn builder() -> OntologyBuilder {
OntologyBuilder::new()
}
pub fn from_file(_path: impl AsRef<Path>) -> Result<Self> {
Err(Error::ParseNotAvailable)
}
#[must_use]
pub fn parse_meta(&self) -> Option<&ParseMeta> {
self.parse_meta.as_ref()
}
pub fn set_parse_meta(&mut self, meta: ParseMeta) {
self.parse_meta = Some(meta);
}
#[must_use]
pub fn entity_count(&self) -> usize {
self.entities.len()
}
#[must_use]
pub fn axiom_count(&self) -> usize {
self.axioms.len()
}
#[must_use]
pub fn iri_count(&self) -> usize {
self.iris.len()
}
#[must_use]
pub fn iris(&self) -> &InternPool {
&self.iris
}
#[must_use]
pub fn entities(&self) -> &EntityRegistry {
&self.entities
}
#[must_use]
pub fn axioms(&self) -> &AxiomStore {
&self.axioms
}
#[must_use]
pub fn index(&self) -> &AxiomIndex {
&self.index
}
pub fn resolve_iri(&self, id: IriId) -> Result<&str> {
self.iris.resolve(id)
}
pub fn entity_id(&mut self, iri: &str, kind: EntityKind) -> Result<EntityId> {
let iri_id = self.iris.intern(iri)?;
let iri_str = self.iris.resolve(iri_id)?;
self.entities.get_or_register(iri_id, iri_str, kind)
}
pub fn try_lookup_entity(&self, iri: &str) -> Result<Option<EntityId>> {
validate_iri(iri)?;
Ok(self
.iris
.get(iri)
.and_then(|iri_id| self.entities.entity_by_iri(iri_id)))
}
#[must_use]
pub fn lookup_entity(&self, iri: &str) -> Option<EntityId> {
self.try_lookup_entity(iri).ok().flatten()
}
pub fn entity(&self, id: EntityId) -> Result<&EntityRecord> {
self.entities.entity(id)
}
pub fn axiom(&self, id: AxiomId) -> Result<&Axiom> {
self.axioms.get(id)
}
#[must_use]
pub fn direct_superclasses(&self, class: EntityId) -> &[EntityId] {
self.index.direct_superclasses(class)
}
#[must_use]
pub fn direct_subclasses(&self, class: EntityId) -> &[EntityId] {
self.index.direct_subclasses(class)
}
#[must_use]
pub fn direct_superproperties(&self, property: EntityId) -> &[EntityId] {
self.index.direct_superproperties(property)
}
#[must_use]
pub fn direct_subproperties(&self, property: EntityId) -> &[EntityId] {
self.index.direct_subproperties(property)
}
#[must_use]
pub fn equivalents_of(&self, class: EntityId) -> Option<&std::collections::HashSet<EntityId>> {
self.index.equivalents_of(class)
}
#[must_use]
pub fn disjoint_with(&self, class: EntityId) -> Option<&std::collections::HashSet<EntityId>> {
self.index.disjoint_with(class)
}
#[must_use]
pub fn inverse_of(&self, property: EntityId) -> Option<EntityId> {
self.index.inverse_of(property)
}
#[must_use]
pub fn existentials_of(&self, subclass: EntityId) -> &[(EntityId, EntityId)] {
self.index.existentials_of(subclass)
}
#[must_use]
pub fn classes_of(&self, individual: EntityId) -> &[EntityId] {
self.index.classes_of(individual)
}
#[must_use]
pub fn individuals_of(&self, class: EntityId) -> &[EntityId] {
self.index.individuals_of(class)
}
#[must_use]
pub fn object_assertions_of(&self, subject: EntityId) -> &[(EntityId, EntityId)] {
self.index.object_assertions_of(subject)
}
#[must_use]
pub fn object_assertions_to(&self, object: EntityId) -> &[(EntityId, EntityId)] {
self.index.object_assertions_to(object)
}
#[must_use]
pub fn equivalent_properties_of(
&self,
property: EntityId,
) -> Option<&std::collections::HashSet<EntityId>> {
self.index.equivalent_properties_of(property)
}
#[must_use]
pub fn same_as(&self, individual: EntityId) -> Option<&std::collections::HashSet<EntityId>> {
self.index.same_as(individual)
}
#[must_use]
pub fn different_from(
&self,
individual: EntityId,
) -> Option<&std::collections::HashSet<EntityId>> {
self.index.different_from(individual)
}
pub fn add_axiom(&mut self, axiom: Axiom) -> Result<AxiomId> {
self.validate_inverse_pair(&axiom)?;
let id = self.axioms.push(axiom, &self.entities)?;
let stored = self.axioms.get(id)?;
self.index.insert(id, stored);
Ok(id)
}
pub fn intern_iri(&mut self, iri: &str) -> Result<IriId> {
self.iris.intern(iri)
}
fn validate_inverse_pair(&self, axiom: &Axiom) -> Result<()> {
let Axiom::InverseObjectProperties { left, right } = axiom else {
return Ok(());
};
if let Some(existing) = self.index.inverse_of(*left) {
if existing != *right {
return Err(Error::InvalidAxiom(format!(
"property {left:?} already has inverse {existing:?}, cannot add inverse {right:?}"
)));
}
}
if let Some(existing) = self.index.inverse_of(*right) {
if existing != *left {
return Err(Error::InvalidAxiom(format!(
"property {right:?} already has inverse {existing:?}, cannot add inverse {left:?}"
)));
}
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct OntologyBuilder {
ontology: Ontology,
}
impl OntologyBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn class(mut self, iri: &str) -> Result<Self> {
self.ontology.entity_id(iri, EntityKind::Class)?;
Ok(self)
}
pub fn individual(mut self, iri: &str) -> Result<Self> {
self.ontology.entity_id(iri, EntityKind::Individual)?;
Ok(self)
}
pub fn object_property(mut self, iri: &str) -> Result<Self> {
self.ontology.entity_id(iri, EntityKind::ObjectProperty)?;
Ok(self)
}
pub fn subclass_of(mut self, subclass: &str, superclass: &str) -> Result<Self> {
let sub = self.ontology.entity_id(subclass, EntityKind::Class)?;
let sup = self.ontology.entity_id(superclass, EntityKind::Class)?;
self.ontology.add_axiom(Axiom::SubClassOf {
subclass: sub,
superclass: sup,
})?;
Ok(self)
}
pub fn subproperty_of(mut self, sub: &str, sup: &str) -> Result<Self> {
let sub_id = self.ontology.entity_id(sub, EntityKind::ObjectProperty)?;
let sup_id = self.ontology.entity_id(sup, EntityKind::ObjectProperty)?;
self.ontology.add_axiom(Axiom::SubObjectPropertyOf {
sub_property: sub_id,
super_property: sup_id,
})?;
Ok(self)
}
pub fn property_domain(mut self, property: &str, domain: &str) -> Result<Self> {
let property_id = self
.ontology
.entity_id(property, EntityKind::ObjectProperty)?;
let domain_id = self.ontology.entity_id(domain, EntityKind::Class)?;
self.ontology.add_axiom(Axiom::ObjectPropertyDomain {
property: property_id,
domain: domain_id,
})?;
Ok(self)
}
pub fn property_range(mut self, property: &str, range: &str) -> Result<Self> {
let property_id = self
.ontology
.entity_id(property, EntityKind::ObjectProperty)?;
let range_id = self.ontology.entity_id(range, EntityKind::Class)?;
self.ontology.add_axiom(Axiom::ObjectPropertyRange {
property: property_id,
range: range_id,
})?;
Ok(self)
}
pub fn class_assertion(mut self, individual: &str, class: &str) -> Result<Self> {
let individual_id = self
.ontology
.entity_id(individual, EntityKind::Individual)?;
let class_id = self.ontology.entity_id(class, EntityKind::Class)?;
self.ontology.add_axiom(Axiom::ClassAssertion {
individual: individual_id,
class: class_id,
})?;
Ok(self)
}
pub fn object_property_assertion(
mut self,
subject: &str,
property: &str,
object: &str,
) -> Result<Self> {
let subject_id = self.ontology.entity_id(subject, EntityKind::Individual)?;
let property_id = self
.ontology
.entity_id(property, EntityKind::ObjectProperty)?;
let object_id = self.ontology.entity_id(object, EntityKind::Individual)?;
self.ontology.add_axiom(Axiom::ObjectPropertyAssertion {
subject: subject_id,
property: property_id,
object: object_id,
})?;
Ok(self)
}
pub fn same_individual(mut self, individuals: &[&str]) -> Result<Self> {
let ids = individuals
.iter()
.map(|iri| self.ontology.entity_id(iri, EntityKind::Individual))
.collect::<Result<Vec<_>>>()?;
self.ontology.add_axiom(Axiom::SameIndividual(ids))?;
Ok(self)
}
pub fn different_individuals(mut self, individuals: &[&str]) -> Result<Self> {
let ids = individuals
.iter()
.map(|iri| self.ontology.entity_id(iri, EntityKind::Individual))
.collect::<Result<Vec<_>>>()?;
self.ontology.add_axiom(Axiom::DifferentIndividuals(ids))?;
Ok(self)
}
pub fn equivalent_object_properties(mut self, properties: &[&str]) -> Result<Self> {
let ids = properties
.iter()
.map(|iri| self.ontology.entity_id(iri, EntityKind::ObjectProperty))
.collect::<Result<Vec<_>>>()?;
self.ontology
.add_axiom(Axiom::EquivalentObjectProperties(ids))?;
Ok(self)
}
pub fn asymmetric_object_property(mut self, property: &str) -> Result<Self> {
let property_id = self
.ontology
.entity_id(property, EntityKind::ObjectProperty)?;
self.ontology
.add_axiom(Axiom::AsymmetricObjectProperty(property_id))?;
Ok(self)
}
pub fn build(self) -> Result<Ontology> {
Ok(self.ontology)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_constructs_taxonomy() {
let ontology = Ontology::builder()
.class("http://example.org/A")
.expect("class A")
.class("http://example.org/B")
.expect("class B")
.subclass_of("http://example.org/A", "http://example.org/B")
.expect("subclass")
.build()
.expect("build");
let a = ontology.lookup_entity("http://example.org/A").expect("A");
let b = ontology.lookup_entity("http://example.org/B").expect("B");
assert_eq!(ontology.direct_superclasses(a), &[b]);
assert_eq!(ontology.direct_subclasses(b), &[a]);
}
#[test]
fn from_file_returns_parse_not_available() {
let err = Ontology::from_file("any.owl").expect_err("should fail");
assert_eq!(err, Error::ParseNotAvailable);
}
#[test]
fn try_lookup_entity_rejects_invalid_iri() {
let ontology = Ontology::new();
let err = ontology
.try_lookup_entity("relative/path")
.expect_err("invalid");
assert!(matches!(err, Error::InvalidIri(_)));
}
}