UNPKG

typir

Version:

General purpose type checking library

119 lines 6.08 kB
/****************************************************************************** * Copyright 2024 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import { assertTrue, removeFromArray } from '../utils/utils.js'; /** * Each Typir instance has one single type graph. * Each type exists only once and is stored inside this type graph. * * Edges with different meaning/purpose will exist in parallel inside the same graph, * otherwise nodes/types need to be duplicated or would be used in multiple graphs. * Graph algorithms will need to filter the required edges regarding $relation. */ export class TypeGraph { constructor() { this.nodes = new Map(); // type name => Type this.edges = []; this.listeners = []; // add reusable graph algorithms here (or introduce a new service for graph algorithms which might be easier to customize/exchange) } /** * Usually this method is called by kinds after creating a corresponding type. * Therefore it is usually not needed to call this method in an other context. * @param type the new type * @param key an optional key to register the type, since it is allowed to register the same type with different keys in the graph */ addNode(type, key) { if (!key) { assertTrue(type.isInStateOrLater('Identifiable')); // the key of the type must be available! } const mapKey = key !== null && key !== void 0 ? key : type.getIdentifier(); if (this.nodes.has(mapKey)) { if (this.nodes.get(mapKey) === type) { // this type is already registered => that is OK } else { throw new Error(`There is already a type with the identifier '${mapKey}'.`); } } else { this.nodes.set(mapKey, type); this.listeners.forEach(listener => { var _a; return (_a = listener.onAddedType) === null || _a === void 0 ? void 0 : _a.call(listener, type, mapKey); }); } } /** * When removing a type/node, all its edges (incoming and outgoing) are removed as well. * Design decision: * This is the central API call to remove a type from the type system in case that it is no longer valid/existing/needed. * It is not required to directly inform the kind of the removed type yourself, since the kind itself will take care of removed types. * @param typeToRemove the type to remove * @param key an optional key to register the type, since it is allowed to register the same type with different keys in the graph */ removeNode(typeToRemove, key) { const mapKey = key !== null && key !== void 0 ? key : typeToRemove.getIdentifier(); // remove all edges which are connected to the type to remove typeToRemove.getAllIncomingEdges().forEach(e => this.removeEdge(e)); typeToRemove.getAllOutgoingEdges().forEach(e => this.removeEdge(e)); // remove the type itself const contained = this.nodes.delete(mapKey); if (contained) { this.listeners.slice().forEach(listener => { var _a; return (_a = listener.onRemovedType) === null || _a === void 0 ? void 0 : _a.call(listener, typeToRemove, mapKey); }); typeToRemove.dispose(); } else { throw new Error(`Type does not exist: ${mapKey}`); } } getNode(key) { return this.nodes.get(key); } getType(key) { return this.getNode(key); } getAllRegisteredTypes() { return [...this.nodes.values()]; } addEdge(edge) { // check constraints: no duplicated edges (same values for: from, to, $relation) if (edge.from.getOutgoingEdges(edge.$relation).some(e => e.to === edge.to)) { throw new Error(`There is already a '${edge.$relation}' edge from '${edge.from.getName()}' to '${edge.to.getName()}'.`); } // TODO what about the other direction for bidirectional edges? for now, the user has to ensure no duplicates here! this.edges.push(edge); // register this new edge at the connected nodes edge.to.addIncomingEdge(edge); edge.from.addOutgoingEdge(edge); this.listeners.forEach(listener => { var _a; return (_a = listener.onAddedEdge) === null || _a === void 0 ? void 0 : _a.call(listener, edge); }); } removeEdge(edge) { // remove this new edge at the connected nodes edge.to.removeIncomingEdge(edge); edge.from.removeOutgoingEdge(edge); if (removeFromArray(edge, this.edges)) { this.listeners.forEach(listener => { var _a; return (_a = listener.onRemovedEdge) === null || _a === void 0 ? void 0 : _a.call(listener, edge); }); } else { throw new Error(`Edge does not exist: ${edge.$relation}`); } } getUnidirectionalEdge(from, to, $relation, cachingMode = 'LINK_EXISTS') { return from.getOutgoingEdges($relation).find(edge => edge.to === to && edge.cachingInformation === cachingMode); } getBidirectionalEdge(from, to, $relation, cachingMode = 'LINK_EXISTS') { // for bidirectional edges, check outgoing and incoming edges, since the graph contains only a single edge! return from.getEdges($relation).find(edge => edge.to === to && edge.cachingInformation === cachingMode); } // register listeners for changed types/edges in the type graph addListener(listener, options) { this.listeners.push(listener); if ((options === null || options === void 0 ? void 0 : options.callOnAddedForAllExisting) && listener.onAddedType) { this.nodes.forEach((type, key) => listener.onAddedType.call(listener, type, key)); } } removeListener(listener) { removeFromArray(listener, this.listeners); } } //# sourceMappingURL=type-graph.js.map