package thegame.io;
import java.awt.datatransfer.DataFlavor;
import java.io.*;
import java.net.URL;
import java.text.DateFormat;
import java.util.*;
import net.sf.jiga.xtended.JXAException;
import net.sf.jiga.xtended.kernel.BitStack;
import net.sf.jiga.xtended.kernel.DebugMap;
import net.sf.jiga.xtended.kernel.Debugger;
import net.sf.jiga.xtended.kernel.JXAenvUtils;
import net.sf.jiga.xtended.kernel.Level;
import net.sf.jiga.xtended.kernel.ThreadWorks;
import thegame.FieldGui;
import thegame.ReadAndSaveSettingsListener;
/**
VM system arg -J-Dthegame.debug.rass=true enables debug output at startup (file read/write operations)
*/
public class ReadAndSaveSettings<DATA extends Serializable> implements Debugger {
/**VM system arg -J-Dthegame.debug.rass=true enables debug output at startup (file read/write operations)*/
public static Level DBUG_RASS = DebugMap._getInstance().newDebugLevel(ReadAndSaveSettings.class);
static {
if(JXAenvUtils._getSysBoolean("thegame.debug.rass"))
DebugMap._getInstance().setDebugLevelEnabled(true, DBUG_RASS);
}
/**
*
*/
public final static String[] _MIME_EXTS = new String[]{"MCO", "mco"};
/**
*
*/
public final DataFlavor DATAFLAVOR;
private File dataFile;
DATA data;
Set listeners = Collections.synchronizedSet(new HashSet());
/**
* creates a IO pipeline for GameProperties. No data will be loaded until {@linkplain #loadData()}
* is invoked.
*
* @param dataFile the path of the saved game properties
*/
public ReadAndSaveSettings(File dataFile, DATA data) {
this.DATAFLAVOR = new DataFlavor(data.getClass(), data.getClass().getSimpleName());
this.dataFile = dataFile;
assert data != null : "must specify a default instance of the data to save/load";
this.data = data;
}
/**
* creates a IO pipeline for GameProperties.
*
* @throws IOException if the loading of props failed in any way
* @param dataFile the path to store data (a file will be created and the
* InputStream is copied into it)
* @param input a resource-stream input of the Serialized DATA to use as
* default data
*/
public ReadAndSaveSettings(File dataFile, DATA data, URL input) {
this(dataFile, data);
if (input == null) {
notify(new JXAException(JXAException.LEVEL.APP, "null input"), new Date(), saving | done);
} else {
try {
BufferedInputStream bis = new BufferedInputStream(input.openStream());
RandomAccessFile raf = new RandomAccessFile(this.dataFile, "rw");
raf.setLength(bis.available());
byte[] r = new byte[512];
int rBLen = 0;
while ((rBLen = bis.read(r)) != -1) {
raf.write(r, 0, rBLen);
}
bis.close();
raf.close();
notify(null, new Date(), saving | done);
} catch (IOException ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
notify(ex, new Date(), saving | done);
}
}
}
public void deleteFile() {
dataFile.delete();
notify(null, null, done | delete);
}
private static BitStack _stateBits = new BitStack();
private final static int ERROR = _stateBits._newBitRange();
private final static int SAVED = _stateBits._newBitRange();
private final static int DELETED = _stateBits._newBitRange();
private final static int LOADED = _stateBits._newBitRange();
private final static int NOTFOUND = _stateBits._newBitRange();
private final static int SAVING = _stateBits._newBitRange();
private final static int LOADING = _stateBits._newBitRange();
private int state = 0;
private ReadAndSaveSettingsListener stateHandler = new ReadAndSaveSettingsListener<DATA>() {
public void dataSaved(DATA data, String date) {
state = SAVED;
}
public void dataDeleted() {
state = DELETED;
}
public void dataLoaded(DATA data, String date) {
state = LOADED;
}
public void dataNotFound() {
state = NOTFOUND;
}
public void dataError(Object e) {
state = ERROR;
}
public void dataSaving(String m) {
state = SAVING;
}
public void dataLoading(String m) {
state = LOADING;
}
};
/**
* returns true if and only if the last activity had errors and clears the
* error bits.
*
* @return true if and only if the last activity had errors
*
*/
public boolean checkError() {
int ret = state;
state = 0;
return (ret & (ERROR | NOTFOUND)) != 0;
}
public void saveData() {
Date now = new Date();
ObjectOutputStream oos = null;
FileOutputStream fos = null;
RandomAccessFile raf = null;
Exception error = null;
notify(error, now, saving);
try {
raf = new RandomAccessFile(dataFile, "rw");
raf.setLength(0);
oos = new ObjectOutputStream(fos = new FileOutputStream(raf.getFD()));
oos.writeObject(data);
oos.writeObject(now);
fos.close();
raf.close();
} catch (Exception e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
error = e;
} finally {
notify(error, now, done | saving);
}
}
public DATA loadData() {
Exception error = null;
ObjectInputStream ois = null;
FileInputStream fis = null;
RandomAccessFile raf = null;
Date date = null;
notify(error, date, loading);
try {
raf = new RandomAccessFile(dataFile, "r");
ois = new ObjectInputStream(fis = new FileInputStream(raf.getFD()));
data = (DATA) ois.readObject();
date = (Date) ois.readObject();
if (data == null && date == null) {
error = new FileNotFoundException("no data could be read");
}
ois.close();
fis.close();
raf.close();
} catch (Exception e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
error = e;
} finally {
notify(error, date, done | loading);
return data;
}
}
final static int done = 1;
final static int loading = 2;
final static int saving = 4;
final static int delete = 8;
private void _notify(ReadAndSaveSettingsListener l, final Exception error, final Date date, final int status) {
switch (status) {
case loading:
l.dataLoading("loading file...");
break;
case saving:
l.dataSaving("saving file...");
break;
default:
if (error == null) {
if ((status & done) != 0) {
String dateFormat = date == null ? "" : DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date);
if ((status & loading) != 0) {
l.dataLoaded(data, dateFormat);
}
if ((status & saving) != 0) {
l.dataSaved(data, dateFormat);
}
if ((status & delete) != 0) {
l.dataDeleted();
}
}
} else if (error instanceof FileNotFoundException) {
l.dataNotFound();
} else {
l.dataError(error);
}
break;
}
}
private void notify(final Exception error, final Date date, final int status) {
_notify(stateHandler, error, date, status);
Runnable r = new Runnable() {
public void run() {
synchronized (listeners) {
for (Iterator<ReadAndSaveSettingsListener> i = listeners.iterator(); i.hasNext();) {
_notify(i.next(), error, date, status);
}
}
}
};
if (ThreadWorks.Swing.isEventDispatchThread()) {
r.run();
} else {
ThreadWorks.Swing.invokeLater(r);
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
public void addListener(ReadAndSaveSettingsListener listener) {
listeners.add(listener);
}
public void removeListener(ReadAndSaveSettingsListener listener) {
listeners.remove(listener);
}
/**
* VM system arg -J-Dthegame.debug.rass=true enables debug output at startup (file read/write operations)*/
public boolean isDebugEnabled() {
return DebugMap._getInstance().isDebuggerEnabled(ReadAndSaveSettings.class);
}
public void setDebugEnabled(boolean b) {
DebugMap._getInstance().setDebuggerEnabled(b, ReadAndSaveSettings.class);
}
}