package org.neo4j.meta.input.rdfs;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.neo4j.graphdb.Transaction;
import org.neo4j.meta.model.ClassRange;
import org.neo4j.meta.model.DataRange;
import org.neo4j.meta.model.DatatypeClassRange;
import org.neo4j.meta.model.MetaModel;
import org.neo4j.meta.model.MetaModelClass;
import org.neo4j.meta.model.MetaModelImpl;
import org.neo4j.meta.model.MetaModelNamespace;
import org.neo4j.meta.model.MetaModelProperty;
import org.neo4j.meta.model.MetaModelRestrictable;
import org.neo4j.meta.model.MetaModelThing;
import org.neo4j.meta.model.PropertyRange;
import org.neo4j.meta.model.RdfDatatypeRange;
import org.neo4j.meta.model.RdfUtil;
import org.ontoware.aifbcommons.collection.ClosableIterator;
import org.ontoware.rdf2go.model.Model;
import org.ontoware.rdf2go.model.Statement;
import org.ontoware.rdf2go.model.node.BlankNode;
import org.ontoware.rdf2go.model.node.Literal;
import org.ontoware.rdf2go.model.node.Node;
import org.ontoware.rdf2go.model.node.Resource;
import org.ontoware.rdf2go.model.node.URI;
import org.ontoware.rdf2go.model.node.Variable;
import org.ontoware.rdf2go.vocabulary.OWL;
import org.ontoware.rdf2go.vocabulary.RDF;
import org.ontoware.rdf2go.vocabulary.RDFS;
/**
* Imports RDF schema graphs and creates a meta model representation of it.
*/
public class RdfsImporter
{
private MetaModel meta;
/**
* @param model the {@link MetaModel} instance to use.
*/
public RdfsImporter( MetaModel model )
{
this.meta = model;
}
/**
* Since all is happening in the global namespace when importing from RDFS
* here's a method for that namespace.
* @return the global namespace.
*/
protected MetaModelNamespace meta()
{
return this.meta.getGlobalNamespace();
}
/**
* Imports an RDF/XML graph from a file.
* @param file the file containing the RDF/XML graph.
* @throws IOException if there were problems reading the file.
*/
public void doImport( File file ) throws IOException
{
Model model = RdfHelper.readModel( file );
Transaction tx = ( ( MetaModelImpl ) meta ).graphDb().beginTx();
try
{
readFrom( model );
tx.success();
}
finally
{
model.close();
tx.finish();
}
}
private void debug( String message )
{
System.out.println( message );
}
private void readFrom( Model model )
{
readClasses( model );
readProperties( model );
readRestrictions( model );
}
private void trySetLabelAndComment( MetaModelThing thing,
Model model, Resource resource )
{
trySetFromLiteral( thing, model, resource,
RDFS.label.toString(), "label" );
trySetFromLiteral( thing, model, resource,
RDFS.comment.toString(), "comment" );
trySetFromResource( thing, model, resource,
RDFS.seeAlso.toString(), "seeAlso" );
trySetFromResource( thing, model, resource,
RDFS.isDefinedBy.toString(), "isDefinedBy" );
}
private void trySetFromLiteral( MetaModelThing thing, Model model,
Resource resource, String property, String key )
{
String value = RdfHelper.tryGetLiteral( model, resource, property );
if ( value != null )
{
debug( "\t" + key + ": " + value );
thing.setAdditionalProperty( key, value );
}
}
private void trySetFromResource( MetaModelThing thing, Model model,
Resource resource, String property, String key )
{
Node node = RdfHelper.subNode( model, resource, property );
if ( node != null )
{
String value = RdfHelper.resourceUri( node );
debug( "\t" + key + ": " + value );
thing.setAdditionalProperty( key, value );
}
}
private abstract class ThingReader<T extends MetaModelThing>
{
abstract T get( String name );
abstract T readThing( Model model, Resource resource, String name );
abstract void couple( String superName, String subName );
}
private class ClassReader extends ThingReader<MetaModelClass>
{
@Override
MetaModelClass get( String name )
{
return meta().getMetaClass( name, true );
}
@Override
MetaModelClass readThing( Model model, Resource resource,
String name )
{
MetaModelClass metaClass = get( name );
trySetLabelAndComment( metaClass, model, resource );
return metaClass;
}
@Override
void couple( String superName, String subName )
{
get( superName ).getDirectSubs().add( get( subName ) );
}
}
private class PropertyReader extends ThingReader<MetaModelProperty>
{
@Override
MetaModelProperty get( String name )
{
return meta().getMetaProperty( name, true );
}
@Override
MetaModelProperty readThing( Model model, Resource resource,
String name )
{
MetaModelProperty metaProperty = get( name );
trySetLabelAndComment( metaProperty, model, resource );
trySetPropertyDomain( metaProperty, model, resource );
trySetPropertyRange( metaProperty, model, resource );
trySetPropertyInverseOf( metaProperty, model, resource );
return metaProperty;
}
@Override
void couple( String superName, String subName )
{
get( superName ).getDirectSubs().add( get( subName ) );
}
}
private <T extends MetaModelThing> void readThings( Model model,
org.ontoware.rdf2go.model.node.URI type, ThingReader<T> reader )
{
ClosableIterator<? extends Statement> itr =
model.findStatements( Variable.ANY, RDF.type, type );
try
{
while ( itr.hasNext() )
{
Statement statement = itr.next();
Resource subject = statement.getSubject();
if ( subject instanceof BlankNode )
{
debug( "Skipping blank " +
RdfHelper.local( type.toString() ) );
continue;
}
String className = RdfHelper.resourceUri( subject );
debug( RdfHelper.local( type.toString() ) + ": " + className );
reader.readThing( model, subject, className );
}
}
finally
{
itr.close();
}
}
private <T extends MetaModelThing> void coupleThings( Model model,
org.ontoware.rdf2go.model.node.URI type, ThingReader<T> reader )
{
ClosableIterator<? extends Statement> itr = model.findStatements(
Variable.ANY, type, Variable.ANY );
try
{
while ( itr.hasNext() )
{
Statement statement = itr.next();
if ( RdfHelper.resourceIsType( model, statement.getObject().
asResource(), OWL.Restriction.toString() ) )
{
// debug( "Skipping restriction" );
continue;
}
String superName =
RdfHelper.resourceUri( statement.getObject() );
// if ( RdfHelper.isW3Uri( superName ) )
// {
// continue;
// }
String subName =
RdfHelper.resourceUri( statement.getSubject() );
reader.couple( superName, subName );
debug( subName + " " + RdfHelper.local( type.toString() ) +
" " + superName );
}
}
finally
{
itr.close();
}
}
private void readClasses( Model model )
{
ClassReader reader = new ClassReader();
readThings( model, RDFS.Class, reader );
readThings( model, OWL.Class, reader );
coupleThings( model, RDFS.subClassOf, reader );
}
private void readProperties( Model model )
{
PropertyReader reader = new PropertyReader();
readThings( model, RDF.Property, reader );
readThings( model, OWL.DatatypeProperty, reader );
readThings( model, OWL.ObjectProperty, reader );
coupleThings( model, RDFS.subPropertyOf, reader );
}
private void trySetPropertyDomain( MetaModelProperty metaProperty,
Model model, Resource property )
{
for ( Node domainNode :
RdfHelper.subNodes( model, property, RDFS.domain.toString() ) )
{
for ( Node classNode : RdfHelper.getClassOrUnionOfClasses( model,
domainNode.asResource() ) )
{
String domainClass = RdfHelper.resourceUri( classNode );
meta().getMetaClass( domainClass, true ).getDirectProperties().
add( metaProperty );
debug( "\tdomain: " + domainClass );
}
}
}
private MetaModelClass[] nodesToClasses( Collection<Node> classNodes )
{
MetaModelClass[] classes =
new MetaModelClass[ classNodes.size() ];
int i = 0;
for ( Node classNode : classNodes )
{
classes[ i++ ] = meta().getMetaClass(
RdfHelper.resourceUri( classNode ), true );
}
return classes;
}
private PropertyRange mergeRange( PropertyRange previousRange,
PropertyRange newRange )
{
if ( previousRange == null )
{
return newRange;
}
PropertyRange result = null;
if ( newRange instanceof DataRange )
{
DataRange previousDataRange = ( DataRange ) previousRange;
DataRange newDataRange = ( DataRange ) newRange;
Collection<Object> values = new ArrayList<Object>(
previousDataRange.getValues() );
values.addAll( newDataRange.getValues() );
result = new DataRange( newDataRange.getRdfDatatype(),
values.toArray() );
}
else if ( newRange instanceof ClassRange )
{
ClassRange previousMetaRange =
( ClassRange ) previousRange;
ClassRange newMetaRange =
( ClassRange ) newRange;
Collection<MetaModelClass> classes =
new ArrayList<MetaModelClass>();
classes.addAll( Arrays.asList(
previousMetaRange.getRangeClasses() ) );
classes.addAll( Arrays.asList(
newMetaRange.getRangeClasses() ) );
result = new ClassRange(
classes.toArray( new MetaModelClass[ classes.size() ] ) );
}
else
{
throw new RuntimeException( "Can't merge property range type " +
newRange );
}
return result;
}
private PropertyRange buildCollectionRange( Model model, Resource resource )
{
PropertyRange propertyRange = null;
String rangeType = RdfHelper.resourceType( model, resource );
if ( RdfHelper.smartMatchThese( rangeType,
OWL.DataRange.toString() ) )
{
Node collectionNode = RdfHelper.subNode( model,
resource.asResource(), OWL.oneOf.toString() );
if ( collectionNode == null )
{
throw new RuntimeException( "No collection" );
}
Node first = RdfHelper.getFirstInCollection( model,
collectionNode.asResource() );
String datatype =
first.asDatatypeLiteral().getDatatype().toString();
Collection<Node> nodes = RdfHelper.parseCollection( model,
collectionNode.asResource() );
Collection<Object> values =
RdfHelper.nodesToLiterals( nodes, datatype );
propertyRange = new DataRange( datatype, values.toArray() );
}
else if ( RdfHelper.smartMatchThese( rangeType,
OWL.Class.toString() ) )
{
Collection<Node> classNodes =
RdfHelper.getClassOrUnionOfClasses( model, resource );
propertyRange = new ClassRange(
nodesToClasses( classNodes ) );
}
else
{
throw new RuntimeException( "Unknown blank range " +
rangeType );
}
return propertyRange;
}
private PropertyRange buildOneValueRange( Model model, Node rangeNode )
{
PropertyRange propertyRange = null;
String rangeType = null;
if ( rangeNode instanceof Resource )
{
rangeType = RdfHelper.resourceUri( rangeNode.asResource() );
}
else
{
rangeType = RdfHelper.literalDatatype( model,
( Literal ) rangeNode );
}
MetaModelClass metaClass =
meta().getMetaClass( rangeType, false );
if ( rangeNode instanceof Literal ||
RdfUtil.recognizesDatatype( rangeType ) )
{
if ( rangeNode instanceof Literal )
{
Literal literal = ( Literal ) rangeNode;
String literalValue = literal.getValue();
propertyRange = new DataRange( rangeType, literalValue );
}
else if ( rangeNode instanceof URI )
{
propertyRange = new RdfDatatypeRange(
( ( URI ) rangeNode ).toString() );
}
else
{
throw new RuntimeException( "Unrecognized type '" +
rangeNode + "'" );
}
}
else if ( metaClass != null )
{
propertyRange = new ClassRange( metaClass );
}
else if ( RdfHelper.smartMatchThese( rangeType,
RDFS.Container.toString(), RDF.Seq.toString(),
RDF.Bag.toString(), RDF.Alt.toString() ) )
{
// TODO
}
else if ( RdfHelper.smartMatchThese( rangeType,
RDFS.Literal.toString(), RDFS.Datatype.toString(),
RDFS.XMLLiteral.toString() ) )
{
propertyRange = new DatatypeClassRange( String.class );
}
else if ( RdfHelper.smartMatchThese( rangeType,
OWL.Thing.toString() ) )
{
// TODO Skip?
}
else
{
// TODO Throw something to let em know?
throw new RuntimeException( "Unknown property range type " +
rangeType );
}
return propertyRange;
}
private PropertyRange buildPropertyRange( Model model, Node rangeNode )
{
PropertyRange propertyRange = null;
if ( rangeNode instanceof BlankNode )
{
propertyRange = buildCollectionRange( model,
rangeNode.asResource() );
}
else
{
propertyRange = buildOneValueRange( model, rangeNode );
}
return propertyRange;
}
private void trySetPropertyRange( MetaModelRestrictable restrictable,
Model model, Resource property )
{
PropertyRange propertyRange = null;
for ( Node rangeNode :
RdfHelper.subNodes( model, property, RDFS.range.toString() ) )
{
PropertyRange newRange = buildPropertyRange( model, rangeNode );
newRange = mergeRange( propertyRange, newRange );
propertyRange = newRange;
}
if ( propertyRange != null )
{
restrictable.setRange( propertyRange );
debug( "\trange: " + propertyRange );
}
if ( restrictable instanceof MetaModelProperty )
{
trySetPropertyFunctionality( model, property,
( MetaModelProperty ) restrictable );
}
}
private void trySetPropertyFunctionality( Model model, Resource property,
MetaModelProperty metaProperty )
{
String propertyFunctionality = null;
if ( RdfHelper.resourceIsType( model, property,
OWL.FunctionalProperty.toString() ) )
{
propertyFunctionality = "functional";
metaProperty.setMinCardinality( 0 );
metaProperty.setMaxCardinality( 1 );
}
else if ( RdfHelper.resourceIsType( model, property,
OWL.InverseFunctionalProperty.toString() ) )
{
propertyFunctionality = "inverseFunctional";
// TODO cardinality here?
}
if ( propertyFunctionality != null )
{
metaProperty.setAdditionalProperty( "functionality",
propertyFunctionality );
debug( "\t" + propertyFunctionality );
}
}
private void trySetPropertyInverseOf( MetaModelProperty metaProperty,
Model model, Resource property )
{
for ( Node inverseOfNode :
RdfHelper.subNodes( model, property, OWL.inverseOf.toString() ) )
{
String inverseProperty = RdfHelper.resourceUri( inverseOfNode );
metaProperty.setInverseOf( meta().getMetaProperty(
inverseProperty, true ) );
debug( "\tinverseOf: " + inverseProperty );
}
}
private void readRestrictions( Model model )
{
ClosableIterator<? extends Statement> itr = model.findStatements(
Variable.ANY, RDFS.subClassOf, Variable.ANY );
try
{
while ( itr.hasNext() )
{
Statement statement = itr.next();
Resource restriction = statement.getObject().asResource();
if ( !RdfHelper.resourceIsType( model, restriction,
OWL.Restriction.toString() ) )
{
continue;
}
Resource ownerClass = statement.getSubject();
Resource onProperty = RdfHelper.subNode( model, restriction,
OWL.onProperty.toString() ).asResource();
MetaModelClass metaClass = meta().getMetaClass(
RdfHelper.resourceUri( ownerClass ), true );
MetaModelProperty metaProperty = meta().getMetaProperty(
RdfHelper.resourceUri( onProperty ), true );
MetaModelRestrictable restrictable =
metaClass.getRestriction( metaProperty, true );
debug( "Created a restriction " + metaClass +
" ----> " + metaProperty );
trySetCardinality( restrictable, model, restriction );
// Try get the values, owl:allValuesFrom etc.
Resource rangeResource = findResourceOutOf( model, restriction,
OWL.allValuesFrom.toString(),
OWL.someValuesFrom.toString(),
OWL.hasValue.toString() );
if ( rangeResource != null )
{
PropertyRange propertyRange =
buildPropertyRange( model, rangeResource );
if ( propertyRange != null )
{
restrictable.setRange( propertyRange );
debug( "\trange: " + propertyRange );
}
}
}
}
finally
{
itr.close();
}
}
private Resource findResourceOutOf( Model model, Resource resource,
String... predicatesToTest )
{
for ( String predicate : predicatesToTest )
{
Node subNode = RdfHelper.subNode( model, resource, predicate );
if ( subNode != null )
{
return subNode.asResource();
}
}
return null;
}
private void trySetCardinality( MetaModelRestrictable restrictable,
Model model, Resource restriction )
{
Integer cardinality = tryGetInteger( model, restriction,
OWL.cardinality.toString() );
Integer maxCardinality = tryGetInteger( model, restriction,
OWL.maxCardinality.toString() );
Integer minCardinality = tryGetInteger( model, restriction,
OWL.minCardinality.toString() );
if ( cardinality != null )
{
restrictable.setCardinality( cardinality );
debug( "\tcardinality: " + cardinality );
}
if ( maxCardinality != null )
{
restrictable.setMaxCardinality( maxCardinality );
debug( "\tmaxCardinality: " + maxCardinality );
}
if ( minCardinality != null )
{
restrictable.setMinCardinality( minCardinality );
debug( "\tminCardinality: " + minCardinality );
}
}
private Integer tryGetInteger( Model model, Resource resource,
String predicate )
{
String literal = RdfHelper.tryGetLiteral( model, resource, predicate );
return literal == null ? null : Integer.parseInt( literal );
}
}