private static void GenerateInterfaces()

in OWL2DTDL/Program.cs [253:628]


        private static void GenerateInterfaces()
        {
            // Working graph
            Graph dtdlModel = new Graph();

            // A whole bunch of language definitions.
            // TODO Extract all of these (often reused) node definitions into statics.

            // RDF/OWL specs
            IUriNode rdfType = dtdlModel.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType));

            // DTDL classes
            IUriNode dtdl_Interface = dtdlModel.CreateUriNode(DTDL.Interface);
            IUriNode dtdl_Property = dtdlModel.CreateUriNode(DTDL.Property);
            IUriNode dtdl_Relationship = dtdlModel.CreateUriNode(DTDL.Relationship);
            IUriNode dtdl_Telemetry = dtdlModel.CreateUriNode(DTDL.Telemetry);
            IUriNode dtdl_Component = dtdlModel.CreateUriNode(DTDL.Component);
            IUriNode dtdl_Enum = dtdlModel.CreateUriNode(DTDL.Enum);
            IUriNode dtdl_EnumValue = dtdlModel.CreateUriNode(DTDL.EnumValue);
            IUriNode dtdl_Map = dtdlModel.CreateUriNode(DTDL.Map);

            // DTDL properties
            IUriNode dtdl_contents = dtdlModel.CreateUriNode(DTDL.contents);
            IUriNode dtdl_name = dtdlModel.CreateUriNode(DTDL.name);
            IUriNode dtdl_displayName = dtdlModel.CreateUriNode(DTDL.displayName);
            IUriNode dtdl_properties = dtdlModel.CreateUriNode(DTDL.properties);
            IUriNode dtdl_mapKey = dtdlModel.CreateUriNode(DTDL.mapKey);
            IUriNode dtdl_mapValue = dtdlModel.CreateUriNode(DTDL.mapValue);

            IUriNode dtdl_extends = dtdlModel.CreateUriNode(DTDL.extends);
            IUriNode dtdl_maxMultiplicity = dtdlModel.CreateUriNode(DTDL.maxMultiplicity);
            IUriNode dtdl_minMultiplicity = dtdlModel.CreateUriNode(DTDL.minMultiplicity);
            IUriNode dtdl_target = dtdlModel.CreateUriNode(DTDL.target);
            IUriNode dtdl_schema = dtdlModel.CreateUriNode(DTDL.schema);
            IUriNode dtdl_valueSchema = dtdlModel.CreateUriNode(DTDL.valueSchema);
            IUriNode dtdl_writable = dtdlModel.CreateUriNode(DTDL.writable);

            IUriNode dtdl_enumValue = dtdlModel.CreateUriNode(DTDL.enumValue);
            IUriNode dtdl_enumValues = dtdlModel.CreateUriNode(DTDL.enumValues);

            // Used to sort JObjects for merged array output, if selected as runtime option
            Dictionary<JObject, int> interfaceDepths = new Dictionary<JObject, int>();

            Console.WriteLine();
            Console.WriteLine("Generating DTDL Interface declarations: ");

            // Start looping through named, non-deprecated, non-ignored classes
            foreach (OntologyClass oClass in _ontologyGraph.OwlClasses.Where(oClass => oClass.IsNamed() && !oClass.IsDeprecated() && !IsIgnored(oClass) && !oClass.SuperClasses.Any(parent => parent.IsNamed() && IsIgnored(parent))))
            {
                // Create Interface
                string interfaceDtmi = GetDTMI(oClass);
                Console.WriteLine($"\t* {interfaceDtmi}");
                IUriNode interfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(interfaceDtmi));
                dtdlModel.Assert(new Triple(interfaceNode, rdfType, dtdl_Interface));

                // If there are rdfs:labels, use them for DTDL displayName
                if (oClass.Label.Any()) {
                    dtdlModel.Assert(GetDtdlDisplayNameTriples(oClass, interfaceNode));
                 }

                // If there are rdfs:comments, generate and assert DTDL description triples from them
                if (oClass.Comment.Any())
                {
                    dtdlModel.Assert(GetDtdlDescriptionTriples(oClass, interfaceNode));
                }

                // If the class is a top-level class, i.e., it has zero superclasses that are
                // not owl:Thing, implement a generic 'name' property and externalIds alignment property
                IEnumerable<OntologyClass> namedDirectSuperClasses = oClass.DirectSuperClasses.Where(superClass => superClass.IsNamed());
                if (!namedDirectSuperClasses.Where(superClass => !superClass.IsOwlThing()).Any())
                {
                    // Create name property node and name
                    IBlankNode namePropertyNode = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, namePropertyNode));
                    dtdlModel.Assert(new Triple(namePropertyNode, rdfType, dtdl_Property));
                    ILiteralNode namePropertyDtdlNameNode = dtdlModel.CreateLiteralNode("name");
                    dtdlModel.Assert(new Triple(namePropertyNode, dtdl_name, namePropertyDtdlNameNode));
                    // Name is string
                    IUriNode namePropertySchemaNode = dtdlModel.CreateUriNode(DTDL._string);
                    dtdlModel.Assert(new Triple(namePropertyNode, dtdl_schema, namePropertySchemaNode));
                    // Display name (of name property) is hardcoded to "name".
                    ILiteralNode namePropertyDisplayNameNode = dtdlModel.CreateLiteralNode("name", "en");
                    dtdlModel.Assert(new Triple(namePropertyNode, dtdl_displayName, namePropertyDisplayNameNode));
                    // Name is writeable
                    ILiteralNode namePropertyTrueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
                    dtdlModel.Assert(new Triple(namePropertyNode, dtdl_writable, namePropertyTrueNode));

                    // Create externalIds
                    IBlankNode externalIds = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, externalIds));
                    dtdlModel.Assert(new Triple(externalIds, rdfType, dtdl_Property));
                    ILiteralNode externalIdsDtdlName = dtdlModel.CreateLiteralNode("externalIds");
                    dtdlModel.Assert(new Triple(externalIds, dtdl_name, externalIdsDtdlName));
                    // External ids is map
                    IBlankNode schemaNode = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(schemaNode, rdfType, dtdl_Map));
                    // Map key
                    IBlankNode schemaMapKey = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(schemaNode, dtdl_mapKey, schemaMapKey));
                    ILiteralNode schemaMapKeyName = dtdlModel.CreateLiteralNode("externalIdName");
                    dtdlModel.Assert(new Triple(schemaMapKey, dtdl_name, schemaMapKeyName));
                    IUriNode schemaMapKeySchema = dtdlModel.CreateUriNode(DTDL._string);
                    dtdlModel.Assert(new Triple(schemaMapKey, dtdl_schema, schemaMapKeySchema));
                    // Map value
                    IBlankNode schemaMapValue = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(schemaNode, dtdl_mapValue, schemaMapValue));
                    ILiteralNode schemaMapValueName = dtdlModel.CreateLiteralNode("externalIdValue");
                    dtdlModel.Assert(new Triple(schemaMapValue, dtdl_name, schemaMapValueName));
                    IUriNode schemaMapValueSchema = dtdlModel.CreateUriNode(DTDL._string);
                    dtdlModel.Assert(new Triple(schemaMapValue, dtdl_schema, schemaMapValueSchema));
                    dtdlModel.Assert(new Triple(externalIds, dtdl_schema, schemaNode));
                    // Display name of external ids is hardcoded to "External IDs".
                    ILiteralNode externalIdsDisplayName = dtdlModel.CreateLiteralNode("External IDs", "en");
                    dtdlModel.Assert(new Triple(externalIds, dtdl_displayName, externalIdsDisplayName));
                    // Name is writeable
                    ILiteralNode externalIdsTrue = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
                    dtdlModel.Assert(new Triple(externalIds, dtdl_writable, externalIdsTrue));

                    // Create customTags
                    IBlankNode customTags = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, customTags));
                    dtdlModel.Assert(new Triple(customTags, rdfType, dtdl_Property));
                    ILiteralNode customTagsDtdlName = dtdlModel.CreateLiteralNode("customTags");
                    dtdlModel.Assert(new Triple(customTags, dtdl_name, customTagsDtdlName));
                    // Custom tags is map
                    IBlankNode customTagsSchemaNode = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(customTagsSchemaNode, rdfType, dtdl_Map));
                    // Map key
                    IBlankNode customTagsSchemaMapKey = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(customTagsSchemaNode, dtdl_mapKey, customTagsSchemaMapKey));
                    ILiteralNode customTagsSchemaMapKeyName = dtdlModel.CreateLiteralNode("tagName");
                    dtdlModel.Assert(new Triple(customTagsSchemaMapKey, dtdl_name, customTagsSchemaMapKeyName));
                    IUriNode customTagsSchemaMapKeySchema = dtdlModel.CreateUriNode(DTDL._string);
                    dtdlModel.Assert(new Triple(customTagsSchemaMapKey, dtdl_schema, customTagsSchemaMapKeySchema));
                    // Map value
                    IBlankNode customTagsSchemaMapValue = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(customTagsSchemaNode, dtdl_mapValue, customTagsSchemaMapValue));
                    ILiteralNode customTagsSchemaMapValueName = dtdlModel.CreateLiteralNode("tagValue");
                    dtdlModel.Assert(new Triple(customTagsSchemaMapValue, dtdl_name, customTagsSchemaMapValueName));
                    IUriNode customTagsSchemaMapValueSchema = dtdlModel.CreateUriNode(DTDL._string);
                    dtdlModel.Assert(new Triple(customTagsSchemaMapValue, dtdl_schema, customTagsSchemaMapValueSchema));
                    dtdlModel.Assert(new Triple(customTags, dtdl_schema, customTagsSchemaNode));
                    // Display name of custom tags is hardcoded to "Custom Tags".
                    ILiteralNode customTagsDisplayName = dtdlModel.CreateLiteralNode("Custom Tags", "en");
                    dtdlModel.Assert(new Triple(customTags, dtdl_displayName, customTagsDisplayName));
                    // Name is writeable
                    ILiteralNode customTagsTrue = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
                    dtdlModel.Assert(new Triple(customTags, dtdl_writable, customTagsTrue));
                }

                // If the class has direct superclasses, implement DTDL extends (for at most two, see limitation in DTDL spec)
                IEnumerable<OntologyClass> namedSuperClasses = oClass.DirectSuperClasses.Where(superClass => superClass.IsNamed() && !superClass.IsOwlThing() && !superClass.IsDeprecated());
                if (namedSuperClasses.Any())
                {
                    foreach (OntologyClass superClass in namedSuperClasses.Take(2))
                    {
                        // Only include non-deprecated subclass relations
                        IUriNode rdfsSubClassOf = _ontologyGraph.CreateUriNode(RDFS.subClassOf);
                        if (PropertyAssertionIsDeprecated(oClass.GetUriNode(), rdfsSubClassOf, superClass.GetUriNode()))
                        {
                            continue;
                        }
                        string superInterfaceDTMI = GetDTMI(superClass);
                        IUriNode superInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(superInterfaceDTMI));
                        dtdlModel.Assert(new Triple(interfaceNode, dtdl_extends, superInterfaceNode));
                    }
                }

                // For any outgoing object properties from the class to other classes, create corresponding DTDL Relationships
                foreach (Relationship relationship in oClass.GetRelationships().Where(relationship => relationship.Property.IsObjectProperty() && 
                !relationship.Property.IsDeprecated() && 
                !relationship.Target.IsDeprecated() &&
                !IsIgnored(relationship.Target) &&
                !relationship.Target.SuperClasses.Any(parent => parent.IsNamed() && IsIgnored(parent)) && 
                !IsIgnored(relationship.Property)))
                {
                    OntologyProperty oProperty = relationship.Property;

                    // Only include relationships with valid names
                    string relationshipName = string.Concat(oProperty.GetLocalName().Take(64));
                    if (!IsCompliantDtdlName(relationshipName))
                    {
                        Console.Error.WriteLine($"Unable to generate Relationship '{relationshipName}' on Interface '{interfaceDtmi}'; underlying property name does not adhere to DTDL regex.");
                        continue;
                    }

                    // If we have seen this relationship linked from a subclass, skip it; DTDL does not allow subinterfaces
                    // to specialise properties/relationships
                    if (PropertyIsDefinedOnChildClass(oClass, oProperty))
                    {
                        continue;
                    }

                    // Define the Relationship and its name
                    IBlankNode relationshipNode = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, relationshipNode));
                    
                    ILiteralNode relationshipNameNode = dtdlModel.CreateLiteralNode(relationshipName);
                    dtdlModel.Assert(new Triple(relationshipNode, dtdl_name, relationshipNameNode));

                    // If there are rdfs:labels, use them for DTDL displayName
                    if (oProperty.Label.Any())
                    {
                        dtdlModel.Assert(GetDtdlDisplayNameTriples(oProperty, relationshipNode));
                    }

                    // If there are rdfs:comments, generate and assert DTDL description triples from them
                    if (oProperty.Comment.Any())
                    {
                        dtdlModel.Assert(GetDtdlDescriptionTriples(oProperty, relationshipNode));
                    }

                    // If this relationship is annotated with the dtdlType annotation,
                    // and that type is a component, then assert the relationship is a component, set its schema, and go to next relationship.
                    if (!relationship.Target.IsOwlThing() && relationship.Target.IsNamed() && relationship.Target.DtdlTypes().Contains("component"))
                    {
                        dtdlModel.Assert(new Triple(relationshipNode, rdfType, dtdl_Component));
                        string schemaDtmi = GetDTMI(relationship.Target);
                        IUriNode schemaInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(schemaDtmi));
                        dtdlModel.Assert(new Triple(relationshipNode, dtdl_schema, schemaInterfaceNode));
                        continue;
                    }

                    // Assert target (if defined)
                    if (!relationship.Target.IsOwlThing())
                    {
                        string targetDtmi = GetDTMI(relationship.Target);
                        IUriNode targetInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(targetDtmi));
                        dtdlModel.Assert(new Triple(relationshipNode, dtdl_target, targetInterfaceNode));
                    }

                    // Assert that this is indeed a Relationship
                    dtdlModel.Assert(new Triple(relationshipNode, rdfType, dtdl_Relationship));

                    // Assert min multiplicity. Hardcoded: per DTDL v2 spec: "For this release, minMultiplicity must always be 0"
                    if (relationship.MinimumCount.HasValue)
                    {   
                        ILiteralNode minMultiplicityNode = dtdlModel.CreateLiteralNode("0", UriFactory.Create(XmlSpecsHelper.XmlSchemaDataTypeInteger));
                        dtdlModel.Assert(new Triple(relationshipNode, dtdl_minMultiplicity, minMultiplicityNode));
                    }

                    // Assert max multiplicity
                    if (relationship.MaximumCount.HasValue)
                    {
                        ILiteralNode maxMultiplicityNode = dtdlModel.CreateLiteralNode(relationship.MaximumCount.Value.ToString(), UriFactory.Create(XmlSpecsHelper.XmlSchemaDataTypeInteger));
                        dtdlModel.Assert(new Triple(relationshipNode, dtdl_maxMultiplicity, maxMultiplicityNode));
                    }

                    // Extract annotations on object properties -- these become DTDL Relationship Properties
                    // We only support annotations w/ singleton ranges (though those singletons may be enumerations)
                    IEnumerable<OntologyProperty> annotationsOnRelationship = _ontologyGraph.OwlAnnotationProperties
                        .Where(annotationProp => annotationProp.Ranges.Count() == 1)
                        .Where(annotationProp => annotationProp.Domains.Select(annotationDomain => annotationDomain.Resource).Contains(oProperty.Resource));
                    foreach (OntologyProperty annotationProperty in annotationsOnRelationship) {

                        // Define nested Property and its name
                        IBlankNode nestedPropertyNode = dtdlModel.CreateBlankNode();
                        dtdlModel.Assert(new Triple(nestedPropertyNode, rdfType, dtdl_Property));
                        dtdlModel.Assert(new Triple(relationshipNode, dtdl_properties, nestedPropertyNode));
                        string nestedPropertyName = string.Concat(annotationProperty.GetLocalName().Take(64));
                        ILiteralNode nestedPropertyNameNode = dtdlModel.CreateLiteralNode(nestedPropertyName);
                        dtdlModel.Assert(new Triple(nestedPropertyNode, dtdl_name, nestedPropertyNameNode));

                        // Assert that the nested property is writable
                        ILiteralNode trueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
                        dtdlModel.Assert(new Triple(nestedPropertyNode, dtdl_writable, trueNode));

                        // If there are rdfs:labels, use them for DTDL displayName
                        if (annotationProperty.Label.Any())
                        {
                            dtdlModel.Assert(GetDtdlDisplayNameTriples(annotationProperty, nestedPropertyNode));
                        }

                        // If there are rdfs:comments, generate and assert DTDL description triples from them
                        if (oProperty.Comment.Any())
                        {
                            dtdlModel.Assert(GetDtdlDescriptionTriples(annotationProperty, nestedPropertyNode));
                        }

                        // Set range
                        OntologyClass annotationPropertyRange = annotationProperty.Ranges.First();
                        HashSet<Triple> schemaTriples = GetDtdlTriplesForRange(annotationPropertyRange, nestedPropertyNode);
                        dtdlModel.Assert(schemaTriples);
                    }
                }

                // For any outgoing data properties from the class to datatypes, create corresponding DTDL Properties
                foreach (Relationship relationship in oClass.GetRelationships().Where(relationship => relationship.Property.IsDataProperty() && !relationship.Property.IsDeprecated() && !IsIgnored(relationship.Property)))
                {
                    OntologyProperty oProperty = relationship.Property;

                    // Only include properties with valid names
                    string propertyName = string.Concat(oProperty.GetLocalName().Take(64));
                    if (!IsCompliantDtdlName(propertyName))
                    {
                        Console.Error.WriteLine($"Unable to generate Property '{propertyName}' on Interface '{interfaceDtmi}'; underlying property name does not adhere to DTDL regex.");
                        continue;
                    }

                    // If we have seen this relationship linked from a subclass, skip it; DTDL does not allow subinterfaces
                    // to specialise properties/relationships
                    if (PropertyIsDefinedOnChildClass(oClass, oProperty))
                    {
                        continue;
                    }

                    // Create Property node and name
                    IBlankNode propertyNode = dtdlModel.CreateBlankNode();
                    dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, propertyNode));
                    dtdlModel.Assert(new Triple(propertyNode, rdfType, dtdl_Property));
                    ILiteralNode propertyNameNode = dtdlModel.CreateLiteralNode(propertyName);
                    dtdlModel.Assert(new Triple(propertyNode, dtdl_name, propertyNameNode));

                    // Assert that the property is writable
                    ILiteralNode trueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
                    dtdlModel.Assert(new Triple(propertyNode, dtdl_writable, trueNode));

                    // Extract and populate schema
                    HashSet<Triple> propertySchemaTriples = GetDtdlTriplesForRange(relationship.Target, propertyNode);
                    dtdlModel.Assert(propertySchemaTriples);

                    // If there are rdfs:labels, use them for DTDL displayName
                    if (oProperty.Label.Any())
                    {
                        dtdlModel.Assert(GetDtdlDisplayNameTriples(oProperty, propertyNode));
                    }

                    // If there are rdfs:comments, generate and assert DTDL description triples from them
                    if (oProperty.Comment.Any())
                    {
                        dtdlModel.Assert(GetDtdlDescriptionTriples(oProperty, propertyNode));
                    }
                }

                // Write JSON-LD to target file.
                JObject modelAsJsonLd = ToJsonLd(dtdlModel);
                interfaceDepths.Add(modelAsJsonLd, oClass.Depth());

                if (!_mergedOutput) {
                    // Create model output directory based on output path and longest parent path
                    // I.e., we put multi-inheritance classes as far down as possible in their respective hierarchy
                    List<string> parentDirectories = oClass.LongestParentPathToOwlThing();
                    string modelPath = string.Join("/", parentDirectories);
                    string modelOutputPath = $"{_outputPath}/{modelPath}/";
                    // If the class has subclasses, place it with them
                    if (oClass.DirectSubClasses.Any()) { modelOutputPath += $"{oClass.GetLocalName()}/"; }
                    Directory.CreateDirectory(modelOutputPath);
                    string outputFileName = modelOutputPath + oClass.GetLocalName() + ".json";
                    using (StreamWriter file = File.CreateText(outputFileName))
                    using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
                    {
                        modelAsJsonLd.WriteTo(writer);
                    }
                }

                // Clear the working graph for next iteration
                dtdlModel.Clear();
            }

            // Sort and generate merged output file
            // TODO remember what I actually did here and document it
            if (_mergedOutput)
            {
                List<KeyValuePair<JObject, int>> interfacesAndDepths = interfaceDepths.ToList();
                interfacesAndDepths.Sort((pair1, pair2) => pair1.Value.CompareTo(pair2.Value));
                IEnumerable<JObject> interfaces = interfacesAndDepths.Select(pair => pair.Key);
                JArray interfaceArray = new JArray(interfaces);
                Directory.CreateDirectory(_outputPath);
                string outputFileName = _outputPath + "FullBuildingModels.json";
                using (StreamWriter file = File.CreateText(outputFileName))
                using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
                {
                    interfaceArray.WriteTo(writer);
                }
            }
        }