diff --git a/projects/emfular/src/lib/binding/reference-creator.ts b/projects/emfular/src/lib/binding/reference-creator.ts index f38b445..d0cb808 100644 --- a/projects/emfular/src/lib/binding/reference-creator.ts +++ b/projects/emfular/src/lib/binding/reference-creator.ts @@ -35,7 +35,7 @@ export function createContainer< } if (meta.containment) { - const defaultEClass = parent.$modelUri + meta.target; + const defaultEClass = parent.$modelMeta.uri + meta.target; if (isList) { return new ReTreeListContainer(parent, propertyKey, meta, defaultEClass ) } else { diff --git a/projects/emfular/src/lib/binding/reference-decorator.ts b/projects/emfular/src/lib/binding/reference-decorator.ts index 1a5b759..16be0d7 100644 --- a/projects/emfular/src/lib/binding/reference-decorator.ts +++ b/projects/emfular/src/lib/binding/reference-decorator.ts @@ -4,6 +4,7 @@ import {createContainer} from "./reference-creator"; import {ReSingleInterface} from "../referencing/referencable/container/re-single-interface"; import {KindFromMeta, RefineReference} from "./reference-typing"; import {ReListInterface} from "../referencing/referencable/container/re-list-interface"; +import {REFERENCE_INITIALIZERS} from "../referencing/referencable/referencable-symbols"; export function reference, M extends ReferenceMeta>( meta: M @@ -65,11 +66,10 @@ export function reference, M extends ReferenceMeta>( } - if (!prototype.__referenceInitializers) { - prototype.__referenceInitializers = []; + if (!prototype[REFERENCE_INITIALIZERS]) { + prototype[REFERENCE_INITIALIZERS] = []; } - - (prototype.__referenceInitializers as Array<(this: any) => void>) + (prototype[REFERENCE_INITIALIZERS] as Array<(this: any) => void>) .push(function (this: any) { this[symbol] = createContainer( this, meta, String(propertyKey) diff --git a/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts b/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts index 4040b29..62f3641 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts @@ -286,7 +286,7 @@ export function createListProxy< const index = Number(prop); const arr = container.get(); if (index < arr.length) { - arr[index].destruct(); + arr[index].$destruct(); return true; } return false; diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts index 161515b..2db6574 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts @@ -6,6 +6,7 @@ import {ListUpdater} from "../../../../utils/list-updater"; import {ReListContainer} from "../re-list-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; import {DeletionMode} from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; export class ReLinkListContainer< T extends Referencable, @@ -15,14 +16,14 @@ implements ReLinkContainer { constructor(parent: P, name: string, refMeta: ReferenceMeta) { super(parent, name, refMeta); - this._parent.$otherReferences.push(this) + this._parent[REFERENCE_INTERNAL_API].otherLinks().push(this) } addWithoutTypeCheck(item: T): boolean { let res = ListUpdater.addToListIfMissing(item, this._instance) if (res) { if(this.inverseName !== undefined) { - return item.addToReferencableContainer(this.inverseName, this._parent) + return item[REFERENCE_INTERNAL_API].addToReference(this.inverseName, this._parent) } return true; } else { @@ -38,20 +39,16 @@ implements ReLinkContainer { const res = ListUpdater.removeFromList(item, this._instance) if (res) { if(this.inverseName !== undefined) { - item.removeFromReferencableContainer(this.inverseName, this._parent, mode) + item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, mode) } } return res; //todo behaviour of flag different to add?? } - override delete(mode: DeletionMode = DeletionMode.RELAXED) { - ListUpdater.destructAllFromChangingList(this._instance, mode) - } - removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this.inverseName !== undefined) { for (const child of [...this._instance]) { - child.removeFromReferencableContainer(this.inverseName, item, mode) + child[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, item, mode) } return true; // todo - refine? } diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts index 5e282f5..4341f74 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts @@ -7,6 +7,7 @@ import { ReSingleChildExample2 } from "../../../test/re-containers-with-single-child"; import {DeletionMode} from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; describe('ReLinkSingleContainer', () => { it('should create an instance', () => { @@ -41,8 +42,8 @@ describe('ReLinkSingleContainer', () => { expect(elem1.link).toEqual(middle1); expect(elem2.link).toBeDefined(); expect(elem2.link).toEqual(middle2); - expect(elem1.$otherReferences[0].remove(middle1)).toBeTrue(); - expect(elem2.$otherReferences[0].remove(middle2)).toBeTrue(); + expect(elem1[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle1)).toBeTrue(); + expect(elem2[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle2)).toBeTrue(); expect(tester1.child).toBeDefined(); expect(tester1.child).toEqual(middle1); expect(tester2.child).toBeDefined(); @@ -84,8 +85,8 @@ describe('ReLinkSingleContainer', () => { expect(elem1.link).toEqual(middle1); expect(elem2.link).toBeDefined(); expect(elem2.link).toEqual(middle2); - expect(elem1.$otherReferences[0].remove(middle1, DeletionMode.CASCADE)).toBeTrue(); - expect(elem2.$otherReferences[0].remove(middle2, DeletionMode.CASCADE)).toBeTrue(); + expect(elem1[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle1, DeletionMode.CASCADE)).toBeTrue(); + expect(elem2[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle2, DeletionMode.CASCADE)).toBeTrue(); expect(tester1.child).toBeDefined(); expect(tester1.child).toEqual(middle1); expect(tester2.child).toBeUndefined(); diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts index 7cb2fec..383ff67 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts @@ -5,6 +5,7 @@ import {ReLinkContainer} from "./re-link-container"; import {ReSingleContainer} from "../re-single-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; import { DeletionMode } from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; export class ReLinkSingleContainer< T extends Referencable, @@ -14,14 +15,14 @@ implements ReLinkContainer { constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { super(parent, referenceName, refMeta); - this._parent.$otherReferences.push(this) + this._parent[REFERENCE_INTERNAL_API].otherLinks().push(this) } protected set(instance: T): void { if(this.inverseName !== undefined) { - this._instance?.removeFromReferencableContainer(this.inverseName, this._parent, DeletionMode.RELAXED) + this._instance?.[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, DeletionMode.RELAXED) this._instance = instance; - instance.addToReferencableContainer(this.inverseName, this._parent) + instance[REFERENCE_INTERNAL_API].addToReference(this.inverseName, this._parent) } else { this._instance = instance; } @@ -40,7 +41,7 @@ implements ReLinkContainer { if(this._instance == item) { this._instance = undefined; if (this.inverseName != undefined) { - item.removeFromReferencableContainer(this.inverseName, this._parent, mode) + item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, mode) } return true; } else { @@ -49,12 +50,12 @@ implements ReLinkContainer { } override delete(mode: DeletionMode = DeletionMode.RELAXED) { - this._instance?.destruct(mode) + this._instance?.$destruct(mode) } removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this.inverseName !== undefined) { - this._instance?.removeFromReferencableContainer(this.inverseName, item, mode) + this._instance?.[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, item, mode) return true; // todo refine? } return false; diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-container.ts index 5cb5d5d..837f19e 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/re-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/re-container.ts @@ -36,7 +36,7 @@ export abstract class ReContainer< isAcceptableItem(item: Referencable): boolean { const expectedType = this.meta.target - const targetEclass = this._parent.$modelUri+expectedType //eclass composition only works since we work inside one model + const targetEclass = this._parent.$modelMeta.uri+expectedType //eclass composition only works since we work inside one model let targetConstr = ModelRegistry.get(targetEclass) return item instanceof targetConstr; } @@ -48,7 +48,7 @@ export abstract class ReContainer< abstract remove(item: T, mode?: DeletionMode): boolean; - //called to destruct all elements in the container (e.g. when destroying a parent + //called to $destruct all elements in the container (e.g. when destroying a parent abstract delete(mode?: DeletionMode): void abstract toJson(ctx: SerializationContext): any diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts index db64e8b..ef626f3 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts @@ -5,32 +5,30 @@ import {ReSingleInterface} from "../re-single-interface"; import {ReShallowInterface} from "./re-shallow-interface"; import {ReferenceMeta} from "../../../../binding/model-definition"; import { DeletionMode } from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; -export class ReTreeParentContainer> - extends ReContainer -implements ReSingleInterface, - ReShallowInterface{ +export class ReTreeParentContainer< + T extends Referencable, + P extends Referencable = T["$ParentType"]> + extends ReContainer +implements ReSingleInterface, + ReShallowInterface{ constructor(parent: T, referenceName: string, refMeta: ReferenceMeta) { super(parent, referenceName, refMeta); // referenceName is actually unused for this container type } - get(): T["ParentType"] | undefined { - return (this._parent.getParentReferencable() as T["ParentType"]) + get(): P | undefined { + return (this._parent.$getEParent()) } //todo rewrite without using item parent explicitly? - addWithoutTypeCheck(item: T["ParentType"]): boolean { - let me: T = this._parent - const currentParentCont = this._parent.parent - if(currentParentCont != undefined) { - currentParentCont.remove(this._parent as T["ParentType"], DeletionMode.RELAXED) - } - return item.addToReferencableContainer(this.inverseName, me) + addWithoutTypeCheck(item: P): boolean { + return item[REFERENCE_INTERNAL_API].addToReference(this.inverseName!, this._parent) } - remove(item: T["ParentType"], mode: DeletionMode = DeletionMode.RELAXED): boolean { - return item.removeFromReferencableContainer(this.inverseName, this._parent, mode) + remove(item: P, mode: DeletionMode = DeletionMode.RELAXED): boolean { + return item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName!, this._parent, mode) } delete(): void {} diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts index d0c1039..247adc8 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts @@ -5,8 +5,9 @@ import { ReContainer } from "../re-container"; import {JsonOf} from "../../../../serialization/json-deserializable"; export interface ReTreeChildrenContainer< - T extends Referencable, -> extends ReContainer { + T extends Referencable

, + P extends Referencable =T["$ParentType"] +> extends ReContainer { // serialization assignRefs(ctx: SerializationContext, path: string) : void diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts index 640ece0..c3b6257 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts @@ -8,23 +8,26 @@ import {ListUpdater} from "../../../../utils/list-updater"; import {DeletionMode} from "../../../../utils/deletion-mode"; import {ReListContainer} from "../re-list-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; -export class ReTreeListContainer> - extends ReListContainer +export class ReTreeListContainer< + T extends Referencable

, + P extends Referencable =T["$ParentType"] +> extends ReListContainer implements ReTreeChildrenContainer { readonly defaultEClass?: string; - constructor(parent: T["ParentType"], name: string, refMeta: ReferenceMeta, eClass?: string) { + constructor(parent: P, name: string, refMeta: ReferenceMeta, eClass?: string) { super(parent, name, refMeta); this.defaultEClass = eClass; - this._parent.$treeChildren.push(this) + this._parent[REFERENCE_INTERNAL_API].treeChildren().push(this) } assignRefs(ctx: SerializationContext, path: string) { const ownPath = RefHandler.computePrefix(path, this.referenceName) this._instance.map((elem, index) => - elem.assignRefs(ctx, RefHandler.mixWithIndex(ownPath, index)) + elem[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.mixWithIndex(ownPath, index)) ) } @@ -36,11 +39,11 @@ implements ReTreeChildrenContainer { //todo rewrite without using item parent explicitly? addWithoutTypeCheck(item: T): boolean { - const oldParent = item.parent; + const oldParent = item[REFERENCE_INTERNAL_API].getParentContainer(); if(oldParent == this) { return false; } else { - item.setParent(this); + item[REFERENCE_INTERNAL_API].setParentContainer(this); oldParent?.remove(item) return ListUpdater.addToListIfMissing(item, this._instance) } @@ -51,23 +54,19 @@ implements ReTreeChildrenContainer { if (this._instance.indexOf(item) > -1) { // if remove is called on an items parent the CASCADE mode would cause an infinite loop, // however this can be easily avoided since parent removal does not require any kind of following cascading deletes - item.destruct(mode); + item.$destruct(mode); return true; } return false; } let removed = ListUpdater.removeFromList(item, this._instance) if(removed){ - item.setParent(undefined); + item[REFERENCE_INTERNAL_API].setParentContainer(undefined); return true } return false; } - override delete(mode: DeletionMode = DeletionMode.RELAXED) { - ListUpdater.destructAllFromChangingList(this._instance, mode) - } - //creates one child level plus calls next createChildren fromJson(formerPrefix: string, context: Deserializer, json: any) { let myJson: JsonOf[] = json[this.referenceName]; @@ -91,7 +90,7 @@ implements ReTreeChildrenContainer { let myJson: JsonOf[] = json[this.referenceName]; if(myJson && myJson.length == this._instance.length) { myJson.forEach((ref, index) => { - this._instance[index].deserializeLinks(context, ref) + this._instance[index][REFERENCE_INTERNAL_API].deserializeOtherReferences(context, ref) }) } } diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts index 96ea634..87353ee 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts @@ -2,6 +2,7 @@ import { ReTreeSingleContainer } from './re-tree-single-container'; import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; import {ReContainersWithSingleChild, ReSingleChildExample} from "../../../test/re-containers-with-single-child"; import {DeletionMode} from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; describe('ReferencableTreeSingletonContainer', () => { it('should create an instance', () => { @@ -23,7 +24,7 @@ describe('ReferencableTreeSingletonContainer', () => { expect(middle.otherLink).toEqual(elem1); expect(elem1.link).toBeDefined(); expect(elem1.link).toEqual(middle); - expect(tester.$treeChildren[0].remove(middle)).toBeTrue(); + expect(tester[REFERENCE_INTERNAL_API].treeChildren()[0].remove(middle)).toBeTrue(); expect(tester.child).toBeUndefined(); expect(middle.myParent).toBeUndefined(); expect(middle.otherLink).toBeDefined(); @@ -46,7 +47,7 @@ describe('ReferencableTreeSingletonContainer', () => { expect(middle.otherLink).toEqual(elem1); expect(elem1.link).toBeDefined(); expect(elem1.link).toEqual(middle); - expect(tester.$treeChildren[0].remove(middle, DeletionMode.CASCADE)).toBeTrue(); + expect(tester[REFERENCE_INTERNAL_API].treeChildren()[0].remove(middle, DeletionMode.CASCADE)).toBeTrue(); expect(tester.child).toBeUndefined(); expect(middle.myParent).toBeUndefined(); expect(middle.otherLink).toBeUndefined(); diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts index 702d1de..a4bc81e 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts @@ -7,21 +7,24 @@ import {ReTreeChildrenContainer} from "./re-tree-children-container"; import {ReSingleContainer} from "../re-single-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; import {DeletionMode} from "../../../../utils/deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; -export class ReTreeSingleContainer> - extends ReSingleContainer +export class ReTreeSingleContainer< + T extends Referencable

, + P extends Referencable =T["$ParentType"] +> extends ReSingleContainer implements ReTreeChildrenContainer { readonly defaultEClass?: string; - constructor(parent: T["ParentType"], referenceName: string, refMeta: ReferenceMeta, eClass?: string) { + constructor(parent: P, referenceName: string, refMeta: ReferenceMeta, eClass?: string) { super(parent, referenceName, refMeta); this.defaultEClass = eClass; - this._parent.$treeChildren.push(this) + this._parent[REFERENCE_INTERNAL_API].treeChildren().push(this) } assignRefs(ctx: SerializationContext, path: string) { - this._instance?.assignRefs(ctx, RefHandler.computePrefix(path, this.referenceName)) + this._instance?.[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.computePrefix(path, this.referenceName)) } toJson(ctx: SerializationContext): JsonOf|undefined { @@ -32,7 +35,7 @@ implements ReTreeChildrenContainer { if(item == this._instance) { return false; } else { - item.setParent(this); + item[REFERENCE_INTERNAL_API].setParentContainer(this); this._instance = item; return true; } @@ -42,12 +45,12 @@ implements ReTreeChildrenContainer { if(this._instance == item) { if (mode === DeletionMode.RELAXED) { this._instance = undefined; - item.setParent(undefined); + item[REFERENCE_INTERNAL_API].setParentContainer(undefined); return true; } // if remove is called on an items parent the CASCADE mode would cause an infinite loop, // however this can be easily avoided since parent removal does not require any kind of following cascading deletes - this._instance?.destruct(mode); + this._instance?.$destruct(mode); return true; } return false; @@ -55,9 +58,9 @@ implements ReTreeChildrenContainer { delete(mode: DeletionMode = DeletionMode.RELAXED) { if (mode === DeletionMode.CASCADE) { - this._instance?.destruct(mode) + this._instance?.$destruct(mode) } else if (mode === DeletionMode.RELAXED) { - this._instance?.parent?.remove(this._instance, mode) + this._instance?.[REFERENCE_INTERNAL_API].getParentContainer()?.remove(this._instance, mode) } } @@ -80,7 +83,7 @@ implements ReTreeChildrenContainer { createRefsOnChildren(context: Deserializer, json: any) { let myJson: JsonOf = json[this.referenceName]; if(this._instance && myJson ) { - this._instance.deserializeLinks(context, myJson) + this._instance[REFERENCE_INTERNAL_API].deserializeOtherReferences(context, myJson) } } } diff --git a/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts b/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts new file mode 100644 index 0000000..3aab504 --- /dev/null +++ b/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts @@ -0,0 +1,4 @@ +// internal API of referencable +export const REFERENCE_INTERNAL_API = Symbol("reference_internal_api"); +// initialization hook for all hidden containers, called on Referencable construction +export const REFERENCE_INITIALIZERS = Symbol("referenceInitializers"); diff --git a/projects/emfular/src/lib/referencing/referencable/referenceable.ts b/projects/emfular/src/lib/referencing/referencable/referenceable.ts index 2e099a0..2b4701c 100644 --- a/projects/emfular/src/lib/referencing/referencable/referenceable.ts +++ b/projects/emfular/src/lib/referencing/referencable/referenceable.ts @@ -12,6 +12,17 @@ import {ReLinkContainer} from "./container/link/re-link-container"; import {ModelRegistry} from "../../binding/model-registry"; import {ClassMeta, ModelDefinition, ReferenceMeta} from "../../binding/model-definition"; import {DeletionMode} from "../../utils/deletion-mode"; +import {REFERENCE_INITIALIZERS, REFERENCE_INTERNAL_API} from "./referencable-symbols"; + +//private, no export +const TREE_CHILDREN = Symbol("treeChildren"); +const LINKS = Symbol("links"); +const PARENT = Symbol("parent"); + +const REFERENCES_TO_JSON = Symbol("referenceToJson"); +const ATTRIBUTES_TO_JSON = Symbol("attributesToJson"); +const INIT_REFERENCES = Symbol("initReferences"); + /** base class for CORE models. * @@ -22,142 +33,64 @@ export abstract class Referencable< $gId: string; //graphical ID - declare readonly ParentType: Parent; - declare $classMeta: ClassMeta; - declare $modelUri: string; //now inside modelMeta declare $modelMeta: ModelDefinition; - private $parent?: ReTreeChildrenContainer; + declare readonly $ParentType: Parent; + private [PARENT]?: ReTreeChildrenContainer; - readonly $treeChildren: ReTreeChildrenContainer[] = []; - readonly $otherReferences: ReLinkContainer[] = []; + private [TREE_CHILDREN]: ReTreeChildrenContainer[] = []; + private [LINKS]: ReLinkContainer[] = []; protected constructor() { this.$gId = uuidv4(); - this.initReferences() - } - - private initReferences() { - const proto = Object.getPrototypeOf(this); - const inits = proto.__referenceInitializers; - if (inits) { - for (const init of inits) { - init.call(this); - } - } + this[INIT_REFERENCES]() } - setParent(parent: ReTreeChildrenContainer | undefined) { - if(this.$parent) { - this.$parent.remove(this) - } - this.$parent = parent; - } - - get parent(): ReTreeChildrenContainer | undefined { - return this.$parent + // ************* public modeling API ******************** + $getEParent(): Parent | undefined { + return this[PARENT]?._parent } - getParentReferencable(): Parent | undefined { - return this.$parent?._parent - } - - getEClass(): string { + $getEClass(): string { return ModelRegistry.getEClassForInstance(this) } - assignRefs(ctx: SerializationContext, path: string) { - const ref: Ref = RefHandler.createRef(path, this.getEClass()) - ctx.put(this, ref) - for(let child of this.$treeChildren) { - child.assignRefs(ctx, path) - } - } - - destruct(mode: DeletionMode = DeletionMode.RELAXED) { + $destruct(mode: DeletionMode = DeletionMode.RELAXED) { // removal from parent is always called with deletion mode RELAXED, otherwise infinite loops occur (see remove in re-tree-list/single-container.ts) // tests in files re-link-list/single-container.spec.ts and re-tree-list/single-container.spec.ts fail when not setting RELAXED mode explicitly - this.$parent?.remove(this, DeletionMode.RELAXED) - this.$otherReferences.forEach(refContainer => { + this[PARENT]?.remove(this, DeletionMode.RELAXED) + this[LINKS].forEach(refContainer => { refContainer.removeFromInverse(this, mode) }) - this.$treeChildren.forEach(child => { + this[TREE_CHILDREN].forEach(child => { child.delete(mode) }) } - protected getContainer>(refName: string): ReContainer { - let proto: any = Object.getPrototypeOf(this); - let meta: ReferenceMeta | undefined; - - // Walk up the prototype chain until we find the reference - while (proto) { - const classMeta = proto.$classMeta; - if (classMeta && classMeta.references && refName in classMeta.references) { - meta = classMeta.references[refName]; - break; + //************** Serialization ************************* + toJson(ctxOPt?: SerializationContext): JsonOf { + const ctx = ctxOPt ?? new SerializationContext() + if(!ctxOPt) {//initialize new context + //1) first find the root + let root: Referencable|undefined = this + let oldRoot: Referencable|undefined = this + while (root !== undefined) { + oldRoot = root + root = root.$getEParent() } - proto = Object.getPrototypeOf(proto); - } - - if (!meta) { - throw new Error(`Reference '${refName}' not found on class '${this.constructor.name}'`); + //2) then assign refs from the root down + oldRoot[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.rootPath) } - const key: symbol = meta.containerKey!; - const container = (this as any)[key]; - - if (!container) { - throw new Error(`Container for reference '${refName}' not initialized`); - } - - return container as ReContainer; - } - - public addToReferencableContainer>(name: string, item: T): boolean { - return this.getContainer(name).add(item) - } - - public removeFromReferencableContainer>(name: string, item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - let container = this.getContainer(name) - let result = container.remove(item, mode) - if (result && mode === DeletionMode.CASCADE && container.isRequired) { - const instance = container.get() - if (instance === undefined || (Array.isArray(instance) && instance.length === 0)) { - container._parent.destruct(mode) - } - } - return result - } - - toJson(ctxOPt?: SerializationContext): JsonOf { - const ctx = ctxOPt ? ctxOPt : new SerializationContext(this) - //todo: this creates one assuming that the current element is root, once we have all parent pointers we can walk up first and then start const json: any = {}; - json["eClass"] = this.getEClass(); //todo not always necessary - this.attributesToJson(json); - this.refContainersToJson(json, ctx); + json["eClass"] = this.$getEClass(); //todo not always necessary + this[ATTRIBUTES_TO_JSON](json); + this[REFERENCES_TO_JSON](json, ctx); return json as JsonOf; } - - private refContainersToJson(json: any, ctx: SerializationContext) { - this.$treeChildren.forEach(child => { - const jsc = child.toJson(ctx) - if (jsc != undefined && !(Array.isArray(jsc) && jsc.length == 0)) { - json[child.referenceName] = jsc - } - }) - this.$otherReferences.forEach(child => { - const jsc = child.toJson(ctx) - if (jsc != undefined && !(Array.isArray(jsc) && jsc.length == 0)) { - json[child.referenceName] = jsc - } - }) - } - - private attributesToJson(json: any) { + private [ATTRIBUTES_TO_JSON](json: any) { const ctor = this.constructor as any; const attributes = getAllAttributes(ctor); attributes.forEach((options: AttributeOptions, key) => { @@ -175,39 +108,180 @@ export abstract class Referencable< }) } - createChildren>(context: Deserializer, parent: Ref, json: J) { - this.$treeChildren.forEach(child => { - child.fromJson(parent.$ref, context, json) - }) - } + private [REFERENCES_TO_JSON](json: any, ctx: SerializationContext) { + const relevantReferences = [ + ...this[TREE_CHILDREN], + ...this[LINKS] + ]; - attributesFromJson>(jsonTyped: J) { - const json: any = jsonTyped as any - const ctor = this.constructor as any; - const attributes = getAllAttributes(ctor); - attributes.forEach((options, key) => { - if (json[key] !== undefined) { - (this as any)[key] = json[key]; - } else if (options?.default !== undefined) { - (this as any)[key] = options.default; + relevantReferences.forEach(child => { + const jsc = child.toJson(ctx) + if (jsc != undefined && !(Array.isArray(jsc) && jsc.length == 0)) { + json[child.referenceName] = jsc } }) } - public deserializeLinks>(context: Deserializer, jsonTyped: J) { - const json = jsonTyped as any - for (let container of this.$otherReferences) { - let jsonElem: Ref[] |Ref | undefined = json[container.referenceName] - if (jsonElem != undefined) { - const refArray = Array.isArray(jsonElem)? jsonElem : [jsonElem] - refArray.map((ref: Ref) => { - container.add(context.get(ref.$ref)); - }) + + public [REFERENCE_INTERNAL_API]: ReferenceApi = { + + //************** Serialization ************************* + serialize_assignRefs: (ctx: SerializationContext, path: string): void => { + const ref: Ref = RefHandler.createRef(path, this.$getEClass()) + ctx.put(this, ref) + for(let child of this[TREE_CHILDREN]) { + child.assignRefs(ctx, path) + } + }, + + //****************** Deserialization ************************ + deserializeAttributes: >(jsonTyped: J): void => { + const json: any = jsonTyped as any; + const ctor = this.constructor as any; + const attributes = getAllAttributes(ctor); + attributes.forEach((options, key) => { + if (json[key] !== undefined) { + (this as any)[key] = json[key]; + } else if (options?.default !== undefined) { + (this as any)[key] = options.default; + } + }); + }, + + deserializeChildren: >( + context: Deserializer, + parent: Ref, + json: J + ): void => { + this[TREE_CHILDREN].forEach(child => { + child.fromJson(parent.$ref, context, json); + }); + }, + + deserializeOtherReferences: >( + context: Deserializer, + jsonTyped: J + ): void => { + const json = jsonTyped as any; + for (const container of this[LINKS]) { + let jsonElem: Ref[] | Ref | undefined = json[container.referenceName]; + if (jsonElem != undefined) { + const refArray = Array.isArray(jsonElem) ? jsonElem : [jsonElem]; + refArray.map((ref: Ref) => { + container.add(context.get(ref.$ref)); + }); + } + } + for (const container of this[TREE_CHILDREN]) { + container.createRefsOnChildren(context, json); + } + }, + + // ****************** inverse handling (called by link containers) ********************** + addToReference: >(name: string, item: U): boolean => { + return this[REFERENCE_INTERNAL_API].getContainer(name).add(item) + }, + + removeFromReference: >(name: string, item: U, mode: DeletionMode = DeletionMode.RELAXED): boolean => { + let container = this[REFERENCE_INTERNAL_API].getContainer(name) + let result = container.remove(item, mode) + if (result && mode === DeletionMode.CASCADE && container.isRequired) { + const instance = container.get() + if (instance === undefined || (Array.isArray(instance) && instance.length === 0)) { + container._parent.$destruct(mode) + } + } + return result + }, + + treeChildren: (): ReTreeChildrenContainer[] => this[TREE_CHILDREN], + otherLinks: (): ReLinkContainer[] => this[LINKS], + + + getContainer: < + U extends Referencable + >(refName: string): ReContainer => { + let proto: any = Object.getPrototypeOf(this); + let meta: ReferenceMeta | undefined; + // Walk up the prototype chain until we find the reference + while (proto) { + const classMeta = proto.$classMeta; + if (classMeta && classMeta.references && refName in classMeta.references) { + meta = classMeta.references[refName]; + break; + } + proto = Object.getPrototypeOf(proto); } + if (!meta) { + throw new Error(`Reference '${refName}' not found on class '${this.constructor.name}'`); + } + const key: symbol = meta.containerKey!; + const container = (this as any)[key]; + if (!container) { + throw new Error(`Container for reference '${refName}' not initialized`); + } + return container as ReContainer; + }, + // parent + getParentContainer: (): ReTreeChildrenContainer | undefined => { + return this[PARENT]; + }, + setParentContainer: (parent: ReTreeChildrenContainer | undefined): void => { + if (this[PARENT]) { + this[PARENT].remove(this); + } + this[PARENT] = (parent as (ReTreeChildrenContainer | undefined)); } - for (let container of this.$treeChildren) { - container.createRefsOnChildren(context, json) + + }; + + // ************* container construction ******************* + private [INIT_REFERENCES]() { + const proto = Object.getPrototypeOf(this); + const inits = proto[REFERENCE_INITIALIZERS]; + if (inits) { + for (const init of inits) { + init.call(this); + } } } } + + +export interface ReferenceApi< + Self extends Referencable, + Parent extends Referencable +> { + //************** Serialization ************************* + serialize_assignRefs: (ctx: SerializationContext, path: string) => void; + + //****************** Deserialization ************************ + deserializeAttributes: >(json: J) => void; + deserializeChildren: >( + context: Deserializer, + parent: Ref, + json: J + ) => void; + deserializeOtherReferences: >( + context: Deserializer, + json: J + ) => void; + + // *************** container accessors ******************* + treeChildren: () => ReTreeChildrenContainer[]; + otherLinks: () => ReLinkContainer[]; + getContainer: >(refName: string) => ReContainer; + // parent + getParentContainer: () => ReTreeChildrenContainer|undefined, + setParentContainer: (parent: ReTreeChildrenContainer|undefined) => void; + + // ****************** inverse handling (called by link containers) ********************** + addToReference: >(name: string, item: U) => boolean; + removeFromReference: >( + name: string, + item: U, + mode?: DeletionMode + ) => boolean; + +} \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts index f2177f0..f9a7c72 100644 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts +++ b/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts @@ -1,5 +1,6 @@ import {A, B, InheritanceRoot, ModelInheritance} from "./re-containers-with-inheritance"; import {ReContainer} from "../referencable/container/re-container"; +import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; describe("ReferencablesWithInheritance", () => { @@ -29,7 +30,7 @@ describe("ReferencablesWithInheritance", () => { const root = new InheritanceRoot(); const root2 = new InheritanceRoot(); const a = new A(); - let ref = root.$treeChildren[0] as ReContainer; + const ref = root[REFERENCE_INTERNAL_API].treeChildren()[0] as ReContainer; expect(root.children.length).toBe(0); const res = ref.add(root2) expect(res).toBeFalse() diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts index f2c1246..11515c5 100644 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts +++ b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts @@ -5,6 +5,7 @@ import { } from "./re-containers-with-single-child"; import {Ref} from "../ref/ref"; import {JsonOf} from "../../serialization/json-deserializable"; +import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; describe('ReContainersWithSingleChild tests', () => { @@ -14,23 +15,23 @@ describe('ReContainersWithSingleChild tests', () => { const child: ReSingleChildExample = new ReSingleChildExample() - expect(root.$treeChildren.length).toBe(1) - expect(root.$otherReferences.length).toBe(1) + expect(root[REFERENCE_INTERNAL_API].treeChildren().length).toBe(1) + expect(root[REFERENCE_INTERNAL_API].otherLinks().length).toBe(1) - expect(child.$treeChildren.length).toBe(0) - expect(child.$otherReferences.length).toBe(1) + expect(child[REFERENCE_INTERNAL_API].treeChildren().length).toBe(0) + expect(child[REFERENCE_INTERNAL_API].otherLinks().length).toBe(1) }) it('should manage parent pointers correctly', () => { const root: ReContainersWithSingleChild = new ReContainersWithSingleChild(); - expect(root.parent).toBeUndefined() + expect(root[REFERENCE_INTERNAL_API].getParentContainer()).toBeUndefined() expect(root.child).toBeUndefined() const child: ReSingleChildExample = new ReSingleChildExample(); expect(child.myParent).toBeUndefined() - expect(child.parent).toBeUndefined() + expect(child[REFERENCE_INTERNAL_API].getParentContainer()).toBeUndefined() //set tree parent: child.myParent = root; - expect(child.parent).toBeDefined() + expect(child[REFERENCE_INTERNAL_API].getParentContainer()).toBeDefined() expect(child.myParent).toEqual(root); expect(root.child).toBe(child); @@ -83,7 +84,6 @@ describe('ReContainersWithSingleChild tests', () => { //todo: must compile withoutcast for correct jsonOf: let ref: Ref|undefined = completeJson?.link as unknown as Ref expect(ref.eClass).toEqual(EClassesSingleChild.ReSingleChildExample) - const childJson: JsonOf |undefined = completeJson.child; const completeFromJson : ReContainersWithSingleChild = ReContainersWithSingleChild.fromJSON(completeJson) expect(completeFromJson.name).toEqual(root.name) expect(completeFromJson.link).toEqual(completeFromJson.child) diff --git a/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts b/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts index 4f7f15d..a99fec0 100644 --- a/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts +++ b/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts @@ -7,9 +7,9 @@ import { RootWithChildren, RootWithChildrenJson } from "./referencables-with-children"; -import {SerializationContext} from "../../serialization/serialization-context"; import {RefHandler} from "../ref/ref-handler"; import {Ref} from "../ref/ref"; +import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; describe('ReContainersWithListChild tests', () => { @@ -79,8 +79,6 @@ describe('ReContainersWithListChild tests', () => { r1.child2.push(r2_1, r2_2) r2_1.child3.push(r3_1) r3_1.link1.push(r1) - const ctx = new SerializationContext(r1) - expect(ctx.get(r3_1).$ref).toEqual("//@child2.0/@child3.0") const r3json: ReChild3Json = { name: 'referencable3', eClass: EClasses.ReChild3, @@ -115,14 +113,14 @@ describe('ReContainersWithListChild tests', () => { //todo deserialization test it("should register the containers correctly on the parent", () => { - expect(r1.$treeChildren.length).toBe(1) - expect(r1.$otherReferences.length).toBe(2) + expect(r1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(1) + expect(r1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(2) - expect(r2_1.$treeChildren.length).toBe(2) - expect(r2_1.$otherReferences.length).toBe(0) + expect(r2_1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(2) + expect(r2_1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(0) - expect(r3_1.$treeChildren.length).toBe(0) - expect(r1.$otherReferences.length).toBe(2) + expect(r3_1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(0) + expect(r1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(2) }) it('should allow swapping elements in a ModelList created via decorators', () => { diff --git a/projects/emfular/src/lib/serialization/deserializer.ts b/projects/emfular/src/lib/serialization/deserializer.ts index fa60161..4bdf2ff 100644 --- a/projects/emfular/src/lib/serialization/deserializer.ts +++ b/projects/emfular/src/lib/serialization/deserializer.ts @@ -8,6 +8,7 @@ import {RefHandler} from "../referencing/ref/ref-handler"; import {Ref} from "../referencing/ref/ref"; import {JsonOf} from "./json-deserializable"; import {ModelRegistry} from "../binding/model-registry"; +import {REFERENCE_INTERNAL_API} from "../referencing/referencable/referencable-symbols"; export class Deserializer { @@ -18,8 +19,8 @@ export class Deserializer { const entry = ModelRegistry.get(ref.eClass) const obj: T = new entry() this.put(ref,obj) - obj.attributesFromJson(json) - obj.createChildren(this, ref, json) + obj[REFERENCE_INTERNAL_API].deserializeAttributes(json) + obj[REFERENCE_INTERNAL_API].deserializeChildren(this, ref, json) return obj } @@ -54,7 +55,7 @@ export class Deserializer { eClass: rootEClass } const model: C = context.createTreeBackbone(ref, json); - model.deserializeLinks(context, json) + model[REFERENCE_INTERNAL_API].deserializeOtherReferences(context, json) return model; } diff --git a/projects/emfular/src/lib/serialization/json-deserializable.ts b/projects/emfular/src/lib/serialization/json-deserializable.ts index 2db955f..e7e8c40 100644 --- a/projects/emfular/src/lib/serialization/json-deserializable.ts +++ b/projects/emfular/src/lib/serialization/json-deserializable.ts @@ -10,7 +10,7 @@ type IsReferencable = T extends Referencable ? true : false; type IsReferenceProp = - K extends "ParentType" ? false : + K extends "$ParentType" ? false : T extends object ? T extends MetaAwareModelList ? true : T extends SingleRef @@ -37,7 +37,7 @@ type StartsWithPrivate = type AttributeKeys = { [K in keyof T]: - K extends "ParentType" ? never : + K extends "$ParentType" ? never : StartsWithPrivate extends true ? never : T[K] extends Function ? never : IsReferenceProp extends true ? never : diff --git a/projects/emfular/src/lib/serialization/serialization-context.spec.ts b/projects/emfular/src/lib/serialization/serialization-context.spec.ts index 55a5b92..c5e5261 100644 --- a/projects/emfular/src/lib/serialization/serialization-context.spec.ts +++ b/projects/emfular/src/lib/serialization/serialization-context.spec.ts @@ -1,8 +1,7 @@ import { SerializationContext } from './serialization-context'; -import {ReferencableTester} from "../referencing/test/referencable-tester"; describe('SerializationContext', () => { it('should create an instance', () => { - expect(new SerializationContext(new ReferencableTester())).toBeTruthy(); + expect(new SerializationContext()).toBeTruthy(); }); }); diff --git a/projects/emfular/src/lib/serialization/serialization-context.ts b/projects/emfular/src/lib/serialization/serialization-context.ts index 34a1f67..3cd5ede 100644 --- a/projects/emfular/src/lib/serialization/serialization-context.ts +++ b/projects/emfular/src/lib/serialization/serialization-context.ts @@ -1,15 +1,10 @@ import {Referencable} from "../referencing/referencable/referenceable"; import {Ref} from "../referencing/ref/ref"; -import {RefHandler} from "../referencing/ref/ref-handler"; export class SerializationContext { private refs = new Map, Ref>(); - constructor(root: Referencable) { - root.assignRefs(this, RefHandler.rootPath) - } - put>(obj: T, ref: Ref) { this.refs.set(obj, ref); } diff --git a/projects/emfular/src/lib/utils/list-updater.ts b/projects/emfular/src/lib/utils/list-updater.ts index a09400d..b4357c4 100644 --- a/projects/emfular/src/lib/utils/list-updater.ts +++ b/projects/emfular/src/lib/utils/list-updater.ts @@ -1,5 +1,6 @@ import {Referencable} from "../referencing/referencable/referenceable"; import { DeletionMode } from "./deletion-mode"; +import {REFERENCE_INTERNAL_API} from "../referencing/referencable/referencable-symbols"; export class ListUpdater { @@ -29,9 +30,9 @@ export class ListUpdater { static destructAllFromChangingList>(list: T[], mode: DeletionMode) { while(list?.length > 0){ if (mode === DeletionMode.CASCADE) { - list[0].destruct(mode) + list[0].$destruct(mode) } else if (mode === DeletionMode.RELAXED) { - list[0].parent?.remove(list[0], mode) + list[0][REFERENCE_INTERNAL_API].getParentContainer()?.remove(list[0], mode) } } } diff --git a/projects/ngx-emfular-integration/src/lib/details/tree-details-service.ts b/projects/ngx-emfular-integration/src/lib/details/tree-details-service.ts index 685403d..277130b 100644 --- a/projects/ngx-emfular-integration/src/lib/details/tree-details-service.ts +++ b/projects/ngx-emfular-integration/src/lib/details/tree-details-service.ts @@ -10,7 +10,7 @@ export interface TreeDetailsService> { modelService: ModelService ): void // instead of opening the generic ModeldetailsCompoennt you might like to consider opening a specific one - //by determining the eClass and switching based on elem.getEClass() + //by determining the eClass and switching based on elem.$getEClass() openModelChoice( modelService: ModelService diff --git a/projects/ngx-emfular-integration/src/lib/details/tree-model-details.service.ts b/projects/ngx-emfular-integration/src/lib/details/tree-model-details.service.ts index a156e8b..34d04fe 100644 --- a/projects/ngx-emfular-integration/src/lib/details/tree-model-details.service.ts +++ b/projects/ngx-emfular-integration/src/lib/details/tree-model-details.service.ts @@ -20,7 +20,7 @@ export class TreeModelDetailsService> implements Tre T extends Referencable >(elem: T, modelService: ModelService) { // instead of opening the generic ModeldetailsCompoennt you might like to consider opening a specific one - //by determining the eClass and switching based on elem.getEClass() + //by determining the eClass and switching based on elem.$getEClass() const overlayRef = this.overlay.create( { hasBackdrop: true,