/*******************************************************************************
* This file is part of ecco.
*
* ecco is distributed under the terms of the GNU Lesser General Public License (LGPL), Version 3.0.
*
* Copyright 2011-2014, The University of Manchester
*
* ecco is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ecco 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with ecco.
* If not, see http://www.gnu.org/licenses/.
******************************************************************************/
package uk.ac.manchester.cs.diff.justifications;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import org.semanticweb.owl.explanation.api.Explanation;
import org.semanticweb.owl.explanation.api.ExplanationGenerator;
import org.semanticweb.owl.explanation.api.ExplanationGeneratorFactory;
import org.semanticweb.owl.explanation.api.ExplanationManager;
import org.semanticweb.owl.explanation.impl.laconic.LaconicExplanationGeneratorFactory;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.io.OWLXMLOntologyFormat;
import org.semanticweb.owlapi.io.StringDocumentSource;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import uk.ac.manchester.cs.diff.axiom.CategoricalDiff;
/**
* @author Rafael S. Goncalves <br>
* Information Management Group (IMG) <br>
* School of Computer Science <br>
* University of Manchester <br>
*/
public class JustificationFinder {
private OWLOntology ont;
private OWLReasonerFactory rf;
private OWLOntologyManager man;
private ExplanationGeneratorFactory<OWLAxiom> regFac, lacFac;
private int lacJustLimit, justLimit;
private int justCounter = 0, entCounter = 0;
/**
* Constructor
* @param ont OWL ontology
* @param nrJusts Maximum number of justifications to compute per entailment
*/
public JustificationFinder(OWLOntology ont, int nrJusts) {
this.ont = ont;
this.justLimit = nrJusts;
this.lacJustLimit = nrJusts;
man = OWLManager.createOWLOntologyManager();
rf = new org.semanticweb.HermiT.Reasoner.ReasonerFactory();
// rf = new JFactFactory();
regFac = ExplanationManager.createExplanationGeneratorFactory(rf);
lacFac = new LaconicExplanationGeneratorFactory<OWLAxiom>(regFac);
}
/**
* Get all justifications for a given set of entailments (concurrently)
* @param entailments Set of entailments
* @return Set of (sets of) justifications for the given entailments
*/
public Map<OWLAxiom,Set<Explanation<OWLAxiom>>> getJustifications(Set<OWLAxiom> entailments) {
ForkJoinPool fjPool = new ForkJoinPool();
return fjPool.invoke(new RegularJustificationFinder(entailments, justLimit));
}
/**
* Get justifications for a given set of entailments (sequentially)
* @param entailments Set of entailments
* @return Set of (sets of) justifications for the given entailments
*/
public Map<OWLAxiom,Set<Explanation<OWLAxiom>>> getJustificationsSequentially(Set<OWLAxiom> entailments) {
Map<OWLAxiom,Set<Explanation<OWLAxiom>>> regExps = new HashMap<OWLAxiom,Set<Explanation<OWLAxiom>>>();
for(OWLAxiom ax : entailments)
regExps.put(ax,getJustifications(ax));
return regExps;
}
/**
* Get justifications for a given entailment
* @param axiom Entailment
* @return Justifications for the given entailment
*/
public Set<Explanation<OWLAxiom>> getJustifications(OWLAxiom axiom) {
ExplanationGenerator<OWLAxiom> exGen = regFac.createExplanationGenerator(ont);
Set<Explanation<OWLAxiom>> justs = exGen.getExplanations(axiom, justLimit);
if(justs.isEmpty())
System.err.println("\n\t !! Could not retrieve justifications for axiom:\n\t\t" +
CategoricalDiff.getManchesterRendering(axiom));
return justs;
}
/**
* Justification finder
*/
public class RegularJustificationFinder extends RecursiveTask<Map<OWLAxiom,Set<Explanation<OWLAxiom>>>> {
private static final long serialVersionUID = 1L;
private Set<OWLAxiom> axioms;
private int limit;
private int MAX_AXIOM_SET_SIZE = 10;
/**
* Constructor
* @param axioms Set of axioms to get justifications for
* @param limit Number of desired justifications per axiom
*/
public RegularJustificationFinder(Set<OWLAxiom> axioms, int limit) {
this.axioms = axioms;
this.limit = limit;
}
/**
* Compute the set of entailments for the given set of axioms
* @return Set of sets of justifications
*/
public Map<OWLAxiom,Set<Explanation<OWLAxiom>>> computeDirectly() {
Map<OWLAxiom,Set<Explanation<OWLAxiom>>> regExps = new HashMap<OWLAxiom,Set<Explanation<OWLAxiom>>>();
for(OWLAxiom ax : axioms) {
ExplanationGenerator<OWLAxiom> exGen = regFac.createExplanationGenerator(ont);
// TODO all good up till here:
Set<Explanation<OWLAxiom>> justs = exGen.getExplanations(ax, limit);
regExps.put(ax, justs);
if(justs.isEmpty())
System.err.println("\n\t !! Could not retrieve justifications for axiom:\n\t\t" + CategoricalDiff.getManchesterRendering(ax));
}
return regExps;
}
protected Map<OWLAxiom,Set<Explanation<OWLAxiom>>> compute() {
Map<OWLAxiom,Set<Explanation<OWLAxiom>>> result = new HashMap<OWLAxiom,Set<Explanation<OWLAxiom>>>();
if(axioms.size() > MAX_AXIOM_SET_SIZE) {
int mid = axioms.size()/2;
OWLAxiom[] axArr = axioms.toArray(new OWLAxiom[axioms.size()]);
Set<OWLAxiom> firstHalf = new HashSet<OWLAxiom>();
Set<OWLAxiom> secondHalf = new HashSet<OWLAxiom>();
for(int i = 0; i < mid; i++)
firstHalf.add(axArr[i]);
for(int i = mid; i < axArr.length; i++)
secondHalf.add(axArr[i]);
RegularJustificationFinder cat1 = new RegularJustificationFinder(firstHalf, limit);
cat1.fork();
RegularJustificationFinder cat2 = new RegularJustificationFinder(secondHalf, limit);
result.putAll(cat2.invoke());
result.putAll(cat1.join());
}
else result.putAll(computeDirectly());
return result;
}
}
/**
* Given an entailment and a set of regular justifications, compute and return the set of laconicized
* justifications for that entailment
* @param ax Entailment
* @param exps Set of regular justifications for the specifed entailment
* @return Set of laconic justifications for the entailment
*/
public Set<Set<OWLAxiom>> getLaconicJustifications(OWLAxiom ax, Set<Explanation<OWLAxiom>> exps) {
Set<Set<OWLAxiom>> results = new HashSet<Set<OWLAxiom>>();
Set<Future<Set<Set<OWLAxiom>>>> futureList = new HashSet<Future<Set<Set<OWLAxiom>>>>();
ExecutorService exec = Executors.newFixedThreadPool(exps.size());
for(Explanation<OWLAxiom> exp : exps) {
boolean forkProcess = false;
for(OWLAxiom axiom : exp.getAxioms()) {
if(axiom.getNestedClassExpressions().size() >= 20) {
forkProcess = true;
break;
}
}
Future<Set<Set<OWLAxiom>>> handler = null;
if(forkProcess)
handler = exec.submit(new ProcessLaconicJustificationFinder(ax, exp.getAxioms()));
else
handler = exec.submit(new ThreadedLaconicJustificationFinder(ax, exp.getAxioms()));
futureList.add(handler);
}
for(Future<Set<Set<OWLAxiom>>> f : futureList) {
try {
if(f.get() != null)
results.addAll(f.get());
} /* Do nothing */
catch(CancellationException e) {}
catch(InterruptedException e) {}
catch(ExecutionException e) {}
}
exec.shutdownNow();
return results;
}
/**
* Process-based laconic justification finder with built-in timeout
*/
public class ProcessLaconicJustificationFinder implements Callable<Set<Set<OWLAxiom>>> {
private OWLAxiom ax;
private Set<OWLAxiom> just;
/**
* Constructor
* @param ax Entailment
* @param just Justification
*/
public ProcessLaconicJustificationFinder(OWLAxiom ax, Set<OWLAxiom> just) {
this.ax = ax;
this.just = just;
}
@Override
public Set<Set<OWLAxiom>> call() {
Set<Set<OWLAxiom>> out = new HashSet<Set<OWLAxiom>>();
List<String> list = new ArrayList<String>();
File entFile = serializeOntAndGetFile("ent", entCounter, Collections.singleton(ax));
list.add(entFile.getAbsolutePath());
File justFile = serializeOntAndGetFile("just", justCounter, just);
list.add(justFile.getAbsolutePath());
String output = null;
try {
Process p = executeOperation(LaconicJustificationFinder.class, false, list);
output = streamToString(p.getInputStream());
} catch(IOException e) {
e.printStackTrace();
} catch(InterruptedException e) {
e.printStackTrace();
}
if(output != null) {
if(output.startsWith("Ontology")) {
try {
out.add(man.loadOntologyFromOntologyDocument(new StringDocumentSource(output)).getAxioms());
} catch (OWLOntologyCreationException e) {
e.printStackTrace();
}
}
}
justFile.deleteOnExit();
entFile.deleteOnExit();
return out;
}
/**
* Create and serialize an ontology containing the given axioms
* @param desc File description
* @param counter File counter
* @param axioms Set of axioms
* @return The serialized ontology file object
*/
private File serializeOntAndGetFile(String desc, int counter, Set<OWLAxiom> axioms) {
File f = new File("temp" + File.separator + desc + counter + ".owl");
try {
OWLOntology ont = man.createOntology(axioms);
man.saveOntology(ont, new OWLXMLOntologyFormat(), IRI.create(f));
if(desc.equals("ent")) entCounter++;
else if(desc.equals("just")) justCounter++;
} catch(OWLOntologyStorageException e) {
e.printStackTrace();
} catch(OWLOntologyCreationException e) {
e.printStackTrace();
}
return f;
}
/**
* Convert a given input stream into a String
* @param in Input stream
* @return String containing the content of the input stream
* @throws IOException IO exception
*/
private String streamToString(InputStream in) throws IOException {
StringBuilder out = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
for(String line = br.readLine(); line != null; line = br.readLine())
out.append(line);
br.close();
return out.toString();
}
/**
* Execute a new process based on the given parameters
* @param c Class to execute
* @param redirectIO true if I/O of the class is to be redirected to this process, false otherwise
* @param args Additional arguments
* @return Executed process Process that is executed
* @throws IOException IO exception
* @throws InterruptedException Interruption exception
*/
private Process executeOperation(Class<? extends Object> c, boolean redirectIO, List<String> args)
throws IOException, InterruptedException {
String javaHome = System.getProperty("java.home");
String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
String classPath = System.getProperty("java.class.path");
String className = c.getCanonicalName();
ArrayList<String> cmdArgs = new ArrayList<String>();
cmdArgs.add(javaBin);
cmdArgs.add("-cp");
cmdArgs.add(classPath);
cmdArgs.add(className);
cmdArgs.addAll(args);
ProcessBuilder builder = new ProcessBuilder(cmdArgs);
builder.redirectErrorStream(true);
// if(redirectIO) builder.redirectOutput(Redirect.INHERIT);
// else builder.redirectOutput(Redirect.PIPE);
Process process = builder.start();
process.waitFor();
return process;
}
}
/**
* Thread-based laconic justification finder
*/
public class ThreadedLaconicJustificationFinder implements Callable<Set<Set<OWLAxiom>>> {
private Set<OWLAxiom> exp;
private OWLAxiom axiom;
/**
* Constructor
* @param axiom Entailment
* @param exp Justification
*/
public ThreadedLaconicJustificationFinder(OWLAxiom axiom, Set<OWLAxiom> exp) {
this.exp = exp;
this.axiom = axiom;
}
@Override
public Set<Set<OWLAxiom>> call() throws Exception {
ExplanationGenerator<OWLAxiom> lacGen = lacFac.createExplanationGenerator(exp);
Set<Set<OWLAxiom>> results = new HashSet<Set<OWLAxiom>>();
try {
Set<Explanation<OWLAxiom>> lacjusts = lacGen.getExplanations(axiom, lacJustLimit);
for(Explanation<OWLAxiom> exp : lacjusts) {
results.add(exp.getAxioms());
}
} catch(IllegalArgumentException e) {
e.printStackTrace();
}
return results;
}
}
}