/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Xml; using Opc.Ua.Schema.Model; namespace Opc.Ua.SourceGeneration { /// /// Resolves the effective method argument metadata used by generators. /// internal static class MethodDesignArgumentResolver { /// /// Resolves the effective input arguments for a method design. /// public static Parameter[] ResolveMethodInputs(MethodDesign method) { return ResolveMethodArguments(method).Inputs; } /// /// Resolves the effective output arguments for a method design. /// public static Parameter[] ResolveMethodOutputs(MethodDesign method) { return ResolveMethodArguments(method).Outputs; } /// /// Resolves the effective input and output arguments for a method design. /// public static (Parameter[] Inputs, Parameter[] Outputs) ResolveMethodArguments( MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } MethodDesign definition = ResolveMethodDefinition(method); Parameter[] inputs = ResolveArguments( definition.InputArguments, definition.MethodDeclarationNode?.InputArguments, definition.MethodType?.InputArguments); Parameter[] outputs = ResolveArguments( definition.OutputArguments, definition.MethodDeclarationNode?.OutputArguments, definition.MethodType?.OutputArguments); return (inputs, outputs); } /// /// Returns whether a method or one of its declaration/type links defines arguments. /// public static bool HasMethodArguments(MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } MethodDesign definition = ResolveMethodDefinition(method); return method.HasArguments || definition.HasArguments || HasDeclaredArguments(definition); } /// /// Returns whether the method directly declares any arguments. /// public static bool HasDeclaredArguments(MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } return method.InputArguments is { Length: > 0 } || method.OutputArguments is { Length: > 0 }; } /// /// Resolves the method design that owns the effective method state and /// argument metadata. /// public static MethodDesign ResolveMethodDefinition(MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } return ResolveMethodDefinition(method, []); } /// /// Resolves the qualified identity used to name the generated method /// state class. /// public static XmlQualifiedName ResolveMethodStateIdentity(MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } if (method.TypeDefinition != null && method.MethodType == null) { return method.TypeDefinition; } MethodDesign definition = ResolveMethodDefinition(method); return definition.TypeDefinition ?? definition.SymbolicName ?? definition.SymbolicId ?? method.SymbolicName ?? method.SymbolicId; } /// /// Returns whether the method state of a method is a typed class that /// is declared by a method type, rather than the base /// MethodState. /// /// /// Typed method-state classes are only generated for method type /// declarations. A method that declares its own arguments inline, /// without a method type or a declaration that resolves to one, has /// no typed class; its argument properties are still generated. /// public static bool HasTypedMethodState(MethodDesign method) { if (method == null) { throw new ArgumentNullException(nameof(method)); } if (method.TypeDefinition != null && method.MethodType == null) { return true; } MethodDesign definition = ResolveMethodDefinition(method); if (definition.TypeDefinition != null || definition.IsMethodTypeDesign()) { return true; } XmlQualifiedName identity = definition.SymbolicName ?? definition.SymbolicId; return identity?.Name != null && identity.Name.EndsWith("MethodType", StringComparison.Ordinal); } /// /// Returns whether two methods have identical effective signatures. /// public static bool HaveSameMethodSignature( MethodDesign first, MethodDesign second) { if (first == null) { throw new ArgumentNullException(nameof(first)); } if (second == null) { throw new ArgumentNullException(nameof(second)); } return HaveSameSignature( ResolveMethodInputs(first), ResolveMethodOutputs(first), ResolveMethodInputs(second), ResolveMethodOutputs(second)); } /// /// Returns whether two methods directly declare identical signatures. /// public static bool HaveSameDeclaredSignature( MethodDesign first, MethodDesign second) { if (first == null) { throw new ArgumentNullException(nameof(first)); } if (second == null) { throw new ArgumentNullException(nameof(second)); } return HaveSameSignature( first.InputArguments ?? [], first.OutputArguments ?? [], second.InputArguments ?? [], second.OutputArguments ?? []); } private static MethodDesign ResolveMethodDefinition( MethodDesign method, List visited) { if (ContainsReference(visited, method)) { return method; } visited.Add(method); bool hasDeclaredArguments = HasDeclaredArguments(method); if (method.MethodType != null && DelegatesTo(method, method.MethodType, hasDeclaredArguments)) { return ResolveMethodDefinition(method.MethodType, visited); } if (method.MethodDeclarationNode != null && !ReferenceEquals(method.MethodDeclarationNode, method) && DelegatesTo(method, method.MethodDeclarationNode, hasDeclaredArguments)) { return ResolveMethodDefinition(method.MethodDeclarationNode, visited); } return method; } /// /// Returns whether takes its signature from /// , which is the case when it declares no /// arguments of its own or declares exactly the target's effective /// signature. /// /// /// The target is resolved once here rather than through /// and /// , which would each walk the target /// chain again from scratch with their own visited list. The resolution /// is deliberately not cached across calls: the model is still being /// mutated while it is validated, and a generator that carried state /// between passes would stop being deterministic. /// private static bool DelegatesTo( MethodDesign method, MethodDesign target, bool hasDeclaredArguments) { if (!hasDeclaredArguments) { return true; } MethodDesign targetDefinition = ResolveMethodDefinition(target, []); return HaveSameSignature( method.InputArguments ?? [], method.OutputArguments ?? [], targetDefinition.InputArguments ?? [], targetDefinition.OutputArguments ?? []); } private static Parameter[] ResolveArguments( Parameter[] methodArguments, Parameter[]? declarationArguments, Parameter[]? methodTypeArguments) { if (methodArguments is { Length: > 0 }) { return methodArguments; } if (declarationArguments is { Length: > 0 }) { return declarationArguments; } if (methodTypeArguments is { Length: > 0 }) { return methodTypeArguments; } return []; } private static bool HaveSameSignature( Parameter[] firstInputs, Parameter[] firstOutputs, Parameter[] secondInputs, Parameter[] secondOutputs) { return HaveSameParameters(firstInputs, secondInputs) && HaveSameParameters(firstOutputs, secondOutputs); } private static bool HaveSameParameters( Parameter[] first, Parameter[] second) { if (first.Length != second.Length) { return false; } for (int ii = 0; ii < first.Length; ii++) { Parameter left = first[ii]; Parameter right = second[ii]; if (left == null || right == null) { if (!ReferenceEquals(left, right)) { return false; } continue; } if (!string.Equals(left.Name, right.Name, StringComparison.Ordinal) || !XmlQualifiedNameEquals(left.DataType, right.DataType) || left.ValueRank != right.ValueRank || !string.Equals( left.ArrayDimensions, right.ArrayDimensions, StringComparison.Ordinal) || left.AllowSubTypes != right.AllowSubTypes || left.IsOptional != right.IsOptional) { return false; } } return true; } private static bool XmlQualifiedNameEquals( XmlQualifiedName first, XmlQualifiedName second) { if (ReferenceEquals(first, second)) { return true; } if (first == null || second == null) { return false; } return string.Equals(first.Name, second.Name, StringComparison.Ordinal) && string.Equals(first.Namespace, second.Namespace, StringComparison.Ordinal); } private static bool ContainsReference( List methods, MethodDesign candidate) { foreach (MethodDesign method in methods) { if (ReferenceEquals(method, candidate)) { return true; } } return false; } } }