/* * Copyright (c) 2007-2009, James Leigh All rights reserved. * Copyright (c) 2011 Talis Inc., Some rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package org.openrdf.repository.object.compiler; import info.aduna.net.ParsedURI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openrdf.model.BNode; import org.openrdf.model.Literal; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.ValueFactory; import org.openrdf.model.impl.URIImpl; import org.openrdf.model.impl.ValueFactoryImpl; import org.openrdf.model.vocabulary.OWL; import org.openrdf.model.vocabulary.RDF; import org.openrdf.model.vocabulary.RDFS; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.repository.object.vocabulary.MSG; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Applies a series of rules against the ontology, making it easier to convert * into Java classes. This includes applying some OWL reasoning on properties, * renaming anonymous and foreign classes. * * @author James Leigh * */ public class OwlNormalizer { private final Logger logger = LoggerFactory.getLogger(OwlNormalizer.class); private RDFDataSource ds; private Set<URI> anonymousClasses = new HashSet<URI>(); private Map<URI, URI> aliases = new HashMap<URI, URI>(); private Map<String, String> implNames = new HashMap<String, String>(); private Set<String> commonNS = new HashSet<String>(Arrays.asList( RDF.NAMESPACE, RDFS.NAMESPACE, OWL.NAMESPACE)); public OwlNormalizer(RDFDataSource ds) { this.ds = ds; } public URI getOriginal(URI alias) { if (anonymousClasses.contains(alias)) return null; if (aliases.containsKey(alias)) return aliases.get(alias); return alias; } public Map<URI, URI> getAliases() { return aliases; } public Set<URI> getAnonymousClasses() { return anonymousClasses; } public Map<String, String> getImplNames() { return implNames; } public void normalize() { infer(); createJavaAnnotations(); checkPropertyDomains(); checkPropertyRanges(); subClassIntersectionOf(); hasValueFromList(); subClassOneOf(); distributeEquivalentClasses(); renameAnonymousClasses(); mergeUnionClasses(); distributeSubMessage(); checkMessageTargets(); } /** * Treat owl:complementOf, owl:intersectionOf, owl:oneOf, and owl:unionOf as * annotations so they will be saved in the concept header. */ private void createJavaAnnotations() { if (ds.contains(RDFS.LITERAL, null, null)) { ds.add(RDFS.LITERAL, RDF.TYPE, RDFS.DATATYPE); } if (ds.contains(null, RDFS.SUBCLASSOF, null)) { ds.add(RDFS.SUBCLASSOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, RDFS.SUBPROPERTYOF, null)) { ds.add(RDFS.SUBPROPERTYOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, OWL.EQUIVALENTCLASS, null)) { ds.add(OWL.EQUIVALENTCLASS, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, OWL.COMPLEMENTOF, null)) { ds.add(OWL.COMPLEMENTOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.COMPLEMENTOF, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.COMPLEMENTOF, RDFS.RANGE, OWL.CLASS); } if (ds.contains(null, OWL.INTERSECTIONOF, null)) { ds.add(OWL.INTERSECTIONOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, OWL.UNIONOF, null)) { ds.add(OWL.UNIONOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, OWL.HASVALUE, null)) { ds.add(OWL.HASVALUE, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.HASVALUE, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.ONEOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } else if (ds.contains(null, OWL.ONEOF, null)) { ds.add(OWL.ONEOF, RDF.TYPE, OWL.ANNOTATIONPROPERTY); } if (ds.contains(null, OWL.SOMEVALUESFROM, null)) { ds.add(OWL.SOMEVALUESFROM, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.SOMEVALUESFROM, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.SOMEVALUESFROM, RDFS.RANGE, OWL.CLASS); } if (ds.contains(null, OWL.MAXCARDINALITY, null)) { ds.add(OWL.MAXCARDINALITY, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.MAXCARDINALITY, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.MAXCARDINALITY, RDFS.RANGE, XMLSchema.NON_NEGATIVE_INTEGER); } if (ds.contains(null, OWL.MINCARDINALITY, null)) { ds.add(OWL.MINCARDINALITY, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.MINCARDINALITY, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.MINCARDINALITY, RDFS.RANGE, XMLSchema.NON_NEGATIVE_INTEGER); } if (ds.contains(null, OWL.CARDINALITY, null)) { ds.add(OWL.CARDINALITY, RDF.TYPE, OWL.FUNCTIONALPROPERTY); ds.add(OWL.CARDINALITY, RDF.TYPE, OWL.ANNOTATIONPROPERTY); ds.add(OWL.CARDINALITY, RDFS.RANGE, XMLSchema.NON_NEGATIVE_INTEGER); } } private void infer() { logger.debug("inferring"); ValueFactory vf = getValueFactory(); propagateSubClassType(RDFS.CLASS); symmetric(OWL.INVERSEOF); symmetric(OWL.EQUIVALENTCLASS); symmetric(OWL.EQUIVALENTPROPERTY); symmetric(OWL.DISJOINTWITH); setSubjectType(RDF.FIRST, null, RDF.LIST); setSubjectType(RDF.REST, null, RDF.LIST); setSubjectType(RDFS.SUBCLASSOF, null, OWL.CLASS); setSubjectType(OWL.ONEOF, null, OWL.CLASS); setSubjectType(OWL.UNIONOF, null, OWL.CLASS); setSubjectType(OWL.DISJOINTWITH, null, OWL.CLASS); setSubjectType(OWL.COMPLEMENTOF, null, OWL.CLASS); setSubjectType(OWL.EQUIVALENTCLASS, null, OWL.CLASS); setSubjectType(OWL.INTERSECTIONOF, null, OWL.CLASS); setSubjectType(OWL.ONPROPERTY, null, OWL.RESTRICTION); setSubjectType(RDF.TYPE, RDFS.CLASS, OWL.CLASS); setSubjectType(RDF.TYPE, OWL.DEPRECATEDCLASS, OWL.CLASS); setSubjectType(RDF.TYPE, OWL.RESTRICTION, OWL.CLASS); setSubjectType(RDF.TYPE, OWL.ANNOTATIONPROPERTY, RDF.PROPERTY); setSubjectType(RDF.TYPE, OWL.DEPRECATEDPROPERTY, RDF.PROPERTY); setSubjectType(RDF.TYPE, OWL.OBJECTPROPERTY, RDF.PROPERTY); setSubjectType(RDF.TYPE, OWL.DATATYPEPROPERTY, RDF.PROPERTY); setSubjectType(RDF.TYPE, OWL.FUNCTIONALPROPERTY, RDF.PROPERTY); setObjectType(RDFS.SUBCLASSOF, OWL.CLASS); setObjectType(OWL.ALLVALUESFROM, OWL.CLASS); setObjectType(OWL.ONEOF, RDF.LIST); setObjectType(OWL.UNIONOF, RDF.LIST); setObjectType(OWL.INTERSECTIONOF, RDF.LIST); setObjectType(RDFS.ISDEFINEDBY, OWL.ONTOLOGY); setSubjectType(OWL.INVERSEOF, null, OWL.OBJECTPROPERTY); setObjectType(OWL.INVERSEOF, OWL.OBJECTPROPERTY); setObjectType(RDFS.RANGE, OWL.CLASS); setObjectType(RDFS.DOMAIN, OWL.CLASS); setSubjectType(RDFS.RANGE, null, RDF.PROPERTY); setSubjectType(RDFS.DOMAIN, null, RDF.PROPERTY); setSubjectType(RDFS.SUBPROPERTYOF, null, RDF.PROPERTY); setObjectType(RDFS.SUBPROPERTYOF, RDF.PROPERTY); setDatatype(vf, OWL.CARDINALITY, XMLSchema.NON_NEGATIVE_INTEGER); setDatatype(vf, OWL.MINCARDINALITY, XMLSchema.NON_NEGATIVE_INTEGER); setDatatype(vf, OWL.MAXCARDINALITY, XMLSchema.NON_NEGATIVE_INTEGER); setMemberType(OWL.UNIONOF, OWL.CLASS); setMemberType(OWL.INTERSECTIONOF, OWL.CLASS); } private void setMemberType(URI pred, URI type) { for (Value list : ds.match(null, pred, null).objects()) { if (list instanceof Resource) { RDFList members = new RDFList(ds, (Resource) list); for (Value member : members.asList()) { if (member instanceof Resource) { ds.add((Resource) member, RDF.TYPE, type); } } } } } private void propagateSubClassType(Resource classDef) { for (Resource c : findClasses(Collections.singleton(classDef))) { if (c.equals(RDFS.DATATYPE)) continue; for (Statement stmt : ds.match(null, RDF.TYPE, c)) { Resource subj = stmt.getSubject(); ds.add(subj, RDF.TYPE, classDef); } } } private Set<Resource> findClasses(Collection<Resource> classes) { Set<Resource> set = new HashSet<Resource>(classes); for (Resource c : classes) { for (Statement stmt : ds.match(null, RDFS.SUBCLASSOF, c)) { Resource subj = stmt.getSubject(); set.add(subj); } } if (set.size() > classes.size()) { return findClasses(set); } else { return set; } } private void symmetric(URI pred) { for (Statement stmt : ds.match(null, pred, null)) { if (stmt.getObject() instanceof Resource) { Resource subj = (Resource) stmt.getObject(); ds.add(subj, pred, stmt.getSubject()); } else { logger.warn("Invalid statement {}", stmt); } } } private void setSubjectType(URI pred, Value obj, URI type) { for (Statement stmt : ds.match(null, pred, obj)) { ds.add(stmt.getSubject(), RDF.TYPE, type); } } private void setObjectType(URI pred, URI type) { for (Statement st : ds.match(null, pred, null)) { if (st.getObject() instanceof Resource) { Resource subj = (Resource) st.getObject(); ds.add(subj, RDF.TYPE, type); } else { logger.warn("Invalid statement {}", st); } } } private void setDatatype(ValueFactory vf, URI pred, URI datatype) { for (Statement stmt : ds.match(null, pred, null)) { String label = ((Literal) stmt.getObject()).getLabel(); Literal literal = vf.createLiteral(label, datatype); ds.remove(stmt.getSubject(), pred, stmt.getObject()); ds.add(stmt.getSubject(), pred, literal); } } private void checkPropertyDomains() { loop: for (Statement st : ds.match(null, RDF.TYPE, RDF.PROPERTY)) { Resource p = st.getSubject(); if (!ds.contains(p, RDFS.DOMAIN, null)) { for (Value sup : ds.match(p, RDFS.SUBPROPERTYOF, null).objects()) { for (Value obj : ds.match(sup, RDFS.DOMAIN, null).objects()) { ds.add(p, RDFS.DOMAIN, obj); continue loop; } } ds.add(p, RDFS.DOMAIN, RDFS.RESOURCE); if (!ds.contains(RDFS.RESOURCE, RDF.TYPE, OWL.CLASS)) { ds.add(RDFS.RESOURCE, RDF.TYPE, OWL.CLASS); } } } } private void checkPropertyRanges() { loop: for (Statement st : ds.match(null, RDF.TYPE, RDF.PROPERTY)) { Resource p = st.getSubject(); if (!ds.contains(p, RDFS.RANGE, null)) { for (Value sup : ds.match(p, RDFS.SUBPROPERTYOF, null).objects()) { for (Value obj : ds.match(sup, RDFS.RANGE, null).objects()) { ds.add(p, RDFS.RANGE, obj); continue loop; } } ds.add(p, RDFS.RANGE, RDFS.RESOURCE); } } } private void distributeSubMessage() { boolean changed = false; for (Resource msg : ds.match(null, RDFS.SUBCLASSOF, MSG.MESSAGE) .subjects()) { for (Resource sub : ds.match(null, RDFS.SUBCLASSOF, msg).subjects()) { if (!ds.contains(sub, RDFS.SUBCLASSOF, MSG.MESSAGE)) { ds.add(sub, RDFS.SUBCLASSOF, MSG.MESSAGE); changed = true; } } } if (changed) { distributeSubMessage(); } } private void checkMessageTargets() { for (Resource msg : ds.match(null, RDFS.SUBCLASSOF, MSG.MESSAGE) .subjects()) { getOrAddTargetRestriction(msg); } } private Value getOrAddTargetRestriction(Resource msg) { for (Value res : ds.match(msg, RDFS.SUBCLASSOF, null).objects()) { if (ds.contains(res, OWL.ONPROPERTY, MSG.TARGET)) { return res; } } Map<Value,Value> restrictions = new LinkedHashMap<Value, Value>(); for (Value sup : ds.match(msg, RDFS.SUBCLASSOF, null).objects()) { if (sup instanceof URI) { Value res = getOrAddTargetRestriction((URI) sup); if (res != null) { restrictions.put(sup, res); } } } if (!restrictions.isEmpty()) { loop: for (Value sup1 : restrictions.keySet()) { for (Value sup2 : restrictions.keySet()) { if (sup1 != sup2 && ds.contains(sup2, RDFS.SUBCLASSOF, sup1)) { continue loop; } } Value res = restrictions.get(sup1); ds.add(msg, RDFS.SUBCLASSOF, res); return res; } } ValueFactory vf = getValueFactory(); BNode res = vf.createBNode(); ds.add(msg, RDFS.SUBCLASSOF, res); ds.add(res, RDF.TYPE, OWL.RESTRICTION); ds.add(res, OWL.ONPROPERTY, MSG.TARGET); ds.add(res, OWL.ALLVALUESFROM, RDFS.RESOURCE); ds.add(RDFS.RESOURCE, RDF.TYPE, OWL.CLASS); return res; } private ValueFactory getValueFactory() { return ValueFactoryImpl.getInstance(); } private void hasValueFromList() { ValueFactory vf = getValueFactory(); for (Statement st : ds.match(null, OWL.HASVALUE, null)) { Resource res = st.getSubject(); Value obj = st.getObject(); if (obj instanceof Resource) { BNode node = vf.createBNode(); ds.add(res, OWL.ALLVALUESFROM, node); ds.add(node, RDF.TYPE, OWL.CLASS); BNode list = vf.createBNode(); ds.add(node, OWL.ONEOF, list); ds.add(list, RDF.TYPE, RDF.LIST); ds.add(list, RDF.FIRST, obj); ds.add(list, RDF.REST, RDF.NIL); for (Value type : ds.match(obj, RDF.TYPE, null).objects()) { ds.add(node, RDFS.SUBCLASSOF, type); } for (Value prop : ds.match(res, OWL.ONPROPERTY, null).objects()) { for (Value range : ds.match(prop, RDFS.RANGE, null).objects()) { ds.add(node, RDFS.SUBCLASSOF, range); } for (Resource cls : ds.match(null, RDFS.SUBCLASSOF, res).subjects()) { for (Value sup : findSuperClasses(cls)) { if (!sup.equals(res) && !ds.match(sup, OWL.ONPROPERTY, prop).isEmpty()) { for (Value from : ds.match(sup, OWL.ALLVALUESFROM, null).objects()) { ds.add(node, RDFS.SUBCLASSOF, from); } } } } } } } } private void subClassOneOf() { for (Statement st : ds.match(null, OWL.ONEOF, null)) { Set<Value> common = null; for (Value of : new RDFList(ds, st.getObject()).asList()) { Set<Value> types = ds.match(of, RDF.TYPE, null).objects(); if (types.isEmpty()) { common = Collections.emptySet(); } else { Set<Value> supers = new HashSet<Value>(); for (Value type : types) { if (type instanceof Resource) { supers.addAll(findSuperClasses((Resource) type)); } } if (common == null) { common = new HashSet<Value>(supers); } else { common.retainAll(supers); } } } if (common != null) { for (Value s : common) { ds.add(st.getSubject(), RDFS.SUBCLASSOF, s); if (OWL.CLASS.equals(s)) { ds.add(OWL.CLASS, RDF.TYPE, OWL.CLASS); } } } } } private void subClassIntersectionOf() { for (Statement st : ds.match(null, OWL.INTERSECTIONOF, null)) { if (st.getObject() instanceof Resource) { RDFList list = new RDFList(ds, (Resource) st.getObject()); for (Value member : list.asList()) { ds.add(st.getSubject(), RDFS.SUBCLASSOF, member); } } } } private void renameAnonymousClasses() { for (Resource res : ds.match(null, RDF.TYPE, OWL.CLASS).subjects()) { if (res instanceof URI) continue; // if not already moved nameAnonymous(res); } } private URI nameAnonymous(Resource clazz) { for (Value eq : ds.match(clazz, OWL.EQUIVALENTCLASS, null).objects()) { if (eq instanceof URI) { nameClass(clazz, (URI) eq); return (URI) eq; } } Resource unionOf = ds.match(clazz, OWL.UNIONOF, null).objectResource(); if (unionOf != null) { return renameClass("", clazz, "Or", new RDFList(ds, unionOf) .asList()); } Resource intersectionOf = ds.match(clazz, OWL.INTERSECTIONOF, null) .objectResource(); if (intersectionOf != null) { return renameClass("", clazz, "And", new RDFList(ds, intersectionOf).asList()); } Resource oneOf = ds.match(clazz, OWL.ONEOF, null).objectResource(); if (oneOf != null) { return renameClass("Is", clazz, "Or", new RDFList(ds, oneOf) .asList()); } Resource complement = ds.match(clazz, OWL.COMPLEMENTOF, null) .objectResource(); if (complement != null) { URI comp = complement instanceof URI ? (URI) complement : null; if (comp == null) { comp = nameAnonymous(complement); if (comp == null) return null; } String name = "Not" + comp.getLocalName(); URI uri = new URIImpl(comp.getNamespace() + name); nameClass(clazz, uri); return uri; } if (ds.contains(clazz, MSG.MATCHING, null)) { return renameClass("", clazz, "Or", ds.match(clazz, MSG.MATCHING, null) .objects()); } return null; } private void distributeEquivalentClasses() { for (Statement st : ds.match(null, OWL.EQUIVALENTCLASS, null)) { Resource subj = st.getSubject(); Value equiv = st.getObject(); for (Value v : ds.match(equiv, OWL.EQUIVALENTCLASS, null).objects()) { ds.add(subj, OWL.EQUIVALENTCLASS, v); } ds.remove(subj, OWL.EQUIVALENTCLASS, subj); } for (Statement st : ds.match(null, OWL.EQUIVALENTCLASS, null)) { Resource subj = st.getSubject(); Value e = st.getObject(); if (!(subj instanceof URI)) continue; for (Value d : ds.match(e, OWL.DISJOINTWITH, null).objects()) { ds.add(subj, OWL.DISJOINTWITH, d); } if (ds.contains(e, OWL.INTERSECTIONOF, null)) { Resource cinter = ds.match(subj, OWL.INTERSECTIONOF, null) .objectResource(); Resource inter = ds.match(e, OWL.INTERSECTIONOF, null) .objectResource(); if (cinter == null) { ds.add(subj, OWL.INTERSECTIONOF, inter); } else if (!inter.equals(cinter)) { new RDFList(ds, cinter) .addAllOthers(new RDFList(ds, inter)); } } if (ds.contains(e, OWL.ONEOF, null)) { Resource co = ds.match(subj, OWL.ONEOF, null).objectResource(); Resource eo = ds.match(e, OWL.ONEOF, null).objectResource(); if (co == null) { ds.add(subj, OWL.ONEOF, ds.match(e, OWL.ONEOF, null) .objectResource()); } else if (!eo.equals(co)) { new RDFList(ds, co).addAllOthers(new RDFList(ds, eo)); } } if (ds.contains(e, OWL.UNIONOF, null)) { for (Value elist : ds.match(e, OWL.UNIONOF, null).objects()) { if (!ds.contains(subj, OWL.UNIONOF, null)) { ds.add(subj, OWL.UNIONOF, elist); } else if (!ds.contains(subj, OWL.UNIONOF, elist)) { for (Value clist : ds.match(subj, OWL.UNIONOF, null) .objects()) { new RDFList(ds, (Resource) clist) .addAllOthers(new RDFList(ds, (Resource) elist)); } } } } if (ds.contains(e, OWL.COMPLEMENTOF, null)) { if (!ds.contains(subj, OWL.COMPLEMENTOF, null)) { Resource comp = ds.match(e, OWL.COMPLEMENTOF, null) .objectResource(); ds.add(subj, OWL.COMPLEMENTOF, comp); } } if (ds.contains(e, OWL.DISJOINTWITH, null)) { for (Value d : ds.match(e, OWL.DISJOINTWITH, null).objects()) { ds.add(subj, OWL.DISJOINTWITH, d); } } if (ds.contains(e, RDFS.SUBCLASSOF, null)) { for (Value d : ds.match(e, RDFS.SUBCLASSOF, null).objects()) { ds.add(subj, RDFS.SUBCLASSOF, d); } } if (ds.contains(e, RDF.TYPE, OWL.RESTRICTION)) { ds.add(subj, RDFS.SUBCLASSOF, e); } } } private void mergeUnionClasses() { for (Resource subj : ds.match(null, RDF.TYPE, OWL.CLASS).subjects()) { List<Value> unionOf = new ArrayList<Value>(); for (Value obj : ds.match(subj, OWL.UNIONOF, null).objects()) { if (obj instanceof Resource) { List<? extends Value> list = new RDFList(ds, (Resource) obj).asList(); list.removeAll(unionOf); unionOf.addAll(list); } } if (!unionOf.isEmpty()) { Set<URI> common = findCommonSupers(unionOf); if (common.contains(subj)) { // if union contains itself then remove it ds.remove(subj, OWL.UNIONOF, null); continue; } else if (findCommon(common, unionOf) != null) { // if union includes the common super class then fold // together URI sup = findCommon(common, unionOf); ds.remove(subj, OWL.UNIONOF, null); nameClass(subj, sup); continue; } for (URI c : common) { ds.add(subj, RDFS.SUBCLASSOF, c); } for (Value ofValue : unionOf) { if (ds.contains(ofValue, RDF.TYPE, RDFS.DATATYPE) && ofValue instanceof URI) { // don't use anonymous class for datatypes nameClass(subj, (URI) ofValue); } else { ds.add((Resource) ofValue, RDFS.SUBCLASSOF, subj); } } } } } private URI findCommon(Set<URI> common, Collection<? extends Value> unionOf) { URI result = null; for (Value e : unionOf) { if (common.contains(e)) { result = (URI) e; } } return result; } private Set<URI> findCommonSupers(List<? extends Value> unionOf) { Set<? extends Value> common = null; for (Value of : unionOf) { if (of instanceof Resource) { Set<Value> supers = findSuperClasses((Resource) of); if (common == null) { common = new HashSet<Value>(supers); } else { common.retainAll(supers); } } } if (common == null) return Collections.emptySet(); Iterator<? extends Value> iter = common.iterator(); while (iter.hasNext()) { if (!(iter.next() instanceof URI)) { iter.remove(); } } return (Set<URI>) common; } private Set<Value> findSuperClasses(Resource of) { HashSet<Value> set = new HashSet<Value>(); set.add(of); return findSuperClasses(of, set); } private Set<Value> findSuperClasses(Resource of, Set<Value> supers) { Set<Value> parent = ds.match(of, RDFS.SUBCLASSOF, null).objects(); if (supers.addAll(parent)) { for (Value s : parent) { if (s instanceof Resource) { findSuperClasses((Resource) s, supers); } } } return supers; } private URI renameClass(String prefix, Resource clazz, String and, Collection<? extends Value> list) { String namespace = null; Set<String> names = new TreeSet<String>(); Set<String> others = new TreeSet<String>(); for (Value of : list) { URI uri = null; if (of instanceof URI) { uri = (URI) of; } else if (of instanceof Literal) { String label = of.stringValue(); StringBuilder sb = new StringBuilder(); if (!label.contains(":")) { sb.append(getMatchNamespace(clazz)); } if (label.startsWith("*")) { sb.append(label.replace("*", "Star")); } else if (label.endsWith("*")) { sb.append(label, 0, label.length() - 1); } else { sb.append(label); } if (label.startsWith("/")) { sb.append("Path"); } if (label.endsWith("*")) { sb.append("Prefix"); } else if (label.startsWith("*")) { sb.append("Suffix"); } uri = new URIImpl(sb.toString()); } else if (ds.contains(of, RDF.TYPE, OWL.CLASS)) { uri = nameAnonymous((Resource) of); } if (uri != null && (namespace == null || commonNS.contains(namespace))) { namespace = uri.getNamespace(); } if (uri == null) { others.add(of.stringValue()); } else if (uri.getLocalName().length() > 0) { names.add(uri.getLocalName()); } else { String str = uri.stringValue(); Matcher m = Pattern.compile("\\b[a-zA-Z]\\w*\\b").matcher(str); while (m.find()) { str = m.group(); } names.add(str); } } if (names.isEmpty()) return null; StringBuilder sb = new StringBuilder(); sb.append(prefix); for (String localPart : names) { sb.append(initcap(localPart)); sb.append(and); } for (String localPart : others) { sb.append(initcap(localPart)); sb.append(and); } sb.setLength(sb.length() - and.length()); URIImpl dest = new URIImpl(namespace + sb.toString()); nameClass(clazz, dest); return dest; } private CharSequence getMatchNamespace(Resource clazz) { for (Resource graph : ds.match(clazz, null, null).contexts()) { if (graph instanceof URI) { return getMatchNamespace(graph); } } // this shouldn't happen, but just in case return "urn:matches:"; } private CharSequence getMatchNamespace(URI ontology) { StringBuilder sb = new StringBuilder(); ParsedURI parsed = new ParsedURI(ontology.stringValue()); if (parsed.getScheme() != null) { sb.append(parsed.getScheme()); sb.append(':'); } if (parsed.isOpaque()) { if (parsed.getSchemeSpecificPart() != null) { sb.append(parsed.getSchemeSpecificPart()); } } else { if (parsed.getAuthority() != null) { sb.append("//"); sb.append(parsed.getAuthority()); } sb.append(parsed.getPath()); sb.append("#"); } return sb; } private void nameClass(Resource orig, URI dest) { if (ds.contains(dest, RDF.TYPE, OWL.CLASS)) { logger.debug("merging {} {}", orig, dest); } else { logger.debug("renaming {} {}", orig, dest); ds.add(dest, RDF.TYPE, OWL.CLASS); anonymousClasses.add(dest); } rename(orig, dest); } private void rename(Resource orig, Resource dest) { for (Statement stmt : ds.match(orig, null, null)) { ds.add(dest, stmt.getPredicate(), stmt.getObject()); } ds.remove(orig, null, null); for (Statement stmt : ds.match(null, null, orig)) { ds.add(stmt.getSubject(), stmt.getPredicate(), dest); } ds.remove((Resource) null, null, orig); } private String initcap(String str) { if (str.length() < 2) return str.toUpperCase(); return str.substring(0, 1).toUpperCase() + str.substring(1); } }