/*******************************************************************************
* Copyright (c) 2010-2011 VIVO Harvester Team. For full list of contributors, please see the AUTHORS file provided.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the new BSD license which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.html
******************************************************************************/
package org.vivoweb.harvester.diff;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vivoweb.harvester.util.FileAide;
import org.vivoweb.harvester.util.InitLog;
import org.vivoweb.harvester.util.args.ArgDef;
import org.vivoweb.harvester.util.args.ArgList;
import org.vivoweb.harvester.util.args.ArgParser;
import org.vivoweb.harvester.util.args.UsageException;
import org.vivoweb.harvester.util.repo.JenaConnect;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFWriter;
/**
* Set math to find difference (subtraction) of one model from another
* @author Stephen Williams
*/
public class Diff {
/**
* SLF4J Logger
*/
private static Logger log = LoggerFactory.getLogger(Diff.class);
/**
* Models to read records from
*/
private JenaConnect minuendJC;
/**
* Models to read records from
*/
private JenaConnect subtrahendJC;
/**
* Model to write records to
*/
private JenaConnect output;
/**
* dump model to file option - filename
*/
private Map<String, String> dumpFile;
/**
* dump model to file option - language
*/
private Map<String, String> dumpLanguage;
/**
* dump model to a ntriple file
*/
private String dumpNTriple;
/**
* dump model to a n3 file
*/
private String dumpN3;
/**
* Constructor
* @param mJC minuend jenaconnect
* @param sJC subtrahend jenaconnect
* @param oJC output jenaconnect
* @param dF dump file path
* @param dL dump language
* @param dTF dump ntriple
* @param n3 dump n3
*/
public Diff(JenaConnect mJC, JenaConnect sJC, JenaConnect oJC, Map<String,String> dF, Map<String,String> dL, String dTF, String n3) {
this.minuendJC = mJC;
this.subtrahendJC = sJC;
this.output = oJC;
this.dumpFile = dF;
this.dumpLanguage = dL;
this.dumpNTriple = dTF;
this.dumpN3=n3;
if(this.minuendJC == null) {
throw new IllegalArgumentException("Must provide a minuend jena model");
}
if(this.subtrahendJC == null) {
throw new IllegalArgumentException("Must provide a subtrahend jena model");
}
if(this.output == null && (this.dumpFile == null )) { // TODO: check the contents of the dumpFiles if any is empty error || this.dumpFile.trim().isEmpty()))
throw new IllegalArgumentException("Must provide at least one of an output jena model or a dump file");
}
checkFileName();
}
/**
* Constructor
* @param args commandline arguments
* @throws IOException error reading config files
* @throws UsageException user requested usage message
*/
private Diff(String[] args) throws IOException, UsageException {
this(getParser().parse(args));
}
/**
* Constructor
* @param argList parsed commandline arguments
* @throws IOException error reading config files
*/
private Diff(ArgList argList) throws IOException {
this(
JenaConnect.parseConfig(argList.get("m"), argList.getValueMap("M")),
JenaConnect.parseConfig(argList.get("s"), argList.getValueMap("S")),
JenaConnect.parseConfig(argList.get("o"), argList.getValueMap("O")),
argList.getValueMap("d"),
argList.getValueMap("l"),
argList.get("t"),
argList.get("n"));
}
/**
* Get the ArgParser for this task
* @return the ArgParser
*/
private static ArgParser getParser() {
ArgParser parser = new ArgParser("Diff");
// Inputs
parser.addArgument(new ArgDef().setShortOption('m').setLongOpt("minuend").withParameter(true, "CONFIG_FILE").setDescription("config file for source jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('M').setLongOpt("minuendOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of source jena model config using VALUE").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('s').setLongOpt("subtrahend").withParameter(true, "CONFIG_FILE").setDescription("config file for removemode jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('S').setLongOpt("subtrahendOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of remove jena model config using VALUE").setRequired(false));
// Outputs
parser.addArgument(new ArgDef().setShortOption('o').setLongOpt("output").withParameter(true, "CONFIG_FILE").setDescription("config file for output jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('O').setLongOpt("outputOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of output jena model config using VALUE").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('l').setLongOpt("dumptolanguage").withParameterValueMap("FILE_NAME", "LANGUAGE").setDescription("language for output").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('d').setLongOpt("dumptofile").withParameterValueMap("FILE_NAME", "FILENAME").setDescription("filename for output").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('t').setLongOpt("dumpntripletofile").withParameter(true, "FILENAME").setDescription("filename for N triple output").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('n').setLongOpt("dumpn3tofile").withParameter(true, "FILENAME").setDescription("filename for N 3 output").setRequired(false));
return parser;
}
/**
* Make sure that every dumpLanguage Key,Value pair has an identical key in
* the dumpFile map.
* @return - false if 1 language definition does not have a path to a file defined
*/
private boolean checkFileName(){
boolean valid = true;
for(String fileName : this.dumpLanguage.keySet()){
if (!this.dumpFile.containsKey(fileName)){
valid = false;
throw new IllegalArgumentException("file name '" + fileName + "' found in languages but not in paths");
}
}
return valid;
}
/**
* Perform diff of mJC and sJC and put result in oJC and/or dF
* @param mJC minuend jenaconnect
* @param sJC subtrahend jenaconnect
* @param oJC output jenaconnect
* @param dF dump file path
* @param dL dump language
* @param dTF dumpfile ntriple
* @param dNTF dumpfile n3
* @throws IOException error accessing file
*/
public static void diff(JenaConnect mJC, JenaConnect sJC, JenaConnect oJC, Map<String,String> dF, Map<String,String> dL, String dTF, String dNTF) throws IOException {
// c - b = a
// minuend - subtrahend = difference
// minuend.diff(subtrahend) = differenece
// c.diff(b) = a
Model diffModel = ModelFactory.createDefaultModel();
Model minuendModel = mJC.getJenaModel();
Model subtrahendModel = sJC.getJenaModel();
diffModel = minuendModel.difference(subtrahendModel);
if (dF != null) {
for(String filename : dF.keySet()) {
String filepath = dF.get(filename);
String filelanguage = "";
if (dL.containsKey(filename)){
filelanguage = dL.get(filename);
} else {
filelanguage = "RDF/XML";
}
RDFWriter fasterWriter = diffModel.getWriter(filelanguage);
if (filelanguage.equals("RDF/XML")){
fasterWriter.setProperty("showXmlDeclaration", "true");
fasterWriter.setProperty("allowBadURIs", "true");
fasterWriter.setProperty("relativeURIs", "");
}
OutputStreamWriter osw = new OutputStreamWriter(FileAide.getOutputStream(filepath), Charset.availableCharsets().get("UTF-8"));
fasterWriter.write(diffModel, osw, "");
log.debug(filelanguage + " Data was exported to " + filepath);
}
}
//Deprecated code (see new format using Map above)
/*if(dF != null) {
RDFWriter fasterWriter = diffModel.getWriter("RDF/XML");
fasterWriter.setProperty("showXmlDeclaration", "true");
fasterWriter.setProperty("allowBadURIs", "true");
fasterWriter.setProperty("relativeURIs", "");
OutputStreamWriter osw = new OutputStreamWriter(FileAide.getOutputStream(dF), Charset.availableCharsets().get("UTF-8"));
fasterWriter.write(diffModel, osw, "");
log.debug("RDF/XML Data was exported");
}
if(dTF != null) {
RDFWriter tripplewriter = diffModel.getWriter("N-TRIPLE");
FileWriter fstream = new FileWriter(dTF);
BufferedWriter out = new BufferedWriter(fstream);
tripplewriter.write(diffModel, out, "");
out.close();
}
if(dNTF != null) {
RDFWriter tripplewriter = diffModel.getWriter("N3");
FileWriter fstream = new FileWriter(dNTF);
BufferedWriter out = new BufferedWriter(fstream);
tripplewriter.write(diffModel, out, "");
out.close();
}
*/
if(oJC != null) {
oJC.getJenaModel().add(diffModel);
oJC.sync();
}
}
/**
* Execute the diff
* @throws IOException error accessing file
*/
public void execute() throws IOException {
diff(this.minuendJC, this.subtrahendJC, this.output, this.dumpFile, this.dumpLanguage, this.dumpNTriple, this.dumpN3);
}
/**
* Main Method
* @param args commandline arguments
*/
public static void main(String... args) {
Exception error = null;
try {
InitLog.initLogger(args, getParser());
log.info(getParser().getAppName() + ": Start");
new Diff(args).execute();
} catch(IllegalArgumentException e) {
log.error(e.getMessage());
log.debug("Stacktrace:", e);
System.out.println(getParser().getUsage());
error = e;
} catch(UsageException e) {
log.info("Printing Usage:");
System.out.println(getParser().getUsage());
error = e;
} catch(Exception e) {
log.error(e.getMessage());
log.debug("Stacktrace:", e);
error = e;
} finally {
log.info(getParser().getAppName() + ": End");
if(error != null) {
System.exit(1);
}
}
}
}