/*
* This software is Copyright 2005,2006,2007,2008 Langdale Consultants.
* Langdale Consultants can be contacted at: http://www.langdale.com.au
*/
package au.com.langdale.validation;
import java.util.Iterator;
import au.com.langdale.inference.LOG;
import au.com.langdale.kena.OntModel;
import au.com.langdale.kena.ModelFactory;
import au.com.langdale.kena.OntResource;
import au.com.langdale.kena.Property;
import au.com.langdale.kena.ResIterator;
import au.com.langdale.kena.Resource;
import au.com.langdale.kena.ResourceFactory;
import au.com.langdale.profiles.MESSAGE;
import com.hp.hpl.jena.graph.FrontsNode;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
/**
* Check a profile for consistency with a base schema.
*
*/
public class ConsistencyChecker {
public static String BUILDLET_NS = "http://langdale.com.au/2007/Buildlet#";
private int errors;
private OntModel log, target, reference;
private String namespace;
/**
* Construct validator from models.
*
* @param target the profile to be checked
* @param ref the base schema
* @param namespace the namespace of the profile
*/
public ConsistencyChecker(OntModel target, OntModel reference, String namespace) {
this.log = ModelFactory.createMem();
this.target = target;
this.reference = reference;
this.namespace = namespace;
}
/**
* Retrieve a model containing error annotations. For each
* inconsistent statement in the target, the log contains one
* or more LOG.hasProblems statements. The subjects of the
* problem is a resource in the target associated with the
* inconsistent statement.
*
* @return the log model
*/
public OntModel getLog() {
return log;
}
public int errorCount() {
return errors;
}
private static class Terms {
FrontsNode[] terms;
Terms( FrontsNode[] terms) { this.terms = terms;}
boolean includes(FrontsNode cand) {
for(int ix = 0; ix < terms.length; ix++)
if( terms[ix].equals(cand))
return true;
return false;
}
boolean hasSubject(OntResource subj, FrontsNode pred) {
for(int ix = 0; ix < terms.length; ix++)
if( subj.hasProperty(pred, terms[ix]))
return true;
return false;
}
boolean hasInstance(OntResource subj) {
return hasSubject(subj, RDF.type);
}
}
private Terms Terms( FrontsNode t1 ) { return new Terms( new FrontsNode[] { t1 }); }
private Terms Terms( FrontsNode t1, FrontsNode t2) { return new Terms( new FrontsNode[] { t1, t2 }); }
private Terms Terms( FrontsNode t1, FrontsNode t2, FrontsNode t3) { return new Terms( new FrontsNode[] { t1, t2, t3 }); }
/**
* Check a target model for inconsistent use of a namespace.
* Any resource in the given namespace that is used in the target
* must be defined in the reference. Errors are recorded in the log
* model.
*
* @return true if there are validation errors
*/
public void run() {
Iterator it = target.getGraph().find(Node.ANY, Node.ANY, Node.ANY);
while( it.hasNext()) {
Triple t = (Triple) it.next();
if( ! t.getObject().isLiteral() ) {
Resource s = ResourceFactory.createResource(t.getSubject());
Property p = ResourceFactory.createProperty(t.getPredicate());
Resource o = ResourceFactory.createResource(t.getObject());
if( isRefNode(s))
checkSubject(s.inModel(reference), p, o.inModel(target));
if( isRefNode(o))
checkObject( s.inModel(target), p, o.inModel(reference));
}
}
}
/**
* True if the resource belongs to the reference schema (as opposed to the profile).
*/
private boolean isRefNode(Resource s) {
if( s.isAnon())
return false;
String ns = s.getNameSpace();
return ! (ns.equals(namespace)
|| ns.equals(OWL.NS)
|| ns.equals(RDFS.getURI())
|| ns.equals(RDF.getURI())
|| ns.equals(MESSAGE.NS)
|| ns.equals(BUILDLET_NS));
}
/**
* Check that the subject is defined in the reference
*/
private void checkSubject(OntResource subj, Property pred, OntResource obj) {
if( ! obj.hasRDFType())
noteUndefined(subj, obj);
}
/**
* Check the object is appropriately defined in the reference.
*/
private void checkObject(OntResource subj, Property pred, OntResource obj) {
if( ! obj.hasRDFType() )
noteUndefined(obj, subj);
else if( Terms( RDFS.subClassOf, RDFS.domain, RDF.type).includes(pred)
&& ! Terms(OWL.Class).hasInstance(obj))
noteExpectedClass(obj, subj);
else if ( Terms(RDFS.subPropertyOf, OWL.onProperty).includes(pred)
&& ! Terms(RDF.Property, OWL.ObjectProperty, OWL.DatatypeProperty).hasInstance(obj))
noteExpectedProperty(obj, subj);
}
private void noteUndefined(Resource refNode, Resource targNode) {
note(refNode, "is undefined in the schema", targNode);
}
private void noteExpectedClass(Resource refNode, Resource targNode) {
note(refNode, "is not defined as a class in the schema", targNode);
}
private void noteExpectedProperty(Resource refNode, Resource targNode) {
note(refNode, "is not defined as a property in the schema", targNode);
}
private Resource findNamedTarget(Resource targNode) {
if( targNode.isAnon()) {
ResIterator it = target.listSubjectsWithProperty(RDFS.subClassOf, targNode);
OntResource parentNode;
while( it.hasNext()) {
parentNode = it.nextResource();
if( ! parentNode.isAnon())
return parentNode;
}
return null;
}
return targNode;
}
/**
* Create an annotation in the log model.
*/
private void note(Resource refNode, String mesg, Resource targNode) {
targNode = findNamedTarget(targNode);
OntResource prob = log.createIndividual(LOG.Problem);
prob.addProperty( RDFS.comment, refNode.getLocalName() + " " + mesg);
prob.addProperty( LOG.problemReference, refNode);
prob.addProperty( LOG.problemDetail, refNode.getURI() + " " + mesg + (targNode == null? "" : ", is linked to profile " + targNode.getURI()));
if( targNode != null)
log.add( targNode, LOG.hasProblems, prob);
errors += 1;
}
}