/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.jena.assembler; import java.util.*; import org.apache.jena.rdf.model.* ; import org.apache.jena.vocabulary.* ; /** The ModelExpansion code expands a model <code>M</code> against a schema <code>S</code>, returning a new model which contains <ul> <li>the statements of M <li>any statements (A rdfs:subClassOf B) from S where neither A nor B is a bnode. <li>statements (A rdf:type T) if M contains (A P any) and S contains (P rdfs:domain T). <li>statements (A rdf:type T) if M contains (any P A) and S contains (P rdfs:range T). <li>statements (A rdf:type T) if (A rdf:type U) and (U rdfs:subClassOf T). </ul> This is sufficient to allow the subjects in <code>M</code> which have properties from <code>S</code> to have enough type information for AssemblerGroup dispatch. */ public class ModelExpansion { /** Answer a new model which is the aggregation of <ul> <li>the statements of <code>model</code> <li>the non-bnode subclass statements of <code>schema</code> <li>the subclass closure of those statements <li>the rdf:type statements implied by the rdfs:domain statements of <code>schema</code> and the <code>model</code> statements using that statements property <li>similarly for rdfs:range <li>the rdf:type statements implied by the subclass closure </ul> */ public static Model withSchema( Model model, Model schema ) { Model result = ModelFactory.createDefaultModel().add( model ); addSubclassesFrom( result, schema ); addSubClassClosure( result ); addDomainTypes( result, schema ); addRangeTypes( result, schema ); addIntersections( result, schema ); addSupertypes( result ); return result; } private static final Property ANY = null; protected static void addSubclassesFrom( Model result, Model schema ) { for (StmtIterator it = schema.listStatements( ANY, RDFS.subClassOf, ANY ); it.hasNext();) { Statement s = it.nextStatement(); if (s.getSubject().isURIResource() && s.getObject().isURIResource()) result.add( s ); } } /** Do (limited) subclass closure on <code>m</code>. <p> Those classes in <code>m</code> that appear in <code>subClassOf</code> statements are given as explicit superclasses all their indirect superclasses. */ public static void addSubClassClosure( Model m ) { Set<Resource> roots = selectRootClasses( m, findClassesBySubClassOf( m ) ); for ( Resource root : roots ) { addSuperClasses( m, root ); } } /** To each subclass X of <code>type</code> add as superclass all the classes between X and <code>type</code>. */ private static void addSuperClasses( Model m, Resource type ) { addSuperClasses( m, new LinkedSeq( type ) ); } /** To each subclass X of <code>parents.item</code> add as superclass all the classes between X and that item and all the items in the rest of <code>parents</code>. */ private static void addSuperClasses( Model m, LinkedSeq parents ) { Model toAdd = ModelFactory.createDefaultModel(); addSuperClasses( m, parents, toAdd ); m.add( toAdd ); } /** Add to <code>toAdd</code> all the superclass statements needed to note that any indirect subclass of <code>X = parents.item</code> has as superclass all the classes between it and X and all the remaining elements of <code>parents</code>. */ private static void addSuperClasses( Model m, LinkedSeq parents, Model toAdd ) { Resource type = parents.item; for (StmtIterator it = m.listStatements( null, RDFS.subClassOf, type ); it.hasNext();) { Resource t = it.nextStatement().getSubject(); for (LinkedSeq scan = parents.rest; scan != null; scan = scan.rest) toAdd.add( t, RDFS.subClassOf, scan.item ); addSuperClasses( m, parents.push( t ), toAdd ); } } /** Answer the subset of <code>classes</code> which have no superclass in <code>m</code>. */ private static Set<Resource> selectRootClasses( Model m, Set<RDFNode> classes ) { Set<Resource> roots = new HashSet<>(); for ( RDFNode aClass : classes ) { Resource type = (Resource) aClass; if ( !m.contains( type, RDFS.subClassOf, (RDFNode) null ) ) { roots.add( type ); } } return roots; } /** Answer the set of all classes which appear in <code>m</code> as the subject or object of a <code>rdfs:subClassOf</code> statement. */ private static Set<RDFNode> findClassesBySubClassOf( Model m ) { Set<RDFNode> classes = new HashSet<>(); StmtIterator it = m.listStatements( null, RDFS.subClassOf, (RDFNode) null ); while (it.hasNext()) addClasses( classes, it.nextStatement() ); return classes; } /** Add to <code>classes</code> the subject and object of the statement <code>xSubClassOfY</code>. */ private static void addClasses( Set<RDFNode> classes, Statement xSubClassOfY ) { classes.add( xSubClassOfY.getSubject() ); classes.add( xSubClassOfY.getObject() ); } protected static void addDomainTypes( Model result, Model schema ) { for (StmtIterator it = schema.listStatements( ANY, RDFS.domain, ANY ); it.hasNext();) { Statement s = it.nextStatement(); Property property = s.getSubject().as( Property.class ); RDFNode type = s.getObject(); for (StmtIterator x = result.listStatements( ANY, property, ANY ); x.hasNext();) { Statement t = x.nextStatement(); result.add( t.getSubject(), RDF.type, type ); } } } protected static void addRangeTypes( Model result, Model schema ) { Model toAdd = ModelFactory.createDefaultModel(); for (StmtIterator it = schema.listStatements( ANY, RDFS.range, ANY ); it.hasNext();) { Statement s = it.nextStatement(); RDFNode type = s.getObject(); Property property = s.getSubject().as( Property.class ); for (StmtIterator x = result.listStatements( ANY, property, ANY ); x.hasNext();) { RDFNode ob = x.nextStatement().getObject(); if (ob.isResource()) toAdd.add( (Resource) ob, RDF.type, type ); } } result.add( toAdd ); } protected static void addSupertypes( Model result ) { Model temp = ModelFactory.createDefaultModel(); for (StmtIterator it = result.listStatements( ANY, RDF.type, ANY ); it.hasNext();) { Statement s = it.nextStatement(); Resource c = AssemblerHelp.getResource( s ); for (StmtIterator subclasses = result.listStatements( c, RDFS.subClassOf, ANY ); subclasses.hasNext();) { RDFNode type = subclasses.nextStatement().getObject(); // System.err.println( ">> adding super type: subject " + s.getSubject() + ", type " + type ); temp.add( s.getSubject(), RDF.type, type ); } } result.add( temp ); } private static void addIntersections( Model result, Model schema ) { StmtIterator it = schema.listStatements( ANY, OWL.intersectionOf, ANY ); while (it.hasNext()) addIntersections( result, schema, it.nextStatement() ); } private static void addIntersections( Model result, Model schema, Statement s ) { Resource type = s.getSubject(); List<RDFNode> types = asJavaList( AssemblerHelp.getResource( s ) ); Set<Resource> candidates = subjectSet( result, ANY, RDF.type, types.get(0) ); for (int i = 1; i < types.size(); i += 1) removeElementsWithoutType( candidates, (Resource) types.get(i) ); addTypeToAll( type, candidates ); } private static void addTypeToAll( Resource type, Set<Resource> candidates ) { List<Resource> types = equivalentTypes( type ); for (Resource element : candidates) { Resource resource = element; for ( Resource type1 : types ) { resource.addProperty( RDF.type, type1 ); } } } private static List<Resource> equivalentTypes( Resource type ) { List<Resource> types = new ArrayList<>(); types.add( type ); for (StmtIterator it = type.getModel().listStatements( ANY, OWL.equivalentClass, type ); it.hasNext();) types.add( it.nextStatement().getSubject() ); return types; } private static void removeElementsWithoutType( Set<Resource> candidates, Resource type ) { for (Iterator<Resource> it = candidates.iterator(); it.hasNext();) { Resource candidate = it.next(); if (!candidate.hasProperty( RDF.type, type )) it.remove(); } } private static Set<Resource> subjectSet( Model result, Resource S, Property P, RDFNode O ) { return result.listStatements( S, P, O ) .mapWith( Statement::getSubject ).toSet(); } private static List<RDFNode> asJavaList( Resource resource ) { return (resource.as( RDFList.class )).asJavaList(); } /** A Lisp-style linked list. Used because we want non-updating cons operations. */ protected static class LinkedSeq { final Resource item; final LinkedSeq rest; LinkedSeq( Resource item ) { this( item, null ); } LinkedSeq( Resource item, LinkedSeq rest ) { this.item = item; this.rest = rest; } LinkedSeq push( Resource item ) { return new LinkedSeq( item, this ); } @Override public String toString() { StringBuffer result = new StringBuffer( "[" ); LinkedSeq scan = this; while (scan != null) { result.append( scan.item ); scan = scan.rest; result.append( " " ); } return result.append( "]" ).toString(); } } }