// 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(); } }