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