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