use std::path::Path;
use std::sync::Arc;
use crate::axiom::{Axiom, AxiomId};
use crate::dirty::{DirtySet, OntologyRevision, axiom_signature};
use crate::dl::DlStore;
use crate::entity::{EntityId, EntityKind, EntityRecord, EntityRegistry};
use crate::error::{Error, Result};
use crate::graph::{AxiomIndex, AxiomStore};
use crate::iri::{InternPool, IriId, normalize_iri_fragment_encoding, validate_iri};
use crate::limits::Limits;
use crate::parse_meta::ParseMeta;
use crate::swrl::SwrlRule;
#[derive(Debug)]
pub struct Ontology {
pub(crate) iris: Arc<InternPool>,
pub(crate) entities: Arc<EntityRegistry>,
pub(crate) axioms: Arc<AxiomStore>,
pub(crate) index: AxiomIndex,
pub(crate) revision: OntologyRevision,
pub(crate) dirty: DirtySet,
pub(crate) dl: Arc<DlStore>,
pub(crate) swrl_rules: Vec<SwrlRule>,
#[doc(hidden)]
pub parse_meta: Option<ParseMeta>,
enforce_limits: Option<Limits>,
}
impl Clone for Ontology {
fn clone(&self) -> Self {
Self {
iris: Arc::clone(&self.iris),
entities: Arc::clone(&self.entities),
axioms: Arc::clone(&self.axioms),
index: self.index.clone(),
revision: self.revision,
dirty: self.dirty.clone(),
dl: Arc::clone(&self.dl),
swrl_rules: self.swrl_rules.clone(),
parse_meta: self.parse_meta.clone(),
enforce_limits: self.enforce_limits,
}
}
}
impl PartialEq for Ontology {
fn eq(&self, other: &Self) -> bool {
*self.iris == *other.iris
&& *self.entities == *other.entities
&& *self.axioms == *other.axioms
&& self.index == other.index
&& self.revision == other.revision
&& self.dirty == other.dirty
&& *self.dl == *other.dl
&& self.swrl_rules == other.swrl_rules
&& self.parse_meta == other.parse_meta
&& self.enforce_limits == other.enforce_limits
}
}
impl Eq for Ontology {}
impl Default for Ontology {
fn default() -> Self {
Self::new()
}
}
impl Ontology {
#[must_use]
pub fn new() -> Self {
Self {
iris: Arc::new(InternPool::new()),
entities: Arc::new(EntityRegistry::new()),
axioms: Arc::new(AxiomStore::new()),
index: AxiomIndex::new(),
revision: OntologyRevision::default(),
dirty: DirtySet::default(),
dl: Arc::new(DlStore::new()),
swrl_rules: Vec::new(),
parse_meta: None,
enforce_limits: None,
}
}
fn uniquify_shared(&mut self) {
Arc::make_mut(&mut self.iris);
Arc::make_mut(&mut self.entities);
Arc::make_mut(&mut self.axioms);
Arc::make_mut(&mut self.dl);
}
#[must_use]
pub fn builder() -> OntologyBuilder {
OntologyBuilder::new()
}
#[deprecated(since = "1.0.0", note = "use ontologos_parser::load_ontology instead")]
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);
}
pub fn set_enforce_limits(&mut self, limits: Limits) {
self.enforce_limits = Some(limits);
}
#[must_use]
pub fn enforce_limits(&self) -> Option<Limits> {
self.enforce_limits
}
pub fn restore_enforce_limits(&mut self, limits: Option<Limits>) {
self.enforce_limits = limits;
}
pub(crate) fn invalidate_profile_constructs(&mut self) {
if let Some(meta) = self.parse_meta.as_mut() {
meta.profile_constructs.clear();
}
}
#[must_use]
pub fn entity_count(&self) -> usize {
self.entities.len()
}
#[must_use]
pub fn axiom_count(&self) -> usize {
self.axioms.active_len()
}
#[must_use]
pub fn dl(&self) -> &DlStore {
&self.dl
}
pub fn dl_mut(&mut self) -> &mut DlStore {
Arc::make_mut(&mut self.dl)
}
#[must_use]
pub fn swrl_rules(&self) -> &[SwrlRule] {
&self.swrl_rules
}
pub fn push_swrl_rule(&mut self, rule: SwrlRule) -> Result<()> {
self.swrl_rules.push(rule);
Ok(())
}
#[must_use]
pub fn revision(&self) -> OntologyRevision {
self.revision
}
#[must_use]
pub fn dirty(&self) -> &DirtySet {
&self.dirty
}
pub fn clear_dirty(&mut self) {
self.dirty.clear();
}
#[must_use]
pub fn strip_inferred_axioms(&mut self) -> usize {
self.uniquify_shared();
let removed = Arc::make_mut(&mut self.axioms).strip_inferred();
if !removed.is_empty() {
self.index.rebuild_from_store(&self.axioms);
self.revision.bump();
}
removed.len()
}
pub fn add_inferred_axiom(&mut self, axiom: Axiom) -> Result<AxiomId> {
self.uniquify_shared();
self.validate_inverse_pair(&axiom)?;
let id = Arc::make_mut(&mut self.axioms).push_inferred(axiom, &self.entities)?;
let stored = self.axioms.get(id)?;
self.index.insert(id, stored);
self.revision.bump();
Ok(id)
}
pub fn signature_of_axiom(&self, id: AxiomId) -> Result<std::collections::HashSet<EntityId>> {
let axiom = self.axioms.get(id)?;
Ok(axiom_signature(axiom))
}
#[must_use]
pub fn dirty_signatures(&self) -> std::collections::HashSet<EntityId> {
let mut sig = std::collections::HashSet::new();
for id in self.dirty.added() {
if let Ok(axiom) = self.axioms.get(*id) {
sig.extend(axiom_signature(axiom));
}
}
for id in self.dirty.removed() {
if let Ok(axiom) = self.axioms.get_raw(*id) {
sig.extend(axiom_signature(axiom));
}
}
sig
}
#[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 = normalize_iri_fragment_encoding(iri);
self.uniquify_shared();
if let Some(limits) = self.enforce_limits {
let already_registered = self
.iris
.get(iri.as_ref())
.and_then(|iri_id| self.entities.entity_by_iri(iri_id))
.is_some();
if !already_registered && self.entity_count() >= limits.max_entities {
return Err(Error::ResourceLimit(format!(
"entity count exceeds maximum of {}",
limits.max_entities
)));
}
}
let iri_id = Arc::make_mut(&mut self.iris).intern(iri.as_ref())?;
let iri_str = self.iris.resolve(iri_id)?;
Arc::make_mut(&mut self.entities).get_or_register(iri_id, iri_str, kind)
}
pub fn try_lookup_entity(&self, iri: &str) -> Result<Option<EntityId>> {
let iri = normalize_iri_fragment_encoding(iri);
validate_iri(iri.as_ref())?;
Ok(self
.iris
.get(iri.as_ref())
.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 set_entity_kind(&mut self, id: EntityId, kind: EntityKind) -> Result<()> {
self.uniquify_shared();
Arc::make_mut(&mut self.entities).set_kind(id, kind)
}
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.uniquify_shared();
if let Some(limits) = self.enforce_limits
&& self.axiom_count() >= limits.max_axioms
{
return Err(Error::ResourceLimit(format!(
"axiom count exceeds maximum of {}",
limits.max_axioms
)));
}
self.validate_inverse_pair(&axiom)?;
let id = Arc::make_mut(&mut self.axioms).push(axiom, &self.entities)?;
let stored = self.axioms.get(id)?;
self.index.insert(id, stored);
self.revision.bump();
self.dirty.record_add(id);
self.invalidate_profile_constructs();
Ok(id)
}
pub fn remove_axiom(&mut self, id: AxiomId) -> Result<()> {
self.uniquify_shared();
let axiom = self.axioms.get(id)?.clone();
Arc::make_mut(&mut self.axioms).remove(id)?;
if AxiomIndex::removal_needs_rebuild(&axiom) {
self.index.rebuild_from_store(&self.axioms);
} else {
self.index.remove(id, &axiom);
}
self.revision.bump();
self.dirty.record_remove(id);
self.invalidate_profile_constructs();
Ok(())
}
pub fn intern_iri(&mut self, iri: &str) -> Result<IriId> {
Arc::make_mut(&mut 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)
&& 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)
&& 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]
#[allow(deprecated)]
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(_)));
}
}