< Summary - pva.SuperV

Information
Class: pva.SuperV.Engine.Project
Assembly: pva.SuperV.Engine
File(s): /home/runner/work/pva.SuperV/pva.SuperV/pva.SuperV.Engine/Project.cs
Tag: dotnet-ubuntu_22190969454
Line coverage
98%
Covered lines: 147
Uncovered lines: 2
Coverable lines: 149
Total lines: 404
Line coverage: 98.6%
Branch coverage
100%
Covered branches: 28
Total branches: 28
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_ProjectsPath()100%11100%
get_CurrentProject()100%210%
get_Name()100%11100%
set_Name(...)100%11100%
get_Description()100%11100%
get_Version()100%11100%
get_Classes()100%11100%
get_FieldFormatters()100%11100%
get_HistoryStorageEngineConnectionString()100%11100%
get_HistoryStorageEngine()100%11100%
set_HistoryStorageEngine(...)100%11100%
get_HistoryRepositories()100%11100%
get_TopicsChannels()100%11100%
get_ScriptDefinitions()100%11100%
get_Projects()100%11100%
CreateProject(...)100%11100%
CreateProject(...)100%22100%
CreateProject(...)100%11100%
AddProjectToCollection(...)100%44100%
BuildAsync()100%11100%
GetClass(...)100%22100%
FindClass(...)100%11100%
GetFormatter(...)100%44100%
FindFormatter(...)100%11100%
GetAssemblyFileName()100%22100%
GetProjectHighestVersion(...)100%22100%
GetNextVersion()100%11100%
Unload(...)100%44100%
Unload()100%11100%
CallGcCleanup(...)100%44100%
Dispose()100%11100%
Dispose(...)100%11100%
SetFieldValueChangeChannel(...)100%44100%
GetTopicNames()100%210%

File(s)

/home/runner/work/pva.SuperV/pva.SuperV/pva.SuperV.Engine/Project.cs

#LineLine coverage
 1using pva.Helpers.Extensions;
 2using pva.SuperV.Engine.Exceptions;
 3using pva.SuperV.Engine.FieldFormatters;
 4using pva.SuperV.Engine.HistoryStorage;
 5using pva.SuperV.Engine.Processing;
 6using System.Collections.Concurrent;
 7using System.Text.Json.Serialization;
 8using System.Threading.Channels;
 9
 10namespace pva.SuperV.Engine
 11{
 12    /// <summary>
 13    /// SuperV Project class. It contains all the information required (<see cref="Class"/>, <see cref="Instance"/>, pro
 14    /// </summary>
 15    public abstract class Project : IDisposable
 16    {
 17        /// <summary>
 18        /// Gets the projects path where all SuperV stuff is stored (generated assemblies, project definitions and snaps
 19        /// </summary>
 20        /// <value>
 21        /// The projects path.
 22        /// </value>
 1639323        public static string ProjectsPath { get; } = Path.Combine(Path.GetTempPath(), "pva.SuperV");
 24
 25        /// <summary>
 26        /// Gets or sets the current project.
 27        /// </summary>
 28        /// <value>
 29        /// The current project.
 30        /// </value>
 031        public static Project? CurrentProject { get; set; }
 32
 33        /// <summary>
 34        /// The name of project. Use <see cref="Name"/> to access its content.
 35        /// </summary>
 36        private string? _name;
 37
 38        /// <summary>
 39        /// Gets or sets the name of the project.
 40        /// </summary>
 41        /// <value>
 42        /// The project name.
 43        /// </value>
 44        public string? Name
 45        {
 532746            get => _name;
 47            set
 43848            {
 43849                IdentifierValidation.ValidateIdentifier("project", value);
 43550                _name = value;
 43551            }
 52        }
 53
 54        /// <summary>
 55        /// Gets or sets the description.
 56        /// </summary>
 57        /// <value>
 58        /// The description.
 59        /// </value>
 37960        public string? Description { get; set; }
 61
 62        /// <summary>
 63        /// Gets or sets the version.
 64        /// </summary>
 65        /// <value>
 66        /// The version.
 67        /// </value>
 215168        public int Version { get; set; }
 69
 70        /// <summary>
 71        /// Gets the classes of project.
 72        /// </summary>
 73        /// <value>
 74        /// The classes.
 75        /// </value>
 933476        public Dictionary<string, Class> Classes { get; init; } = new(StringComparer.OrdinalIgnoreCase);
 77
 78        /// <summary>
 79        /// Gets the field formatters.
 80        /// </summary>
 81        /// <value>
 82        /// The field formatters.
 83        /// </value>
 203484        public Dictionary<string, FieldFormatter> FieldFormatters { get; init; } = new(StringComparer.OrdinalIgnoreCase)
 85
 86        private IHistoryStorageEngine? historyStorageEngine;
 87
 88        /// <summary>
 89        /// The history storage engin connection string.
 90        /// </summary>
 71891        public string? HistoryStorageEngineConnectionString { get; set; }
 92
 93        /// <summary>
 94        /// Gets the history repositories.
 95        /// </summary>
 96        /// <value>
 97        /// The history repositories.
 98        /// </value>
 99        [JsonIgnore]
 100        public IHistoryStorageEngine? HistoryStorageEngine
 101        {
 671102            get => historyStorageEngine;
 103            set
 388104            {
 388105                historyStorageEngine = value;
 389106                HistoryRepositories.Values.ForEach(historyRepository => historyRepository.HistoryStorageEngine = history
 388107            }
 108        }
 109
 110        /// <summary>
 111        /// Gets the history repositories.
 112        /// </summary>
 113        /// <value>
 114        /// The history repositories.
 115        /// </value>
 4066116        public Dictionary<string, HistoryRepository> HistoryRepositories { get; init; } = new(StringComparer.OrdinalIgno
 117
 118        /// <summary>
 119        /// Gets the channels associated to topics.
 120        /// </summary>
 121        /// <value>
 122        /// The channels.
 123        /// </value>
 124        [JsonIgnore]
 1819125        public Dictionary<string, Channel<FieldValueChangedEvent>> TopicsChannels { get; init; } = new(StringComparer.Or
 126
 127        /// <summary>
 128        /// Gets the script definitions.
 129        /// </summary>
 130        /// <value>
 131        /// The script definitions.
 132        /// </value>
 1325133        public Dictionary<string, ScriptDefinition> ScriptDefinitions { get; init; } = new(StringComparer.OrdinalIgnoreC
 134
 135        /// <summary>
 136        /// List of projects in use.
 137        /// </summary>
 1929138        public static ConcurrentDictionary<string, Project> Projects { get; } = new(StringComparer.OrdinalIgnoreCase);
 139
 140        /// <summary>
 141        /// Creates an empty <see cref="WipProject"/>.
 142        /// </summary>
 143        /// <param name="projectName">Name of the project.</param>
 144        /// <returns>The created <see cref="WipProject"/></returns>
 145        public static WipProject CreateProject(string projectName)
 92146        {
 92147            return CreateProject(projectName, null);
 89148        }
 149
 150        /// <summary>
 151        /// Creates an empty <see cref="WipProject"/>.
 152        /// </summary>
 153        /// <param name="projectName">Name of the project.</param>
 154        /// <param name="historyStorageEngineConnectionString">History storage connection string.</param>
 155        /// <returns>The created <see cref="WipProject"/></returns>
 156        public static WipProject CreateProject(string projectName, string? historyStorageEngineConnectionString)
 252157        {
 252158            WipProject project = new(projectName);
 249159            AddProjectToCollection(project);
 249160            if (string.IsNullOrEmpty(historyStorageEngineConnectionString))
 91161            {
 91162                return project;
 163            }
 164
 158165            project.HistoryStorageEngineConnectionString = historyStorageEngineConnectionString;
 158166            project.HistoryStorageEngine = HistoryStorageEngineFactory.CreateHistoryStorageEngine(historyStorageEngineCo
 158167            return project;
 249168        }
 169
 170        /// <summary>
 171        /// Creates a <see cref="WipProject"/> from a <see cref="RunnableProject"/> for modification.
 172        /// </summary>
 173        /// <param name="runnableProject">The runnable project from which to create the new <see cref="WipProject"/>.</p
 174        /// <returns>The new <see cref="WipProject"/></returns>
 175        public static WipProject CreateProject(RunnableProject runnableProject)
 38176        {
 38177            WipProject wipProject = new(runnableProject);
 38178            AddProjectToCollection(wipProject);
 38179            return wipProject;
 38180        }
 181
 182        internal static void AddProjectToCollection(Project project)
 433183        {
 433184            if (Projects.TryGetValue(project.GetId(), out Project? previousProject))
 48185            {
 48186                previousProject?.Unload();
 48187            }
 433188            Projects[project.GetId()] = project;
 433189        }
 190
 191        /// <summary>
 192        /// Builds the specified <see cref="WipProject"/>.
 193        /// </summary>
 194        /// <param name="wipProject">The WIP project.</param>
 195        /// <returns>a <see cref="RunnableProject"/></returns>
 196        public static async Task<RunnableProject> BuildAsync(WipProject wipProject)
 134197        {
 134198            RunnableProject runnableProject = await ProjectBuilder.BuildAsync(wipProject);
 132199            AddProjectToCollection(runnableProject);
 132200            return runnableProject;
 132201        }
 202
 203        /// <summary>
 204        /// Gets a class for a name.
 205        /// </summary>
 206        /// <param name="className">Name of the class.</param>
 207        /// <returns>Found class</returns>
 208        /// <exception cref="UnknownEntityException">Class wasn't found.</exception>
 209        public Class GetClass(string className)
 6113210        {
 6113211            if (Classes.TryGetValue(className, out Class? value))
 6111212            {
 6111213                return value;
 214            }
 215
 2216            throw new UnknownEntityException("Class", className);
 6111217        }
 218
 219        /// <summary>
 220        /// Finds a class for a name.
 221        /// </summary>
 222        /// <param name="className">Name of the class.</param>
 223        /// <returns><see cref="Class"/> if found or null otherwise.</returns>
 224        public Class? FindClass(string className)
 2225        {
 226            try
 2227            {
 2228                return GetClass(className);
 229            }
 3230            catch { return null; }
 2231        }
 232
 233        /// <summary>
 234        /// Gets a formatter for name.
 235        /// </summary>
 236        /// <param name="formatterName">Name of the formatter.</param>
 237        /// <returns><see cref="FieldFormatter"/></returns>
 238        /// <exception cref="UnknownEntityException">The field formatter doesn't exist</exception>
 239        public FieldFormatter? GetFormatter(string? formatterName)
 2232240        {
 2232241            if (String.IsNullOrEmpty(formatterName))
 1888242            {
 1888243                return null;
 244            }
 344245            if (FieldFormatters.TryGetValue(formatterName, out FieldFormatter? value))
 339246            {
 339247                return value;
 248            }
 249
 5250            throw new UnknownEntityException("Field formatter", formatterName);
 2227251        }
 252
 253        /// <summary>
 254        /// Finds a formatter for name.
 255        /// </summary>
 256        /// <param name="formatterName">Name of the formatter.</param>
 257        /// <returns><see cref="FieldFormatter"/> if found or null otherwise.</returns>
 258        public FieldFormatter? FindFormatter(string formatterName)
 2259        {
 260            try
 2261            {
 2262                return GetFormatter(formatterName);
 263            }
 3264            catch { return null; }
 2265        }
 266
 267        /// <summary>
 268        /// Gets the name of the generated assembly file for project.
 269        /// </summary>
 270        /// <returns>Assembly file name</returns>
 271        public string GetAssemblyFileName()
 552272        {
 552273            if (!Directory.Exists(ProjectsPath))
 1274            {
 1275                Directory.CreateDirectory(ProjectsPath);
 1276            }
 552277            return Path.Combine(ProjectsPath, $"{Name}-V{Version}.dll");
 552278        }
 279
 280        /// <summary>
 281        /// Gets the project highest version from generated assemblies of project.
 282        /// </summary>
 283        /// <param name="projectName">Name of the project.</param>
 284        /// <returns>The highest version or 0 if first version.</returns>
 285        protected static int GetProjectHighestVersion(string projectName)
 287286        {
 287287            return Directory.Exists(ProjectsPath)
 287288                ? Directory.EnumerateFiles(ProjectsPath, $"{projectName}-V*.dll")
 287289                    .Select(fileName =>
 14705290                    Convert.ToInt32(fileName
 14705291                        .Replace(ProjectsPath, "")
 14705292                        .Replace(Path.DirectorySeparatorChar.ToString(), "")
 14705293                        .Replace($"{projectName}-V", "")
 14705294                        .Replace(".dll", "")))
 287295                    .Order()
 287296                    .LastOrDefault()
 287297                : 0;
 287298        }
 299
 300        /// <summary>
 301        /// Gets the next version for project.
 302        /// </summary>
 303        /// <returns>Next project version</returns>
 304        protected int GetNextVersion()
 287305        {
 287306            return GetProjectHighestVersion(Name!) + 1;
 287307        }
 308
 309        /// <summary>
 310        /// Unloads a project.
 311        /// </summary>
 312        /// <param name="project">The project to be unloaded.</param>
 313        public static void Unload(Project project)
 15314        {
 15315            WeakReference? projectAssemblyLoaderWeakRef = null;
 15316            if (project is RunnableProject runnableProject)
 15317            {
 15318                projectAssemblyLoaderWeakRef = runnableProject.ProjectAssemblyLoaderWeakRef;
 15319            }
 15320            project.Dispose();
 15321            if (projectAssemblyLoaderWeakRef is not null)
 15322            {
 15323                CallGcCleanup(projectAssemblyLoaderWeakRef);
 15324            }
 15325        }
 326
 327
 328        /// <summary>
 329        /// Unloads the project.
 330        /// </summary>
 331        public virtual void Unload()
 430332        {
 430333            FieldFormatters.Clear();
 430334            Classes.Clear();
 430335            Projects.Remove(GetId(), out _);
 430336        }
 337
 338        [System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S1215:\"GC.Collect\" should not be call
 339            Justification = "Need to call GC to remove references to assembly")]
 340        public static void CallGcCleanup(WeakReference palWeakRef)
 15341        {
 314342            for (int i = 0; palWeakRef.IsAlive && i < 10; i++)
 142343            {
 142344                GC.Collect();
 142345                GC.WaitForPendingFinalizers();
 142346            }
 15347        }
 348
 349        /// <summary>
 350        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 351        /// </summary>
 352        public void Dispose()
 381353        {
 381354            Dispose(true);
 381355            GC.SuppressFinalize(this);
 381356        }
 357
 358        /// <summary>
 359        /// Releases unmanaged and - optionally - managed resources.
 360        /// </summary>
 361        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 362        protected virtual void Dispose(bool disposing)
 381363        {
 381364            Unload();
 381365        }
 366
 367        /// <summary>
 368        /// Gets the identifier of project.
 369        /// </summary>
 370        /// <returns>Project ID</returns>
 371        public abstract string GetId();
 372
 373        /// <summary>
 374        /// Sets the field value change channel.
 375        /// </summary>
 376        /// <param name="fieldDefinition">The field definition.</param>
 377        public void SetFieldValueChangeChannel(IFieldDefinition fieldDefinition)
 3514378        {
 3514379            if (!String.IsNullOrEmpty(fieldDefinition.TopicName))
 318380            {
 318381                Channel<FieldValueChangedEvent>? topicChannel = null;
 318382                if (!TopicsChannels.TryGetValue(fieldDefinition.TopicName, out topicChannel))
 318383                {
 318384                    topicChannel = Channel.CreateUnbounded<FieldValueChangedEvent>(
 318385                        new UnboundedChannelOptions
 318386                        {
 318387                            SingleWriter = true,
 318388                            SingleReader = false,
 318389                            AllowSynchronousContinuations = false
 318390                        });
 318391                    TopicsChannels.Add(fieldDefinition.TopicName, topicChannel);
 318392                }
 318393                fieldDefinition.FieldValueChangedEventChannel = topicChannel;
 318394            }
 3514395        }
 396
 397        /// <summary>
 398        /// Gets the topic names.
 399        /// </summary>
 400        /// <returns>List of topic names.</returns>
 401        public HashSet<string> GetTopicNames() =>
 0402            TopicsChannels.Keys.ToHashSet();
 403    }
 404}