import * as path from "path" ; import * as vscode from "vscode" ; import { Constants } from "../common/constants" ; import { Utility } from "../common/utility" ; import { DigitalTwinConstants } from "./digitalTwinConstants" ; import { IntelliSenseUtility } from "./intelliSenseUtility" ; /**
 * Class node of DigitalTwin graph
 */ export interface ClassNode { id : string ; label ? : string ; isAbstract ? : boolean ; children ? : ClassNode [ ] ; properties ? : PropertyNode [ ] ; enums ? : string [ ] ; constraint ? : ConstraintNode ; version ? : VersionNode ; } /**
 * Property node of DigitalTwin graph
 */ export interface PropertyNode { id : string ; label ? : string ; isArray ? : boolean ; comment ? : string ; range ? : ClassNode [ ] ; constraint ? : ConstraintNode ; version ? : VersionNode ; } /**
 * Constraint node of DigitalTwin graph
 */ export interface ConstraintNode { minItems ? : number ; maxItems ? : number ; minLength ? : number ; maxLength ? : number ; pattern ? : string ; required ? : string [ ] ; } /**
 * Version node of DigitalTwin graph
 */ export interface VersionNode { includeSince ? : number ; excludeSince ? : number ; } /**
 * Context node of DigitalTwin graph
 */ interface ContextNode { name : string ; container : ContainerType ; } /**
 * Value schema definition for DigitalTwin graph
 */ export enum ValueSchema { String = "http://www.w3.org/2001/XMLSchema#string" , Int = "http://www.w3.org/2001/XMLSchema#int" , Boolean = "http://www.w3.org/2001/XMLSchema#boolean" } /**
 * Node type definition for DigitalTwin graph
 */ enum NodeType { Class = "http://www.w3.org/2000/01/rdf-schema#Class" , Property = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property" } /**
 * Edge type definition for DigitalTwin graph
 */ enum EdgeType { Type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" , Range = "http://www.w3.org/2000/01/rdf-schema#range" , Label = "http://www.w3.org/2000/01/rdf-schema#label" , Domain = "http://www.w3.org/2000/01/rdf-schema#domain" , SubClassOf = "http://www.w3.org/2000/01/rdf-schema#subClassOf" , Comment = "http://www.w3.org/2000/01/rdf-schema#comment" } /**
 * Container type of JSON-LD
 */ enum ContainerType { None , Array , Language } /**
 * DigitalTwin graph
 */ export class DigitalTwinGraph { param context static async getInstance ( context ) { if ( ! DigitalTwinGraph . instance ) { DigitalTwinGraph . instance = new DigitalTwinGraph ( ) ; await DigitalTwinGraph . instance . init ( context ) ; } return DigitalTwinGraph . instance ; } private static instance : DigitalTwinGraph ; param object private static isConstraintNode ( object ) { return ( object . minItems || object . maxItems || object . minLength || object . maxLength || object . pattern || object . required ) ; } param object private static isValidEdge ( object ) { return object . SourceNode && object . TargetNode && object . Label ; } param object private static resolveContainerType ( object ) { const container = object [ DigitalTwinConstants . CONTAINER ] ; if ( ! container || typeof container !== "string" ) { return ContainerType . None ; } switch ( container ) { case DigitalTwinConstants . LIST : case DigitalTwinConstants . SET : return ContainerType . Array ; case DigitalTwinConstants . LANGUAGE : return ContainerType . Language ; default : return ContainerType . None ; } } param context param fileName private static async resolveDefinition ( context , fileName ) { const filePath = context . asAbsolutePath ( path . join ( Constants . RESOURCE_FOLDER , Constants . DEFINITION_FOLDER , fileName ) ) ; return await Utility . getJsonContent ( filePath ) ; } private classNodes : Map < string , ClassNode > ; private propertyNodes : Map < string , PropertyNode > ; private contextNodes : Map < string , ContextNode > ; private constraintNodes : Map < string , ConstraintNode > ; private reversedIndex : Map < string , string > ; private contextVersions : Map < string , number > ; private vocabulary : string ; private constructor ( ) { this . classNodes = new Map < string , ClassNode > ( ) ; this . propertyNodes = new Map < string , PropertyNode > ( ) ; this . contextNodes = new Map < string , ContextNode > ( ) ; this . constraintNodes = new Map < string , ConstraintNode > ( ) ; this . reversedIndex = new Map < string , string > ( ) ; this . contextVersions = new Map < string , number > ( ) ; this . vocabulary = Constants . EMPTY_STRING ; } /**
     * check if DigitalTwin graph has been initialized
     */ initialized ( ) { return this . vocabulary !== Constants . EMPTY_STRING ; } param name getPropertyNode ( name ) { const id = this . reversedIndex . get ( name ) || name ; return this . propertyNodes . get ( id ) ; } param name getClassNode ( name ) { const id = this . reversedIndex . get ( name ) || this . getId ( name ) ; return this . classNodes . get ( id ) ; } param context getVersion ( context ) { return this . contextVersions . get ( context ) || 0 ; } param context private async init ( context ) { let contextJson ; let constraintJson ; let graphJson ; try { contextJson = await DigitalTwinGraph . resolveDefinition ( context , Constants . CONTEXT_FILE_NAME ) ; constraintJson = await DigitalTwinGraph . resolveDefinition ( context , Constants . CONSTRAINT_FILE_NAME ) ; graphJson = await DigitalTwinGraph . resolveDefinition ( context , Constants . GRAPH_FILE_NAME ) ; } catch ( error ) { return ; } this . buildContext ( contextJson ) ; this . buildConstraint ( constraintJson ) ; this . buildGraph ( graphJson ) ; } param name private getId ( name ) { return this . vocabulary + name ; } param contextJson private buildContext ( contextJson ) { let id ; const context = contextJson [ DigitalTwinConstants . CONTEXT ] ; this . vocabulary = context [ DigitalTwinConstants . VOCABULARY ] as string ; for ( const key in context ) { if ( IntelliSenseUtility . isReservedName ( key ) ) { continue ; } const value = context [ key ] ; if ( typeof value === "string" ) { id = this . getId ( value ) ; this . contextNodes . set ( id , { name : key , container : ContainerType . None } ) ; } else { const containerType = DigitalTwinGraph . resolveContainerType ( value ) ; id = this . getId ( value [ DigitalTwinConstants . ID ] as string ) ; this . contextNodes . set ( id , { name : key , container : containerType } ) ; } this . reversedIndex . set ( key , id ) ; } } param constraintJson private buildConstraint ( constraintJson ) { for ( const key in constraintJson ) { if ( DigitalTwinGraph . isConstraintNode ( constraintJson [ key ] ) ) { this . constraintNodes . set ( key , constraintJson [ key ] ) ; } else if ( key === DigitalTwinConstants . CONTEXT ) { const versions = constraintJson [ key ] ; for ( const vKey in versions ) { this . contextVersions . set ( versions [ vKey ] as string , parseInt ( vKey . slice ( 1 ) ) ) ; } } } } param graphJson private buildGraph ( graphJson ) { for ( const edge of graphJson . Edges ) { if ( DigitalTwinGraph . isValidEdge ( edge ) ) { this . handleEdge ( edge ) ; } } this . adjustNode ( ) ; this . expandProperties ( ) ; this . buildEntryNode ( ) ; } param edge private handleEdge ( edge ) { switch ( edge . Label ) { case EdgeType . Type : this . handleEdgeOfType ( edge ) ; break ; case EdgeType . Label : this . handleEdgeOfLabel ( edge ) ; break ; case EdgeType . Domain : this . handleEdgeOfDomain ( edge ) ; break ; case EdgeType . Range : this . handleEdgeOfRange ( edge ) ; break ; case EdgeType . SubClassOf : this . handleEdgeOfSubClassOf ( edge ) ; break ; case EdgeType . Comment : this . handleEdgeOfComment ( edge ) ; break ; default : } } param edge private handleEdgeOfType ( edge ) { const id = edge . SourceNode . Id as string ; const type = edge . TargetNode . Id as string ; switch ( type ) { case NodeType . Class : this . ensureClassNode ( id ) ; break ; case NodeType . Property : this . ensurePropertyNode ( id ) ; break ; default : { const contextNode = this . contextNodes . get ( id ) ; const enumValue = contextNode ? contextNode . name : id ; const enumNode = this . ensureClassNode ( type ) ; if ( ! enumNode . enums ) { enumNode . enums = [ ] ; } enumNode . enums . push ( enumValue ) ; } } } param edge private handleEdgeOfLabel ( edge ) { const id = edge . SourceNode . Id as string ; const label = edge . TargetNode . Value as string ; const propertyNode = this . propertyNodes . get ( id ) ; if ( propertyNode ) { return ; } const classNode = this . ensureClassNode ( id ) ; if ( ! classNode . label ) { classNode . label = label ; const constraintNode = this . constraintNodes . get ( label ) ; if ( constraintNode ) { classNode . constraint = constraintNode ; } } } param edge private handleEdgeOfDomain ( edge ) { const id = edge . SourceNode . Id as string ; const classId = edge . TargetNode . Id as string ; const propertyNode = this . ensurePropertyNode ( id ) ; const classNode = this . ensureClassNode ( classId ) ; if ( ! classNode . properties ) { classNode . properties = [ ] ; } classNode . properties . push ( propertyNode ) ; } param edge private handleEdgeOfRange ( edge ) { const id = edge . SourceNode . Id as string ; const classId = edge . TargetNode . Id as string ; const propertyNode = this . ensurePropertyNode ( id ) ; const classNode = this . ensureClassNode ( classId ) ; if ( ! propertyNode . range ) { propertyNode . range = [ ] ; } propertyNode . range . push ( classNode ) ; } param edge private handleEdgeOfSubClassOf ( edge ) { const id = edge . SourceNode . Id as string ; const baseId = edge . TargetNode . Id as string ; const classNode = this . ensureClassNode ( id ) ; const baseClassNode = this . ensureClassNode ( baseId ) ; if ( ! baseClassNode . children ) { baseClassNode . children = [ ] ; } baseClassNode . children . push ( classNode ) ; } param edge private handleEdgeOfComment ( edge ) { const id = edge . SourceNode . Id as string ; const comment = edge . TargetNode . Value as string ; const propertyNode = this . propertyNodes . get ( id ) ; if ( propertyNode ) { propertyNode . comment = comment ; } } param id private ensureClassNode ( id ) { let classNode = this . classNodes . get ( id ) ; if ( ! classNode ) { classNode = { id } ; const contextNode = this . contextNodes . get ( id ) ; if ( contextNode ) { classNode . label = contextNode . name ; const constraintNode = this . constraintNodes . get ( contextNode . name ) ; if ( constraintNode ) { classNode . constraint = constraintNode ; } } this . classNodes . set ( id , classNode ) ; } return classNode ; } param id private ensurePropertyNode ( id ) { let propertyNode = this . propertyNodes . get ( id ) ; if ( ! propertyNode ) { propertyNode = { id } ; const contextNode = this . contextNodes . get ( id ) ; if ( contextNode ) { propertyNode . label = contextNode . name ; propertyNode . isArray = contextNode . container === ContainerType . Array ; if ( contextNode . container === ContainerType . Language ) { const languageNode = this . ensureClassNode ( DigitalTwinConstants . LANGUAGE ) ; languageNode . label = DigitalTwinConstants . LANGUAGE ; propertyNode . range = [ languageNode ] ; } const constraintNode = this . constraintNodes . get ( contextNode . name ) ; if ( constraintNode ) { propertyNode . constraint = constraintNode ; } } this . propertyNodes . set ( id , propertyNode ) ; } return propertyNode ; } /**
     * adjust node to achieve special definitions
     */ private adjustNode ( ) { const stringNode = this . ensureClassNode ( ValueSchema . String ) ; this . buildReservedProperty ( DigitalTwinConstants . ID , stringNode ) ; this . markAbstractClass ( DigitalTwinConstants . SCHEMA_NODE ) ; this . markAbstractClass ( DigitalTwinConstants . UNIT_NODE ) ; this . handleInterfaceSchema ( stringNode ) ; } param id param classNode private buildReservedProperty ( id , classNode ) { const propertyNode = { id , range : [ classNode ] } ; const constraintNode = this . constraintNodes . get ( id ) ; if ( constraintNode ) { propertyNode . constraint = constraintNode ; } this . propertyNodes . set ( id , propertyNode ) ; } param name private markAbstractClass ( name ) { const classNode = this . classNodes . get ( this . getId ( name ) ) ; if ( classNode ) { classNode . isAbstract = true ; } } /**
     * update label and range of interfaceSchema property
     */ private handleInterfaceSchema ( classNode ) { const propertyNode = this . propertyNodes . get ( this . getId ( DigitalTwinConstants . INTERFACE_SCHEMA_NODE ) ) ; if ( propertyNode ) { propertyNode . label = DigitalTwinConstants . SCHEMA ; if ( propertyNode . range ) { propertyNode . range . push ( classNode ) ; propertyNode . constraint = this . constraintNodes . get ( DigitalTwinConstants . ID ) ; } } } /**
     * expand properties from base class node
     */ private expandProperties ( ) { let classNode = this . classNodes . get ( this . getId ( DigitalTwinConstants . BASE_CLASS ) ) ; if ( ! classNode ) { return ; } const queue = [ ] ; while ( classNode ) { if ( classNode . children ) { for ( const child of classNode . children ) { if ( child . enums ) { continue ; } if ( classNode . properties ) { if ( ! child . properties ) { child . properties = [ ] ; } child . properties . push ( ... classNode . properties ) ; } queue . push ( child ) ; } } classNode = queue . shift ( ) ; } } /**
     * build entry node of DigitalTwin graph
     */ private buildEntryNode ( ) { const interfaceNode = this . classNodes . get ( this . getId ( DigitalTwinConstants . INTERFACE_NODE ) ) ; const capabilityModelNode = this . classNodes . get ( this . getId ( DigitalTwinConstants . CAPABILITY_MODEL_NODE ) ) ; if ( interfaceNode && capabilityModelNode ) { const entryNode = { id : DigitalTwinConstants . ENTRY_NODE , range : [ interfaceNode , capabilityModelNode ] } ; this . propertyNodes . set ( entryNode . id , entryNode ) ; } } }