typir
Version:
General purpose type checking library
119 lines • 6.08 kB
JavaScript
/******************************************************************************
* 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