OWL2DTDL/Relationship.cs (150 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using VDS.RDF;
using VDS.RDF.Ontology;
namespace OWL2DTDL
{
/// <summary>
/// A representation of the relationships (data properties or object properties) that can be expressed on an OWL class.
/// TODO Here be gremlins! Thid code is ugly and written in a hurry. It should probably be signficantly refactored and cleaned up,
/// if not removed entirely. The relationship merge logic is particularly hairy.
/// </summary>
public class Relationship
{
public OntologyProperty Property
{ get; }
public OntologyClass Target
{ get; set; }
public int? MinimumCount
{ get; set; }
public int? MaximumCount
{ get; set; }
public int? ExactCount
{
get
{
if (MinimumCount.HasValue && MaximumCount.HasValue && MinimumCount == MaximumCount)
{
return MinimumCount;
}
return null;
}
set
{
MinimumCount = value;
MaximumCount = value;
}
}
public Relationship(OntologyProperty property, OntologyClass target)
{
if (!property.IsNamed() || !(target.IsNamed() || target.IsDatatype()))
{
throw new ArgumentException("Only named properties and named or datatype targets allowed.");
}
Property = property;
Target = target;
}
/// <summary>
/// Merge another Relationship into this one.
/// This only works when the properties of the relationships are identical; else an exception is thrown.
/// If the two relationships are to different target classes of which one subsumes the other, then the
/// most specific relationship is kept. If the classes do not subsume one another, OWL thing is set as target.
/// If the relationship targets are the same, then the narrower cardinality restrictions are kept.
/// </summary>
/// <param name="other">Another Relationship</param>
public void MergeWith(Relationship other)
{
if (!other.Property.Resource.Equals(this.Property.Resource))
{
throw new Exception("Properties are not identical");
}
// If target classes are not the same, then we need to keep the most specific one
if (!other.Target.Resource.Equals(this.Target.Resource))
{
// If both target classes are both datatypes,
if (this.Target.IsDatatype() && other.Target.IsDatatype())
{
// If the two targets are of same type but not the same, fall back to rdf:Literal
if (
(this.Target.IsEnumerationDatatype() && other.Target.IsEnumerationDatatype()) ||
(this.Target.IsSimpleXsdWrapper() && other.Target.IsSimpleXsdWrapper()) ||
(this.Target.IsXsdDatatype() && other.Target.IsXsdDatatype()))
{
IGraph targetsGraph = this.Target.Graph;
IUriNode rdfLiteral = targetsGraph.CreateUriNode(VocabularyHelper.RDFS.Literal);
this.Target = new OntologyClass(rdfLiteral, targetsGraph);
this.MinimumCount = null;
this.MaximumCount = null;
return;
}
else
{
// Preference order is enumeration, custom xsd wrapper type, built-in xsd type
List<Relationship> relationshipCandidates = new List<Relationship>() { this, other };
relationshipCandidates.OrderBy(candidate => !candidate.Target.IsEnumerationDatatype())
.ThenBy(candidate => !candidate.Target.IsSimpleXsdWrapper())
.ThenBy(candidate => !candidate.Target.IsXsdDatatype());
if (relationshipCandidates.First() == this)
{
return;
}
else
{
this.Target = other.Target;
this.MinimumCount = other.MinimumCount;
this.MaximumCount = other.MaximumCount;
return;
}
}
}
// If this relationship has the more specific target class, keep it as-is, i.e., return
if (this.Target.SuperClassesWithOwlThing().Contains(other.Target))
{
return;
}
// If the other relationhip has the more specific target class, keep it instead and return
else if (other.Target.SuperClassesWithOwlThing().Contains(this.Target))
{
this.Target = other.Target;
this.MinimumCount = other.MinimumCount;
this.MaximumCount = other.MaximumCount;
return;
}
// The classes do not subsume one another; fall back to OWL:Thing as target class
else
{
IGraph targetsGraph = this.Target.Graph;
IUriNode owlThing = targetsGraph.CreateUriNode(VocabularyHelper.OWL.Thing);
this.Target = new OntologyClass(owlThing, targetsGraph);
this.MinimumCount = null;
this.MaximumCount = null;
return;
}
}
// If either restriction has an exact count, then that is the most specific restriction possible and min/max need not be inspected
if (ExactCount.HasValue || other.ExactCount.HasValue)
{
// If both restrictions have exact values they either diverge (e.g., caused by an inconsistent ontology) or they converge (no change is required)
if (ExactCount.HasValue && other.ExactCount.HasValue)
{
if (ExactCount.Value != other.ExactCount.Value)
{
throw new Exception("Conflicting ExactCounts");
}
// The exact value is identical; simply return
return;
}
// Assign exact count from other only if our own is null, else keep our old exactcount
ExactCount ??= other.ExactCount.Value;
return;
}
int? newMinimum = new int?();
if (MinimumCount.HasValue || other.MinimumCount.HasValue)
{
// If both have minimum counts, keep the larger one
if (MinimumCount.HasValue && other.MinimumCount.HasValue)
{
newMinimum = MinimumCount > other.MinimumCount ? MinimumCount : other.MinimumCount;
}
else
{
// Else, keep whichever is non-null
newMinimum = MinimumCount.HasValue ? MinimumCount : other.MinimumCount;
}
}
int? newMaximum = new int?();
if (MaximumCount.HasValue || other.MaximumCount.HasValue)
{
// If both have maximum counts, keep the smaller one
if (MaximumCount.HasValue && other.MaximumCount.HasValue)
{
newMaximum = MaximumCount < other.MaximumCount ? MaximumCount : other.MaximumCount;
}
else
{
// Else, keep whichever is non-null
newMaximum = MaximumCount.HasValue ? MaximumCount : other.MaximumCount;
}
}
// At this point newMinimum is maximized or null and newMaximum is minimized or null
// If they are inconsistent, i.e., newMinimum is larger than new Maximum, the model is inconsistent;
// throw an error; else store
if (newMinimum.HasValue && newMaximum.HasValue && newMinimum > newMaximum)
{
throw new Exception("Resulting min > resulting max");
}
MinimumCount = newMinimum;
MaximumCount = newMaximum;
}
}
}