// a persistent map using a clever combination of persisting and logging
// (well, only logging as of now)
// Uses TreeMap as default
// Note: don't put in static initializer (programID not set yet)
static class PersistentMap extends AbstractMap {
Map m = makeMap();
File file;
S id; // unique ID of this map
Map makeMap() {
ret new TreeMap();
}
PersistentMap(S fileName) {
this(getProgramFile(fileName));
}
PersistentMap(S progID, S fileName) {
this(getProgramFile(progID, fileName));
}
*(File *file) {
for (S s : scanLog(file)) pcall {
L l = cast unstructure(s);
O cmd = l.get(0);
if (eq(cmd, "add"))
m.put((A) l.get(1), (B) l.get(2));
else if (eq(cmd, "remove"))
m.remove((A) l.get(1));
else if (eq(cmd, "id"))
id = (S) l.get(1);
else
print("Unknown command in log: " + l.get(0));
}
if (id == null)
makeID_priv();
}
void makeID_priv() {
id = makeRandomID(12);
logQuoted(file, structure(litlist("id", id)));
}
// just delegates
public synchronized int size() {
ret m.size();
}
public synchronized B get(O a) {
ret m.get(a);
}
// TODO: calling remove() in the iterator will have unpersisted
// effects.
public synchronized Set> entrySet() {
ret m.entrySet(); // synchronize this?
}
// delegates with logging
public synchronized B put(A a, B b) {
B c = m.get(a);
if (neq(b, c)) {
m.put(a, b);
logQuoted(file, structure(litlist("add", a, b)));
}
ret c;
}
public synchronized B remove(O a) {
B result = m.remove(a);
logQuoted(file, structure(litlist("remove", a)));
ret result;
}
// unique id of this map
public S id() {
ret id;
}
// clear map, make new ID
public synchronized void clear() {
m.clear();
file.delete();
makeID_priv();
}
}