blob: 45f37126b396a842defeabd7234552fc0eebbfba [file] [log] [blame]
/*
* 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();
}
}
}