import type { types } from "@aas-core-works/aas-core3.0-typescript"; import type { AIMCMap } from "./types/aimcConf"; import type { ConnectionConfiguration } from "./types/requests"; import type { InterfaceAction, InterfaceActionForm, InterfaceEvent, InterfaceEventForm, InterfaceProperty, InterfacePropertyForm } from "./types/aidConf"; import AIMCParser from "./parser/AIMCParser"; import Traverser from "./helper/traverser"; import AIDParser from "./parser/AIDParser"; /** * The AIMC Mapper. * @remarks * This class is used to map properties to endpoints. * @public */ export default class AIMCMapper { /** * The map. * @private */ private map: AIMCMap = new Map(); /** * Creates the mapper. * @param env The AAS Environment * @public */ public constructor(private readonly env: types.Environment) { this.generate(); } /** * Generates the map from the AAS Environment. * @private */ private generate(): void { const aimc = AIMCParser.parse(this.env); if (aimc === null) return; for (const endpointConf of Object.values(aimc)) { for (const entry of endpointConf) { const ep = AIDParser.parseEndpointMetaData(entry.EndpointMetaDataReference); if (ep === null) continue; for (const mapping of entry.MappingConfiguration) { //const ifm = AIDParser.parseInterfaceMetaData(mapping.InterfaceMetaDataReference); //if (ifm === null) continue; // Why do we need this? We already have the interface metadata reference in every ssm for (const ssm of mapping.SourceSinkMappings) { const resolved = Traverser.resolveRelationship(this.env, ssm); if (resolved === null) continue; // To know if we currently view a property, action or event, they reference keys can help us. // We know the third to last must be the "InterfaceMetadata" and the second to last must be the "Properties", "Actions" or "Events". // As we _maybe_ don't know if the first and second reference are switched we can check for the third to last. const elemType = (ssm as types.RelationshipElement).first.keys.at(-3)?.value.toLocaleLowerCase() === "interfacemetadata" ? (ssm as types.RelationshipElement).first.keys.at(-2)?.value : (ssm as types.RelationshipElement).second.keys.at(-2)?.value; let {first, second} = resolved; let parsedPropConf: InterfaceProperty | InterfaceAction | InterfaceEvent | null = null; switch (elemType?.toLocaleLowerCase()) { case "properties": parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); break; case "actions": parsedPropConf = AIDParser.parseInterfaceMetadataAction(first as types.SubmodelElementCollection); break; case "events": parsedPropConf = AIDParser.parseInterfaceMetadataEvent(first as types.SubmodelElementCollection); break; default: // Error. We have not found a valid type of element to parse. continue; } if (parsedPropConf === null) { // maybe the property is in the first element [first, second] = [second, first]; switch (elemType?.toLocaleLowerCase()) { case "properties": parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); break; case "actions": parsedPropConf = AIDParser.parseInterfaceMetadataAction(first as types.SubmodelElementCollection); break; case "events": parsedPropConf = AIDParser.parseInterfaceMetadataEvent(first as types.SubmodelElementCollection); break; } } if (parsedPropConf === null) continue; this.map.set(second, ({ ...ep, ...parsedPropConf } as any as ConnectionConfiguration)); } } } } } /** * * @returns The complete map * @public */ public getMap(): AIMCMap { return this.map; } /** * Get endpoint for a specific Element * @param element Element * @returns Endpoint description or undefined if not found * @public */ public get(element: types.Class): ConnectionConfiguration | undefined { return this.map.get(element); } /** * Returns all endpoints for a idShort * Can return multiple as idShorts are not necessarily unique * @param idShort idShort of the element * @returns Endpoints descriptions of elements with that idShort * @public */ public getByIdShort(idShort: string): ConnectionConfiguration[] { const result = []; for (const [e, cc] of this.map.entries()) { if ((e as any).idShort?.toLocaleLowerCase() === idShort.toLocaleLowerCase()) result.push(cc); } return result; } /** * Get an element by its endpoint * @param path Absolute endpoint path * @returns Elements that use that path * @public */ public reverseGet(path: string): types.Class[] { const result = []; for (const [e, cc] of this.map.entries()) { const formsPaths = cc.forms.map((f: InterfaceActionForm | InterfacePropertyForm | InterfaceEventForm) => cc.base + f.href); if (formsPaths.includes(path)) result.push(e); } return result; } }