From 94e75cd14865a5a90ca196a27f3e5b85f599cf22 Mon Sep 17 00:00:00 2001 From: "@nheuser" Date: Tue, 10 Mar 2026 14:08:12 +0100 Subject: [PATCH] proof that using [...this._instance] instead of this._instance in removeFromInverse() fixes bug --- .../container/link/re-link-list-container.ts | 2 +- .../referencing/test/min-keml-example.spec.ts | 131 ++++++ .../lib/referencing/test/min-keml-example.ts | 433 ++++++++++++++++++ 3 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 projects/emfular/src/lib/referencing/test/min-keml-example.spec.ts create mode 100644 projects/emfular/src/lib/referencing/test/min-keml-example.ts 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 65290d4..1edf598 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 @@ -51,7 +51,7 @@ export class ReLinkListContainer< override removeFromInverse(item: T): boolean { if(this.inverseName !== undefined) { - for (const child of this._instance) { + for (const child of [...this._instance]) { child.removeFromReferencableContainer(this.inverseName, item) } return true; // todo - refine? diff --git a/projects/emfular/src/lib/referencing/test/min-keml-example.spec.ts b/projects/emfular/src/lib/referencing/test/min-keml-example.spec.ts new file mode 100644 index 0000000..6841910 --- /dev/null +++ b/projects/emfular/src/lib/referencing/test/min-keml-example.spec.ts @@ -0,0 +1,131 @@ +import { + Author, + ConversationPartner, + InformationLink, + InformationLinkType, + NewInformation, + ReceiveMessage, + SendMessage +} from "./min-keml-example"; + +describe('MinKemlExample', () => { + 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(); + }); +}); \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/min-keml-example.ts b/projects/emfular/src/lib/referencing/test/min-keml-example.ts new file mode 100644 index 0000000..ba9d2cc --- /dev/null +++ b/projects/emfular/src/lib/referencing/test/min-keml-example.ts @@ -0,0 +1,433 @@ +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 + } + +} + + +