package se.chalmers.gdcn.compare;
import se.chalmers.gdcn.control.ThreadService;
import se.chalmers.gdcn.files.FileDep;
import se.chalmers.gdcn.files.TaskMeta;
import se.chalmers.gdcn.taskbuilder.ExitFailureException;
import se.chalmers.gdcn.taskbuilder.HaskellCompiler;
import se.chalmers.gdcn.taskbuilder.Validifier;
import se.chalmers.gdcn.taskbuilder.communicationToClient.ValidityListener;
import se.chalmers.gdcn.taskbuilder.fileManagement.PathManager;
import se.chalmers.gdcn.utils.ByteArray;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
/**
* Class for test quality of results
*/
public class QualityControl {
private static final String QUALITY_PROGRAM_NAME = "quality";
private static final String QUALITY_PROGRAM_SOURCE = "quality.hs";
private final Set<ByteArray> resultSet;
private final Map<ByteArray, TrustQuality> trustMap = new HashMap<>();
private final PathManager pathMan;
private final String program;
private final String resultFileInit;
private final List<String> taskDeps = new ArrayList<>();
//MIN_VALUE is the minimal positive number, not the minimal number
private double bestQuality = -Double.MAX_VALUE;
private final CountDownLatch waitForAll;
/**
* A method for testing the quality and validity of result data, using a job owner defined program
* @param jobName the name of the job for the task
* @param taskMeta the task metadata
* @param resultSet the set of results to test quality
* @return a map of the result data with their trust level as values
* @throws IOException
*/
public static Map<ByteArray, TrustQuality> compareQuality(String jobName, TaskMeta taskMeta, Set<ByteArray> resultSet) throws IOException{
QualityControl qualityControl = new QualityControl(jobName, taskMeta, resultSet);
if (qualityControl.program != null) {
return qualityControl.compare();
}
else {
return qualityControl.fastCompare();
}
}
public static TrustQuality singleQualityTest(String jobName, TaskMeta taskMeta, ByteArray data) throws IOException{
Set<ByteArray> resultSet = new HashSet<>();
resultSet.add(data);
QualityControl qualityControl = new QualityControl(jobName, taskMeta, resultSet);
if (qualityControl.program != null) {
return qualityControl.compare().get(data);
}
else {
return qualityControl.fastCompare().get(data);
}
}
private QualityControl(String jobName, TaskMeta taskMeta, Set<ByteArray> resultSet) throws IOException {
this.resultSet = resultSet;
waitForAll = new CountDownLatch(resultSet.size());
pathMan = PathManager.jobOwner(jobName);
program = locateQualityProgram();
for (FileDep fileDep : taskMeta.getDependencies()) {
taskDeps.add(pathMan.projectDir() + fileDep.getFileLocation() + File.separator + fileDep.getFileName());
}
resultFileInit = pathMan.projectTempDir() + taskMeta.getTaskName() + "_";
}
private String locateQualityProgram() {
String qualityProgramPath = pathMan.taskBinaryDir() + QUALITY_PROGRAM_NAME;
File qualityProgram = new File(qualityProgramPath);
if (!qualityProgram.canExecute()) {
boolean status = compileQualityProgram(qualityProgramPath);
if (!(status && qualityProgram.canExecute())) {
return null;
}
}
return qualityProgramPath;
}
private boolean compileQualityProgram(String qualityProgramPath) {
String qualitySourcePath = pathMan.taskCodeDir() + QUALITY_PROGRAM_SOURCE;
File qualitySource = new File(qualitySourcePath);
if (qualitySource.isFile()) {
// Compiling quality
new File(qualityProgramPath).getParentFile().mkdirs();
String[] command = {"ghc", qualitySourcePath, "-o", qualityProgramPath,
"-i" + pathMan.taskCodeDir(),
"-outputdir", pathMan.projectTempDir()};
HaskellCompiler haskellCompiler = new HaskellCompiler();
System.out.println("Compiling quality checker...");
try {
haskellCompiler.compile(command);
} catch (ExitFailureException e) {
return false;
} catch (InterruptedException | IOException e) {
e.printStackTrace();
return false;
}
finally {
pathMan.deleteTemps();
}
return true;
}
else {
System.out.println("Quality source file " + qualitySourcePath + " does not exist");
return false;
}
}
private Map<ByteArray, TrustQuality> compare() throws IOException {
for (ByteArray resultData : resultSet) {
String resultFile = writeResultFile(resultData);
Listener listener = new Listener(resultData, new File(resultFile));
Validifier validifier = new Validifier(listener);
ValidifierRunner runner = new ValidifierRunner(validifier, resultFile);
ThreadService.submit(runner);
//new Thread(runner).start();
}
try {
waitForAll.await();
}
catch (InterruptedException e) {
addRemaining(e.getMessage());
}
return trustMap;
}
private Map<ByteArray, TrustQuality> fastCompare() throws IOException {
for (ByteArray result : resultSet) {
if (resultSet.size() == 1) {
// Possible danger if quality program is defined afterward and a new result has quality
reward(result, -Double.MAX_VALUE);
}
else {
writeResultFile(result);
unknown(result, "Undefined quality program");
}
}
return trustMap;
}
private String writeResultFile(ByteArray data) throws IOException {
FileOutputStream output = null;
try {
int hash = data.hashCode();
while (new File(resultFileInit + hash).exists()) {
// Find unused filename
hash++;
}
String resultFile = resultFileInit + hash;
File parent = new File(resultFile).getParentFile();
parent.mkdirs();
output = new FileOutputStream(resultFile);
output.write(data.getData());
return resultFile;
}
finally {
if (output != null)
output.close();
}
}
private synchronized void reward(ByteArray result, double quality) {
if (quality == bestQuality) {
trustMap.put(result, TrustQuality.trustworthy(quality));
}
else if (quality > bestQuality) {
for (Map.Entry<ByteArray, TrustQuality> entry : trustMap.entrySet()) {
if (entry.getValue().getTrust() == Trust.TRUSTWORTHY) {
entry.setValue(TrustQuality.deceitful(entry.getValue().getQuality()));
}
}
trustMap.put(result, TrustQuality.trustworthy(quality));
bestQuality = quality;
}
else {
trustMap.put(result, TrustQuality.deceitful(quality));
}
waitForAll.countDown();
}
private synchronized void punish(ByteArray result) {
trustMap.put(result, TrustQuality.deceitful(-Double.MAX_VALUE));
waitForAll.countDown();
}
private synchronized void unknown(ByteArray result, String reason) {
trustMap.put(result, TrustQuality.unknown(reason));
waitForAll.countDown();
}
private synchronized void addRemaining(String reason) {
for (ByteArray resultData : resultSet) {
if (!trustMap.containsKey(resultData)) {
trustMap.put(resultData, TrustQuality.unknown(reason));
}
}
}
private class ValidifierRunner implements Runnable {
private final String resultFile;
private final Validifier validifier;
private ValidifierRunner(Validifier validifier, String resultFile) {
this.resultFile = resultFile;
this.validifier = validifier;
}
@Override
public void run() {
validifier.testResult(program, resultFile, taskDeps);
}
}
private class Listener implements ValidityListener {
private final ByteArray myResult;
private final File myFile;
private Listener(ByteArray myResult, File myFile) {
this.myResult = myResult;
this.myFile = myFile;
}
@Override
public void validityOk(double quality) {
reward(myResult, quality);
myFile.delete();
}
@Override
public void validityCorrupt() {
punish(myResult);
myFile.delete();
}
@Override
public void validityError(String reason) {
unknown(myResult, reason);
}
}
}