sclass ConceptsShadowLogger implements AutoCloseable { // concepts we are logging Concepts cc; int delay = 500; // how long to wait before writing changes (ms) bool verbose; // all the shadows, sorted by ID L shadows; // IDs of concepts that have changed (fullChange = all changed) new HashSet changedIDs; bool fullChange; // where to send the shadow log (optional) PrintWriter writer; // our internal lock & queue Lock lock = lock(); transient Q q = startQ("ConceptsShadowLogger"); IVF1 changeHandler = change -> { lock lock; if (change cast ConceptChange) changedIDs.add(change.c.id); else if (change cast ConceptCreate) changedIDs.add(change.c.id); else if (change cast ConceptDelete) changedIDs.add(change.id); else if (change cast FullChange) fullChange = true; }; *() {} *(Concepts *cc) {} void install { makeShadows(); cc.onChange(changeHandler); //cc.addPreSave(); } void uninstall { cc.removeChangeListener(changeHandler); } void makeShadows { shadows = allConceptShadows(cc); } void flush { q.add(r flush_impl); } void flush_impl { sleep(delay); Set changedIDs; { lock lock; if (fullChange) { changedIDs = allConceptIDs(cc); fullChange = false; } else { changedIDs = this.changedIDs; this.changedIDs = new HashSet; } } // make new shadows (id, shadow) - shadow will be null when object was deleted LPair newShadows = map(sorted(changedIDs), id -> pair(id, conceptShadow(cc.getConcept(id)))); new L> diffs; new L updatedShadows; int i1 = 0, i2 = 0; LPair l1 = newShadows; L l2 = shadows; while (i1 < l(l1) && i2 < l(l2)) { long id1, ConceptShadow s1 = unpair newShadows.get(i1); ConceptShadow s2 = shadows.get(i2); long id2 = s2.id(); if (id1 < id2) { diffs.add(new CreatedDeletedChanged.Created(s1)); updatedShadows.add(s1); ++i1; } else if (id1 > id2) { updatedShadows.add(s2); ++i2; } else { if (s1 == null) diffs.add(new CreatedDeletedChanged.Deleted(s2)); else { if (eq(s1, s2)) print("Unchanged concept: " + id1); else diffs.add(new CreatedDeletedChanged.Changed(s2, s1)); updatedShadows.add(s1); } ++i1; ++i2; } } while (i1 < l(l1)) { long id, ConceptShadow s = unpair l1.get(i1++); if (s != null) { diffs.add(new CreatedDeletedChanged.Created(s)); updatedShadows.add(s); } else print("Concept already gone again: " + id); } updatedShadows.addAll(subList(l2, i2)); shadows = updatedShadows; if (verbose) pnl("SHADOW DIFF", diffs); if (writer != null && nempty(diffs)) { writer.println(structure(diffs)); writer.flush(); } } public void close { runInQAndWait(q, r { dispose writer }); } }