diff --git a/NodeSetToAML.cs b/NodeSetToAML.cs index ab9ddd3..855b156 100644 --- a/NodeSetToAML.cs +++ b/NodeSetToAML.cs @@ -718,9 +718,9 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str ListOfPrefix = ListOf; string path = BuildLibraryReference(ATLPrefix, sURI, ListOfPrefix + sUADataType); var ob = m_cAEXDocument.FindByPath(path); - var at = ob as AttributeFamilyType; - AttributeType a = seq[name]; //find the existing attribute with the name - if (a == null) + var sourceAttribute = ob as AttributeFamilyType; + AttributeType desiredAttribute = seq[name]; //find the existing attribute with the name + if (desiredAttribute == null) { if (bListOf == false && val.TypeInfo != null) // look for reasons not to add the attribute because missing == default value { @@ -729,10 +729,10 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if (name == "Symmetric" && val == false) return null; } - a = seq.Append(name); // not found so create a new one + desiredAttribute = seq.Append(name); // not found so create a new one } - a.RecreateAttributeInstance(at); + RecreateAttributeInstance(sourceAttribute, desiredAttribute); if (val.TypeInfo != null) { @@ -760,7 +760,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if( refDataType == "LocalizedText" && referenceName == "EnumStrings" ) { addElements = false; - AttributeTypeType attributeType = a as AttributeTypeType; + AttributeTypeType attributeType = desiredAttribute as AttributeTypeType; if( attributeType != null ) { AttributeValueRequirementType stringValueRequirement = new AttributeValueRequirementType( @@ -790,7 +790,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if ( refDataType == "EnumValueType" && referenceName == "EnumValues" ) { - AttributeTypeType attributeType = a as AttributeTypeType; + AttributeTypeType attributeType = desiredAttribute as AttributeTypeType; if( attributeType != null ) { AttributeValueRequirementType stringValueRequirement = new AttributeValueRequirementType( @@ -841,7 +841,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str bool elementListOf = elementVariant.TypeInfo.ValueRank >= ValueRanks.OneDimension; - AddModifyAttribute(a.Attribute, index.ToString(), typeId, elementVariant, elementListOf); + AddModifyAttribute(desiredAttribute.Attribute, index.ToString(), typeId, elementVariant, elementListOf); } } } @@ -859,7 +859,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str { Variant elementVariant = new Variant( valueAsList[ index ] ); bool elementListOf = elementVariant.TypeInfo.ValueRank >= ValueRanks.OneDimension; - AddModifyAttribute( a.Attribute, index.ToString(), refDataType, + AddModifyAttribute( desiredAttribute.Attribute, index.ToString(), refDataType, elementVariant, elementListOf, sURI ); } } @@ -890,7 +890,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str case BuiltInType.UInteger: case BuiltInType.Enumeration: { - a.AttributeValue = val; + desiredAttribute.AttributeValue = val; break; } @@ -900,7 +900,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if ( bytes != null ) { string encoded = Convert.ToBase64String( bytes, 0, bytes.Length ); - a.AttributeValue = new Variant( encoded.ToString() ); + desiredAttribute.AttributeValue = new Variant( encoded.ToString() ); } break; @@ -923,8 +923,8 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if ( nodeId != null ) { - a.AttributeValue = nodeId; - AttributeType rootNodeId = a.Attribute[ "RootNodeId" ]; + desiredAttribute.AttributeValue = nodeId; + AttributeType rootNodeId = desiredAttribute.Attribute[ "RootNodeId" ]; if ( rootNodeId != null ) { AttributeType namespaceUri = rootNodeId.Attribute[ "NamespaceUri" ]; @@ -970,10 +970,10 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str } } } - a.DefaultValue = null; - a.Value = null; + desiredAttribute.DefaultValue = null; + desiredAttribute.Value = null; - MinimizeNodeId( a ); + MinimizeNodeId( desiredAttribute ); } if ( expandedNodeId != null ) @@ -983,7 +983,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str if( expandedNodeId.ServerIndex < m_modelManager.ModelNamespaceIndexes.Count ) { string serverUri = m_modelManager.ModelNamespaceIndexes[ (int)expandedNodeId.ServerIndex ].NamespaceUri; - AttributeType serverInstanceUri = a.Attribute[ "ServerInstanceUri" ]; + AttributeType serverInstanceUri = desiredAttribute.Attribute[ "ServerInstanceUri" ]; if ( serverInstanceUri != null ) { serverInstanceUri.Value = serverUri; @@ -998,24 +998,24 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str case BuiltInType.StatusCode: { StatusCode statusCode = (StatusCode)val.Value; - a.AttributeValue = statusCode.Code; + desiredAttribute.AttributeValue = statusCode.Code; break; } case BuiltInType.QualifiedName: { - a.AttributeValue = val; + desiredAttribute.AttributeValue = val; QualifiedName qualifiedName = val.Value as QualifiedName; if( qualifiedName != null ) { - AttributeType uri = a.Attribute[ "NamespaceUri" ]; + AttributeType uri = desiredAttribute.Attribute[ "NamespaceUri" ]; uri.Value = m_modelManager.ModelNamespaceIndexes[ qualifiedName.NamespaceIndex ].NamespaceUri; - AttributeType nameAttribute = a.Attribute[ "Name" ]; + AttributeType nameAttribute = desiredAttribute.Attribute[ "Name" ]; nameAttribute.Value = qualifiedName.Name; - a.DefaultValue = null; - a.Value = null; + desiredAttribute.DefaultValue = null; + desiredAttribute.Value = null; } break; @@ -1026,14 +1026,14 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str Opc.Ua.LocalizedText localizedText = (Opc.Ua.LocalizedText)val.Value; if( localizedText != null && localizedText.Text != null ) { - a.AttributeValue = localizedText.Text; + desiredAttribute.AttributeValue = localizedText.Text; if ( !string.IsNullOrEmpty( localizedText.Locale ) ) { CAEXObject findObject = m_cAEXDocument.FindByPath( "AutomationMLBaseAttributeTypeLib/LocalizedAttribute" ); AttributeFamilyType localizedAttributeFamilyType = findObject as AttributeFamilyType; - AttributeType textAttribute = a.Attribute.Append( localizedText.Locale ); + AttributeType textAttribute = desiredAttribute.Attribute.Append( localizedText.Locale ); textAttribute.RecreateAttributeInstance( localizedAttributeFamilyType ); textAttribute.Value = localizedText.Text; } @@ -1047,7 +1047,7 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str ExtensionObject extensionObject = val.Value as ExtensionObject; if ( extensionObject != null && extensionObject.Body != null ) { - AddModifyExtensionObject( a, extensionObject ); + AddModifyExtensionObject( desiredAttribute, extensionObject ); } break; @@ -1063,21 +1063,21 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str bool actualListOf = actualValue.TypeInfo.ValueRank >= ValueRanks.OneDimension; - AddModifyAttribute( a.Attribute, "Value", dataTypeNodeId, actualValue, actualListOf ); - AddModifyAttribute( a.Attribute, "StatusCode", "StatusCode", + AddModifyAttribute( desiredAttribute.Attribute, "Value", dataTypeNodeId, actualValue, actualListOf ); + AddModifyAttribute( desiredAttribute.Attribute, "StatusCode", "StatusCode", dataValue.StatusCode ); - AddModifyAttribute( a.Attribute, "SourceTimestamp", "DateTime", + AddModifyAttribute( desiredAttribute.Attribute, "SourceTimestamp", "DateTime", dataValue.SourceTimestamp ); if( dataValue.SourcePicoseconds > 0 ) { - AddModifyAttribute( a.Attribute, "SourcePicoseconds", "UInt16", + AddModifyAttribute( desiredAttribute.Attribute, "SourcePicoseconds", "UInt16", dataValue.SourcePicoseconds ); } - AddModifyAttribute( a.Attribute, "ServerTimestamp", "DateTime", + AddModifyAttribute( desiredAttribute.Attribute, "ServerTimestamp", "DateTime", dataValue.ServerTimestamp ); if ( dataValue.ServerPicoseconds > 0 ) { - AddModifyAttribute( a.Attribute, "ServerPicoseconds", "UInt16", + AddModifyAttribute( desiredAttribute.Attribute, "ServerPicoseconds", "UInt16", dataValue.ServerPicoseconds ); } } @@ -1091,14 +1091,14 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str bool internalListOf = internalVariant.TypeInfo.ValueRank >= ValueRanks.OneDimension; - AddModifyAttribute( a.Attribute, "Value", dataTypeNodeId, internalVariant, internalListOf ); + AddModifyAttribute( desiredAttribute.Attribute, "Value", dataTypeNodeId, internalVariant, internalListOf ); break; } case BuiltInType.DiagnosticInfo: { - AddModifyAttributeObject( a, val.Value ); + AddModifyAttributeObject( desiredAttribute, val.Value ); break; } @@ -1113,12 +1113,12 @@ private AttributeType AddModifyAttribute(AttributeSequence seq, string name, str // this is specifically the variant case NodeId dataTypeFromBase = new NodeId( (uint)val.TypeInfo.BuiltInType ); string variantDataType = GetAttributeDataType( dataTypeFromBase ); - a.AttributeDataType = variantDataType; + desiredAttribute.AttributeDataType = variantDataType; } } } - return a; + return desiredAttribute; } private bool MinimizeNodeId( AttributeType nodeIdAttribute ) @@ -2756,12 +2756,7 @@ private void ProcessReferenceType(ref InterfaceClassLibType icl, NodeId nodeId) } } - // Only sets it if it is true - - // It doesn't matter if 'References' is set x times, - // it would take more time to look it up each time - RemoveUnwantedNodeIdAttribute( - OverrideBooleanAttribute( - added.Attribute, "IsAbstract", refnode.IsAbstract, typeOnly: true)); + AddIsAbstractAttribute(added.Attribute, refnode.IsAbstract); // override any attribute values if (BaseNodeId != null) @@ -2819,18 +2814,78 @@ private void ProcessReferenceType(ref InterfaceClassLibType icl, NodeId nodeId) } + /// + /// Recreate the attribute instance to remove any inherited attributes that are not part of the base type. + /// These are automatically added by the Aml Engine. This causes a huge problem with IsAbstract and NodeId + /// + /// + /// + /// + private AttributeType RecreateAttributeInstance( + AttributeFamilyType sourceAttribute, AttributeType newAttribute ) + { + newAttribute.RecreateAttributeInstance(sourceAttribute); + + List inheritedAttributes = new List(); + + foreach(AttributeType attribute in newAttribute.Attribute) + { + if (sourceAttribute.Attribute[attribute.Name] == null) + { + inheritedAttributes.Add(attribute); + } + } + + foreach(AttributeType attribute in inheritedAttributes) + { + newAttribute.Attribute.RemoveElement(attribute); + } + + return newAttribute; + } + private void RemoveUnwantedAttribute(AttributeType attributeType, string attributeName) { if (attributeType != null) { - AttributeType unwantedAttribute = attributeType.Attribute[attributeName]; + RemoveUnwantedAttribute(attributeType.Attribute, attributeName); + } + } + + private void RemoveUnwantedAttribute(AttributeSequence attributes, string attributeName) + { + if (attributes != null) + { + AttributeType unwantedAttribute = attributes[attributeName]; if (unwantedAttribute != null) { - attributeType.Attribute.RemoveElement(unwantedAttribute); + attributes.RemoveElement(unwantedAttribute); } } } + private void AddIsAbstractAttribute( AttributeSequence sequence + , bool isAbstract ) + { + if (sequence != null ) + { + AttributeType isAbstractAttribute = sequence["IsAbstract"]; + if ( isAbstractAttribute != null && !isAbstract ) + { + sequence.RemoveElement(isAbstractAttribute); + } + + if ( isAbstract ) + { + isAbstractAttribute = OverrideBooleanAttribute(sequence, "IsAbstract", true, typeOnly: true); + // Now, because of the way the library recreates attributes, it will come back with nodeid + // due to adding inherited Attributes. + RemoveUnwantedAttribute(isAbstractAttribute.Attribute, "NodeId"); + } + } + } + + private void RemoveUnwantedNodeIdAttribute(AttributeType attribute) { RemoveUnwantedAttribute(attribute, "NodeId"); @@ -3120,13 +3175,21 @@ private void AddAttributeData( AttributeFamilyType attribute, UANode uaNode ) MinimizeNodeId( nodeIdAttribute ); + UADataType dataType = uaNode as UADataType; + if (dataType != null && dataType.IsAbstract) + { + AttributeType isAbstractAttribute = AddModifyAttribute( + attribute.Attribute, "IsAbstract", "Boolean", true); + isAbstractAttribute.AdditionalInformation.Append(OpcUaTypeOnly); + } + nodeIdAttribute.AdditionalInformation.Append( OpcUaTypeOnly ); } private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode uaNode ) { - if( m_modelManager.IsTypeOf( uaNode.DecodedNodeId, structureNode.DecodedNodeId ) ) + if ( m_modelManager.IsTypeOf( uaNode.DecodedNodeId, structureNode.DecodedNodeId ) ) { attribute.AttributeDataType = ""; NodeSet.UADataType uaDataType = uaNode as NodeSet.UADataType; @@ -3156,7 +3219,7 @@ private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode structureFieldAttribute = new AttributeType( new System.Xml.Linq.XElement( defaultNS + "Attribute" ) ); - structureFieldAttribute.RecreateAttributeInstance( structureFieldDefinition as AttributeFamilyType ); + RecreateAttributeInstance( structureFieldDefinition , structureFieldAttribute); structureFieldAttribute.Name = "StructureFieldDefinition"; structureFieldAttribute.AdditionalInformation.Append( OpcUaTypeOnly ); @@ -3418,6 +3481,7 @@ private AttributeFamilyType ProcessDataType(NodeSet.UANode node) var att = added as AttributeTypeType; added.Name = node.DecodedBrowseName.Name; added.ID = AmlIDFromNodeId(node.DecodedNodeId); + m_atl_temp.AttributeType.Insert(added); added.AttributeDataType = GetAttributeDataType(node.DecodedNodeId); @@ -3552,6 +3616,8 @@ InternalElementType RecursiveAddModifyInstance(ref T parent, UANode toAdd, bo ie.Name = toAdd.DecodedBrowseName.Name; AddBaseNodeClassAttributes(ie.Attribute, toAdd); + RemoveUnwantedAttribute(ie.Attribute, "IsAbstract"); + // set the values to match the values in the nodeset if (toAdd.NodeClass == NodeClass.Variable) { diff --git a/SystemTest/IsAbstract.cs b/SystemTest/IsAbstract.cs index be2e99a..be93b5d 100644 --- a/SystemTest/IsAbstract.cs +++ b/SystemTest/IsAbstract.cs @@ -18,7 +18,8 @@ public class IsAbstract // Test reads the nodeset file, finds everything that should be abstract, // Then walks the Amlx, and verifies both that the attribute is properly set, and properly not set const string NodeSetFile = "Modified.Opc.Ua.NodeSet2.xml"; - + const string NodeSetFileContainer = NodeSetFile + ".amlx"; + CAEXDocument m_document = null; Dictionary AbstractNodeIds = new Dictionary(); @@ -150,6 +151,83 @@ public void TestAllIsAbstract() " shouldNotBeAbstract errors - check ShouldNotBeAbstract.txt" ); } + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow (TestHelper.Uris.Test, 6182u, DisplayName = "StringNodeId has ExpandedNodeId Value")] + [DataRow(TestHelper.Uris.Root, 2994u, DisplayName = "Auditing has Boolean Value")] + public void TestInstanceIsAbstract(TestHelper.Uris uri, uint nodeId ) + { + SystemUnitClassType element = GetObject(uri, nodeId) as SystemUnitClassType; + Assert.IsNull(element.Attribute["IsAbstract"], "Instances should not have IsAbstract"); + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow(TestHelper.Uris.Root, 62u, false, DisplayName = "BaseVariableType")] + [DataRow(TestHelper.Uris.Root, 58u, true, DisplayName = "BaseObjectType")] + public void TestSystemUnitClassIsAbstract(TestHelper.Uris uri, uint nodeId, bool isNull) + { + SystemUnitClassType element = GetObject(uri, nodeId) as SystemUnitClassType; + AttributeType isAbstract = element.Attribute["IsAbstract"]; + if (isNull) + { + Assert.IsNull(isAbstract); + } + else + { + Assert.IsNotNull(isAbstract); + Assert.IsNotNull(isAbstract.Value); + Assert.AreEqual("true", isAbstract.Value); + Assert.AreEqual(0, isAbstract.Attribute.Count); + } + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow(TestHelper.Uris.Root, 24u, false, DisplayName = "BaseDataType")] + [DataRow(TestHelper.Uris.Root, 1u, true, DisplayName = "Boolean")] + public void TestAttributeIsAbstract(TestHelper.Uris uri, uint nodeId, bool isNull) + { + AttributeFamilyType element = GetObject(uri, nodeId) as AttributeFamilyType; + AttributeType isAbstract = element.Attribute["IsAbstract"]; + if (isNull) + { + Assert.IsNull(isAbstract); + } + else + { + Assert.IsNotNull(isAbstract); + Assert.IsNotNull(isAbstract.Value); + Assert.AreEqual("true", isAbstract.Value); + Assert.AreEqual(0, isAbstract.Attribute.Count); + } + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow(false, DisplayName = "Aggregates")] + [DataRow(true, DisplayName = "Inverse")] + public void TestInterfaceIsAbstract(bool isNull) + { + InterfaceFamilyType element = GetObject(TestHelper.Uris.Root, 44, "f") as InterfaceFamilyType; + AttributeSequence attributes = element.Attribute; + if ( isNull ) + { + InterfaceFamilyType inverse = element.InterfaceClass["AggregatedBy"]; + Assert.IsNotNull(inverse); + attributes = inverse.Attribute; + } + + AttributeType isAbstract = attributes["IsAbstract"]; + if (isNull) + { + Assert.IsNull(isAbstract); + } + else + { + Assert.IsNotNull(isAbstract); + Assert.IsNotNull(isAbstract.Value); + Assert.AreEqual("true", isAbstract.Value); + Assert.AreEqual(0, isAbstract.Attribute.Count); + } + } + public void WriteTestFile( DirectoryInfo outputDirectory, string fileName, List output) { if( output.Count == 0 ) @@ -336,14 +414,25 @@ public int ExtractNodeId( string id ) return numeric; } - private CAEXDocument GetDocument() + public CAEXObject GetObject( TestHelper.Uris uri, uint nodeId, string prefix = "" ) { - if( m_document == null ) - { - m_document = TestHelper.GetReadOnlyDocument( NodeSetFile + ".amlx" ); - } - Assert.IsNotNull( m_document, "Unable to retrieve Document" ); - return m_document; + CAEXDocument document = TestHelper.GetReadOnlyDocument("TestAml.xml.amlx"); + string amlNodeId = TestHelper.BuildAmlId(prefix, uri, nodeId.ToString()); + + CAEXObject initialObject = document.FindByID(amlNodeId); + + Assert.IsNotNull(initialObject, "Unable to find Initial Object"); + + return initialObject; + + } + + private CAEXDocument GetDocument( string fileName = NodeSetFileContainer ) + { + CAEXDocument document = TestHelper.GetReadOnlyDocument( NodeSetFileContainer ); + + Assert.IsNotNull( document, "Unable to retrieve Document " + fileName ); + return document; } } diff --git a/SystemTest/NodeSetFiles/TestAml.xml b/SystemTest/NodeSetFiles/TestAml.xml index 3a7b406..5c88a2d 100644 --- a/SystemTest/NodeSetFiles/TestAml.xml +++ b/SystemTest/NodeSetFiles/TestAml.xml @@ -305,7 +305,7 @@ - +