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 791f1a5..4040b29 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 @@ -1,6 +1,7 @@ import {Referencable} from "../../referenceable"; import {ModelList} from "./model-list"; import {ReListInterface} from "../re-list-interface"; +import {DeletionMode} from "../../../../utils/deletion-mode"; export function createListProxy< T extends Referencable, @@ -103,20 +104,25 @@ export function createListProxy< items.some(item => container.remove(item)); } + if (prop === "removeCascade") { + return (...items: T[]) => + items.some(item => container.remove(item, DeletionMode.CASCADE)) + } + if(prop === "delete") { - return () => { - container.delete(); + return (mode?: DeletionMode) => { + container.delete(mode); } } // The pop() method removes the last element from an array and returns that value to the caller. // If you call pop() on an empty array, it returns undefined. if (prop === "pop") { - return () => { + return (mode?: DeletionMode) => { const arr = container.get(); if (arr.length === 0) return undefined; const last = arr[arr.length - 1]; - container.remove(last); + container.remove(last, mode); return last; }; } @@ -124,11 +130,11 @@ export function createListProxy< // The shift() method of Array instances removes the first element from an array and returns that removed element. // If you call shift() on an empty array, it returns undefined. if (prop === "shift") { - return () => { + return (mode?: DeletionMode) => { const arr = container.get(); if (arr.length === 0) return undefined; const first = arr[0]; - container.remove(first); + container.remove(first, mode); return first; }; } diff --git a/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts b/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts index 6f4a1dc..2eda522 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts @@ -20,6 +20,7 @@ export interface MetaAwareModelList move(from: number, to: number): void; swap(from: number, to: number): void; remove(...items: T[]): boolean; + removeCascade(...items: T[]): boolean; delete(): void; readonly __item: T; diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts index 0daa85a..929b656 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts @@ -2,18 +2,19 @@ import {Referencable} from "../../referenceable"; import { ReContainer } from "../re-container"; import {SerializationContext} from "../../../../serialization/serialization-context"; import {Ref} from "../../../ref/ref"; +import { DeletionMode } from "../../../../utils/deletion-mode"; export interface ReLinkContainer< T extends Referencable, P extends Referencable > extends ReContainer { - /*protected constructor(parent: P, referenceName: string, inverseName?: string) { - super(parent, referenceName, inverseName); + /*protected constructor(parent: P, referenceName: string, isRequired: boolean, inverseName?: string) { + super(parent, referenceName, isRequired, inverseName); this._parent.$otherReferences.push(this) }*/ - removeFromInverse(item: T): boolean; + removeFromInverse(item: T, mode?: DeletionMode): boolean; toJson(ctx: SerializationContext): Ref[] | Ref | undefined } diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts index 4cfb65b..6e633af 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts @@ -1,6 +1,6 @@ import { ReLinkListContainer } from './re-link-list-container'; import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; -import {RootWithChildren, ReChild3} from "../../../test/referencables-with-children"; +import {RootWithChildren, ReChild3, ReChild4, Middle2WithChildren} from "../../../test/referencables-with-children"; describe('ReLinkListContainer', () => { it('should create an instance', () => { @@ -8,14 +8,84 @@ describe('ReLinkListContainer', () => { expect(new ReLinkListContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); }); - it("should give true if the remove and remove inverse chain triggered an element removal", () => { + it("should give true if the remove and remove inverse chain triggered an element removal without removing element from different container", () => { let tester = new RootWithChildren() - let elem2 = new ReChild3() - expect(tester.link3.remove(elem2)).toBeFalse() - tester.link3.push(elem2) + let middle = new Middle2WithChildren() + let elem1 = new ReChild3() + let elem2 = new ReChild4() + expect(tester.link3.remove(elem1)).toBeFalse() + expect(tester.link4.remove(elem2)).toBeFalse() + tester.link3.push(elem1) + tester.link4.push(elem2) + middle.child3.push(elem1) + middle.child4.push(elem2) expect(tester.link3.length).toBe(1) - expect(tester.link3).toContain(elem2) - expect(tester.link3.remove(elem2)).toBeTrue() + expect(tester.link4.length).toBe(1) + expect(tester.link3).toContain(elem1) + expect(tester.link4).toContain(elem2) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(middle.child3).toContain(elem1) + expect(middle.child4).toContain(elem2) + expect(elem1.link1.length).toBe(1) + expect(elem2.link1.length).toBe(1) + expect(elem1.link1).toContain(tester) + expect(elem2.link1).toContain(tester) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + expect(tester.link3.remove(elem1)).toBeTrue() + expect(tester.link4.remove(elem2)).toBeTrue() expect(tester.link3.length).toBe(0) + expect(tester.link4.length).toBe(0) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(elem1.link1.length).toBe(0) + expect(elem2.link1.length).toBe(0) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + }); + + it("should give true if the removeCascade and remove inverse chain triggered an element removal, also removing it from other containers if required reference is deleted", () => { + let tester = new RootWithChildren() + let middle = new Middle2WithChildren() + let elem1 = new ReChild3() + let elem2 = new ReChild4() + expect(tester.link3.removeCascade(elem1)).toBeFalse() + expect(tester.link4.removeCascade(elem2)).toBeFalse() + tester.link3.push(elem1) + tester.link4.push(elem2) + middle.child3.push(elem1) + middle.child4.push(elem2) + expect(tester.link3.length).toBe(1) + expect(tester.link4.length).toBe(1) + expect(tester.link3).toContain(elem1) + expect(tester.link4).toContain(elem2) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(middle.child3).toContain(elem1) + expect(middle.child4).toContain(elem2) + expect(elem1.link1.length).toBe(1) + expect(elem2.link1.length).toBe(1) + expect(elem1.link1).toContain(tester) + expect(elem2.link1).toContain(tester) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + expect(tester.link3.removeCascade(elem1)).toBeTrue() + expect(tester.link4.removeCascade(elem2)).toBeTrue() + expect(tester.link3.length).toBe(0) + expect(tester.link4.length).toBe(0) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(0) + expect(elem1.link1.length).toBe(0) + expect(elem2.link1.length).toBe(0) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeUndefined() + expect(elem1.parentPointer).toEqual(middle) }) }); 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 d31da9c..161515b 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 @@ -5,6 +5,7 @@ import {ReLinkContainer} from "./re-link-container"; import {ListUpdater} from "../../../../utils/list-updater"; import {ReListContainer} from "../re-list-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; +import {DeletionMode} from "../../../../utils/deletion-mode"; export class ReLinkListContainer< T extends Referencable, @@ -33,24 +34,24 @@ implements ReLinkContainer { return this._instance.map(i => ctx.get(i)) } - override remove(item: T): boolean { + override remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { const res = ListUpdater.removeFromList(item, this._instance) if (res) { if(this.inverseName !== undefined) { - item.removeFromReferencableContainer(this.inverseName, this._parent) + item.removeFromReferencableContainer(this.inverseName, this._parent, mode) } } return res; //todo behaviour of flag different to add?? } - override delete() { - ListUpdater.destructAllFromChangingList(this._instance) + override delete(mode: DeletionMode = DeletionMode.RELAXED) { + ListUpdater.destructAllFromChangingList(this._instance, mode) } - removeFromInverse(item: T): boolean { + removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this.inverseName !== undefined) { for (const child of [...this._instance]) { - child.removeFromReferencableContainer(this.inverseName, item) + child.removeFromReferencableContainer(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 49cb250..5e282f5 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 @@ -1,9 +1,100 @@ -import { ReLinkSingleContainer } from './re-link-single-container'; +import {ReLinkSingleContainer} from './re-link-single-container'; import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; +import { + ReContainersWithSingleChild, + ReContainersWithSingleChild2, + ReSingleChildExample, + ReSingleChildExample2 +} from "../../../test/re-containers-with-single-child"; +import {DeletionMode} from "../../../../utils/deletion-mode"; describe('ReLinkSingleContainer', () => { it('should create an instance', () => { let tester = new ReferencableTester() - expect(new ReLinkSingleContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); + expect(new ReLinkSingleContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); + }); + + it('should remove child from container without it getting removed from other containers, even if required reference is removed', () => { + let tester1 = new ReContainersWithSingleChild(); + let middle1 = new ReSingleChildExample(); + let elem1 = new ReContainersWithSingleChild(); + let tester2 = new ReContainersWithSingleChild2(); + let middle2 = new ReSingleChildExample2(); + let elem2 = new ReContainersWithSingleChild2(); + tester1.child = middle1; + middle1.otherLink = elem1; + tester2.child = middle2; + middle2.otherLink = elem2; + expect(tester1.child).toBeDefined(); + expect(tester1.child).toEqual(middle1); + expect(tester2.child).toBeDefined(); + expect(tester2.child).toEqual(middle2); + expect(middle1.myParent).toBeDefined(); + expect(middle1.myParent).toEqual(tester1); + expect(middle2.myParent).toBeDefined(); + expect(middle2.myParent).toEqual(tester2); + expect(middle1.otherLink).toBeDefined(); + expect(middle1.otherLink).toEqual(elem1); + expect(middle2.otherLink).toBeDefined(); + expect(middle2.otherLink).toEqual(elem2); + expect(elem1.link).toBeDefined(); + 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(tester1.child).toBeDefined(); + expect(tester1.child).toEqual(middle1); + expect(tester2.child).toBeDefined(); + expect(tester2.child).toEqual(middle2); + expect(middle1.myParent).toBeDefined(); + expect(middle1.myParent).toEqual(tester1); + expect(middle2.myParent).toBeDefined(); + expect(middle2.myParent).toEqual(tester2); + expect(middle1.otherLink).toBeUndefined(); + expect(middle2.otherLink).toBeUndefined(); + expect(elem1.link).toBeUndefined(); + expect(elem2.link).toBeUndefined(); + }); + + it('should remove reference from container, triggering its deletion in case of required reference getting removed', () => { + let tester1 = new ReContainersWithSingleChild(); + let middle1 = new ReSingleChildExample(); + let elem1 = new ReContainersWithSingleChild(); + let tester2 = new ReContainersWithSingleChild2(); + let middle2 = new ReSingleChildExample2(); + let elem2 = new ReContainersWithSingleChild2(); + tester1.child = middle1; + middle1.otherLink = elem1; + tester2.child = middle2; + middle2.otherLink = elem2; + expect(tester1.child).toBeDefined(); + expect(tester1.child).toEqual(middle1); + expect(tester2.child).toBeDefined(); + expect(tester2.child).toEqual(middle2); + expect(middle1.myParent).toBeDefined(); + expect(middle1.myParent).toEqual(tester1); + expect(middle2.myParent).toBeDefined(); + expect(middle2.myParent).toEqual(tester2); + expect(middle1.otherLink).toBeDefined(); + expect(middle1.otherLink).toEqual(elem1); + expect(middle2.otherLink).toBeDefined(); + expect(middle2.otherLink).toEqual(elem2); + expect(elem1.link).toBeDefined(); + 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(tester1.child).toBeDefined(); + expect(tester1.child).toEqual(middle1); + expect(tester2.child).toBeUndefined(); + expect(middle1.myParent).toBeDefined(); + expect(middle1.myParent).toEqual(tester1); + expect(middle2.myParent).toBeUndefined(); + expect(middle1.otherLink).toBeUndefined(); + expect(middle2.otherLink).toBeUndefined(); + expect(elem1.link).toBeUndefined(); + expect(elem2.link).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 870e8ed..7cb2fec 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 @@ -4,6 +4,7 @@ import {SerializationContext} from "../../../../serialization/serialization-cont import {ReLinkContainer} from "./re-link-container"; import {ReSingleContainer} from "../re-single-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; +import { DeletionMode } from "../../../../utils/deletion-mode"; export class ReLinkSingleContainer< T extends Referencable, @@ -18,7 +19,7 @@ implements ReLinkContainer { protected set(instance: T): void { if(this.inverseName !== undefined) { - this._instance?.removeFromReferencableContainer(this.inverseName, this._parent) + this._instance?.removeFromReferencableContainer(this.inverseName, this._parent, DeletionMode.RELAXED) this._instance = instance; instance.addToReferencableContainer(this.inverseName, this._parent) } else { @@ -35,25 +36,25 @@ implements ReLinkContainer { } } - remove(item: T): boolean { + remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this._instance == item) { + this._instance = undefined; if (this.inverseName != undefined) { - item.removeFromReferencableContainer(this.inverseName, this._parent) + item.removeFromReferencableContainer(this.inverseName, this._parent, mode) } - this._instance = undefined; return true; } else { return false; } } - override delete() { - this._instance?.destruct() + override delete(mode: DeletionMode = DeletionMode.RELAXED) { + this._instance?.destruct(mode) } - removeFromInverse(item: T): boolean { + removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this.inverseName !== undefined) { - this._instance?.removeFromReferencableContainer(this.inverseName, item) + this._instance?.removeFromReferencableContainer(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 fc8c496..5cb5d5d 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/re-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/re-container.ts @@ -2,6 +2,7 @@ import {Referencable} from "../referenceable"; import {SerializationContext} from "../../../serialization/serialization-context"; import {ReferenceMeta} from "../../../binding/model-definition"; import {ModelRegistry} from "../../../binding/model-registry"; +import {DeletionMode} from "../../../utils/deletion-mode"; export abstract class ReContainer< T extends Referencable, @@ -11,11 +12,13 @@ export abstract class ReContainer< readonly meta: ReferenceMeta; readonly referenceName: string; readonly inverseName?: string; + readonly isRequired: boolean; protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { this._parent = parent; this.meta = refMeta; this.referenceName = referenceName; + this.isRequired = this.meta.min != undefined && this.meta.min > 0; this.inverseName = refMeta.opposite; } @@ -43,10 +46,10 @@ export abstract class ReContainer< return this.isAcceptableItem( new srcConstr()) //todo } - abstract remove(item: T): boolean; + abstract remove(item: T, mode?: DeletionMode): boolean; //called to destruct all elements in the container (e.g. when destroying a parent - abstract delete(): void + abstract delete(mode?: DeletionMode): void abstract toJson(ctx: SerializationContext): any } diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts index 36d1f22..fe52bef 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts @@ -5,6 +5,7 @@ import {ModelList} from "./hide/model-list"; import {createListProxy} from "./hide/list-proxy"; import {ReListInterface} from "./re-list-interface"; import {ReferenceMeta} from "../../../binding/model-definition"; +import {DeletionMode} from "../../../utils/deletion-mode"; export abstract class ReListContainer< @@ -32,8 +33,8 @@ implements ReListInterface{ return this._proxy; } - override delete() { - ListUpdater.destructAllFromChangingList(this._instance) + override delete(mode: DeletionMode = DeletionMode.RELAXED) { + ListUpdater.destructAllFromChangingList(this._instance, mode) } move(from: number, to: number) { diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts index cea350e..cb1e542 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts @@ -11,7 +11,7 @@ implements ReSingleInterface{ protected _instance?: T ; - protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta ) { + protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { super(parent, referenceName, refMeta); } diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts index 9632db0..c8d76c0 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts @@ -22,7 +22,7 @@ export class ReDerivedListContainer< parent: P, computeOrSymbol: ((owner: P) => T[]) | symbol, referenceName: string, - refMeta: ReferenceMeta, + refMeta: ReferenceMeta ) { super(parent, referenceName, refMeta); this.resolver = new ReDerivationResolver(computeOrSymbol); 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 02ea046..db64e8b 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 @@ -4,6 +4,7 @@ import {ReContainer} from "../re-container"; import {ReSingleInterface} from "../re-single-interface"; import {ReShallowInterface} from "./re-shallow-interface"; import {ReferenceMeta} from "../../../../binding/model-definition"; +import { DeletionMode } from "../../../../utils/deletion-mode"; export class ReTreeParentContainer> extends ReContainer @@ -23,13 +24,13 @@ implements ReSingleInterface, let me: T = this._parent const currentParentCont = this._parent.parent if(currentParentCont != undefined) { - currentParentCont.remove(this._parent as T["ParentType"]) + currentParentCont.remove(this._parent as T["ParentType"], DeletionMode.RELAXED) } return item.addToReferencableContainer(this.inverseName, me) } - remove(item: T["ParentType"]): boolean { - return item.removeFromReferencableContainer(this.inverseName, this._parent) + remove(item: T["ParentType"], mode: DeletionMode = DeletionMode.RELAXED): boolean { + return item.removeFromReferencableContainer(this.inverseName, this._parent, mode) } delete(): void {} diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts index 911d218..46c1d80 100644 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts +++ b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts @@ -1,9 +1,90 @@ import { ReTreeListContainer } from './re-tree-list-container'; import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; +import {Middle2WithChildren, ReChild3, ReChild4, RootWithChildren} from "../../../test/referencables-with-children"; describe('ReferencableTreeListContainer', () => { it('should create an instance', () => { let tester = new ReferencableTester() expect(new ReTreeListContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); }); + + it("should give true if the remove and remove inverse chain triggered an element removal without removing element from different container", () => { + let tester = new RootWithChildren() + let middle = new Middle2WithChildren() + let elem1 = new ReChild3() + let elem2 = new ReChild4() + expect(tester.child2.remove(middle)).toBeFalse() + tester.link3.push(elem1) + tester.link4.push(elem2) + tester.child2.push(middle) + middle.child3.push(elem1) + middle.child4.push(elem2) + expect(tester.link3.length).toBe(1) + expect(tester.link4.length).toBe(1) + expect(tester.link3).toContain(elem1) + expect(tester.link4).toContain(elem2) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(middle.child3).toContain(elem1) + expect(middle.child4).toContain(elem2) + expect(elem1.link1.length).toBe(1) + expect(elem2.link1.length).toBe(1) + expect(elem1.link1).toContain(tester) + expect(elem2.link1).toContain(tester) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + expect(tester.child2.remove(middle)).toBeTrue() + expect(tester.child2.length).toBe(0) + expect(tester.link3.length).toBe(1) + expect(tester.link4.length).toBe(1) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(elem1.link1.length).toBe(1) + expect(elem2.link1.length).toBe(1) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + }); + + it("should give true if the removeCascade and remove inverse chain triggered an element removal, also removing it from other containers if required reference is deleted", () => { + let tester = new RootWithChildren() + let middle = new Middle2WithChildren() + let elem1 = new ReChild3() + let elem2 = new ReChild4() + expect(tester.child2.removeCascade(middle)).toBeFalse() + tester.link3.push(elem1) + tester.link4.push(elem2) + tester.child2.push(middle) + middle.child3.push(elem1) + middle.child4.push(elem2) + expect(tester.link3.length).toBe(1) + expect(tester.link4.length).toBe(1) + expect(tester.link3).toContain(elem1) + expect(tester.link4).toContain(elem2) + expect(middle.child3.length).toBe(1) + expect(middle.child4.length).toBe(1) + expect(middle.child3).toContain(elem1) + expect(middle.child4).toContain(elem2) + expect(elem1.link1.length).toBe(1) + expect(elem2.link1.length).toBe(1) + expect(elem1.link1).toContain(tester) + expect(elem2.link1).toContain(tester) + expect(elem1.parentPointer).toBeDefined() + expect(elem2.parentPointer).toBeDefined() + expect(elem1.parentPointer).toEqual(middle) + expect(elem2.parentPointer).toEqual(middle) + expect(tester.child2.removeCascade(middle)).toBeTrue() + expect(tester.child2.length).toBe(0) + expect(tester.link3.length).toBe(0) + expect(tester.link4.length).toBe(0) + expect(middle.child3.length).toBe(0) + expect(middle.child4.length).toBe(0) + expect(elem1.link1.length).toBe(0) + expect(elem2.link1.length).toBe(0) + expect(elem1.parentPointer).toBeUndefined() + expect(elem2.parentPointer).toBeUndefined() + }) }); 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 54dfce6..076546f 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 @@ -5,6 +5,7 @@ import {JsonOf} from "../../../../serialization/json-deserializable"; import {SerializationContext} from "../../../../serialization/serialization-context"; import {ReTreeChildrenContainer} from "./re-tree-children-container"; import {ListUpdater} from "../../../../utils/list-updater"; +import {DeletionMode} from "../../../../utils/deletion-mode"; import {ReListContainer} from "../re-list-container"; import {ReferenceMeta} from "../../../../binding/model-definition"; @@ -45,7 +46,14 @@ implements ReTreeChildrenContainer { } } - override remove(item: T): boolean { + override remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { + if (mode === DeletionMode.CASCADE) { + if (this._instance.indexOf(item) > -1) { + item.destruct(mode); + return true; + } + return false; + } let removed = ListUpdater.removeFromList(item, this._instance) if(removed){ item.setParent(undefined); @@ -54,8 +62,8 @@ implements ReTreeChildrenContainer { return false; } - override delete() { - ListUpdater.destructAllFromChangingList(this._instance) + override delete(mode: DeletionMode = DeletionMode.RELAXED) { + ListUpdater.destructAllFromChangingList(this._instance, mode) } //creates one child level plus calls next createChildren 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 5fd42bf..1fe0916 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 @@ -1,9 +1,55 @@ 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"; describe('ReferencableTreeSingletonContainer', () => { it('should create an instance', () => { let tester = new ReferencableTester() expect(new ReTreeSingleContainer(tester, 'test', refTesterRef.references.test)).toBeTruthy(); }); + + it('should be working', () => { + let tester = new ReContainersWithSingleChild(); + let middle = new ReSingleChildExample(); + let elem1 = new ReContainersWithSingleChild(); + tester.child = middle; + middle.otherLink = elem1; + expect(tester.child).toBeDefined(); + expect(tester.child).toEqual(middle); + expect(middle.myParent).toBeDefined(); + expect(middle.myParent).toEqual(tester); + expect(middle.otherLink).toBeDefined(); + expect(middle.otherLink).toEqual(elem1); + expect(elem1.link).toBeDefined(); + expect(elem1.link).toEqual(middle); + expect(tester.$treeChildren[0].remove(middle)).toBeTrue(); + expect(tester.child).toBeUndefined(); + expect(middle.myParent).toBeUndefined(); + expect(middle.otherLink).toBeDefined(); + expect(middle.otherLink).toEqual(elem1); + expect(elem1.link).toBeDefined(); + expect(elem1.link).toEqual(middle); + }); + + it('should be working 2', () => { + let tester = new ReContainersWithSingleChild(); + let middle = new ReSingleChildExample(); + let elem1 = new ReContainersWithSingleChild(); + tester.child = middle; + middle.otherLink = elem1; + expect(tester.child).toBeDefined(); + expect(tester.child).toEqual(middle); + expect(middle.myParent).toBeDefined(); + expect(middle.myParent).toEqual(tester); + expect(middle.otherLink).toBeDefined(); + 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.child).toBeUndefined(); + expect(middle.myParent).toBeUndefined(); + expect(middle.otherLink).toBeUndefined(); + expect(elem1.link).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 f3387c7..6487431 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 @@ -6,6 +6,7 @@ import {SerializationContext} from "../../../../serialization/serialization-cont 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"; export class ReTreeSingleContainer> extends ReSingleContainer @@ -37,17 +38,25 @@ implements ReTreeChildrenContainer { } } - remove(item: T): boolean { + remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { if(this._instance == item) { - this._instance = undefined; - item.setParent(undefined); + if (mode === DeletionMode.RELAXED) { + this._instance = undefined; + item.setParent(undefined); + return true; + } + this._instance?.destruct(mode); return true; } return false; } - delete() { - this._instance?.destruct() + delete(mode: DeletionMode = DeletionMode.RELAXED) { + if (mode === DeletionMode.CASCADE) { + this._instance?.destruct(mode) + } else if (mode === DeletionMode.RELAXED) { + this._instance?.parent?.remove(this._instance, mode) + } } fromJson(formerPrefix: string, context: Deserializer, json: any) { diff --git a/projects/emfular/src/lib/referencing/referencable/referenceable.ts b/projects/emfular/src/lib/referencing/referencable/referenceable.ts index c0ff546..a87d5c6 100644 --- a/projects/emfular/src/lib/referencing/referencable/referenceable.ts +++ b/projects/emfular/src/lib/referencing/referencable/referenceable.ts @@ -1,5 +1,5 @@ import {Ref} from "../ref/ref"; -import { v4 as uuidv4 } from 'uuid'; +import {v4 as uuidv4} from 'uuid'; import {ReContainer} from "./container/re-container"; import {Deserializer} from "../../serialization/deserializer"; import {getAllAttributes} from "../../binding/attribute-collector"; @@ -11,6 +11,7 @@ import {ReTreeChildrenContainer} from "./container/tree/re-tree-children-contain 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"; /** base class for CORE models. * @@ -74,13 +75,14 @@ export abstract class Referencable< } } - destruct() { + destruct(mode: DeletionMode = DeletionMode.RELAXED) { + // removal from parent is always called with deletion mode RELAXED, otherwise infinite loops occur this.$parent?.remove(this) this.$otherReferences.forEach(refContainer => { - refContainer.removeFromInverse(this) + refContainer.removeFromInverse(this, mode) }) this.$treeChildren.forEach(child => { - child.delete() + child.delete(mode) }) } @@ -111,13 +113,20 @@ export abstract class Referencable< return container as ReContainer; } - public addToReferencableContainer>(name: string, item: T): boolean { return this.getContainer(name).add(item) } - public removeFromReferencableContainer>(name: string, item: T): boolean { - return this.getContainer(name).remove(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 { diff --git a/projects/emfular/src/lib/referencing/test/conversation-example.spec.ts b/projects/emfular/src/lib/referencing/test/conversation-example.spec.ts deleted file mode 100644 index d9721e7..0000000 --- a/projects/emfular/src/lib/referencing/test/conversation-example.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - Author, - ConversationPartner, - InformationLink, - InformationLinkType, - NewInformation, - ReceiveMessage, - SendMessage -} from "./conversation-example"; - -describe('Conversation Example', () => { - it('deletion of NewInformation should delete reference to source', () => { - let partner: ConversationPartner = new ConversationPartner(); - let recMess: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message'); - let newInfoA: NewInformation = NewInformation.create(recMess, 'Info A'); - let newInfoB: NewInformation = NewInformation.create(recMess, 'Info B'); - expect(newInfoA.source).toBeDefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(2); - newInfoA.destruct(); - expect(newInfoA.source).toBeUndefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(1); - }); - - it('deletion of NewInformation should delete reference to isUsedOn', () => { - let partner: ConversationPartner = new ConversationPartner(); - let recMess: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message'); - let newInfoA: NewInformation = NewInformation.create(recMess, 'Info A'); - let newInfoB: NewInformation = NewInformation.create(recMess, 'Info B'); - let sendMessA: SendMessage = SendMessage.create(partner, 0, 'Sent message A'); - let sendMessB: SendMessage = SendMessage.create(partner, 0, 'Sent message B'); - let author: Author = Author.create('Author'); - author.addMessage(sendMessA, sendMessB); - sendMessA.addUsage(newInfoA); - sendMessA.addUsage(newInfoB); - sendMessB.addUsage(newInfoA); - sendMessB.addUsage(newInfoB); - expect(author.messages.length).toBe(2); - expect(newInfoA.source).toBeDefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(2); - expect(newInfoA.isUsedOn.length).toBe(2); - expect(newInfoB.isUsedOn.length).toBe(2); - expect(sendMessA.uses.length).toBe(2); - expect(sendMessB.uses.length).toBe(2); - newInfoA.destruct(); - expect(author.messages.length).toBe(2); - expect(newInfoA.source).toBeUndefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(1); - expect(newInfoA.isUsedOn.length).toBe(0); - expect(newInfoB.isUsedOn.length).toBe(2); - expect(sendMessA.uses.length).toBe(1); - expect(sendMessB.uses.length).toBe(1); - }); - - it('deletion of NewInformation should delete reference to repeatedBy', () => { - let partner: ConversationPartner = new ConversationPartner(); - let recMess: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message'); - let recMessRepA: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message RepA'); - let recMessRepB: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message RepB'); - let newInfoA: NewInformation = NewInformation.create(recMess, 'Info A'); - let newInfoB: NewInformation = NewInformation.create(recMess, 'Info B'); - recMessRepA.addRepetition(newInfoA); - recMessRepA.addRepetition(newInfoB); - recMessRepB.addRepetition(newInfoA); - recMessRepB.addRepetition(newInfoB); - expect(newInfoA.source).toBeDefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(2); - expect(recMessRepA.repeats.length).toBe(2); - expect(recMessRepB.repeats.length).toBe(2); - expect(newInfoA.repeatedBy.length).toBe(2); - expect(newInfoB.repeatedBy.length).toBe(2); - newInfoA.destruct(); - expect(newInfoA.source).toBeUndefined(); - expect(newInfoB.source).toBeDefined(); - expect(recMess.generates.length).toBe(1); - expect(recMessRepA.repeats.length).toBe(1); - expect(recMessRepB.repeats.length).toBe(1); - expect(newInfoA.repeatedBy.length).toBe(0); - expect(newInfoB.repeatedBy.length).toBe(2); - }); - - it('deletion of NewInformation should delete reference to causes and delete children', () => { - let partner: ConversationPartner = new ConversationPartner(); - let recMess: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message'); - let newInfoA: NewInformation = NewInformation.create(recMess, 'Info A'); - let newInfoB: NewInformation = NewInformation.create(recMess, 'Info B'); - let newInfoC: NewInformation = NewInformation.create(recMess, 'Info C'); - let infoLinkAB: InformationLink = InformationLink.create(newInfoA, newInfoB, InformationLinkType.ATTACK); - let infoLinkAC: InformationLink = InformationLink.create(newInfoA, newInfoC, InformationLinkType.SUPPORT); - expect(newInfoA.source).toBeDefined(); - expect(newInfoA.causes.length).toBe(2); - expect(infoLinkAB.source).toBeDefined(); - expect(infoLinkAC.source).toBeDefined(); - expect(infoLinkAB.target).toBeDefined(); - expect(infoLinkAC.target).toBeDefined(); - newInfoA.destruct(); - expect(newInfoA.source).toBeUndefined(); - expect(newInfoA.causes.length).toBe(0); - expect(infoLinkAB.source).toBeUndefined(); - expect(infoLinkAC.source).toBeUndefined(); - expect(infoLinkAB.target).toBeUndefined(); - expect(infoLinkAC.target).toBeUndefined(); - }); - - it('deletion of NewInformation should delete reference to targetedBy and delete children', () => { - let partner: ConversationPartner = new ConversationPartner(); - let recMess: ReceiveMessage = ReceiveMessage.create(partner, 0, 'Received message'); - let newInfoA: NewInformation = NewInformation.create(recMess, 'Info A'); - let newInfoB: NewInformation = NewInformation.create(recMess, 'Info B'); - let newInfoC: NewInformation = NewInformation.create(recMess, 'Info C'); - let infoLinkBA: InformationLink = InformationLink.create(newInfoB, newInfoA, InformationLinkType.ATTACK); - let infoLinkCA: InformationLink = InformationLink.create(newInfoC, newInfoA, InformationLinkType.SUPPORT); - expect(newInfoA.source).toBeDefined(); - expect(newInfoA.targetedBy.length).toBe(2); - expect(infoLinkBA.source).toBeDefined(); - expect(infoLinkCA.source).toBeDefined(); - expect(infoLinkBA.target).toBeDefined(); - expect(infoLinkCA.target).toBeDefined(); - newInfoA.destruct(); - expect(newInfoA.source).toBeUndefined(); - expect(newInfoA.targetedBy.length).toBe(0); - expect(infoLinkBA.source).toBeDefined(); - expect(infoLinkCA.source).toBeDefined(); - expect(infoLinkBA.target).toBeUndefined(); - expect(infoLinkCA.target).toBeUndefined(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/test/conversation-example.ts b/projects/emfular/src/lib/referencing/test/conversation-example.ts deleted file mode 100644 index ba9d2cc..0000000 --- a/projects/emfular/src/lib/referencing/test/conversation-example.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { ReLinkListContainer } from "../referencable/container/link/re-link-list-container"; -import { ReLinkSingleContainer } from "../referencable/container/link/re-link-single-container"; -import { ReTreeListContainer } from "../referencable/container/tree/re-tree-list-container"; -import { ReTreeParentContainer } from "../referencable/container/tree/re-tree-parent-container"; -import { ReTreeSingleContainer } from "../referencable/container/tree/re-tree-single-container"; -import { Referencable } from "../referencable/referenceable"; - -export enum InformationLinkType { - SUPPLEMENT = 'SUPPLEMENT', - SUPPORT = 'SUPPORT', - STRONG_SUPPORT = 'STRONG_SUPPORT', - ATTACK = 'ATTACK', - STRONG_ATTACK = 'STRONG_ATTACK', -} - -export class Conversation extends Referencable { - static readonly $authorName = 'author'; - static readonly $conversationPartnersName = 'conversationPartners'; - - title: string; - - _author: ReTreeSingleContainer; - get author(): Author { - return this._author.get()!! - } - set author(author: Author) { - this._author.add(author); - } - _conversationPartners: ReTreeListContainer; - get conversationPartners(): ConversationPartner[] { - return this._conversationPartners.get() - } - addCP(...cps: ConversationPartner[]) { - cps.map(cp => { - this._conversationPartners.add(cp) - }) - } - - constructor( - title: string = 'New Conversation' - ) { - super(); - this._author = new ReTreeSingleContainer(this, Conversation.$authorName); - this._conversationPartners = new ReTreeListContainer(this, Conversation.$conversationPartnersName); - this.title = title; - this.author = new Author(); - } - - static create(title: string = 'New conversation', author?: Author): Conversation { - const conv = new Conversation('New Conversation'); - conv.title = title; - conv.author = author? author: new Author(); - return conv; - } - -} - -export abstract class LifeLine extends Referencable{ - name: string; - xPosition: number; //int todo - - protected constructor(name?: string, xPosition: number = 0) { - super(); - this.name = name? name: ''; - this.xPosition = xPosition; - } - -} - -export class ConversationPartner extends LifeLine { - - constructor(name: string = 'NewPartner', xPosition?: number) { - super(name, xPosition); - } - - static create(name: string = 'NewPartner', xPosition?: number): ConversationPartner { - const cp = new ConversationPartner() - cp.name = name - cp.xPosition = xPosition? xPosition : 0; - return cp; - } - -} - -export class Author extends LifeLine{ - static readonly $preknowledgeName: string = 'preknowledge'; - static readonly $messagesName: string = 'messages'; - - _preknowledge: ReTreeListContainer; - get preknowledge(): Preknowledge[] { - return this._preknowledge.get() - } - addPreknowledge(...preknowledge: Preknowledge[]) { - preknowledge.map(p => { - this._preknowledge.add(p) - }) - } - - _messages: ReTreeListContainer; - get messages(): Message[] { - return this._messages.get() - } - addMessage(...msgs: Message[]) { - msgs.map(m => { - this._messages.add(m) - }) - } - - constructor() { - super(); - this._preknowledge = new ReTreeListContainer(this, Author.$preknowledgeName) - this._messages = new ReTreeListContainer(this, Author.$messagesName) - } - - static create(name?: string, xPosition: number = 0): Author { - const auth = new Author() - auth.name = name? name: '' - auth.xPosition = xPosition - return auth - } - -} - -export abstract class Message extends Referencable { - public static readonly $counterPartName = 'counterPart' - - _counterPart: ReLinkSingleContainer; - get counterPart(): ConversationPartner { - return this._counterPart.get()!! //todo - } - set counterPart(value: ConversationPartner) { - this._counterPart.add(value); - } - - timing: number; - content: string; - originalContent?: string; - - protected constructor( - timing: number = 0, - content: string = "", - originalContent?: string, - ) { - super(); - this.timing = timing; - this.content = content; - this.originalContent = originalContent; - this._counterPart = new ReLinkSingleContainer(this, Message.$counterPartName) - } - - static isSend(eClass: string) { - return eClass.endsWith("SendMessage"); - } - - isSend(): this is SendMessage { - return this instanceof SendMessage - } - - isReceive(): this is ReceiveMessage { - return this instanceof ReceiveMessage - } - - static newMessage(isSend: boolean, counterPart: ConversationPartner, timing: number, content: string, originalContent: string = 'Original content'): Message { - if (isSend) { - return SendMessage.create(counterPart, timing, content, originalContent) - } else { - return ReceiveMessage.create(counterPart, timing, content, originalContent) - } - } -} - - -export class SendMessage extends Message { - public static readonly $usesName = 'uses' - - private readonly _uses: ReLinkListContainer; - get uses(): Information[] { - return this._uses.get(); - } - addUsage(info: Information) { - this._uses.add(info) - } - removeUsage(info: Information): boolean { - return this._uses.remove(info) - } - - constructor( - timing?: number, - content: string = 'New send content', - originalContent?: string, - ) { - super(timing, content, originalContent); - this._uses = new ReLinkListContainer(this, SendMessage.$usesName, Information.$isUsedOnName); - } - - static create(counterPart: ConversationPartner, - timing: number, - content: string = 'New send content', - originalContent?: string, - ): SendMessage { - const send = new SendMessage(timing, content, originalContent); - send.counterPart = counterPart; - return send; - } - -} - -export class ReceiveMessage extends Message { - static readonly $generatesName: string = 'generates'; - static readonly $repeatsName: string = 'repeats'; - - _generates: ReTreeListContainer; - get generates(): NewInformation[] { - return this._generates.get()!! - } - - _repeats: ReLinkListContainer; - get repeats(): Information[] { - return this._repeats.get(); - } - addRepetition(info: Information) { - this._repeats.add(info); - } - removeRepetition(info: Information): boolean { - return this._repeats.remove(info); - } - - isInterrupted: boolean = false; - - constructor( - timing?: number, - content: string = "New receive content", - originalContent?: string, - isInterrupted: boolean = false, - ) { - super(timing, content, originalContent); - this._generates = new ReTreeListContainer(this, ReceiveMessage.$generatesName, NewInformation.$sourceName); - this._repeats = new ReLinkListContainer(this, ReceiveMessage.$repeatsName, Information.$repeatedByName); - this.isInterrupted = isInterrupted; - } - - static create(counterPart: ConversationPartner, - timing: number, - content?: string, - originalContent?: string, - isInterrupted: boolean = false,): ReceiveMessage { - const rec = new ReceiveMessage(timing, content, originalContent, isInterrupted); - rec.counterPart = counterPart; - return rec - } - -} - -export abstract class Information< - P extends Referencable=Referencable -> extends Referencable

{ - - message: string = ""; - isInstruction: boolean = false; - initialTrust: number | undefined; - currentTrust: number | undefined; - feltTrustImmediately: number | undefined; - feltTrustAfterwards: number | undefined; - - abstract getTiming(): number; - - static readonly $causesName: string = 'causes' - static readonly $isUsedOnName: string = 'isUsedOn' - static readonly $repeatedByName: string = 'repeatedBy' - static readonly $targetedByName: string = 'targetedBy' - readonly _causes: ReTreeListContainer; - get causes(): InformationLink[] { - return this._causes.get(); - } - - readonly _targetedBy: ReLinkListContainer - get targetedBy(): InformationLink[] { - return this._targetedBy.get(); - } - - readonly _isUsedOn: ReLinkListContainer - get isUsedOn(): SendMessage[] { - return this._isUsedOn.get(); - } - addIsUsedOn(...send: SendMessage[]){ - send.map(s => this._isUsedOn.add(s)) - } - removeIsUsedOn(send: SendMessage){ - this._isUsedOn.remove(send) - } - - readonly _repeatedBy: ReLinkListContainer - get repeatedBy(): ReceiveMessage[] { - return this._repeatedBy.get(); - } - - addRepeatedBy(msg: ReceiveMessage) { - this._repeatedBy.add(msg) - } - removeRepeatedBy(msg: ReceiveMessage) { - this._repeatedBy.remove(msg) - } - - protected constructor() { - super(); - - this._causes = new ReTreeListContainer(this, NewInformation.$causesName, InformationLink.$sourceName); - this._targetedBy = new ReLinkListContainer(this, Information.$targetedByName, InformationLink.$targetName) - this._isUsedOn = new ReLinkListContainer(this, 'isUsedOn', 'uses'); - this._repeatedBy = new ReLinkListContainer(this, NewInformation.$repeatedByName, ReceiveMessage.$repeatsName); - } - - abstract duplicate(): Information; - -} -export class NewInformation extends Information { - - public static readonly $sourceName = 'source' - - readonly _source: ReTreeParentContainer; - set source(rec: ReceiveMessage) { - this._source.add(rec) - } - get source(): ReceiveMessage { - return this._source.get()!! - } - - override getTiming(): number { - return this.source.timing - } - - constructor() { - super(); - this._source = new ReTreeParentContainer(this, NewInformation.$sourceName, ReceiveMessage.$generatesName); - } - - override duplicate(): NewInformation { - return NewInformation.create(this.source, 'Copy of ' + this.message, this.isInstruction, this.initialTrust, this.currentTrust, this.feltTrustImmediately, this.feltTrustAfterwards); - } - - static create(source: ReceiveMessage, - message: string, isInstruction: boolean = false, - initialTrust?: number, currentTrust?: number, feltTrustImmediately?: number, feltTrustAfterwards?: number,): NewInformation { - const info = new NewInformation(); - info.source = source; - info.message = message; - info.isInstruction = isInstruction; - info.initialTrust = initialTrust; - info.currentTrust = currentTrust; - info.feltTrustImmediately = feltTrustImmediately; - info.feltTrustAfterwards = feltTrustAfterwards; - return info; - } - -} - -export class Preknowledge extends Information { - - constructor() { - super(); - } - - getTiming(): number { - let timing; - if (this.isUsedOn?.length >0) { - timing = Math.min(...this.isUsedOn.map(send => send.timing)); - } else { - timing = 0 - } - return timing - } - - override duplicate(): Preknowledge { - return Preknowledge.create('Copy of ' + this.message, this.isInstruction, this.initialTrust, this.currentTrust, this.feltTrustImmediately, this.feltTrustAfterwards); - } - - static create(message: string = 'Preknowledge', isInstruction: boolean = false, - initialTrust?: number, currentTrust?: number, - feltTrustImmediately?: number, feltTrustAfterwards?: number): Preknowledge { - const pre = new Preknowledge() - pre.message = message - pre.isInstruction = isInstruction - pre.initialTrust = initialTrust - pre.currentTrust = currentTrust - pre.feltTrustImmediately = feltTrustImmediately - pre.feltTrustAfterwards = feltTrustAfterwards - return pre - } - -} - -export class InformationLink extends Referencable { - - public static readonly $sourceName = 'source' - public static readonly $targetName = 'target' - readonly _source: ReTreeParentContainer - get source(): Information { - return this._source.get()!!; //todo - } - set source(source: Information) { - this._source.add(source) - } - - readonly _target: ReLinkSingleContainer - get target(): Information { - return this._target.get()!!; - } - set target(target: Information) { - this._target.add(target); - } - - type: InformationLinkType = InformationLinkType.SUPPLEMENT; - linkText?: string; - - constructor() { - super(); - this._source = new ReTreeParentContainer(this, InformationLink.$sourceName, NewInformation.$causesName); - this._target = new ReLinkSingleContainer(this, InformationLink.$targetName, Information.$targetedByName); - } - - static create(source: Information, target: Information, type: InformationLinkType, linkText?: string,): InformationLink { - const link = new InformationLink() - link.source = source - link.target = target; - link.type = type - link.linkText = linkText - return link - } - -} - - - diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts index 40582c1..c86fa65 100644 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts +++ b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts @@ -27,6 +27,21 @@ export const ModelSingleChild: ModelDefinition = { } } }, + ReContainersWithSingleChild2: { + references: { + child: { + target: "ReSingleChildExample2", + containment: true, + opposite: "myParent", + max: 1 + }, + link: { + target: "ReSingleChildExample2", + opposite: "otherLink", + max: 1 + } + } + }, ReSingleChildExample: { references: { @@ -42,6 +57,23 @@ export const ModelSingleChild: ModelDefinition = { max: 1 } } + }, + + ReSingleChildExample2: { + references: { + myParent: { + target: "ReContainersWithSingleChild2", + isParent: true, + opposite: "child", + max: 1 + }, + otherLink: { + target: "ReContainersWithSingleChild2", + opposite: "link", + min: 1, + max: 1 + } + } } } } as const; @@ -52,14 +84,26 @@ export const ReContainersWithSingleChildRefs = { link: ModelSingleChild.classes["ReContainersWithSingleChild"].references["link"] }; +export const ReContainersWithSingleChild2Refs = { + child: ModelSingleChild.classes["ReContainersWithSingleChild2"].references["child"], + link: ModelSingleChild.classes["ReContainersWithSingleChild2"].references["link"] +}; + export const ReSingleChildExampleRefs = { myParent: ModelSingleChild.classes["ReSingleChildExample"].references["myParent"], otherLink: ModelSingleChild.classes["ReSingleChildExample"].references["otherLink"] }; +export const ReSingleChildExample2Refs = { + myParent: ModelSingleChild.classes["ReSingleChildExample2"].references["myParent"], + otherLink: ModelSingleChild.classes["ReSingleChildExample2"].references["otherLink"] +}; + export enum EClassesSingleChild { 'ReContainersWithSingleChild' = 'class://ReContainersWithSingleChild', - 'ReSingleChildExample' = 'class://ReSingleChildExample' + 'ReContainersWithSingleChild2' = 'class://ReContainersWithSingleChild2', + 'ReSingleChildExample' = 'class://ReSingleChildExample', + 'ReSingleChildExample2' = 'class://ReSingleChildExample2' } @eClass(ModelSingleChild, "ReContainersWithSingleChild") @@ -87,6 +131,31 @@ export class ReContainersWithSingleChild extends Referencable { } +@eClass(ModelSingleChild, "ReContainersWithSingleChild2") +export class ReContainersWithSingleChild2 extends Referencable { + + @reference(ReContainersWithSingleChild2Refs.child) + declare child: ReSingleChildExample2 | undefined; + + @reference(ReContainersWithSingleChild2Refs.link) + declare link: ReSingleChildExample2 | undefined; + + @attribute() + name: string = "re1"; + + constructor() { + super(); + } + + static fromJSON (convJson: JsonOf): ReContainersWithSingleChild2 { + return Deserializer.fromJSON( + convJson, + EClassesSingleChild.ReContainersWithSingleChild2 + ) + } + +} + @eClass(ModelSingleChild, "ReSingleChildExample") export class ReSingleChildExample extends Referencable { @@ -103,3 +172,20 @@ export class ReSingleChildExample extends Referencable { + + @reference(ReSingleChildExample2Refs.myParent) + declare myParent: ReContainersWithSingleChild2 | undefined; + + @reference(ReSingleChildExample2Refs.otherLink) + declare otherLink: ReContainersWithSingleChild2 | undefined; + + @attribute() + myBool = true; + + constructor() { + super(); + } +} diff --git a/projects/emfular/src/lib/referencing/test/referencable-tester.ts b/projects/emfular/src/lib/referencing/test/referencable-tester.ts index 0526134..f00f45d 100644 --- a/projects/emfular/src/lib/referencing/test/referencable-tester.ts +++ b/projects/emfular/src/lib/referencing/test/referencable-tester.ts @@ -12,10 +12,10 @@ export const refTesterRef = { } export const modelDef= { - name: "", prefix: "", uri: "", + name: "", prefix: "", uri: "", classes: { - ReferencableTester: refTesterRef - } + ReferencableTester: refTesterRef + } } as const satisfies ModelDefinition; @eClass(modelDef, "ReferencableTester") 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 709a7f2..f855dcf 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 @@ -1,8 +1,11 @@ import { EClasses, - RootWithChildren, RootWithChildrenJson, - Middle2WithChildren, Middle2WithChildrenJson, - ReChild3, ReChild3Json + Middle2WithChildren, + Middle2WithChildrenJson, + ReChild3, + ReChild3Json, + RootWithChildren, + RootWithChildrenJson } from "./referencables-with-children"; import {SerializationContext} from "../../serialization/serialization-context"; import {RefHandler} from "../ref/ref-handler"; @@ -69,6 +72,7 @@ describe('ReContainersWithListChild tests', () => { expect(r2_1.child3.length).toBe(0); expect(r3_1.parentPointer).toEqual(undefined) expect(r3_2.parentPointer).toEqual(r2_2) + }); it ('should serialize a Referencable1WithChildren correctly', () => { @@ -135,7 +139,6 @@ describe('ReContainersWithListChild tests', () => { expect(r.child2.map(x => x)).toEqual([m3, m2, m1]); }); - it('should allow flatMap on ModelList proxies (shows proxy behaves like array)', () => { // Build a small containment structure r1.child2.push(r2_1, r2_2); @@ -150,5 +153,4 @@ describe('ReContainersWithListChild tests', () => { expect(allChildren).toContain(r3_1); expect(allChildren).toContain(r3_2); }); - }); \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/referencables-with-children.ts b/projects/emfular/src/lib/referencing/test/referencables-with-children.ts index fe5b00d..f87be80 100644 --- a/projects/emfular/src/lib/referencing/test/referencables-with-children.ts +++ b/projects/emfular/src/lib/referencing/test/referencables-with-children.ts @@ -25,6 +25,12 @@ export const ModelWithChildren: ModelDefinition = { opposite: "link1", min: 0, max: -1 + }, + link4: { + target: "ReChild4", + opposite: "link1", + min: 0, + max: -1 } } }, @@ -37,6 +43,13 @@ export const ModelWithChildren: ModelDefinition = { opposite: "parentPointer", min: 0, max: -1 + }, + child4: { + target: "ReChild4", + containment: true, + opposite: "parentPointer", + min: 0, + max: -1 } } }, @@ -58,6 +71,24 @@ export const ModelWithChildren: ModelDefinition = { max: 1 } } + }, + ReChild4: { + references: { + link1: { + target: "RootWithChildren", + opposite: "link4", + min: 1, + max: -1 + }, + parentPointer: { + target: "Middle2WithChildren", + containment: true, + isParent: true, + opposite: "child4", + min: 0, + max: 1 + } + } } } } as const; @@ -65,19 +96,26 @@ export const ModelWithChildren: ModelDefinition = { export const RootWithChildrenRefs = { child2: ModelWithChildren.classes["RootWithChildren"].references["child2"], link3: ModelWithChildren.classes["RootWithChildren"].references["link3"], + link4: ModelWithChildren.classes["RootWithChildren"].references["link4"] } as const; export const Middle2WithChildrenRefs = { child3: ModelWithChildren.classes["Middle2WithChildren"].references["child3"], + child4: ModelWithChildren.classes["Middle2WithChildren"].references["child4"] } as const; export const ReChild3Refs = { link1: ModelWithChildren.classes["ReChild3"].references["link1"], parentPointer: ModelWithChildren.classes["ReChild3"].references["parentPointer"], } as const; +export const ReChild4Refs = { + link1: ModelWithChildren.classes["ReChild4"].references["link1"], + parentPointer: ModelWithChildren.classes["ReChild4"].references["parentPointer"], +} as const; export enum EClasses { 'RootWithChildren' = 'namespace/RootWithChildren', 'Middle2WithChildren' = 'namespace/Middle2WithChildren', - 'ReChild3' = 'namespace/ReChild3' + 'ReChild3' = 'namespace/ReChild3', + 'ReChild4' = 'namespace/ReChild4' } @eClass(ModelWithChildren, "RootWithChildren") @@ -87,6 +125,8 @@ export class RootWithChildren extends Referencable { declare child2: ModelList @reference(RootWithChildrenRefs.link3) declare link3: ModelList; + @reference(RootWithChildrenRefs.link4) + declare link4: ModelList; @attribute() name: string = "referencable1"; @@ -98,9 +138,11 @@ export class RootWithChildren extends Referencable { @eClass(ModelWithChildren, "Middle2WithChildren") export class Middle2WithChildren extends Referencable { - + @reference(Middle2WithChildrenRefs.child3) declare child3: ModelList + @reference(Middle2WithChildrenRefs.child4) + declare child4: ModelList @attribute() name: string = "referencable2"; @@ -126,8 +168,25 @@ export class ReChild3 extends Referencable { } } +@eClass(ModelWithChildren, "ReChild4") +export class ReChild4 extends Referencable { + @reference(ReChild4Refs.link1) + declare link1: ModelList + + @reference(ReChild4Refs.parentPointer) + declare parentPointer?: Middle2WithChildren; + + @attribute() + name: string = "referencable4"; + + constructor() { + super(); + } +} + export type RootWithChildrenJson = JsonOf export type Middle2WithChildrenJson = JsonOf export type ReChild3Json = JsonOf +export type ReChild4Json = JsonOf diff --git a/projects/emfular/src/lib/utils/deletion-mode.ts b/projects/emfular/src/lib/utils/deletion-mode.ts new file mode 100644 index 0000000..baec52f --- /dev/null +++ b/projects/emfular/src/lib/utils/deletion-mode.ts @@ -0,0 +1,4 @@ +export enum DeletionMode { + CASCADE = "CASCADE", // delete all classes that become inconsistent after deletion + RELAXED = "RELAXED" // delete only the specified class, even if it leaves the model in an inconsistent state +} \ No newline at end of file diff --git a/projects/emfular/src/lib/utils/list-updater.ts b/projects/emfular/src/lib/utils/list-updater.ts index dd97126..a09400d 100644 --- a/projects/emfular/src/lib/utils/list-updater.ts +++ b/projects/emfular/src/lib/utils/list-updater.ts @@ -1,4 +1,5 @@ import {Referencable} from "../referencing/referencable/referenceable"; +import { DeletionMode } from "./deletion-mode"; export class ListUpdater { @@ -25,9 +26,13 @@ export class ListUpdater { } } - static destructAllFromChangingList>(list: T[]) { + static destructAllFromChangingList>(list: T[], mode: DeletionMode) { while(list?.length > 0){ - list[0].destruct() + if (mode === DeletionMode.CASCADE) { + list[0].destruct(mode) + } else if (mode === DeletionMode.RELAXED) { + list[0].parent?.remove(list[0], mode) + } } }