package com.monead.semantic.education;
/**
* DiffInferencing - Show inferencing at different levels of semantic reasoning
*
* This program uses Jena and Pellet to show the progression of inferencing
* that occurs for the provided ontology.
*
* Copyright (C) 2010 David S. Read
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* For information on Jena: http://jena.sourceforge.net/
* For information on Pellet: http://clarkparsia.com/pellet
*
* This program extends the concepts introduced in:
* Hebeler, John. "Modeling Knowledge in the Real World."
* Semantic Web Programming. Indianapolis, IN: Wiley,
* 2009. 166-67. Print.
*/
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mindswap.pellet.jena.PelletReasonerFactory;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
public class DiffInferencing implements Runnable {
/**
* The version identifier
*/
public final static String VERSION = "1.0";
/**
* The set of formats that can be loaded. These are defined by Jena
*/
private final static String[] FORMATS = { "N3", "N-Triples", "RDF/XML",
"Turtle" };
/**
* The set of reasoning levels that will be compared.
*/
protected final static String[] REASONING_LEVELS = { "none", "rdfs", "owl" };
/**
* A default file to process - in case one is not supplied
* on the command line
*/
private final static String DEFAULT_INPUT_FILE = "DSRDiffInferencingTestOntology.turtle";
/**
* Constant used if a value cannot be found in an array
*/
private final static int UNKNOWN = -1;
/**
* The name (and path if necessary) to the ontology being loaded
*/
private String inputFileName;
/**
* The name (and path if necessary) to the output file for the diff report
*/
private String outputFileName;
/**
* The loaded ontology
*/
private OntModel ontModel;
/**
* Map of the text values in the model First map is keyed on reasoner Inner
* map is keyed on the subject Innermost List contains a collection of
* 2-element String array String arrays contain the predicate [0] and the
* object [1]
*/
private Map>> triples;
/**
* Constructor - sets up the input and output file paths and the triples map
*
* @param inputFileName The name (and optional path) to an ontology
* @param outputFileName The name (and optional path) for the output
*/
public DiffInferencing(String inputFileName, String outputFileName) {
setInputFileName(inputFileName);
setOutputFileName(outputFileName);
triples = new HashMap>>();
}
/**
* Perform the steps to load, compare and report on the ontology
*/
public void run() {
for (String reasoner : REASONING_LEVELS) {
System.out.println("Load model with reasoner: " + reasoner);
loadModel(reasoner);
storeModel(reasoner);
}
reportTriples("All Assertions at this Level", false);
diffMaps();
reportTriples("Assertions Unique at this Level", true);
}
/**
* Place the triples in the model into the map for the supplied reasoner
*
* @param reasoner The reasoning level being used
*/
private void storeModel(String reasoner) {
triples.put(reasoner, getTriples());
}
/**
* Perform the comparison between the sets of triples for each reasoning
* level. The algorithm walks through the reasoners backward since triples
* are removed if they are found in an earlier reasoner's triples.
*/
private void diffMaps() {
for (int reasonerIndex = REASONING_LEVELS.length - 1;
reasonerIndex > 0;
--reasonerIndex) {
diffMaps(REASONING_LEVELS[reasonerIndex],
REASONING_LEVELS[reasonerIndex - 1]);
}
}
/**
* Update the superset by removing anything that is also in the subset e.g.
* at the end of processing, the superset should be disjoint from the subset
*
* @param superset Reasoner level being checked for duplicates entries
* @param subset Reasoner level whose values should not be in the superset
*/
private void diffMaps(String superset, String subset) {
Map> supersetMap;
Map> subsetMap;
List supersetSubjectAssertions;
List subsetSubjectAssertions;
String[] supersetAssertion;
List subjectsToRemoveFromSuperset;
supersetMap = triples.get(superset);
subsetMap = triples.get(subset);
subjectsToRemoveFromSuperset = new ArrayList();
for (String subject : supersetMap.keySet()) {
supersetSubjectAssertions = supersetMap.get(subject);
subsetSubjectAssertions = subsetMap.get(subject);
if (subsetSubjectAssertions != null) {
for (String[] subsetAssertion : subsetSubjectAssertions) {
for (int supersetAssertionIndex = 0;
supersetAssertionIndex <
supersetSubjectAssertions.size();
++supersetAssertionIndex) {
supersetAssertion = supersetSubjectAssertions
.get(supersetAssertionIndex);
if (supersetAssertion[0].equals(subsetAssertion[0])
&& supersetAssertion[1]
.equals(subsetAssertion[1])) {
supersetSubjectAssertions
.remove(supersetAssertionIndex);
break;
}
}
}
if (supersetSubjectAssertions.size() > 0) {
supersetMap.put(subject, supersetSubjectAssertions);
} else {
/**
* Removal here can lead to ConcurrentModificationException
*/
subjectsToRemoveFromSuperset.add(subject);
}
}
}
// remove any subjects that have no associated assertions
for (String subject : subjectsToRemoveFromSuperset) {
supersetMap.remove(subject);
}
}
/**
* Write the resulting triples out to a file, separated by the reasoning
* level where that triple was first found
*
* @param title The text to use as the title of the triples being output
* @param append Whether to append to an existing file (overwrite if false)
*/
private void reportTriples(String title, boolean append) {
String outputFileName;
PrintWriter out;
Map> reasonerTriples;
List subjectAssertions;
outputFileName = getOutputFileName();
out = null;
try {
out = new PrintWriter(new FileWriter(outputFileName, append));
} catch (Throwable throwable) {
System.err.println("Failed to open output file: " + outputFileName);
throwable.printStackTrace();
System.exit(5);
}
for (String reasoner : REASONING_LEVELS) {
out.println(title + " (Reasoning Level: " + reasoner + ")\n");
reasonerTriples = triples.get(reasoner);
for (String subject : reasonerTriples.keySet()) {
out.println(" Individual: " + subject);
subjectAssertions = reasonerTriples.get(subject);
for (String assertion[] : subjectAssertions) {
out.println(" " + assertion[0] + ": " + assertion[1]);
}
out.println();
}
out.println();
}
System.out.println("Wrote triples to " + outputFileName);
try {
out.close();
} catch (Throwable throwable) {
System.err.println("Error closing output file");
throwable.printStackTrace();
System.exit(6);
}
}
/**
* Get the set of defined ontology file formats that the program can load as
* a CSV list String
*
* @return The known ontology file formats as a CSV list
*/
public final static String getFormatsAsCSV() {
return getArrayAsCSV(FORMATS);
}
/**
* Get the set of reasoning levels that the program will use as a CSV list
* String
*
* @return The known reasoning levels as a CSV list
*/
public final static String getReasoningLevelsAsCSV() {
return getArrayAsCSV(REASONING_LEVELS);
}
/**
* Create a CSV list from a String array
*
* @param array An array
* @return The array values in a CSV list
*/
public final static String getArrayAsCSV(String[] array) {
StringBuffer csv;
csv = new StringBuffer();
for (String value : array) {
if (csv.length() > 0) {
csv.append(", ");
}
csv.append(value);
}
return csv.toString();
}
/**
* Set the input file name, where the ontology is located
*
* @param inputFileName The name of the file containing the ontology
*/
public void setInputFileName(String inputFileName) {
this.inputFileName = inputFileName;
}
/**
* Get the input file name for the location of the ontology
*
* @return The input file name where the ontology is located
*/
public String getInputFileName() {
return inputFileName;
}
/**
* Set the output file name, where the report should be written
*
* @param outputFileName The output file name
*/
public void setOutputFileName(String outputFileName) {
this.outputFileName = outputFileName;
}
/**
* Get the output file name for the location of the generated report
*
* @return The output file name
*/
public String getOutputFileName() {
return outputFileName;
}
/**
* Convert the ontology into a set of Strings representing the triples
*
* @return A Map containing Lists that relate subjects to objects and
* predicates
*/
protected Map> getTriples() {
Map> triples;
List oneSubject;
String subject;
String[] predicateObject;
ExtendedIterator iIndividuals;
StmtIterator iProperties;
triples = new HashMap>();
iIndividuals = ontModel.listIndividuals();
while (iIndividuals.hasNext()) {
Individual individual = iIndividuals.next();
subject = individual.getLocalName();
oneSubject = new ArrayList();
iProperties = individual.listProperties();
while (iProperties.hasNext()) {
Statement statement = (Statement) iProperties.next();
predicateObject = new String[2];
predicateObject[0] = statement.getPredicate().getLocalName();
predicateObject[1] = statement.getObject().toString();
oneSubject.add(predicateObject);
}
iProperties.close();
triples.put(subject, oneSubject);
}
iIndividuals.close();
return triples;
}
/**
* Create a model with a reasoner set based on the chosen reasoning level.
*
* @param reasoningLevel The reasoning level for this model
*
* @return The created ontology model
*/
private OntModel createModel(String reasoningLevel) {
OntModel model;
int reasoningLevelIndex;
model = null;
reasoningLevelIndex = getReasoningLevelIndex(reasoningLevel);
if (reasoningLevelIndex == 0) { // None
model = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);
} else if (reasoningLevelIndex == 1) { // RDFS
model = ModelFactory
.createOntologyModel(OntModelSpec.OWL_DL_MEM_RDFS_INF);
} else if (reasoningLevelIndex == 2) { // OWL
Reasoner reasoner = PelletReasonerFactory.theInstance().create();
Model infModel = ModelFactory.createInfModel(reasoner, ModelFactory
.createDefaultModel());
model = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM,
infModel);
}
return model;
}
/**
* Obtain an ontology model set to the chosen reasoning level. Load the
* ontology into the model
*
* @param reasoningLevel The selected reasoning level
*/
private void loadModel(String reasoningLevel) {
FileInputStream inputStream = null;
String modelFormat;
try {
} catch (Throwable throwable) {
System.err.println("Failed to open input file: " + inputFileName);
throwable.printStackTrace();
System.exit(3);
}
modelFormat = null;
for (String format : FORMATS) {
try {
inputStream = new FileInputStream(inputFileName);
ontModel = createModel(reasoningLevel);
ontModel.read(inputStream, null, format.toUpperCase());
modelFormat = format;
break;
} catch (Throwable throwable) {
System.err.println("Error reading file: "
+ throwable.getClass().getName() + ": as format: "
+ format + ": " + throwable.getMessage());
} finally {
try {
inputStream.close();
} catch (Throwable throwable) {
System.err.println("Error closing input file");
throwable.printStackTrace();
System.exit(4);
}
}
}
if (modelFormat == null) {
throw new IllegalStateException(
"The format of the input file cannot be determined.\nTried: "
+ getFormatsAsCSV());
} else {
System.out.println("Loaded model " + inputFileName
+ " using format: " + modelFormat);
}
}
/**
* Get the index position of the supplied reasoning level label
*
* @param reasonerName A reasoning level label
*
* @return The index position of the reasoning level. Will be equal to the
* constant UNKNOWN if the value cannot be found in the collection
* of known reasoning levels
*/
public final static int getReasoningLevelIndex(String reasonerName) {
return getIndexValue(REASONING_LEVELS, reasonerName);
}
/**
* Find a String value within and array of Strings. Return the index
* position where the value was found.
*
* @param array An array of string to search
* @param name The value to find in the array
*
* @return The position where the value was found in the array. Will be
* equal to the constant UNKNOWN if the value cannot be found in the
* collection of known reasoning levels
*/
public final static int getIndexValue(String[] array, String name) {
Integer indexValue;
indexValue = null;
for (int index = 0; index < array.length && indexValue == null; ++index) {
if (array[index].toUpperCase().equals(name.toUpperCase())) {
indexValue = index;
}
}
return indexValue == null ? UNKNOWN : indexValue;
}
/**
* The execution point for the program. Verifies the input arguments have
* been supplied, creates an instance of the DiffInferencing class and
* creates a thread to run the instance. The program requires an input file
* name to be supplied on the command line. An optional output file name may
* also be supplied.
*
* @param args The array of input arguments
*/
public static void main(String[] args) {
Runnable runnable;
int argNum;
String inputFileName = null;
String outputFileName = null;
if (args.length > 2) {
System.err
.println("usage: DiffInferencing [ [