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 [ []]"); System.exit(1); } argNum = 0; if (args.length > 0) { inputFileName = args[argNum++].trim(); } if (inputFileName == null || inputFileName.length() == 0) { inputFileName = DEFAULT_INPUT_FILE; System.out .println("Using default input file: " + inputFileName); } if (args.length == 2) { outputFileName = args[argNum++].trim(); } if (outputFileName == null || outputFileName.length() == 0) { outputFileName = inputFileName + ".compare.out"; System.out.println("Defaulting output file name to: " + outputFileName); } runnable = new DiffInferencing(inputFileName, outputFileName); new Thread(runnable).start(); } }