io.konig.shacl.ClassStructure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of konig-core Show documentation
Show all versions of konig-core Show documentation
A library for core classes (Graph, Vertex, Edge, etc.)
package io.konig.shacl;
/*
* #%L
* Konig Core
* %%
* Copyright (C) 2015 - 2017 Gregory McFall
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.konig.core.*;
import org.openrdf.model.Namespace;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.OWL;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.konig.core.impl.RdfUtil;
import io.konig.core.util.SimpleValueMap;
import io.konig.core.util.ValueFormat;
import io.konig.core.vocab.Konig;
import io.konig.core.vocab.OwlVocab;
import io.konig.core.vocab.Schema;
/**
* An entity that manages a collection of "logical" shapes for each known OWL class.
* The logical shape contains the union of all property constraints from across the various
* shapes of a given OWL class.
* @author Greg McFall
*
*/
public class ClassStructure {
private static final Logger logger = LoggerFactory.getLogger(ClassStructure.class);
private static final Integer ZERO = new Integer(0);
private Map shapeMap = new HashMap<>();
private Map propertyMap = new HashMap<>();
private Shape nullShape = new Shape(Konig.NullShape);
private ValueFormat iriTemplate;
private boolean failOnDatatypeConflict = false;
private OwlReasoner reasoner;
public ClassStructure() {
}
public ClassStructure(ValueFormat iriTemplate) {
this.iriTemplate = iriTemplate;
}
public OwlReasoner getReasoner() {
return reasoner;
}
public ClassStructure(ValueFormat iriTemplate, ShapeManager shapeManager, OwlReasoner reasoner) {
this.iriTemplate = iriTemplate;
init(shapeManager, reasoner);
}
public Collection listProperties() {
return propertyMap.values();
}
public PropertyStructure getProperty(URI propertyId) {
return propertyMap.get(propertyId);
}
public Set domainIncludes(URI propertyId) {
PropertyStructure info = propertyMap.get(propertyId);
if (info == null) {
throw new KonigException("Property not found: " + propertyId);
}
Set set = info.getDomainIncludes();
if (set == null) {
set = new HashSet<>();
if (info.getDomain()!=null) {
set.add(info.getDomain());
}
}
return set;
}
public Collection listClassShapes() {
return shapeMap.values();
}
public Set listClasses() {
return shapeMap.keySet();
}
public void init(ShapeManager shapeManager, OwlReasoner reasoner) {
if(reasoner != null && reasoner.getGraph() != null && reasoner.getGraph().getNamespaceManager() != null) {
reasoner.getGraph().getNamespaceManager().add("owl", "http://www.w3.org/2002/07/owl#");
}
Builder builder = new Builder(shapeManager, reasoner);
builder.build();
}
/**
* Get the "canonical" shape for a given OWL class.
* @param classId The id for the OWL class whose shape is to be returned.
* @return The shape with the following characteristics: (1) has a PropertyConstraint
* for each property that is locally declared (2) has an "OR" list of Shapes for all subclasses,
* (3) has an "AND" list of Shapes for all superclasses.
*/
public Shape getShapeForClass(Resource classId) throws OwlClassNotFoundException {
Shape result = shapeForClass(classId);
if (result == null) {
throw new OwlClassNotFoundException(classId);
}
return result;
}
public Shape shapeForClass(Resource classId) {
if (classId == null) {
throw new IllegalArgumentException("classId cannot be null");
}
if (classId.equals(Schema.Thing)) {
classId = OWL.THING;
}
Shape result = shapeMap.get(classId);
return result;
}
public boolean isNullShape(Shape shape) {
return Konig.NullShape.equals(shape.getId());
}
public boolean hasSubClass(Shape shape) {
OrConstraint or = shape.getOr();
return or!=null && !or.getShapes().isEmpty();
}
public Map getPropertyMap(Shape shape) {
Map map = new HashMap<>();
addProperties(map, shape);
return map;
}
private void addProperties(Map map, Shape shape) {
for (PropertyConstraint p : shape.getProperty()) {
URI predicate = p.getPredicate();
if (predicate != null && !map.containsKey(predicate)) {
map.put(predicate, p);
}
}
List superList = superClasses(shape);
for (Shape s : superList) {
addProperties(map, s);
}
}
/**
* Get the properties declared by a given shape or any of its ancestors.
* @param shape The shape whose properties are to be returned.
*/
public List getProperties(Shape shape) {
// if (!hasSuperClass(shape)) {
// return shape.getProperty();
// }
// List list = new ArrayList<>();
// LinkedList stack = new LinkedList<>();
// stack.add(shape);
// while (!stack.isEmpty()) {
// Shape s = stack.removeFirst();
// for (PropertyConstraint p : s.getProperty()) {
// if (!contains(list, p)) {
// list.add(p);
// }
// }
// stack.addAll(superClasses(s));
// }
//
// return list;
Map map = getPropertyMap(shape);
return new ArrayList<>(map.values());
}
public List superClasses(Shape shape) {
List list = new ArrayList<>();
addSuper(list, shape);
return list;
}
private void addSuper(List list, Shape shape) {
if (hasSuperClass(shape)) {
for (Shape s : shape.getAnd().getShapes()) {
if (!contains(list, s)) {
list.add(s);
}
addSuper(list, s);
}
}
}
/**
* Get the set consisting of a given shape plus all of its superclasses and subclasses.
*/
public List transitiveClosure(Shape s) {
List list = superClasses(s);
list.addAll(subClasses(s));
list.add(s);
return list;
}
public List subClasses(Shape shape) {
List list = new ArrayList<>();
addSubclassShapes(list, shape);
return list;
}
private void addSubclassShapes(List list, Shape shape) {
OrConstraint or = shape.getOr();
if (or != null) {
for (Shape s : or.getShapes()) {
if (!isNullShape(s) && !contains(list, s)) {
list.add(s);
}
addSubclassShapes(list, s);
}
}
}
private boolean contains(List list, Shape shape) {
for (Shape s : list) {
if (s == shape || shape.getId().equals(s.getId())) {
return true;
}
}
return false;
}
/**
* Test whether a given class or one of its ancestors declares a specific property.
* @param owlClass The class to be tested
* @param property The specific property to be tested
* @return True if owlClass or one of its ancestors declares the property, and false otherwise.
*/
public boolean hasProperty(Resource owlClass, URI property) {
Shape shape = getShapeForClass(owlClass);
if (shape == null) {
throw new KonigException("Class not found: " + owlClass);
}
return hasProperty(shape, property);
}
/**
* Test whether a given class or one of its ancestors declares a specific property.
* @param canonicalShape The canonical shape for the class to be tested
* @param property The specific property to be tested
* @return True if the canonical shape or one of its ancestors declares the property, and false otherwise.
*/
public boolean hasProperty(Shape canonicalShape, URI property) {
if (canonicalShape.getPropertyConstraint(property)!=null) {
return true;
}
AndConstraint and = canonicalShape.getAnd();
if (and != null) {
for (Shape ancestor : and.getShapes()) {
if (hasProperty(ancestor, property)) {
return true;
}
}
}
return false;
}
/**
* Test whether some ancestor of a given class declares a specific property
* @param owlClass The given class to be tested.
* @param property The specific property to be tested
* @return True if some ancestor of the supplied owlClass declares the specified property, and false otherwise.
*/
public boolean ancestorHasProperty(Resource owlClass, URI property) throws OwlClassNotFoundException {
Shape shape = getShapeForClass(owlClass);
AndConstraint and = shape.getAnd();
if (and != null) {
for (Shape ancestor : and.getShapes()) {
if (hasProperty(ancestor, property)) {
return true;
}
}
}
return false;
}
public boolean hasSuperClass(Shape shape) {
AndConstraint and = shape.getAnd();
if (and != null) {
List list = and.getShapes();
if (list.size()>1 || !OWL.THING.equals(list.get(0).getTargetClass())) {
return true;
}
}
return false;
}
private class Builder {
private ShapeManager shapeManager;
public Builder(ShapeManager shapeManager, OwlReasoner reasoner) {
this.shapeManager = shapeManager;
ClassStructure.this.reasoner = reasoner;
}
private void build() {
scanShapes();
scanProperties();
scanClasses();
buildShapes();
buildHierarchy();
injectThingAndNullShape();
bubbleUp();
bubbleDown();
}
private void bubbleDown() {
for (Shape shape : listClassShapes()) {
bubbleDown(shape);
}
}
private void bubbleDown(Shape shape) {
for (PropertyConstraint p : shape.getProperty()) {
bubbleDown(shape, p);
}
}
private void bubbleUp() {
for (Shape shape : listClassShapes()) {
bubbleUp(shape);
}
}
private void bubbleUp(Shape shape) {
for (PropertyConstraint p : shape.getProperty()) {
bubbleUp(shape, p);
}
}
private void bubbleDown(Shape shape, PropertyConstraint p) {
URI predicate = p.getPredicate();
Integer maxCount = p.getMaxCount();
if (maxCount != null) {
List list = superClasses(shape);
for (Shape superclass : list) {
PropertyConstraint q = superclass.getPropertyConstraint(predicate);
if (q!=null && q.getMaxCount() == null) {
p.setMaxCount(null);
break;
}
}
}
}
private void bubbleUp(Shape shape, PropertyConstraint p) {
URI predicate = p.getPredicate();
Integer maxCount = p.getMaxCount();
if (maxCount != null) {
List list = subClasses(shape);
for (Shape subClass : list) {
PropertyConstraint q = subClass.getPropertyConstraint(predicate);
if (q!=null && q.getMaxCount() == null) {
p.setMaxCount(null);
break;
}
}
}
}
private void scanShapes() {
Graph graph = reasoner.getGraph();
for (Shape shape : shapeManager.listShapes()) {
URI targetClass = shape.getTargetClass();
if (targetClass != null) {
graph.edge(targetClass, RDF.TYPE, OWL.CLASS);
Shape classShape = produceShape(targetClass);
addProperties(shape, classShape);
}
}
}
private void addProperties(Shape shape, Shape classShape) {
for (PropertyConstraint source : shape.getProperty()) {
applyGlobalProperty(shape, source);
URI predicate = source.getPredicate();
PropertyConstraint target = classShape.getPropertyConstraint(predicate);
if (target == null) {
PropertyConstraint p = source.clone();
Shape valueShape = p.getShape();
if (valueShape != null) {
Resource valueClass = p.getValueClass();
if (valueClass == null) {
p.setValueClass(valueShape.getTargetClass());
}
p.setShape(null);
}
classShape.add(p);
} else {
merge(source, target);
}
}
}
private void applyGlobalProperty(Shape shape, PropertyConstraint p) {
URI targetClass = shape.getTargetClass();
URI predicate = p.getPredicate();
if (predicate != null) {
PropertyStructure info = produceProperty(predicate);
info.addShape(shape);
setDomain(info, targetClass);
setDatatype(info, p.getDatatype());
setMaxCount(info, p.getMaxCount());
setMinCount(info, p.getMinCount());
setEquivalentPath(info, p.getEquivalentPath());
setValueClass(info, p.getValueClass());
if (p.getShape() != null) {
setValueClass(info, p.getShape().getTargetClass());
}
}
if (p.getValueClass() instanceof URI) {
reasoner.getGraph().edge(p.getValueClass(), RDF.TYPE, OWL.CLASS);
}
}
private void merge(PropertyConstraint source, PropertyConstraint target) {
setDatatype(source, target);
setEquivalentPath(source, target);
setMaxCount(source, target);
setMinCount(source, target);
setValueClass(source, target);
}
private void setValueClass(PropertyConstraint source, PropertyConstraint p) {
Resource value = source.getValueClass();
if (value == null) {
Shape shape = source.getShape();
if (shape != null) {
value = shape.getTargetClass();
}
}
if (value != null) {
if (p.getDatatype() != null) {
p.setDatatype(null);
p.setValueClass(OWL.THING);
return;
}
Resource valueClass = p.getValueClass();
if (valueClass == null) {
p.setValueClass(value);
} else {
Resource result = reasoner.leastCommonSuperClass(valueClass, value);
p.setValueClass(result);
}
}
}
private void setMinCount(PropertyConstraint source, PropertyConstraint target) {
Integer a = minCount(source);
Integer b = minCount(target);
target.setMinCount(ab)) {
target.setMaxCount(a);
}
}
private void setEquivalentPath(PropertyConstraint source, PropertyConstraint target) {
if (source.getEquivalentPath() != null) {
target.setEquivalentPath(source.getEquivalentPath());
}
}
private void setDatatype(PropertyConstraint source, PropertyConstraint p) {
URI value = source.getDatatype();
if (value != null) {
if (p.getPredicate().equals(RDF.TYPE)) {
p.setDatatype(null);
p.setValueClass(OWL.CLASS);
return;
}
if (p.getValueClass()!=null) {
p.setDatatype(null);
p.setValueClass(OWL.THING);
}
Resource prior = p.getDatatype();
if (prior!=null && !prior.equals(value)) {
if (failOnDatatypeConflict) {
throw new KonigException("Conflicting datatype on property <" + p.getPredicate() + ">: Found <" +
prior + "> and <" + value + ">"
);
} else {
logger.warn("Conflicting datatype on property <{}>: Found <{}> and <{}>. Using rdfs:Literal", p.getPredicate().stringValue(), prior.stringValue(), value.stringValue());
value = RDFS.LITERAL;
}
}
p.setDatatype(value);
}
}
private void subClassOf(Shape subtype, Shape supertype) {
OrConstraint or = supertype.getOr();
if (or == null) {
or = new OrConstraint();
supertype.setOr(or);
}
or.add(subtype);
AndConstraint and = subtype.getAnd();
if (and == null) {
and = new AndConstraint();
subtype.setAnd(and);
}
and.add(supertype);
}
private void injectThingAndNullShape() {
Shape thing = produceShape(OWL.THING);
for (Shape shape : shapeMap.values()) {
if (shape != thing) {
if (!hasSuperClass(shape)) {
subClassOf(shape, thing);
}
OrConstraint or = shape.getOr();
if (or != null) {
if (or.getShapes().size()==1) {
or.add(nullShape);
}
}
}
}
}
private void scanClasses() {
List classList = reasoner.getGraph().v(OWL.CLASS).in(RDF.TYPE).toVertexList();
for (Vertex v : classList) {
produceShape(v.getId());
}
}
private void buildHierarchy() {
List shapeList = new ArrayList<>(shapeMap.values());
for (Shape shape : shapeList) {
URI targetClass = shape.getTargetClass();
if (targetClass != null) {
Set subClasses = reasoner.subClasses(targetClass);
if (!subClasses.isEmpty()) {
OrConstraint orList = new OrConstraint();
shape.setOr(orList);
for (URI subclassId : subClasses) {
Shape s = produceShape(subclassId);
orList.add(s);
}
}
Set superClasses = reasoner.superClasses(targetClass);
if (!superClasses.isEmpty()) {
AndConstraint andList = new AndConstraint();
shape.setAnd(andList);
for (URI superclassId : superClasses) {
andList.add(produceShape(superclassId));
}
}
}
}
}
private void buildShapes() {
for (PropertyStructure info : propertyMap.values()) {
Resource domain = info.getDomain();
URI predicate = info.getPredicate();
if (domain != null) {
Shape shape = produceShape(domain);
if (shape.getPropertyConstraint(predicate) == null) {
shape.add(info.asPropertyConstraint());
}
}
Set domainIncludes = info.getDomainIncludes();
if (domainIncludes != null) {
for (Resource owlClass : domainIncludes) {
Shape shape = produceShape(owlClass);
if (shape.getPropertyConstraint(predicate) == null) {
shape.add(info.asPropertyConstraint());
}
}
}
}
}
private void scanProperties() {
List propertyList = reasoner.getGraph().v(RDF.PROPERTY).union(
OWL.DATATYPEPROPERTY, OWL.OBJECTPROPERTY, OWL.FUNCTIONALPROPERTY, OWL.INVERSEFUNCTIONALPROPERTY, OWL.SYMMETRICPROPERTY,
OWL.TRANSITIVEPROPERTY, OWL.ANNOTATIONPROPERTY, OWL.DEPRECATEDPROPERTY, OWL.ONTOLOGYPROPERTY, OwlVocab.ReflexiveProperty,
OwlVocab.IrreflexiveProperty, OwlVocab.AsymetricProperty).in(RDF.TYPE).distinct().toVertexList();
for (Vertex v : propertyList) {
if (v.getId() instanceof URI) {
URI propertyId = (URI) v.getId();
boolean isFunctional = reasoner.instanceOf(propertyId, OWL.FUNCTIONALPROPERTY);
URI domain = v.getURI(RDFS.DOMAIN);
URI range = v.getURI(RDFS.RANGE);
String comment = RdfUtil.getDescription(v);
PropertyStructure p = produceProperty(propertyId);
p.setDescription(comment);
if (domain != null) {
p.setDomain(domain);
p.setDomainLocked(true);
} else {
Set set = v.asTraversal().out(Schema.domainIncludes).toUriSet();
if (!set.isEmpty()) {
for (URI uri : set) {
p.domainIncludes(uri);
}
p.setDomainIncludesLocked(true);
}
}
if (isFunctional) {
setMaxCount(p, 1);
}
if (range != null) {
if (reasoner.isSubclassOfLiteral(range)) {
setDatatype(p, range);
} else {
setValueClass(p, range);
}
}
}
}
}
private void setDomain(PropertyStructure p, Resource value) {
if (value != null) {
if (Schema.Thing.equals(value)) {
value = OWL.THING;
}
Resource prior = p.getDomain();
if (!value.equals(prior)) {
if(p.getDomainIncludes() != null) {
Iterator sequence = p.getDomainIncludes().iterator();
while (sequence.hasNext()) {
Resource other = sequence.next();
if (other.equals(value)) {
return;
}
if (p.isDomainIncludesLocked()) {
if (reasoner.isSubClassOf(value, other)) {
return;
}
if (reasoner.isSubClassOf(other, value)) {
sequence.remove();
}
} else {
Resource common = reasoner.leastCommonSuperClass(value, other);
if (common.equals(other)) {
return;
}
if (!common.equals(OWL.THING)) {
sequence.remove();
value = common;
}
}
}
p.domainIncludes(value);
} else if (prior == null) {
p.setDomain(value);
} else {
if (OWL.THING.equals(prior)) {
p.domainIncludes(value);
} else if (OWL.THING.equals(value)) {
p.domainIncludes(prior);
} else {
Resource common = reasoner.leastCommonSuperClass(prior, value);
if (OWL.THING.equals(common)) {
p.domainIncludes(prior);
p.domainIncludes(value);
if (!p.isDomainLocked()) {
p.setDomain(null);
}
} else if (p.isDomainLocked()) {
p.domainIncludes(common);
} else {
p.setDomain(common);
}
}
}
}
}
}
private void setValueClass(PropertyStructure p, Resource value) {
if (value != null) {
if (p.getDatatype() != null) {
throw new KonigException("Property <" + p.getPredicate() + "> has sh:datatype <" + p.getDatatype() + "> and sh:class <" +
value + ">"
);
}
Resource valueClass = p.getValueClass();
if (valueClass == null) {
p.setValueClass(value);
} else {
Resource result = reasoner.leastCommonSuperClass(valueClass, value);
p.setValueClass(result);
}
}
}
private void setDatatype(PropertyStructure p, URI value) {
if (value != null) {
if (p.getPredicate().equals(RDF.TYPE)) {
p.setDatatype(null);
p.setValueClass(OWL.CLASS);
return;
}
if (p.getValueClass() != null) {
throw new KonigException("Property <" + p.getPredicate() + "> has sh:datatype <" + value + "> and sh:class <" +
p.getValueClass() + ">"
);
}
Resource prior = p.getDatatype();
if (prior!=null && !prior.equals(value)) {
if (failOnDatatypeConflict) {
throw new KonigException("Conflicting datatype on property <" + p.getPredicate() + ">: Found <" +
prior + "> and <" + value + ">"
);
} else {
logger.warn("Conflicting datatype on property <{}>: Found <{}> and <{}>. Using rdfs:Literal", p.getPredicate().stringValue(), prior.stringValue(), value.stringValue());
value = RDFS.LITERAL;
}
}
p.setDatatype(value);
}
}
private void setEquivalentPath(PropertyStructure p, Path path) {
if (path != null) {
p.setEquivalentPath(path);
}
}
private void setMaxCount(PropertyStructure p, Integer value) {
if (value != null) {
Integer prior = p.getMaxCount();
if (prior==null || value>prior) {
p.setMaxCount(value);
}
}
}
private void setMinCount(PropertyStructure info, Integer value) {
if (value != null) {
Integer prior = info.getMinCount();
if (prior==null || value listSubclasses(Shape shape) {
List list = new ArrayList<>();
addSubclasses(list, shape);
return list;
}
private void addSubclasses(List list, Shape shape) {
if (shape.getOr() != null) {
for (Shape s : shape.getOr().getShapes()) {
if (nullShape == s) {
continue;
}
URI targetClass = s.getTargetClass();
list.add(targetClass);
addSubclasses(list, s);
}
}
}
public Shape getNullShape() {
return nullShape;
}
}