/*
* Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
*
* Copyright (c) 2011 - 2014 Michael Andonie and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ea;
import ea.compat.CompatDateiManager;
import ea.internal.gra.PixelFeld;
import ea.internal.util.Logger;
import javax.xml.bind.DatatypeConverter;
import java.awt.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Der Dateimanager liest und schreibt Dateien und beachtet dabei die jeweiligen System-abhängigen
* Zeichen zur Pfadtrennung.
* <p/>
* Ausserdem kann sie die Informationen eines Pixelfeldes im <code>.eaf</code>-Format
* (Engine-Alpha-Figur-Format) speichern sowie die eines String- oder Integer-Arrays im
* <code>.eaa</code>-Format (Engine-Alpha-Array-Format) lesen und speichern.
*
* @author Michael Andonie, Niklas Keller
*/
public class DateiManager {
/**
* Das für das aktuelle System gültige Zeichen für einen Zeilenumbruch.
*/
public static final String bruch = System.getProperty("line.separator");
/**
* Das für das aktuelle System gültige Zeichen für ein Unterverzeichnis
*/
public static final String sep = System.getProperty("file.separator");
/**
* Das grundlegende Verzeichnis. Dies ist die absolute Pfadangabe zum aktuellen
* Arbeitsverzeichnis. Das Arbeitsverzeichnis ist das Verzeichnis in dem sich deine
* <code>.jar</code>-Datei bzw. dein Projekt befindet.
*/
public static final String verz = System.getProperty("user.dir") + sep;
/**
* Eine Liste, die alle bereits verwendeten Farben einmalig listet
*/
private static final List<Color> colors = new ArrayList<>();
static {
colors.add(Color.RED);
colors.add(Color.GREEN);
colors.add(Color.BLUE);
colors.add(Color.YELLOW);
colors.add(Color.GRAY);
colors.add(Color.MAGENTA);
colors.add(Color.CYAN);
colors.add(Color.BLACK);
colors.add(Color.ORANGE);
colors.add(Color.LIGHT_GRAY);
}
private DateiManager () {
// keine Instanzen erlaubt!
}
/**
* Schreibt ein <code>String</code>-Array (bzw. ein <code>String[]</code>-Objekt) als
* eigenständige Datei auf.
* <p/>
* Hierfür wird das <code>.eaa</code>-Format verwendet (Engine-Alpha-Array).
*
* @param array
* Das zu schreibende Array.
* @param pfad
* Der Dateipfad, der sowohl das Verzeichnis wie auch den Dateinamen angibt.
* <p/>
* Dieser sollte mit <code>.eaa</code> enden. Wenn nicht, wird dies automatisch angehängt.
*
* @return <code>true</code>, falls die Datei erfolgreich geschrieben wurde, sonst
* <code>false</code>.
*/
@API
public static boolean stringArraySchreiben (String[] array, String pfad) {
if (array == null) {
throw new IllegalArgumentException("Das Array war null. Das ist nicht erlaubt!");
}
pfad = normalizePath(pfad);
if (!pfad.endsWith(".eaa")) {
pfad += ".eaa";
}
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(verz + pfad));
writer.write("version:2,typ:string,length:" + array.length);
String line;
for (int i = 0; i < array.length; i++) {
writer.newLine();
line = array[i];
if (line == null) {
line = Character.toString((char) 0);
} else {
line = DatatypeConverter.printBase64Binary(line.getBytes());
}
writer.write(line);
}
writer.close();
return true;
} catch (Exception e) {
Logger.error("Fehler beim Schreiben der Datei!");
}
return false;
}
/**
* Normalisiert einen Pfad, sodass er für das Dateisystem des jeweiligen Systems passt.
*
* @param path
* zu normalisierender Pfad
*
* @return normalisierter Pfad
*/
private static String normalizePath (String path) {
return path.replace("\\", "/").replace("/", sep);
}
/**
* Liest eine <code>.eaa</code>-String-Array-Datei ein.
*
* @param pfad
* Der Dateipfad, der sowohl das Verzeichnis wie auch den Dateinamen angibt.
* <p/>
* Dieser sollte mit <code>.eaa</code> enden. Wenn nicht, wird dies automatisch angehängt.
*
* @return Array, das eingelesen wurde oder <code>null</code>, wenn ein Fehler aufgetreten ist.
*/
@API
public static String[] stringArrayEinlesen (String pfad) {
pfad = normalizePath(pfad);
if (!pfad.endsWith(".eaa")) {
pfad += ".eaa";
}
BufferedReader reader = null;
String[] ret;
try {
String line;
reader = new BufferedReader(new FileReader(pfad));
line = reader.readLine();
if (line.equals("typ:String")) {
return CompatDateiManager.stringArrayEinlesen(pfad);
}
String[] metaInfos = line.split(",");
Map<String, String> meta = new HashMap<>();
for (String metaInfo : metaInfos) {
String[] info = metaInfo.split(":");
meta.put(info[0], info[1]);
}
if (!meta.get("version").equals("2")) {
Logger.error("Unbekannte Dateiformatsversion!");
return null;
}
if (!meta.get("typ").equals("string")) {
Logger.error("Datei hat einen anderen Datentyp gespeichert: " + meta.get("typ"));
}
ret = new String[Integer.parseInt(meta.get("length"))];
for (int i = 0; i < ret.length; i++) {
line = reader.readLine();
ret[i] = line.equals(Character.toString((char) 0)) ? null : new String(DatatypeConverter.parseBase64Binary(line));
}
return ret;
} catch (IOException e) {
Logger.error("Fehler beim Lesen der Datei. Existiert die Datei mit diesem Namen wirklich?\n" + pfad);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* Schreibt ein <code>int</code>-Array (bzw. ein <code>int[]</code>-Objekt) als eigenständige
* Datei auf.
* <p/>
* Hierfür wird das <code>.eaa</code>-Format verwendet (Engine-Alpha-Array).
*
* @param array
* Das zu schreibende Array.
* @param pfad
* Der Dateipfad, der sowohl das Verzeichnis wie auch den Dateinamen angibt.
* <p/>
* Dieser sollte mit <code>.eaa</code> enden. Wenn nicht, wird dies automatisch angehängt.
*
* @return <code>true</code>, falls die Datei erfolgreich geschrieben wurde, sonst
* <code>false</code>.
*/
@API
public static boolean integerArraySchreiben (int[] array, String pfad) {
if (array == null) {
throw new IllegalArgumentException("Das Array war null. Das ist nicht erlaubt!");
}
pfad = normalizePath(pfad);
if (!pfad.endsWith(".eaa")) {
pfad += ".eaa";
}
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(verz + pfad));
writer.write("version:2,typ:int,length:" + array.length);
for (int i = 0; i < array.length; i++) {
writer.newLine();
writer.write(Integer.toString(array[i]));
}
writer.close();
return true;
} catch (Exception e) {
Logger.error("Fehler beim Schreiben der Datei!");
}
return false;
}
/**
* Liest eine <code>.eaa</code>-int-Array-Datei ein.
*
* @param pfad
* Der Dateipfad, der sowohl das Verzeichnis wie auch den Dateinamen angibt.
* <p/>
* Dieser sollte mit <code>.eaa</code> enden. Wenn nicht, wird dies automatisch angehängt.
*
* @return Array, das eingelesen wurde oder <code>null</code>, wenn ein Fehler aufgetreten ist.
*/
@API
public static int[] integerArrayEinlesen (String pfad) {
pfad = normalizePath(pfad);
if (!pfad.endsWith(".eaa")) {
pfad += ".eaa";
}
int[] ret;
try {
String line;
BufferedReader reader = new BufferedReader(new FileReader(pfad));
line = reader.readLine();
if (line.compareTo("typ:String") == 0) {
return CompatDateiManager.integerArrayEinlesen(pfad);
}
String[] metaInfos = line.split(",");
Map<String, String> meta = new HashMap<>();
for (String metaInfo : metaInfos) {
String[] info = metaInfo.split(":");
meta.put(info[0], info[1]);
}
if (!meta.get("version").equals("2")) {
Logger.error("Unbekannte Dateiformatsversion!");
return null;
}
if (!meta.get("typ").equals("int")) {
Logger.error("Datei hat einen anderen Datentyp gespeichert: " + meta.get("typ"));
}
ret = new int[Integer.parseInt(meta.get("length"))];
for (int i = 0; i < ret.length; i++) {
ret[i] = Integer.parseInt(reader.readLine());
}
reader.close();
return ret;
} catch (IOException e) {
Logger.error("Fehler beim Lesen der Datei. Existiert die Datei mit diesem Namen wirklich?\n" + pfad);
}
return null;
}
/**
* Vereinfachte Version der Schreibmethode.<br /> Hierbei wird die eingegebene Figur nach dem
* selben Algorythmus geschrieben, jedoch gibt der eine Eingabeparameter den Namen und den
* gesamten Pfad an.
*
* @param f
* Die zu schreibende Figur
* @param pfad
* Der absolute (oder auch relative) Dateipfad, der sowohl das Verzeichnis wie auch den
* Dateinamen angibt.
*
* @return Ist <code>true</code>, wenn die Datei erfolgreich geschrieben wurde, ansonsten
* <code>false</code>.
*
* @see #schreiben(Figur, String, String)
*/
@API
@SuppressWarnings ( "unused" )
public static boolean schreiben (Figur f, String pfad) {
return schreiben(f, "", pfad);
}
/**
* Schreibt die ".eaf"-Datei zu einer Figur.
* <p/>
* Hierbei wird eine eventuell bestehende Datei dieses Namens rigoros gelöscht, sofern möglich.
* <p/>
* Diese Methode gibt zurück, ob das schreiben der Datei erfolgreich war oder nicht.
*
* @param f
* Die zu schreibende Figur
* @param name
* Der Name der Datei. Dieser sollte mit ".eaf" enden, wenn nicht, wird dies automatisch
* angehaengt.<br /> <b>Sollte der String allerdings sonst ein "."-Zeichen enthalten</b>, wird
* nur eine Fehlermeldung ausgespuckt!
* @param verzeichnis
* Das Verzeichnis, in dem die Datei gespeichert werden soll. Ist dies ein leerer String (""),
* so wird die Figur nur nach ihrem namen gespeichert.
*
* @return Ist <code>true</code>, wenn die Datei erfolgreich geschrieben wurde, ansonsten
* <code>false</code>.
*/
@API
public static boolean schreiben (Figur f, String verzeichnis, String name) {
BufferedWriter writer;
PixelFeld[] feld = f.animation();
if (!name.endsWith(".eaf")) {
name += ".eaf";
}
String verz = verzeichnis.isEmpty() ? name : verzeichnis + sep + name;
try {
writer = new BufferedWriter(new FileWriter(verz));
// Basics
writer.write("_fig_"); // Basisdeklaration
writer.newLine();
writer.write("an:" + feld.length); // Die Anzahl an PixelFeldern
writer.newLine();
writer.write("f:" + feld[0].faktor()); // Der Groessenfaktor
writer.newLine();
writer.write("x:" + feld[0].breiteN()); // Die X-Groesse
writer.newLine();
writer.write("y:" + feld[0].hoeheN()); // Die Y-Groesse
writer.newLine();
writer.write("p:" + (int) f.dimension().x); // Die Position X
writer.newLine();
writer.write("q:" + (int) f.dimension().y); // Die Position Y
writer.newLine();
// Die Felder
for (int i = 0; i < feld.length; i++) {
writer.write("-");
writer.newLine();
writer.write(feldInfo(feld[i]));
}
writer.close();
return true;
} catch (IOException e) {
Logger.error("Fehler beim Erstellen der Datei. Sind die Zugriffsrechte zu stark?" + bruch + verz);
Logger.error(e.getMessage());
return false;
}
}
/**
* Schreibt die ".eaf"-Datei zu einer Figur.
* <p/>
* Hierbei wird eine eventuell bestehende Datei dieses Namens rigoros gelöscht, sofern möglich.
* <p/>
* Diese Methode gibt zurück, ob das schreiben der Datei erfolgreich war oder nicht.
*
* @param f
* Die zu schreibende Figur
* @param name
* Der Name der Datei. Dieser sollte mit ".eaf" enden, wenn nicht, wird dies automatisch
* angehaengt.<br /> <b>Sollte der String allerdings sonst ein "."-Zeichen enthalten</b>, wird
* nur eine Fehlermeldung ausgespuckt!
* @param verzeichnis
* Das Verzeichnis, in dem die Datei gespeichert werden soll. Ist dies ein leerer String (""),
* so wird die Figur nur nach ihrem namen gespeichert.
* @param relativ
* Gibt an, ob das Verzeichnis relativ zum Spielprojekt geshen werden soll (standard)
*
* @return Ist <code>true</code>, wenn die Datei erfolgreich geschrieben wurde, sonst
* <code>false</code>.
*
* @deprecated Deprecated, weil Pfade, die nicht mit <code>/</code> beginnen (bzw.
* <code>C:\</code> auf Windows) automatisch relativ sind.
*/
@API
@Deprecated
public static boolean schreiben (Figur f, String name, String verzeichnis, boolean relativ) {
return schreiben(f, name, verzeichnis);
}
/**
* Berechnet aus einem PixelFeld die Informationen und gibt sie als String zurück.
* <p/>
* <b>ACHTUNG</b>: Umbruchzeichen werden gesetzt, jedoch endet der String <b>nicht</b> mit einem
* Zeilenumbruch, daher muss bei der Informationsbindung aus mehreren Feldern eine Zeile nach
* dem verwenden dieses Strings geschaltet werden.
*/
public static String feldInfo (PixelFeld f) {
Color[][] farbe = f.getPic();
String ret = "";
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
ret += "Z" + i + "-" + j + ":" + farbeAnalysieren(farbe[i][j]) + bruch;
}
}
return ret;
}
/**
* Analysiert eine Farbe und weist ihr einen String zu.
*
* @param c
* Zu analysierende Farbe
*
* @return Stringrepräsentation der Farbe
*/
public static String farbeAnalysieren (Color c) {
if (c == null) {
return "%%;";
}
if (c.equals(Color.black)) {
return "schwarz;";
}
if (c.equals(Color.gray)) {
return "grau;";
}
if (c.equals(Color.green)) {
return "gruen;";
}
if (c.equals(Color.yellow)) {
return "gelb;";
}
if (c.equals(Color.blue)) {
return "blau;";
}
if (c.equals(Color.white)) {
return "weiss;";
}
if (c.equals(Color.orange)) {
return "orange;";
}
if (c.equals(Color.red)) {
return "rot;";
}
if (c.equals(Color.pink)) {
return "pink;";
}
if (c.equals(Color.magenta)) {
return "magenta;";
}
if (c.equals(Color.cyan)) {
return "cyan;";
}
if (c.equals(Color.darkGray)) {
return "dunkelgrau;";
}
if (c.equals(Color.lightGray)) {
return "hellgrau;";
}
return "&" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + ";";
}
/**
* Liest eine Figur ein und gibt die geladene Figur zurück.
* <p/>
* Diese Methode macht nichts weiter als die Methode <code>figurEinlesen(String)</code>. Diese
* wurde aufgrund der Namensnähe zur Verhinderung ungeliebter Falschschreibungen hinzugefuegt
* und wrappt diese Methode lediglich.
*
* @param verzeichnis
* Das Verzeichnis der einzulesenden Datei.<br /> Die Eingabe <b>muss</b> ein Dateiname mit
* dem ende ".eaf" sein. Dies kann ohne Ordnerangaben gemacht werden, wenn die Datei im
* Quelltextordner ist.
*
* @return Die eingelesene Figur.<br /> <b>Tritt ein Fehler auf</b>, weil die Datei nicht
* einlesbar ist oder nicht existiert, ist dieser wert <code>null</code>.<br /> Trotzdem kann es
* sein, dass eine beschuedigte Datei nicht mehr korrekt einlesbar ist, dennoch ein Ergebnis
* liefert.
*
* @see #figurEinlesen(String)
*/
@API
@SuppressWarnings ( "unused" )
public static Figur figurLaden (String verzeichnis) {
return figurEinlesen(verzeichnis);
}
/**
* Liest eine Figur ein.
*
* @param verzeichnis
* Verzeichnis der einzulesenden Datei.
* <p/>
* Die Eingabe <b>muss</b> ein Dateiname mit dem Ende <code>.eaf</code> sein. Dies kann ohne
* Ordnerangaben gemacht werden, wenn die Datei im Quelltextordner ist.
*
* @return Eingelesene Figur.
* <p/>
* Tritt ein Fehler auf, weil die Datei nicht einlesbar ist oder nicht existiert, ist dieser
* wert <code>null</code>.
* <p/>
* Trotzdem kann es sein, dass eine beschädigte Datei nicht mehr korrekt einlesbar ist, dennoch
* ein Ergebnis liefert.
*/
@API
public static Figur figurEinlesen (String verzeichnis) {
return figurEinlesen(new File(normalizePath(verzeichnis)));
}
/**
* Liest eine Figur ein.
*
* @param file
* Verzeichnis der einzulesenden Datei.
* <p/>
* Die Eingabe <b>muss</b> ein Dateiname mit dem Ende <code>.eaf</code> sein. Dies kann ohne
* Ordnerangaben gemacht werden, wenn die Datei im Quelltextordner ist.
*
* @return Eingelesene Figur.
* <p/>
* Tritt ein Fehler auf, weil die Datei nicht einlesbar ist oder nicht existiert, ist dieser
* wert <code>null</code>.
* <p/>
* Trotzdem kann es sein, dass eine beschädigte Datei nicht mehr korrekt einlesbar ist, dennoch
* ein Ergebnis liefert.
*
* @see #figurLaden(String)
* @see #figurEinlesen(String)
*/
@API
public static Figur figurEinlesen (File file) {
String verzeichnis = file.getAbsolutePath();
if (!verzeichnis.endsWith(".eaf")) {
Logger.warning("Datei hatte nicht die Dateierweiterung .eaf. Diese wurde automatisch ergänzt.");
verzeichnis += ".eaf";
}
Figur fig = new Figur();
LineNumberReader f = null;
String line;
try {
String add = "";
f = new LineNumberReader(new FileReader(add + verzeichnis));
line = f.readLine();
if (line.compareTo("_fig_") != 0) { // Format bestätigen
Logger.error("Die Datei ist keine Figur-Datei!" + line);
return null;
}
line = f.readLine();
final int animationsLaenge = Integer.valueOf(line.substring(3)); // Die Anzahl an PixelFeldern
// System.out.println("PixelFelder: " + animationsLaenge);
line = f.readLine();
final int fakt = Integer.valueOf(line.substring(2)); // Der
// Groessenfaktor
// System.out.println("Der Groessenfaktor: " + fakt);
line = f.readLine();
final int x = Integer.valueOf(line.substring(2)); // Die X-Groesse
line = f.readLine();
final int y = Integer.valueOf(line.substring(2)); // Die Y-Groesse
// System.out.println("X-Gr: " + x + "; Y-Gr: " + y);
line = f.readLine();
final int px = Integer.valueOf(line.substring(2)); // Die X-Position
line = f.readLine();
final int py = Integer.valueOf(line.substring(2)); // Die Y-Position
// System.out.println("P-X: " + px + " - P-Y: " + py);
PixelFeld[] ergebnis = new PixelFeld[animationsLaenge];
for (int i = 0; i < ergebnis.length; i++) { // Felder basteln
if (f.readLine().compareTo("-") != 0) { // Sicherheitstest
Logger.error("Die Datei ist beschädigt");
}
ergebnis[i] = new PixelFeld(x, y, fakt);
for (int xT = 0; xT < x; xT++) { // X
for (int yT = 0; yT < y; yT++) { // Y
line = f.readLine();
Color c = farbeEinlesen(line.split(":")[1]);
if (c != null) {
c = ausListe(c);
}
ergebnis[i].farbeSetzen(xT, yT, c);
}
}
}
fig.animationSetzen(ergebnis);
fig.positionSetzen(px, py);
fig.animiertSetzen((animationsLaenge != 1));
f.close();
} catch (IOException e) {
Logger.error("Fehler beim Lesen der Datei. Existiert die Datei mit diesem Namen wirklich?" + bruch + verzeichnis);
e.printStackTrace();
} finally {
if (f != null) {
try {
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return fig;
}
/**
* Liest einen String ein und konvertiert ihn zu einer Farbe.
*
* @param s
* zu konvertierender String
*
* @return Color-Objekt, das gelesen wurde.
* <p/>
* <code>null</code>, wenn der String nicht eingelesen werden konnte!
*/
public static Color farbeEinlesen (String s) {
if (s.compareTo("%%;") == 0) {
return null;
} else if (s.charAt(0) != '&') {
return Raum.zuFarbeKonvertieren(s.replace(";", ""));
} else {
int[] rgb = new int[3];
int cnt = 0;
int temp = 1;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ',' || s.charAt(i) == ';') {
rgb[cnt] = Integer.valueOf(s.substring(temp, i));
temp = i + 1;
cnt++;
}
}
return new Color(rgb[0], rgb[1], rgb[2]);
}
}
/**
* Die Listenmethode beim Figureinlesen und für das speicherarme Raum-Objekt-Färben.
* <p/>
* Diese Methode wird verwendet um den Speicher zu entlasten, da Farbobjekte, die bereits in der
* Liste enthalten sind, nicht zurückgegeben werden, sondern durch den vorhandenen Farbewert
* ersetzt werden.
* <p/>
* Somit hat jede Farbe beim Einlesen genau eine Instanz innerhalb der gesamten Engine.
*
* @param farbe
* Farbe, die auf Existenz in der Liste geprüft werden soll.
*
* @return Das zurückgegebene Farbobjekt ist vom Zustand her genau das selbe wie das
* Eingegebene.
* <p/>
* Jedoch bleibt dank dieser Methode für jede Farbe nur ein Farbobjekt, was Speicherplatz
* spart.
*/
public static Color ausListe (Color farbe) {
for (Color c : colors) {
if (c.equals(farbe)) {
return c;
}
}
colors.add(farbe);
return farbe;
}
/**
* Diese Methode ist veraltet, da ein Pfad automatisch relativ ist, wenn er nicht mit
* <code>/</code> (Linux) bzw. <code>C:</code> (Windows, andere Buchstaben ebenso möglich)
* beginnt.
*/
@Deprecated
@SuppressWarnings ( "unused" )
public static Figur figurEinlesen (String verzeichnis, boolean relativ) {
return figurEinlesen(new File(verzeichnis));
}
}