// A concept should be an object, not just a string. // Functions that should always be there for child processes: please include function dbLock. static interface Derefable { Concept get(); } sclass Concept extends DynamicObject { long id; //double energy; //bool defunct; long created; // used only internally (cnew) *(S className) { super(className); _created(); } *() { className = shortClassName(this); if (!DynamicObject_loading) { //print("New concept of type " + className); _created(); } } new L refs; new L backRefs; void _created() { created = now(); if (!isTrue(_unlisted.get())) register(); } void put(S field, O value) { fieldValues.put(field, value); change(); } O get(S field) { ret fieldValues.get(field); } class Ref implements Derefable { A value; *() { if (!DynamicObject_loading) refs.add(this); } *(A *value) { refs.add(this); index(); } // get owning concept (source) Concept concept() { ret Concept.this; } // get target public A get() { ret value; } bool has() { ret value != null; } bool empty() { ret value == null; } void set(A a) { if (a == value) ret; unindex(); value = a; index(); } void set(Ref ref) { set(ref.get()); } void clear() { set((A) null); } void index() { if (value != null) value.backRefs.add(this); change(); } void unindex() { if (value != null) value.backRefs.remove(this); } public S toString() { ret str(get()); } } class RefL extends AbstractList { new L> l; public A set(int i, A o) { A prev = l.get(i).get(); l.get(i).set(o); change(); ret prev; } public void add(int i, A o) { l.add(i, new Ref(o)); change(); } public A get(int i) { ret l.get(i).get(); } public A remove(int i) { A a = l.remove(i).get(); change(); ret a; } public int size() { ret l.size(); } // derefs void addAll_(L l) { for (O x : l) add_(x); } // derefs void add_(O o) { add((A) deref(o)); } } void delete() { if (id == 0) ret; concepts.remove(id); conceptsByClass.get(getClass()).remove(id); id = 0; //name = "[defunct " + name + "]"; //defunct = true; //energy = 0; for (Ref r : refs) r.unindex(); refs.clear(); change(); } // register a previously unlisted concept void register() { if (id != 0) ret; // already registered id = newID(); concepts.put(id, this); _addToClassMap(); change(); } void _addToClassMap() { Class c = getClass(); Map map = conceptsByClass.get(c); if (map == null) { // we're the first object, register this class Class superclass = c.getSuperclass(); if (superclass != Concept) subclasses.put(superclass, c); conceptsByClass.put(c, map = new TreeMap); } map.put(id, this); } } // class Concept // for inter-process communication // prepared for string ids if we do them later sclass PassRef { S id; *() {} // make serialisation happy *(long id) { this.id = str(id); } *(Concept c) { this(c.id); } long longID() { ret parseLong(id); } public S toString() { ret id; } } sclass Event extends Concept {} // This can be set by client static bool concepts_quietSave; static Map concepts = synchroTreeMap(); static Map> conceptsByClass = synchroHashMap(); static new MultiMap subclasses; static long idCounter; static volatile long changes = 1, changesWritten; static volatile java.util.Timer autoSaver; static volatile bool savingConcepts; static new ThreadLocal _unlisted; static bool concepts_saveOneMore; static S conceptsLoadedFrom; static int concepts_internStringsLongerThan = 10; static int concepts_autoSaveInterval = 1000; static long newID() { do { ++idCounter; } while (hasConcept(idCounter)); ret idCounter; } ssvoid loadConceptsFrom(S programID) { conceptsLoadedFrom = programID; clearConcepts(); DynamicObject_loading = true; try { structure_internStringsLongerThan = concepts_internStringsLongerThan; readLocally(programID, "concepts"); readLocally(programID, "idCounter"); assignConceptsToUs(); } finally { DynamicObject_loading = false; } } svoid assignConceptsToUs() { for (Concept c : values(concepts)) { c._addToClassMap(); callOpt(c, "_doneLoading2"); } } ssvoid loadConcepts() { loadConceptsFrom(programID()); } static Concept getConcept(S id) { ret empty(id) ? null : getConcept(parseLong(id)); } static Concept getConcept(long id) { ret (Concept) concepts.get((long) id); } static Concept getConcept(PassRef ref) { ret ref == null ? null : getConcept(ref.longID()); } static bool hasConcept(long id) { ret concepts.containsKey((long) id); } static bool hasConcept(Class c, O... params) { ret conceptWhere(c, params) != null; } static Concept deleteConcept(long id) { Concept c = getConcept(id); if (c == null) print("Concept " + id + " not found"); else c.delete(); ret c; } // call in only one thread static void saveConceptsIfDirty() { saveConceptsIfDirty(programID()); } static void saveConceptsIfDirty(S programID) { savingConcepts = true; try { S s; synchronized(main.class) { bool change = changes != changesWritten; if (!change && !concepts_saveOneMore) ret; changesWritten = changes; concepts_saveOneMore = change; save(programID, "idCounter"); s = structure(cloneMap(concepts)); } if (!concepts_quietSave) print("Saving " + l(s) + " chars (" + changesWritten + ")"); saveTextFile(getProgramFile(programID, "concepts.structure"), s); copyFile(getProgramFile(programID, "concepts.structure"), getProgramFile(programID, "concepts.structure.backup" + ymd() + "-" + formatInt(hours(), 2))); } finally { savingConcepts = false; } } static void saveConcepts() { saveConceptsIfDirty(); } static void saveConceptsTo(S programID) { saveConceptsIfDirty(programID); } static void clearConcepts() { concepts.clear(); conceptsByClass.clear(); subclasses.clear(); change(); } static synchronized void change() { ++changes; } // auto-save every second if dirty static synchronized void autoSaveConcepts() { if (autoSaver == null) autoSaver = doEvery(concepts_autoSaveInterval, r { saveConcepts(); }); } static void cleanMeUp() { if (autoSaver != null) { autoSaver.cancel(); autoSaver = null; } while (savingConcepts) sleep(10); saveConceptsIfDirty(); } static Map getIDsAndNames() { new Map map; Map cloned = cloneMap(concepts); for (long id : keys(cloned)) map.put(id, cloned.get(id).className); ret map; } static A findBackRef(Concept c, Class type) { ret findBackRefOfType(c, type); } static A findBackRefOfType(Concept c, Class type) { for (Concept.Ref r : c.backRefs) if (instanceOf(r.concept(), type)) ret (A) r.concept(); null; } static L findBackRefs(Concept c, Class type) { new L l; for (Concept.Ref r : c.backRefs) if (instanceOf(r.concept(), type)) l.add((A) r.concept()); ret l; } static A conceptOfType(Class type) { ret firstOfType(allConcepts(), type); } static L conceptsOfType(Class type) { //ret filterByType(allConcepts(), type); new L l; _collectInstances(type, l); ret l; } svoid _collectInstances(Class c, L l) { l.addAll(values(conceptsByClass.get(c))); for (Class sub : subclasses.get(c)) _collectInstances(sub, l); } static L listConcepts(Class type) { ret conceptsOfType(type); } static L list(S type, O... params) { ret empty(params) ? conceptsOfType(type) : conceptsWhere(type, params); } static L list(Class type, O... params) { ret empty(params) ? conceptsOfType(type) : conceptsWhere(type, params); } static L conceptsOfType(S type) { ret filterByDynamicType(allConcepts(), "main$" + type); } static bool hasConceptOfType(Class type) { ret hasType(allConcepts(), type); } static void persistConcepts() { concepts_quietSave = true; loadConcepts(); autoSaveConcepts(); } static void loadAndAutoSaveConcepts() { persistConcepts(); } // We love synonyms static void conceptPersistence() { persistConcepts(); } // Runs r if there is no concept of that type static A ensureHas(Class c, Runnable r) { A a = conceptOfType(c); if (a == null) { r.run(); a = conceptOfType(c); if (a == null) fail("Concept not made by " + r + ": " + shortClassName(c)); } ret a; } // Ensures that every concept of type c1 is ref'd by a concept of // type c2. // Type of func: voidfunc(concept) static void ensureHas(Class c1, Class c2, O func) { for (Concept a : conceptsOfType(c1)) { Concept b = findBackRef(a, c2); if (b == null) { callF(func, a); b = findBackRef(a, c2); if (b == null) fail("Concept not made by " + func + ": " + shortClassName(c2)); } } } // Type of func: voidfunc(concept) static void forEvery(Class type, O func) { for (Concept c : conceptsOfType(type)) callF(func, c); } static int deleteAll(Class type) { L l = (L) conceptsOfType(type); for (Concept c : l) c.delete(); ret l(l); } static Collection allConcepts() { synchronized(concepts) { ret new L(values(concepts)); } } static Concept cnew(S name, O... values) { Class cc = findClass(name); Concept c = cc != null ? nuObject(cc) : new Concept(name); csetAll(c, values); ret c; } static A cnew(Class cc, O... values) { A c = nuObject(cc); csetAll(c, values); ret c; } static void csetAll(Concept c, O... values) { cset(c, values); } static void cset(Concept c, O... values) ctex { values = expandParams(c.getClass(), values); warnIfOddCount(values); for (int i = 0; i+1 < l(values); i += 2) { S field = (S) values[i]; O value = values[i+1]; Field f = setOpt_findField(c.getClass(), field); //print("cset: " + c.id + " " + field + " " + struct(value) + " " + f); if (value instanceof PassRef) value = getConcept((PassRef) value); value = deref(value); if (value instanceof S && l((S) value) >= concepts_internStringsLongerThan) value = ((S) value).intern(); if (f == null) c.fieldValues.put(field, value instanceof Concept ? c.new Ref((Concept) value) : value); else if (isSubtypeOf(f.getType(), Concept.Ref.class)) ((Concept.Ref) f.get(c)).set((Concept) value); else f.set(c, value); } change(); } static O cget(Concept c, S field) { O o = getOptDyn(c, field); if (o instanceof Concept.Ref) ret ((Concept.Ref) o).get(); ret o; } static PassRef toPassRef(Concept c) { ret new PassRef(c); } // inter-process methods static PassRef xnew(S name, O... values) { ret new PassRef(cnew(name, values)); } static void xset(long id, S field, O value) { xset(new PassRef(id), field, value); } static void xset(PassRef c, S field, O value) { if (value instanceof PassRef) value = getConcept((PassRef) value); cset(getConcept(c), field, value); } // get class name of concept static O xclass(PassRef id) { Concept c = getConcept(id); ret c == null ? null : c.className; } static O xget(long id, S field) { ret xget(new PassRef(id), field); } static O xget(PassRef c, S field) { ret export(cget(getConcept(c), field)); } static void xdelete(long id) { xdelete(new PassRef(id)); } static void xdelete(PassRef c) { getConcept(c).delete(); } static L xlist() { ret map("toPassRef", allConcepts()); } static L xlist(S className) { ret map("toPassRef", conceptsOfType(className)); } // end of IPC methods // make concept instance that is not connected to DB static A unlisted(Class c) { _unlisted.set(true); try { ret nuObject(c); } finally { _unlisted.set(null); } } static A unary(Class c) { A a = conceptOfType(c); if (a == null) a = nuObject(c); ret a; } static Android3 makeDBBot(S name) { ret makeBot(name, makeDBResponder()); } static L exposedDBMethods = ll("xlist", "xnew", "xset", "xdelete", "xget", "xclass"); static O makeDBResponder() { ret new O { S answer(S s) { ret exposeMethods(s, exposedDBMethods); } }; } static O export(O o) { if (o instanceof Concept) ret new PassRef(((Concept) o).id); ret o; } ssvoid saveConceptsBack() { saveConceptsTo(assertNotNull(conceptsLoadedFrom)); } static bool eq(O a, O b) { ret _eq(deref(a), deref(b)); }