package org.pixelgaffer.turnierserver.codr;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.CopyOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.json.JSONObject;
import org.pixelgaffer.turnierserver.codr.AiBase.AiMode;
import org.pixelgaffer.turnierserver.codr.utilities.ErrorLog;
import org.pixelgaffer.turnierserver.codr.utilities.Libraries;
import org.pixelgaffer.turnierserver.codr.utilities.Paths;
import org.pixelgaffer.turnierserver.compile.CompileFailureException;
import org.pixelgaffer.turnierserver.compile.Compiler;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TreeItem;
/**
* Speichert eine Version eines Spieles.
*
* @author Philip
*/
public class Version {
public final AiBase ai;
public final int number;
public String executeCommand = "";
public String executeArgs[] = new String[0];
public SimpleBooleanProperty compiled = new SimpleBooleanProperty(false);
public SimpleBooleanProperty qualified = new SimpleBooleanProperty(false);
public SimpleBooleanProperty finished = new SimpleBooleanProperty(false);
public String compileOutput = "";
public String qualifyOutput = "";
public List<CodeEditor> files = new ArrayList<CodeEditor>();
public TreeItem<File> rootFile = null;
public Version(AiBase aai, int n, JSONObject json) {
ai = aai;
number = n;
compiled.set(json.getBoolean("compiled"));
qualified.set(json.getBoolean("qualified"));
finished.set(json.getBoolean("frozen"));
setCompiledListener();
}
/**
* Instanziert eine neue Version und lädt automatisch den Quellcode
*
* @param p der Spieler
* @param n die Nummer
*/
public Version(AiBase aai, int n, AiMode mmode) {
ai = aai;
number = n;
if (ai.mode == AiMode.saved || ai.mode == AiMode.simplePlayer) {
if (!exists()) {
ai.gametype = MainApp.actualGameType.get();
copyFromFile(Paths.simplePlayer("" + ai.gametype, ai.language));
storeProps();
} else {
loadProps();
}
findCode();
}
setCompiledListener();
}
public Version(AiBase aai, int n, AiMode mmode, String path) {
ai = aai;
number = n;
exists();
if (ai.mode == AiMode.saved)
copyFromFile(path);
storeProps();
findCode();
setCompiledListener();
}
/**
* setzt einen Listener auf die compiled-Property, der den compileOutput und ähnliches löscht/aufräumt
*/
private void setCompiledListener() {
compiled.addListener((observableValue, oldValue, newValue) -> {
if (newValue == false && oldValue == true) {
compileOutput = "";
qualified.set(false);
qualifyOutput = "";
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein speicherbares Objekt (compiledListener)");
return;
}
File bin = new File(Paths.versionBin(this));
try {
FileUtils.deleteDirectory(bin);
} catch (Exception e) {
}
}
});
}
/**
* Prüft, ob die Version bereits im Dateisystem existiert.
*
* @return true, wenn die Version bereits existiert
*/
public boolean exists() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern && ai.mode != AiMode.simplePlayer) {
ErrorLog.write("dies ist kein speicherbares Objekt (exists)");
return true;
}
File dir = new File(Paths.version(this));
return !dir.mkdirs();
}
/**
* Kopiert alle Dateien von einem bestimmten Pfad in das Verzeichnis der
* Version.
*
* @param path der Pfad, von dem kopiert werden soll
*/
public void copyFromFile(String path) {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein speicherbares Objekt (copyFromFile)");
return;
}
Path srcPath = new File(path).toPath();
Path destPath = new File(Paths.version(this)).toPath();
try {
Files.walkFileTree(srcPath, new CopyVisitor(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING));
} catch (IOException e) {
e.printStackTrace();
ErrorLog.write("Version konnte nicht kopiert werden: " + e.getMessage());
}
}
/**
* Sucht alle Dateien innerhalb des Versionsordners und speichert sie in files
*/
public void findCode() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.simplePlayer && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein lesbares Objekt (findCode)");
return;
}
files.clear();
rootFile = new TreeItem<File>(new File(Paths.versionSrc(this)));
rootFile.setExpanded(true);
recursiveFileBuild(rootFile);
}
/**
* Baut einen Verzeichnisbaum aller in der Version befindlichen Dateien auf.
* Diese werden sofort als CodeEditor angelegt.
*
* @param item das aktuell betrachtete Element
*/
private void recursiveFileBuild(TreeItem<File> item) {
File[] underFiles = item.getValue().listFiles();
if (underFiles == null)
return;
for (File file : underFiles) {
if (file.getName().startsWith(".")) {
continue;
}
TreeItem<File> actual = new TreeItem<File>(file);
actual.setExpanded(true);
item.getChildren().add(actual);
if (file.isDirectory()) {
recursiveFileBuild(actual);
} else {
files.add(new CodeEditor(file));
}
}
}
/**
* Speichert alle Dateien aus den CodeEditoren in files im Dateisystem ab
*/
public void saveCode() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
if (ai.mode != AiMode.simplePlayer)
ErrorLog.write("dies ist kein speicherbares Objekt (saveCode)");
return;
}
if (finished.get() == true) {
ErrorLog.write("Man kann den Code einer fertiggestellten Version nicht speichern");
return;
}
for (int i = 0; i < files.size(); i++) {
if (files.get(i).save()) {
compiled.set(false);
qualified.set(false);
storeProps();
}
}
}
/**
* Lädt aus dem Dateiverzeichnis die Eigenschaften des Players.
*/
public void loadProps() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.simplePlayer && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein lesbares Objekt (Version.loadProps)");
return;
}
try {
Reader reader = new FileReader(Paths.versionProperties(this));
Properties prop = new Properties();
prop.load(reader);
reader.close();
executeCommand = prop.getProperty("executeCommand");
executeArgs = new String[Integer.parseInt(prop.getProperty("executeArgs.size", "0"))];
for (int i = 0; i < executeArgs.length; i++)
executeArgs[i] = prop.getProperty("executeArgs." + i);
compiled.set(Boolean.parseBoolean(prop.getProperty("compiled")));
qualified.set(Boolean.parseBoolean(prop.getProperty("qualified")));
finished.set(Boolean.parseBoolean(prop.getProperty("finished")));
compileOutput = prop.getProperty("compileOutput");
qualifyOutput = prop.getProperty("qualifyOutput");
} catch (IOException e) {
ErrorLog.write("Fehler bei Laden aus der properties.txt (Version)");
}
}
/**
* Speichert die Eigenschaften des Players in das Dateiverzeichnis.
*/
public void storeProps() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein speicherbares Objekt (Version.storeProps)");
return;
}
Properties prop = new Properties();
prop.setProperty("executeCommand", executeCommand);
prop.setProperty("executeArgs.size", Integer.toString(executeArgs.length));
for (int i = 0; i < executeArgs.length; i++)
prop.setProperty("executeArgs." + i, executeArgs[i]);
prop.setProperty("compiled", "" + compiled.get());
prop.setProperty("qualified", "" + qualified.get());
prop.setProperty("finished", "" + finished.get());
prop.setProperty("compileOutput", compileOutput);
prop.setProperty("qualifyOutput", qualifyOutput);
try {
Writer writer = new FileWriter(Paths.versionProperties(this));
prop.store(writer, ai.title + " v" + number);
writer.close();
} catch (IOException e) {
ErrorLog.write("Es kann keine Properties-Datei angelegt werden. (Version)");
}
}
/**
* Kompiliert die Quellcodedateien
*
* @return false, wenn die Kompilierung fehlgeschlagen ist
*/
public boolean compile() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.simplePlayer && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein kompilierbares Objekt (compile)");
return false;
}
saveCode();
try {
Compiler c = Compiler.getCompiler(ai.language);
compileOutput = c.compile(new File(Paths.versionSrc(this)), new File(Paths.versionBin(this)), new File(Paths.versionSettingsProp(this)), new Libraries());
executeCommand = c.getCommand();
executeArgs = c.getArguments();
compileOutput += "\nKompilierung erfolgreich\n";
compiled.set(true);
} catch (ReflectiveOperationException roe) {
ErrorLog.write("Fehler beim Laden des Compilers: " + roe);
compiled.set(false);
} catch (InterruptedException | IOException e) {
ErrorLog.write("Fehler beim Kompilieren: " + e);
e.printStackTrace();
compiled.set(false);
} catch (CompileFailureException cfe) {
compileOutput = cfe.getMessage();
compileOutput += "\nKompilierung fehlgeschlagen\n";
compiled.set(false);
}
storeProps();
return compiled.get();
}
/**
* Qualifiziert die Ki
*
* @return false, wenn die Qualifikation fehlgeschlagen ist
*/
public boolean qualify() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein speicherbares Objekt (qualify)");
return false;
}
if (!compiled.get())
if (!compile())
return false;
qualified.set(true);
qualifyOutput = "Qualifikation fertig!";
storeProps();
return true;
}
/**
* Stellt die Ki fertig, was bedeutet, dass sie nicht mehr bearbeitet werden
* kann.
*/
public void finish() {
if (ai.mode != AiMode.saved && ai.mode != AiMode.extern) {
ErrorLog.write("dies ist kein speicherbares Objekt (finish)");
return;
}
finished.set(true);
storeProps();
}
/**
* damit in der ChoiceBox die Nummer angezeigt wird
*/
public String toString() {
return "" + number;
}
/**
* Ein FileVisitor, der eine Datei bei ihrem Besuch kopiert
* http://codingjunkie.net/java-7-copy-move/
*/
public static class CopyVisitor extends SimpleFileVisitor<Path> {
private final Path fromPath;
private final Path toPath;
private final CopyOption copyOption;
public CopyVisitor(Path _fromPath, Path _toPath, CopyOption _copyOption) {
fromPath = _fromPath;
toPath = _toPath;
copyOption = _copyOption;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetPath = toPath.resolve(fromPath.relativize(dir));
if (!Files.exists(targetPath)) {
Files.createDirectory(targetPath);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, toPath.resolve(fromPath.relativize(file)), copyOption);
return FileVisitResult.CONTINUE;
}
}
/**
* Ein FileVisitor, der jede Datei als Version abspeichert
*/
public static class VersionVisitor extends SimpleFileVisitor<Path> {
public TreeItem<File> rootFile;
public List<CodeEditor> files = new ArrayList<CodeEditor>();
public VersionVisitor(File file) {
rootFile = new TreeItem<>(file);
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (file.toFile().getName().startsWith(".")) {
return FileVisitResult.CONTINUE;
}
if (file.toFile().getName().equals("libraries.txt")) {
return FileVisitResult.CONTINUE;
}
files.add(new CodeEditor(file.toFile()));
return FileVisitResult.CONTINUE;
}
}
}