1 | // A concept should be an object, not just a string. |
2 | |
3 | // Functions that should always be there for child processes: |
4 | please include function dbLock. |
5 | |
6 | static int concepts_internStringsLongerThan = 10; |
7 | |
8 | static new ThreadLocal<Bool> concepts_unlisted; |
9 | sbool concepts_unlistedByDefault; // true = we can create instances of concepts with "new" without registering them automatically |
10 | |
11 | static interface Derefable { |
12 | Concept get(); |
13 | } |
14 | |
15 | static interface IConceptIndex { |
16 | void update(Concept c); // also for adding |
17 | void remove(Concept c); |
18 | } |
19 | |
20 | static interface IFieldIndex<A extends Concept, Val> { |
21 | Cl<A> getAll(Val val); |
22 | L<Val> allValues(); // returns a cloned list |
23 | MultiSet<Val> allValues_multiSet(); |
24 | } |
25 | |
26 | // Approach to persisting the Concepts object itself (in normal |
27 | // DB operation, this is not done): For simplification, speed and |
28 | // compactness, we make almost all the fields transient and store only // the concepts and the idCounter. To unstructure the Concepts object, |
29 | // use unstructureConcepts() or postUnstructureConcepts(), then |
30 | // re-set up any indices, listeners etc. |
31 | |
32 | sclass Concepts implements AutoCloseable { |
33 | Map<Long, Concept> concepts = synchroTreeMap(); |
34 | long idCounter; |
35 | |
36 | transient HashMap<Class, O> perClassData; |
37 | transient Map miscMap; // don't use directly, call miscMap... methods to access |
38 | |
39 | // set to "-" for non-persistent (possibly not implemented) |
40 | // also, can include a case ID ("#123/1") |
41 | // TODO: have an actual directory instead |
42 | transient S programID; |
43 | |
44 | transient volatile long changes, changesWritten, lastChange; |
45 | transient volatile java.util.Timer autoSaver; |
46 | transient volatile bool dontSave; |
47 | transient volatile bool savingConcepts, noXFullGrab; |
48 | transient bool vmBusSend = true; |
49 | transient bool initialSave = true; // set to false to avoid initial useless saving |
50 | transient int autoSaveInterval = -1000; // 1 second + wait logic |
51 | transient bool useGZIP = true, quietSave; |
52 | transient ReentrantLock lock = new ReentrantLock(true); |
53 | transient ReentrantLock saverLock = new ReentrantLock(true); |
54 | transient long lastSaveTook = -1, lastSaveWas, loadTook, uncompressedSize; |
55 | transient float maxAutoSavePercentage = 10; |
56 | transient L<IConceptIndex> conceptIndices; |
57 | transient Map<Class<? extends Concept>, Map<S, IFieldIndex>> fieldIndices; |
58 | transient Map<Class<? extends Concept>, Map<S, IFieldIndex>> ciFieldIndices; |
59 | transient L saveActions = synchroList(); |
60 | transient O classFinder = _defaultClassFinder(); |
61 | transient L onAllChanged = synchroList(); // list of runnables |
62 | transient O saveWrapper; // VF1<Runnable>, to profile saving |
63 | transient bool modifyOnCreate; // set _modified == created initially |
64 | transient bool modifyOnBackRef; // set modified if back refs change |
65 | transient bool useFileLock = true; // instead of locking by bot |
66 | transient FileBasedLock fileLock; |
67 | |
68 | *() {} |
69 | *(S *programID) {} |
70 | |
71 | synchronized long internalID() { |
72 | do { |
73 | ++idCounter; |
74 | } while (hasConcept(idCounter)); |
75 | ret idCounter; |
76 | } |
77 | |
78 | synchronized HashMap<Class, O> perClassData () { |
79 | if (perClassData == null) perClassData = new HashMap; |
80 | ret perClassData; |
81 | } |
82 | |
83 | void initProgramID() { |
84 | if (programID == null) |
85 | programID = getDBProgramID(); |
86 | } |
87 | |
88 | // Now tries to load from bot first, then go to disk. |
89 | Concepts load() { |
90 | ret load(false); |
91 | } |
92 | |
93 | Concepts safeLoad() { |
94 | ret load(true); |
95 | } |
96 | |
97 | Concepts load(bool allDynamic) { |
98 | initProgramID(); |
99 | |
100 | // try custom grabber |
101 | O dbGrabber = miscMapGet("dbGrabber"); |
102 | if (dbGrabber != null && !isFalse(callF(dbGrabber))) |
103 | this; |
104 | |
105 | try { |
106 | if (tryToGrab(allDynamic)) ret this; |
107 | } catch e { |
108 | if (!exceptionMessageContains(e, "no xfullgrab")) |
109 | printShortException(e); |
110 | print("xfullgrab failed - loading DB of " + programID + " from disk"); |
111 | } |
112 | ret loadFromDisk(allDynamic); |
113 | } |
114 | |
115 | Concepts loadFromDisk() { ret loadFromDisk(false); } |
116 | |
117 | Concepts loadFromDisk(bool allDynamic) { |
118 | if (nempty(concepts)) clearConcepts(); |
119 | //DynamicObject_loading.set(true); // now done in unstructure() |
120 | //try { |
121 | // minimal crash recovery |
122 | restoreLatestBackupIfConceptsFileEmpty(programID, doIt := true); |
123 | |
124 | long time = now(); |
125 | Map<Long, Concept> _concepts = concepts; // empty map |
126 | readLocally2_allDynamic.set(allDynamic); |
127 | temp tempSetTL(readLocally2_classFinder, classFinder); |
128 | readLocally2(this, programID, "concepts"); |
129 | Map<Long, Concept> __concepts = concepts; |
130 | concepts = _concepts; |
131 | concepts.putAll(__concepts); |
132 | int l = readLocally_stringLength; |
133 | int tokrefs = unstructure_tokrefs; |
134 | assignConceptsToUs(); |
135 | loadTook = now()-time; |
136 | done("Loaded " + n(l(concepts), "concepts"), time); |
137 | try { |
138 | if (fileSize(getProgramFile(programID, "idCounter.structure")) != 0) |
139 | readLocally2(this, programID, "idCounter"); |
140 | else |
141 | calcIdCounter(); |
142 | } catch print e { |
143 | calcIdCounter(); |
144 | } |
145 | /*} finally { |
146 | DynamicObject_loading.set(null); |
147 | }*/ |
148 | if (initialSave) allChanged(); |
149 | ret this; |
150 | } |
151 | |
152 | Concepts loadConcepts() { ret load(); } |
153 | |
154 | bool tryToGrab(bool allDynamic) { |
155 | if (sameSnippetID(programID, getDBProgramID())) false; |
156 | temp RemoteDB db = connectToDBOpt(programID); |
157 | if (db != null) { |
158 | loadGrab(db.fullgrab(), allDynamic); |
159 | true; |
160 | } |
161 | false; |
162 | } |
163 | |
164 | Concepts load(S grab) { |
165 | ret loadGrab(grab, false); |
166 | } |
167 | |
168 | Concepts safeLoad(S grab) { |
169 | ret loadGrab(grab, true); |
170 | } |
171 | |
172 | Concepts loadGrab(S grab, bool allDynamic) { |
173 | clearConcepts(); |
174 | DynamicObject_loading.set(true); |
175 | try { |
176 | Map<Long, Concept> map = (Map) unstructure(grab, allDynamic, classFinder); |
177 | concepts.putAll(map); |
178 | assignConceptsToUs(); |
179 | for (long l : map.keySet()) |
180 | idCounter = max(idCounter, l); |
181 | } finally { |
182 | DynamicObject_loading.set(null); |
183 | } |
184 | allChanged(); |
185 | ret this; |
186 | } |
187 | |
188 | void assignConceptsToUs() { |
189 | // fix unstructure bugs |
190 | |
191 | for (Pair<Long, O> p: mapToPairs((Map<Long, O>) (Map) concepts)) |
192 | if (!(p.b instanceof Concept)) { |
193 | print("DROPPING non-existant concept " + p.a + ": " + dynShortName(p.b)); |
194 | concepts.remove(p.a); |
195 | } |
196 | |
197 | for (Concept c : values(concepts)) c._concepts = this; |
198 | for (Concept c : values(concepts)) |
199 | c._doneLoading2(); // doneLoading2 is called on all concepts after all concepts are loaded |
200 | } |
201 | |
202 | S progID() { |
203 | ret programID == null ? getDBProgramID() : programID; |
204 | } |
205 | |
206 | Concept getConcept(S id) { |
207 | ret empty(id) ? null : getConcept(parseLong(id)); |
208 | } |
209 | |
210 | Concept getConcept(long id) { |
211 | ret (Concept) concepts.get((long) id); |
212 | } |
213 | |
214 | Concept getConcept(RC ref) { |
215 | ret ref == null ? null : getConcept(ref.longID()); |
216 | } |
217 | |
218 | bool hasConcept(long id) { |
219 | ret concepts.containsKey((long) id); |
220 | } |
221 | |
222 | void deleteConcept(long id) { |
223 | Concept c = getConcept(id); |
224 | if (c == null) |
225 | print("Concept " + id + " not found"); |
226 | else |
227 | c.delete(); |
228 | } |
229 | |
230 | void calcIdCounter() { |
231 | long id_ = 0; |
232 | for (long id : keys(concepts)) |
233 | id_ = max(id_, id); |
234 | idCounter = id_+1; |
235 | saveLocally2(this, programID, "idCounter"); |
236 | } |
237 | |
238 | File conceptsFile() { |
239 | ret getProgramFile(programID, useGZIP ? "concepts.structure.gz" : "concepts.structure"); |
240 | } |
241 | |
242 | // used for locking when useFileLock is activated |
243 | File lockFile() { |
244 | ret getProgramFile(programID, "concepts.lock"); |
245 | } |
246 | |
247 | FileBasedLock fileLock() { |
248 | if (fileLock == null) |
249 | fileLock = new FileBasedLock(lockFile()); |
250 | ret fileLock; |
251 | } |
252 | |
253 | void saveConceptsIfDirty() { saveConcepts(); } |
254 | void save() { saveConcepts(); } |
255 | |
256 | void saveConcepts() { |
257 | if (dontSave) ret; |
258 | initProgramID(); |
259 | saverLock.lock(); |
260 | savingConcepts = true; |
261 | long start = now(), time; |
262 | try { |
263 | S s = null; |
264 | //synchronized(main.class) { |
265 | long _changes = changes; |
266 | if (_changes == changesWritten) ret; |
267 | |
268 | File f = conceptsFile(); |
269 | |
270 | lock.lock(); |
271 | long fullTime = now(); |
272 | try { |
273 | saveLocally2(this, programID, "idCounter"); |
274 | |
275 | if (useGZIP) { |
276 | callRunnableWithWrapper(saveWrapper, r { |
277 | uncompressedSize = saveGZStructureToFile(f, cloneMap(concepts)); |
278 | }); |
279 | getProgramFile(programID, "concepts.structure").delete(); |
280 | } else |
281 | s = fullStructure(); |
282 | } finally { |
283 | lock.unlock(); |
284 | } |
285 | |
286 | while (nempty(saveActions)) |
287 | pcallF(popFirst(saveActions)); |
288 | |
289 | changesWritten = _changes; // only update when structure didn't fail |
290 | |
291 | if (!useGZIP) { |
292 | time = now()-start; |
293 | if (!quietSave) |
294 | print("Saving " + toM(l(s)) + "M chars (" /*+ changesWritten + ", "*/ + time + " ms)"); |
295 | start = now(); |
296 | saveTextFile(f, javaTokWordWrap(s)); |
297 | getProgramFile(programID, "concepts.structure.gz").delete(); |
298 | } |
299 | |
300 | copyFile(f, getProgramFile(programID, "backups/concepts.structure" + (useGZIP ? ".gz" : "") + ".backup" + ymd() + "-" + formatInt(hours(), 2))); |
301 | time = now()-start; |
302 | if (!quietSave) |
303 | print(programID + ": Saved " + toK(f.length()) + " K, " + n(concepts, "concepts") + " (" + time + " ms)"); |
304 | lastSaveWas = fullTime; |
305 | lastSaveTook = now()-fullTime; |
306 | } finally { |
307 | savingConcepts = false; |
308 | saverLock.unlock(); |
309 | } |
310 | } |
311 | |
312 | void _autoSaveConcepts() { |
313 | if (autoSaveInterval < 0 && maxAutoSavePercentage != 0) { |
314 | long pivotTime = Math.round(lastSaveWas+lastSaveTook*100.0/maxAutoSavePercentage); |
315 | if (now() < pivotTime) { |
316 | //print("Skipping auto-save (last save took " + lastSaveTook + ")"); |
317 | ret; |
318 | } |
319 | } |
320 | try { |
321 | saveConcepts(); |
322 | } catch e { |
323 | print("Concept save failed, will try again: " + e); |
324 | } |
325 | } |
326 | |
327 | S fullStructure() { |
328 | ret structure(cloneMap(concepts)); |
329 | } |
330 | |
331 | void clearConcepts() { |
332 | concepts.clear(); |
333 | allChanged(); |
334 | } |
335 | |
336 | void allChanged() { |
337 | synchronized(this) { ++changes; lastChange = sysNow(); } |
338 | if (vmBusSend) vmBus_send('conceptsChanged, this); |
339 | pcallFAll(onAllChanged); |
340 | } |
341 | |
342 | // auto-save every second if dirty |
343 | synchronized void autoSaveConcepts() { |
344 | if (autoSaver == null) { |
345 | if (isTransient()) fail("Can't persist transient database"); |
346 | autoSaver = doEvery_daemon(abs(autoSaveInterval), r { _autoSaveConcepts() }); |
347 | // print("Installed auto-saver (" + autoSaveInterval + " ms, " + progID() + ")"); |
348 | } |
349 | } |
350 | |
351 | public void close { cleanMeUp(); } |
352 | |
353 | void cleanMeUp() { |
354 | pcall { |
355 | bool shouldSave = autoSaver != null; |
356 | if (autoSaver != null) { |
357 | autoSaver.cancel(); |
358 | autoSaver = null; |
359 | } |
360 | while (savingConcepts) sleepInCleanUp(10); |
361 | if (shouldSave) |
362 | saveConceptsIfDirty(); |
363 | } |
364 | dispose fileLock; |
365 | } |
366 | |
367 | Map<Long, S> getIDsAndNames() { |
368 | new Map<Long, S> map; |
369 | Map<Long, Concept> cloned = cloneMap(concepts); |
370 | for (long id : keys(cloned)) |
371 | map.put(id, cloned.get(id).className); |
372 | ret map; |
373 | } |
374 | |
375 | void deleteConcepts(L l) { |
376 | ping(); |
377 | if (l != null) for (O o : cloneList(l)) |
378 | if (o instanceof Long) { |
379 | Concept c = concepts.get(o); |
380 | if (c != null) c.delete(); |
381 | } else if (o cast Concept) |
382 | o.delete(); |
383 | else |
384 | warn("Can't delete " + getClassName(o)); |
385 | } |
386 | |
387 | <A extends Concept> A conceptOfType(Class<A> type) { |
388 | IConceptCounter counter = conceptCounterForClass(type); |
389 | if (counter != null) ret (A) first(counter.allConcepts()); |
390 | ret firstOfType(allConcepts(), type); |
391 | } |
392 | |
393 | <A extends Concept> L<A> conceptsOfType(Class<A> type) { |
394 | ping(); |
395 | IConceptCounter counter = conceptCounterForClass(type); |
396 | if (counter != null) ret (L<A>) cloneList(counter.allConcepts()); |
397 | ret filterByType(allConcepts(), type); |
398 | } |
399 | |
400 | <A extends Concept> L<A> listConcepts(Class<A> type) { |
401 | ret conceptsOfType(type); |
402 | } |
403 | |
404 | <A extends Concept> L<A> list(Class<A> type) { |
405 | ret conceptsOfType(type); |
406 | } |
407 | |
408 | // TODO: would be better to make this Cl (indices may return sets) |
409 | L<Concept> list(S type) { |
410 | ret conceptsOfType(type); |
411 | } |
412 | |
413 | L<Concept> conceptsOfType(S type) { |
414 | ret filterByDynamicType(allConcepts(), "main$" + type); |
415 | } |
416 | |
417 | bool hasConceptOfType(Class<? extends Concept> type) { |
418 | ret hasType(allConcepts(), type); |
419 | } |
420 | |
421 | void persistConcepts() { |
422 | loadConcepts(); |
423 | autoSaveConcepts(); |
424 | } |
425 | |
426 | // We love synonyms |
427 | void conceptPersistence() { persistConcepts(); } |
428 | |
429 | Concepts persist() { persistConcepts(); ret this; } |
430 | void persist(Int interval) { |
431 | if (interval != null) autoSaveInterval = interval; |
432 | persist(); |
433 | } |
434 | |
435 | // Runs r if there is no concept of that type |
436 | <A extends Concept> A ensureHas(Class<A> c, Runnable r) { |
437 | A a = conceptOfType(c); |
438 | if (a == null) { |
439 | r.run(); |
440 | a = conceptOfType(c); |
441 | if (a == null) |
442 | fail("Concept not made by " + r + ": " + shortClassName(c)); |
443 | } |
444 | ret a; |
445 | } |
446 | |
447 | // Ensures that every concept of type c1 is ref'd by a concept of |
448 | // type c2. |
449 | // Type of func: voidfunc(concept) |
450 | void ensureHas(Class<? extends Concept> c1, Class<? extends Concept> c2, O func) { |
451 | for (Concept a : conceptsOfType(c1)) { |
452 | Concept b = findBackRef(a, c2); |
453 | if (b == null) { |
454 | callF(func, a); |
455 | b = findBackRef(a, c2); |
456 | if (b == null) |
457 | fail("Concept not made by " + func + ": " + shortClassName(c2)); |
458 | } |
459 | } |
460 | } |
461 | |
462 | // Type of func: voidfunc(concept) |
463 | void forEvery(Class<? extends Concept> type, O func) { |
464 | for (Concept c : conceptsOfType(type)) |
465 | callF(func, c); |
466 | } |
467 | |
468 | int deleteAll(Class<? extends Concept> type) { |
469 | L<Concept> l = (L) conceptsOfType(type); |
470 | for (Concept c : l) c.delete(); |
471 | ret l(l); |
472 | } |
473 | |
474 | Collection<Concept> allConcepts() { |
475 | synchronized(concepts) { |
476 | ret new L(values(concepts)); |
477 | } |
478 | } |
479 | |
480 | IConceptCounter conceptCounterForClass(Class<? extends Concept> c) { |
481 | for (IFieldIndex idx : values(mapGet(fieldIndices, c))) |
482 | if (idx cast IConceptCounter) ret idx; |
483 | for (IFieldIndex idx : values(mapGet(ciFieldIndices, c))) |
484 | if (idx cast IConceptCounter) ret idx; |
485 | null; |
486 | } |
487 | |
488 | <A extends Concept> int countConcepts(Class<A> c, O... params) { |
489 | ping(); |
490 | if (empty(params)) { |
491 | IConceptCounter counter = conceptCounterForClass(c); |
492 | if (counter != null) ret counter.countConcepts(); |
493 | ret l(list(c)); |
494 | } |
495 | int n = 0; |
496 | for (A x : list(c)) if (checkConceptFields(x, params)) ++n; |
497 | ret n; |
498 | } |
499 | |
500 | int countConcepts(S c, O... params) { |
501 | ping(); |
502 | if (empty(params)) ret l(list(c)); |
503 | int n = 0; |
504 | for (Concept x : list(c)) if (checkConceptFields(x, params)) ++n; |
505 | ret n; |
506 | } |
507 | |
508 | int countConcepts() { |
509 | ret l(concepts); |
510 | } |
511 | |
512 | synchronized void addConceptIndex(IConceptIndex index) { |
513 | if (conceptIndices == null) |
514 | conceptIndices = new L; |
515 | conceptIndices.add(index); |
516 | } |
517 | |
518 | synchronized void removeConceptIndex(IConceptIndex index) { |
519 | if (conceptIndices == null) ret; |
520 | conceptIndices.remove(index); |
521 | if (empty(conceptIndices)) conceptIndices = null; |
522 | } |
523 | |
524 | synchronized void addFieldIndex(Class<? extends Concept> c, S field, IFieldIndex index) { |
525 | if (fieldIndices == null) |
526 | fieldIndices = new HashMap; |
527 | Map<S, IFieldIndex> map = fieldIndices.get(c); |
528 | if (map == null) |
529 | fieldIndices.put(c, map = new HashMap); |
530 | map.put(field, index); |
531 | } |
532 | |
533 | synchronized IFieldIndex getFieldIndex(Class<? extends Concept> c, S field) { |
534 | if (fieldIndices == null) null; |
535 | Map<S, IFieldIndex> map = fieldIndices.get(c); |
536 | ret map == null ? null : map.get(field); |
537 | } |
538 | |
539 | synchronized void addCIFieldIndex(Class<? extends Concept> c, S field, IFieldIndex index) { |
540 | if (ciFieldIndices == null) |
541 | ciFieldIndices = new HashMap; |
542 | Map<S, IFieldIndex> map = ciFieldIndices.get(c); |
543 | if (map == null) |
544 | ciFieldIndices.put(c, map = new HashMap); |
545 | map.put(field, index); |
546 | } |
547 | |
548 | synchronized IFieldIndex getCIFieldIndex(Class<? extends Concept> c, S field) { |
549 | if (ciFieldIndices == null) null; |
550 | Map<S, IFieldIndex> map = ciFieldIndices.get(c); |
551 | ret map == null ? null : map.get(field); |
552 | } |
553 | |
554 | // inter-process methods |
555 | |
556 | RC xnew(S name, O... values) { |
557 | ret new RC(cnew(name, values)); |
558 | } |
559 | |
560 | void xset(long id, S field, O value) { |
561 | xset(new RC(id), field, value); |
562 | } |
563 | |
564 | void xset(RC c, S field, O value) { |
565 | if (value instanceof RC) |
566 | value = getConcept((RC) value); |
567 | cset(getConcept(c), field, value); |
568 | } |
569 | |
570 | O xget(long id, S field) { |
571 | ret xget(new RC(id), field); |
572 | } |
573 | |
574 | O xget(RC c, S field) { |
575 | ret xgetPost(cget(getConcept(c), field)); |
576 | } |
577 | |
578 | O xgetPost(O o) { |
579 | o = deref(o); |
580 | if (o instanceof Concept) |
581 | ret new RC((Concept) o); |
582 | ret o; |
583 | } |
584 | |
585 | void xdelete(long id) { |
586 | xdelete(new RC(id)); |
587 | } |
588 | |
589 | void xdelete(RC c) { |
590 | getConcept(c).delete(); |
591 | } |
592 | |
593 | void xdelete(L<RC> l) { |
594 | for (RC c : l) |
595 | xdelete(c); |
596 | } |
597 | |
598 | L<RC> xlist() { |
599 | ret map("toPassRef", allConcepts()); |
600 | } |
601 | |
602 | L<RC> xlist(S className) { |
603 | ret map("toPassRef", conceptsOfType(className)); |
604 | } |
605 | |
606 | bool isTransient() { ret eq(programID, "-"); } |
607 | |
608 | S xfullgrab() { |
609 | if (noXFullGrab) fail("no xfullgrab (DB too large)"); |
610 | lock lock(); |
611 | if (changes == changesWritten && !isTransient()) |
612 | ret loadConceptsStructure(programID); |
613 | ret fullStructure(); |
614 | } |
615 | |
616 | /* dev. |
617 | Either<File, byte[]> xfullgrabGZipped() { |
618 | lock lock(); |
619 | if (changes == changesWritten && !isTransient()) |
620 | ret loadConceptsStructure(programID); |
621 | ret fullStructure(); |
622 | }*/ |
623 | |
624 | void xshutdown() { |
625 | // Killing whole VM if someone wants this DB to shut down |
626 | cleanKillVM(); |
627 | } |
628 | |
629 | long xchangeCount() { ret changes; } |
630 | int xcount() { ret countConcepts(); } |
631 | |
632 | void register(Concept c) { |
633 | ping(); |
634 | if (c._concepts == this) ret; |
635 | if (c._concepts != null) fail("Can't re-register"); |
636 | c.id = internalID(); |
637 | c.created = now(); |
638 | if (modifyOnCreate) c._setModified(c.created); |
639 | register_phase2(c); |
640 | } |
641 | |
642 | void register_phase2(Concept c) { |
643 | c._concepts = this; |
644 | concepts.put((long) c.id, c); |
645 | for (Concept.Ref r : unnull(c.refs)) |
646 | r.index(); |
647 | c.change(); |
648 | } |
649 | |
650 | void registerKeepingID(Concept c) { |
651 | if (c._concepts == this) ret; |
652 | if (c._concepts != null) fail("Can't re-register"); |
653 | c._concepts = this; |
654 | concepts.put((long) c.id, c); |
655 | c.change(); |
656 | } |
657 | |
658 | void conceptChanged(Concept c) { |
659 | allChanged(); |
660 | if (conceptIndices != null) |
661 | for (IConceptIndex index : conceptIndices) |
662 | index.update(c); |
663 | } |
664 | |
665 | bool hasUnsavedData() { |
666 | ret changes != changesWritten || savingConcepts; |
667 | } |
668 | |
669 | synchronized O miscMapGet(O key) { |
670 | ret mapGet(miscMap, key); |
671 | } |
672 | |
673 | synchronized O miscMapPut(O key, O value) { |
674 | if (miscMap == null) miscMap = new Map; |
675 | ret miscMap.put(key, value); |
676 | } |
677 | |
678 | synchronized void miscMapRemove(O key) { |
679 | mapRemove(miscMap, key); |
680 | } |
681 | |
682 | // Note: auto-typing can fool you, make sure create returns |
683 | // a wide enough type |
684 | synchronized <A> A miscMapGetOrCreate(O key, IF0<A> create) { |
685 | if (containsKey(miscMap, key)) ret (A) miscMap.get(key); |
686 | A value = create!; |
687 | miscMapPut(key, value); |
688 | ret value; |
689 | } |
690 | } // end of Concepts |
691 | |
692 | sclass Concept extends DynamicObject { |
693 | transient Concepts _concepts; // Where we belong |
694 | long id; |
695 | long created, _modified; |
696 | L<Ref> refs; |
697 | L<Ref> backRefs; |
698 | |
699 | // used only internally (cnew) |
700 | *(S className) { |
701 | super(className); |
702 | _created(); |
703 | } |
704 | |
705 | *() { |
706 | if (!_loading()) { |
707 | //className = shortClassName(this); // XXX - necessary? |
708 | //print("New concept of type " + className); |
709 | _created(); |
710 | } |
711 | } |
712 | |
713 | *(bool unlisted) { |
714 | if (!unlisted) _created(); |
715 | } |
716 | |
717 | toString { |
718 | ret shortDynamicClassName(this) + " " + id; |
719 | } |
720 | |
721 | static bool loading() { ret _loading(); } |
722 | static bool _loading() { ret dynamicObjectIsLoading(); } |
723 | |
724 | void _created() { |
725 | if (!concepts_unlistedByDefault && !eq(concepts_unlisted!, true)) |
726 | db_mainConcepts().register(this); |
727 | } |
728 | |
729 | /*void put(S field, O value) { |
730 | fieldValues.put(field, value); |
731 | change(); |
732 | } |
733 | |
734 | O get(S field) { |
735 | ret fieldValues.get(field); |
736 | }*/ |
737 | |
738 | class Ref<A extends Concept> implements Derefable, IF0<A> { |
739 | A value; |
740 | |
741 | *() { |
742 | if (!dynamicObjectIsLoading()) refs = addDyn_quickSync(refs, this); |
743 | } |
744 | |
745 | *(A *value) { |
746 | refs = addDyn_quickSync(refs, this); |
747 | index(); |
748 | } |
749 | |
750 | // get owning concept (source) |
751 | Concept concept() { |
752 | ret Concept.this; |
753 | } |
754 | |
755 | // get target |
756 | public A get() { ret value; } |
757 | bool has() { ret value != null; } |
758 | |
759 | bool set(A a) { |
760 | if (a == value) false; |
761 | unindex(); |
762 | value = a; |
763 | index(); |
764 | true; |
765 | } |
766 | |
767 | void set(Ref<A> ref) { set(ref.get()); } |
768 | void clear() { set((A) null); } |
769 | |
770 | bool validRef() { |
771 | ret value != null && _concepts != null && _concepts == value._concepts; |
772 | } |
773 | |
774 | // TODO: sync all the indexing and unindexing!? |
775 | void index() { |
776 | if (validRef()) { |
777 | value._addBackRef(this); |
778 | change(); |
779 | } |
780 | } |
781 | |
782 | Ref<A> unindex() { |
783 | if (validRef()) { |
784 | value._removeBackRef(this); |
785 | change(); |
786 | } |
787 | this; |
788 | } |
789 | |
790 | void unindexAndDrop { |
791 | unindex(); |
792 | _removeRef(this); |
793 | } |
794 | |
795 | void change() { |
796 | Concept.this.change(); |
797 | } |
798 | |
799 | toString { ret str(value); } |
800 | } |
801 | |
802 | class RefL<A extends Concept> extends AbstractList<A> { |
803 | new L<Ref<A>> l; |
804 | |
805 | public void clear { |
806 | while (!isEmpty()) removeLast(this); |
807 | } |
808 | |
809 | public void replaceWithList(L<A> l) { |
810 | clear(); |
811 | fOr (A a : l) add(a); |
812 | } |
813 | |
814 | public A set(int i, A o) { |
815 | Ref<A> ref = syncGet(l, i); |
816 | A prev = ref!; |
817 | ref.set(o); |
818 | ret prev; |
819 | } |
820 | |
821 | public void add(int i, A o) { |
822 | syncAdd(l, i, new Ref(o)); |
823 | } |
824 | |
825 | public A get(int i) { |
826 | ret syncGet(l, i)!; |
827 | } |
828 | |
829 | public A remove(int i) { |
830 | ret syncRemove(l, i)!; |
831 | } |
832 | |
833 | public int size() { |
834 | ret syncL(l); |
835 | } |
836 | |
837 | public bool contains(O o) { |
838 | if (o instanceof Concept) |
839 | for (Ref<A> r : l) if (eq(r!, o)) true; |
840 | ret super.contains(o); |
841 | } |
842 | } |
843 | |
844 | void delete() { |
845 | //name = "[defunct " + name + "]"; |
846 | //defunct = true; |
847 | //energy = 0; |
848 | |
849 | // clean refs |
850 | |
851 | for (Ref r : unnull(refs)) |
852 | r.unindex(); |
853 | refs = null; |
854 | |
855 | // set back refs to null |
856 | |
857 | for (Ref r : cloneList(backRefs)) |
858 | r.set((Concept) null); |
859 | backRefs = null; |
860 | |
861 | if (_concepts != null) { |
862 | _concepts.concepts.remove((long) id); |
863 | _concepts.allChanged(); |
864 | if (_concepts.conceptIndices != null) |
865 | for (IConceptIndex index : _concepts.conceptIndices) |
866 | index.remove(this); |
867 | _concepts = null; |
868 | } |
869 | id = 0; |
870 | } |
871 | |
872 | BaseXRef export() { |
873 | ret new BaseXRef(_concepts.progID(), id); |
874 | } |
875 | |
876 | // notice system of a change in this object |
877 | void change() { |
878 | _setModified(now()); |
879 | _change_withoutUpdatingModifiedField(); |
880 | } |
881 | |
882 | void _setModified(long modified) { |
883 | _modified = modified; |
884 | } |
885 | |
886 | void _change_withoutUpdatingModifiedField() { |
887 | if (_concepts != null) _concepts.conceptChanged(this); |
888 | } |
889 | |
890 | void _change() { change(); } |
891 | |
892 | S _programID() { |
893 | ret _concepts == null ? getDBProgramID() : _concepts.progID(); |
894 | } |
895 | |
896 | // overridable |
897 | |
898 | void _addBackRef(Concept.Ref ref) { |
899 | backRefs = addDyn_quickSync(backRefs, ref); |
900 | _backRefsModified(); |
901 | } |
902 | |
903 | void _backRefsModified { |
904 | if (_concepts != null && _concepts.modifyOnBackRef) change(); |
905 | } |
906 | |
907 | void _removeBackRef(Concept.Ref ref) { |
908 | backRefs = removeDyn_quickSync(backRefs, ref); |
909 | _backRefsModified(); |
910 | } |
911 | |
912 | void _removeRef(Concept.Ref ref) { |
913 | refs = removeDyn_quickSync(refs, ref); |
914 | } |
915 | |
916 | // convenience methods |
917 | |
918 | void _setField(S field, O value) { |
919 | cset(this, field, value); |
920 | } |
921 | |
922 | void _setFields(O... values) { |
923 | cset(this, values); |
924 | } |
925 | |
926 | Concepts concepts() { ret _concepts; } |
927 | |
928 | bool isDeleted() { ret id == 0; } |
929 | |
930 | void _doneLoading2() { |
931 | Map<S, FieldMigration> map = _fieldMigrations(); |
932 | if (map != null) for (S oldField, FieldMigration m : map) |
933 | crenameField_noOverwrite(this, oldField, m.newField); |
934 | } |
935 | |
936 | srecord FieldMigration(S newField) {} |
937 | |
938 | // value is |
939 | Map<S, FieldMigration> _fieldMigrations() { null; } |
940 | } // end of Concept |
941 | |
942 | // remote reference (for inter-process communication or |
943 | // external databases). Formerly "PassRef". |
944 | // prepared for string ids if we do them later |
945 | sclass RC { |
946 | transient O owner; |
947 | S id; |
948 | |
949 | *() {} // make serialisation happy |
950 | *(long id) { this.id = str(id); } |
951 | *(O owner, long id) { this.id = str(id); this.owner = owner; } |
952 | *(Concept c) { this(c.id); } |
953 | long longID() { ret parseLong(id); } |
954 | |
955 | public S toString() { |
956 | ret id; |
957 | } |
958 | } |
959 | |
960 | // Reference to a concept in another program |
961 | sclass BaseXRef { |
962 | S programID; |
963 | long id; |
964 | |
965 | *() {} |
966 | *(S *programID, long *id) {} |
967 | |
968 | public bool equals(O o) { |
969 | if (!(o instanceof BaseXRef)) false; |
970 | BaseXRef r = cast o; |
971 | ret eq(programID, r.programID) && eq(id, r.id); |
972 | } |
973 | |
974 | public int hashCode() { |
975 | ret programID.hashCode() + (int) id; |
976 | } |
977 | } |
978 | |
979 | // BaseXRef as a concept |
980 | sclass XRef extends Concept { |
981 | BaseXRef ref; |
982 | |
983 | *() {} |
984 | *(BaseXRef *ref) { _doneLoading2(); } |
985 | |
986 | // after we have been added to concepts |
987 | void _doneLoading2() { |
988 | getIndex().put(ref, this); |
989 | } |
990 | |
991 | HashMap<BaseXRef, XRef> getIndex() { |
992 | ret getXRefIndex(_concepts); |
993 | } |
994 | } |
995 | |
996 | static synchronized HashMap<BaseXRef, XRef> getXRefIndex(Concepts concepts) { |
997 | HashMap cache = (HashMap) concepts.perClassData().get(XRef.class); |
998 | if (cache == null) |
999 | concepts.perClassData.put(XRef.class, cache = new HashMap); |
1000 | ret cache; |
1001 | } |
1002 | |
1003 | // uses mainConcepts |
1004 | static XRef lookupOrCreateXRef(BaseXRef ref) { |
1005 | XRef xref = getXRefIndex(db_mainConcepts()).get(ref); |
1006 | if (xref == null) |
1007 | xref = new XRef(ref); |
1008 | ret xref; |
1009 | } |
1010 | |
1011 | // define standard concept functions to use main concepts |
1012 | |
1013 | // Now in db_mainConcepts() |
1014 | /*static void cleanMeUp_concepts() { |
1015 | if (db_mainConcepts() != null) db_mainConcepts().cleanMeUp(); |
1016 | // mainConcepts = null; // TODO |
1017 | }*/ |
1018 | |
1019 | svoid loadAndAutoSaveConcepts { |
1020 | db_mainConcepts().persist(); |
1021 | } |
1022 | |
1023 | svoid loadAndAutoSaveConcepts(int interval) { |
1024 | db_mainConcepts().persist(interval); |
1025 | } |
1026 | |
1027 | static RC toPassRef(Concept c) { |
1028 | ret new RC(c); |
1029 | } |
1030 | |
1031 | // so we can instantiate the program to run as a bare DB bot |
1032 | please include function bareDBMode. |
1033 | |
1034 | // so unstructuring Concepts works |
1035 | please include function dynamicObjectIsLoading_threadLocal. |
1036 | |
1037 | svoid concepts_setUnlistedByDefault(bool b) { |
1038 | concepts_unlistedByDefault = b; |
1039 | } |
Began life as a copy of #1004863
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt
No comments. add comment
Snippet ID: | #1030886 |
Snippet name: | "Concepts" [backup before parent] |
Eternal ID of this version: | #1030886/1 |
Text MD5: | 59d330fd9fb92002072ba02d22565c09 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-04-06 12:19:00 |
Source code size: | 27824 bytes / 1039 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 246 / 268 |
Referenced in: | [show references] |