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