/* ======================================================================== * 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.IO; using System.Linq; using Opc.Ua.Schema.Model; namespace Opc.Ua.SourceGeneration { /// /// Model options /// public sealed record class DesignFileOptions { /// /// -version [v104, v105] /// public string Version { get; init; } /// /// -id [start id] /// public uint StartId { get; init; } /// /// -mv [model version] /// public string ModelVersion { get; init; } /// /// -pd [publication date] /// public string ModelPublicationDate { get; init; } /// /// -rc /// public bool ReleaseCandidate { get; init; } = true; /// /// When true, also emits a {Namespace}NodeManager /// (a partial class deriving from /// Opc.Ua.Server.AsyncCustomNodeManager) and a matching /// {Namespace}NodeManagerFactory implementing /// Opc.Ua.Server.INodeManagerFactory. /// /// The generated manager exposes a /// partial void Configure(INodeManagerBuilder builder) /// extensibility hook that user code can implement in a sibling /// partial to wire callbacks via the fluent /// Opc.Ua.Server.Fluent API. /// /// public bool GenerateNodeManager { get; init; } /// /// Optional override for the namespace of the generated /// NodeManager partial. When set, the generator emits /// partial class {NodeManagerClassName} in this namespace /// instead of using the design file Prefix. Used by the /// [NodeManager] attribute discovery path. /// public string NodeManagerNamespace { get; init; } /// /// Optional override for the class name of the generated /// NodeManager partial. Defaults to /// {Prefix}NodeManager. Used by the [NodeManager] /// attribute discovery path. /// public string NodeManagerClassName { get; init; } /// /// Whether to also emit the {ClassName}Factory. Defaults /// to true. Set to false when the consumer wants /// to author the factory by hand. /// public bool EmitNodeManagerFactory { get; init; } = true; } /// /// Collection of design files and options /// public sealed record class DesignFileCollection { /// /// Design files to generate code for. Typically a single file. /// public IReadOnlyList Targets { get; init; } = []; /// /// Design files referenced by but not /// generated. They provide node resolution only (e.g. when a /// target imports types from another companion specification). /// public IReadOnlyList Dependencies { get; init; } = []; /// /// Optional identifier file if not same name and side /// by side with design files /// public string IdentifierFilePath { get; init; } /// /// Design file options /// public DesignFileOptions Options { get; init; } } /// /// Validate model design /// internal static class DesignFileExtensions { /// /// Get design file groups for processing. A group is a set of design files /// in the same common folder with an optional csv file included. Each group /// carries the same so that /// cross-model references resolve for every target. /// /// /// is null. public static IEnumerable Group( this DesignFileCollection collection, List identifierFiles = null) { if (collection is null) { throw new ArgumentNullException(nameof(collection)); } var idFiles = new Dictionary>(); if (identifierFiles != null) { foreach (string idFile in identifierFiles) { string dir = Path.GetDirectoryName(idFile); if (!idFiles.TryGetValue(dir, out List value)) { value = []; idFiles[dir] = value; } value.Add(idFile); } } IReadOnlyList dependencies = collection.Dependencies ?? []; return collection.Targets .GroupBy(Path.GetDirectoryName) .Select(g => new DesignFileCollection { Targets = [.. g], Dependencies = dependencies, IdentifierFilePath = idFiles.TryGetValue(g.Key, out List files) ? files.FirstOrDefault() : collection.IdentifierFilePath, Options = collection.Options }); } /// /// Validates the model design files /// public static IModelDesign OpenModelDesign( this IFileSystem fileSystem, DesignFileCollection designFiles, IReadOnlyList exclusions, ITelemetryContext telemetry, bool useAllowSubtypes = true) { DesignFileOptions options = designFiles.Options ?? new DesignFileOptions(); var validator = new ModelDesignValidator( fileSystem, options.StartId, exclusions, telemetry, SpecificationVersion.V105) { UseAllowSubtypes = useAllowSubtypes, ReleaseCandidate = options.ReleaseCandidate, ModelVersion = options.ModelVersion, ModelPublicationDate = options.ModelPublicationDate }; string identifierFilePath = designFiles.IdentifierFilePath; validator.Validate( designFiles.Targets, designFiles.Dependencies ?? [], identifierFilePath); return validator; } } }