package tudelft.healthpsychology.traumaontologies.owltree;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDataPropertyDomainAxiom;
import org.semanticweb.owlapi.model.OWLDataPropertyRangeAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.reasoner.InferenceType;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerConfiguration;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.semanticweb.owlapi.reasoner.SimpleConfiguration;
import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory;
import uk.ac.manchester.cs.owl.owlapi.OWLDatatypeImpl;
import uk.ac.manchester.cs.owl.owlapi.OWLLiteralImplString;
/**
* OWLReasoner that assumes that the ontology contains a tree (not a graph)
*
* - each node has at most 1 parent
*
- no cycles
*
* *
*/
public class OwlTreeReasoner {
private final OWLOntology ontology;
private final OWLReasoner reasoner;
public transient final OWLClass thing; // owl:Thing
public OwlTreeReasoner(OWLOntology ontology) {
this.ontology = ontology;
/*
* use reasoner, to do class hierarchy the inference stuff. There might
* be some tricky restrictions or other OWL tricks that we don't know of
* so better leave this to the official reasoners.
*/
OWLReasonerFactory reasonerFactory = new StructuralReasonerFactory();
OWLReasonerConfiguration config = new SimpleConfiguration();
reasoner = reasonerFactory.createReasoner(ontology, config);
reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);
// ignore if it's inconsistent. As long as we can get the superclasses,
// don't care.
thing = ontology.getOWLOntologyManager().getOWLDataFactory()
.getOWLThing();
}
/**
* @param obj the {@link OWLClass} to get the children of
* @return direct children of the given obj
*
*/
public List getChildren(OWLClass obj) {
return reasoner.getSubClasses(obj, true).entities()
.filter(o -> !o.isOWLNothing()).collect(Collectors.toList());
}
/**
* @param maxdepth Must be positive or 0. 0 means node itself, 1 means
* direct children, 2 means children of the children, etc.
* @return list with only this node if this is a leaf node or depth<=0. If
* this is not a leaf node, then returns a list joining all
* getChildren(depth-1) of each child. The difference with getLeaves
* is that this includes non-leave nodes at maxdepth.
*/
public List getChildren(OWLClass obj, int maxdepth) {
if (maxdepth <= 0) {
return Arrays.asList(obj);
}
List children = reasoner.getSubClasses(obj, true).entities()
.filter(o -> !o.isOWLNothing()).collect(Collectors.toList());
if (children.isEmpty()) {
return Arrays.asList(obj);
}
return children.stream().map(child -> getChildren(child, maxdepth - 1))
.flatMap(List::stream).collect(Collectors.toList());
}
/**
* @param obj the {@link OWLClass} to get the leaf nodes from
* @param maxdepth the maximum depth of the leafs to be collected. 0 means
* obj itself.
* @return all leave nodes up do maxdepth deep. If given node is a leaf node
* (no children), returns list with only that node. returns empty
* list if maxdeptn<=0 and this node has children, or when the node
* has no laves at given depth.
*
*/
public List getLeaves(OWLClass obj, int maxdepth) {
// We have to remove "owl.NOTHING" objects, these are weird empty
// solution "objects".
List children = reasoner.getSubClasses(obj, true).entities()
.filter(o -> !o.isOWLNothing()).collect(Collectors.toList());
if (children.isEmpty()) {
return Arrays.asList(obj);
}
// this is not a leaf node.
if (maxdepth <= 0) {
return Collections.emptyList();
}
return children.stream().map(child -> getLeaves(child, maxdepth - 1))
.flatMap(List::stream).collect(Collectors.toList());
}
/**
* @param obj the {@link OWLClass} to get the ancestors of
* @return all ancestors of the given obj, up to the root. Does not include
* the class itself.
*
*/
public List getAncestors(OWLClass obj) {
return new LinkedList(
reasoner.getSuperClasses(obj, false).entities()
.filter(o -> !isThing(o)).collect(Collectors.toList()));
}
/**
*
* @param name the name for which an IRI is needed
* @return URI for the given object name in this ontology: the ontolyg
* basename followed by "#" and name.
*/
public IRI getIri(String name) {
return IRI.create(getBaseName() + "#" + name);
}
public String getBaseName() {
return ontology.getOntologyID().getOntologyIRI().get().toString();
}
/**
*
* @param name the object name. See also {@link #getIri(String)}
* @return {@link OWLClass} with the given name
*/
public OWLClass getObject(String name) {
IRI iri = getIri(name);
if (!ontology.containsClassInSignature(iri)) {
throw new IllegalArgumentException(
"Ontology does not contain object '#" + name + "'");
}
OWLClass obj = ontology.getOWLOntologyManager().getOWLDataFactory()
.getOWLClass(iri);
return obj;
}
/**
* @param obj the {@link OWLClass} to get the parent of
* @return direct parent of the given obj, or null if no parent
* @throws IllegalStateException if object has more than 1 parent.
*
*/
public OWLClass getParent(OWLClass obj) {
Set superClass = reasoner.getSuperClasses(obj, true)
.getFlattened();
if (superClass.size() > 1) {
throw new IllegalStateException(
"There is a bug in the ontology: class " + obj
+ " has multiple parents: " + superClass);
}
if (superClass.isEmpty())
return null;
OWLClass s = superClass.iterator().next();
if (thing.equals(s)) // OWL adds "thing" at root. Remove it..
return null;
return s;
}
/**
* @param object
* @return all {@link OWLDataPropertyDomainAxiom}s of object and all
* ancestor classes. These axioms are basically URIs about which
* other info like domain, range and comments are stored.
*/
public List getProperties(OWLClass object) {
List thisAndAncestors = getAncestors(object);
thisAndAncestors.add(object);
List properties = new LinkedList<>();
for (OWLClass clas : thisAndAncestors) {
ontology.axioms(AxiomType.DATA_PROPERTY_DOMAIN)
.filter(dp -> clas.equals(dp.getDomain()))
.forEach(dp -> properties.add(dp));
}
return properties;
}
/**
*
* @param axiom an {@link OWLDataProperty}, typically an
* {@link OWLDataPropertyDomainAxiom}
* @return list of comment annotations for this axiom
*/
public String getComment(OWLDataPropertyDomainAxiom axiom) {
List list = axiom.dataPropertiesInSignature()
.map(odp -> getCommentAnnotation(odp))
.collect(Collectors.toList());
if (list.size() != 1) {
throw new IllegalStateException(
"There must be exactly 1 comment for " + axiom
+ " but found " + list);
}
return list.get(0);
}
/**
* @param odp the {@link OWLDataPropertyDomainAxiom}
* @return the data ranges set for the given odp
*/
public String getRange(OWLDataPropertyDomainAxiom odp) {
IRI property = (IRI) odp.getProperty().components().findFirst().get();
List axioms = ontology
.axioms(AxiomType.DATA_PROPERTY_RANGE)
.filter(p -> property
.equals(p.getProperty().components().findFirst().get()))
.collect(Collectors.toList());
if (axioms.size() != 1) {
throw new IllegalStateException(
"There are must be exactly 1 range axiom for " + odp
+ " but found " + axioms);
}
// there is no doc on how to do this without nasty casts...
return ((OWLDatatypeImpl) axioms.get(0).getRange()).getIRI().toString();
}
/**
*
* @param c an {@link OWLClass} object
* @return true iff c equals to Owl:Thing.
*/
public boolean isThing(OWLClass c) {
return thing.equals(c);
}
/**
* @param odp
* @return the annotation for this OWLDataProperty.
*/
private String getCommentAnnotation(OWLDataProperty odp) {
List annotationaxioms = ontology
.annotationAssertionAxioms(odp.getIRI())
.collect(Collectors.toList());
if (annotationaxioms.size() != 1) {
// we don't allow because we can't figure out the range in that
// case.
throw new IllegalStateException(
"There must be exactly 1 comment for " + odp
+ " but found " + annotationaxioms);
}
// there is no doc on how to do this without nasty casts...
return ((OWLLiteralImplString) annotationaxioms.get(0).getAnnotation()
.annotationValue()).getLiteral();
}
}