diff --git a/Skyline.DataMiner.CICD.Validators.sln b/Skyline.DataMiner.CICD.Validators.sln index 38cc520c..942d9063 100644 --- a/Skyline.DataMiner.CICD.Validators.sln +++ b/Skyline.DataMiner.CICD.Validators.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Testing", "Common.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtocolTests.SchemaGenerator", "ProtocolTests.SchemaGenerator\ProtocolTests.SchemaGenerator.csproj", "{A3278AF2-05A5-4953-9746-2821B4D07740}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validator Management Tool", "Validator Management Tool\Validator Management Tool.csproj", "{82F5E0F7-A112-489E-9480-D4ECD9FACB4E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {A3278AF2-05A5-4953-9746-2821B4D07740}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3278AF2-05A5-4953-9746-2821B4D07740}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3278AF2-05A5-4953-9746-2821B4D07740}.Release|Any CPU.Build.0 = Release|Any CPU + {82F5E0F7-A112-489E-9480-D4ECD9FACB4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82F5E0F7-A112-489E-9480-D4ECD9FACB4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82F5E0F7-A112-489E-9480-D4ECD9FACB4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82F5E0F7-A112-489E-9480-D4ECD9FACB4E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Validator Management Tool/App.xaml b/Validator Management Tool/App.xaml new file mode 100644 index 00000000..caf586c9 --- /dev/null +++ b/Validator Management Tool/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Validator Management Tool/App.xaml.cs b/Validator Management Tool/App.xaml.cs new file mode 100644 index 00000000..488bc011 --- /dev/null +++ b/Validator Management Tool/App.xaml.cs @@ -0,0 +1,11 @@ +namespace Validator_Management_Tool +{ + using System.Windows; + + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Validator Management Tool/BindableBase.cs b/Validator Management Tool/BindableBase.cs new file mode 100644 index 00000000..9dbcbf06 --- /dev/null +++ b/Validator Management Tool/BindableBase.cs @@ -0,0 +1,26 @@ +namespace Validator_Management_Tool +{ + using System.ComponentModel; + using System.Runtime.CompilerServices; + + public class BindableBase : INotifyPropertyChanged + { + protected virtual void SetProperty(ref T member, T val, [CallerMemberName] string propertyName = null) + { + if (Equals(member, val)) + { + return; + } + + member = val; + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(string propertyName) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + } +} diff --git a/Validator Management Tool/Common/ExportManager.cs b/Validator Management Tool/Common/ExportManager.cs new file mode 100644 index 00000000..d614eb5b --- /dev/null +++ b/Validator Management Tool/Common/ExportManager.cs @@ -0,0 +1,244 @@ +namespace Validator_Management_Tool.Common +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Windows; + using Microsoft.Win32; + using NPOI.SS.UserModel; + using NPOI.SS.Util; + using NPOI.XSSF.UserModel; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Model; + + /// + /// Static class that has methods to export the error messages. + /// + public static class ExportManager + { + /// + /// Used in Jenkins pipeline (exportErrorMessages.ps1). + /// + public static void ExportToExcelJenkins(Version version) + { + try + { + // Retrieve checks + var checks = new List(); + string directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + Serialization.Serializer.ReadXml(Path.Combine(directory, Settings.XmlPath)); + + foreach (var check in Serialization.Serializer.GetChecks()) + { + checks.Add(check); + } + + string fileName = Settings.ExportFile + " - " + version.ToString().Replace('.', '_'); + CreateWorksheet(checks, Path.Combine(directory, fileName)); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + /// + /// Creates a chosen file with all the error messages in, + /// after it asked the location of the file with a save file dialog. + /// + /// The list of checks for which a file has to be created. + public static void ExportToExcel(ICollection checks) + { + SaveFileDialog saveFileDialog = new SaveFileDialog + { + DefaultExt = ".xlsx", + Filter = "Excel Worksheet (*.xlsx)|*.xlsx", + FileName = Settings.ExportPath + Settings.ExportFile, + }; + + if (saveFileDialog.ShowDialog() == true) + { + string extension = saveFileDialog.FileName.Split(new char[] { '.' })[1]; + string filename = saveFileDialog.FileName.Replace("." + extension, String.Empty).Split(new char[] { '\\' }).Last(); + Settings.ExportPath = saveFileDialog.FileName.Replace(filename + "." + extension, String.Empty); + Settings.ExportFile = filename; + + // Export to Excel worksheet + CreateWorksheet(checks, saveFileDialog.FileName); + + MessageBox.Show("Generating Done!", "Generation Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + /// + /// Creates a Excel Worksheet in the specified location with all the error messages from the list. + /// + /// List of checks that has to be written to the file. + /// Path of the file that has to be created. + private static void CreateWorksheet(ICollection checks, string filename) + { + if (!Path.HasExtension(filename)) + { + filename = filename + ".xlsx"; + } + + IWorkbook workbook = new XSSFWorkbook(); + + CreateWorksheetSplit(workbook, checks, "All Error Messages"); + CreateWorksheetSplit(workbook, checks.Where(x => x.Source == Source.Validator).ToList(), "Validator"); + CreateWorksheetSplit(workbook, checks.Where(x => x.Source == Source.MajorChangeChecker).ToList(), "Major Change Checker"); + + using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) + { + workbook.Write(fs, false); + } + + workbook.Close(); + } + + private static void CreateWorksheetSplit(IWorkbook workbook, ICollection checks, string sheetName) + { + try + { + ISheet excelWorkSheet = workbook.CreateSheet(sheetName); + + IRow headerRow = excelWorkSheet.CreateRow(0); + + // Add table headers going cell by cell. + headerRow.CreateCell(0).SetCellValue("Full ID"); + headerRow.CreateCell(1).SetCellValue("Category"); + headerRow.CreateCell(2).SetCellValue("Namespace"); + headerRow.CreateCell(3).SetCellValue("Check Name"); + headerRow.CreateCell(4).SetCellValue("Error Message Name"); + headerRow.CreateCell(5).SetCellValue("Description"); + headerRow.CreateCell(6).SetCellValue("Severity"); + headerRow.CreateCell(7).SetCellValue("Certainty"); + headerRow.CreateCell(8).SetCellValue("Fix Impact"); + headerRow.CreateCell(9).SetCellValue("Has Code Fix"); + headerRow.CreateCell(10).SetCellValue("Source"); + headerRow.CreateCell(11).SetCellValue("Details"); + headerRow.CreateCell(12).SetCellValue("Example Code"); + headerRow.CreateCell(13).SetCellValue("How To Fix"); + + int rowCount = 2; + + var sortedChecks = checks.OrderBy(c => c.CategoryId).ThenBy(c => c.CheckId).ThenBy(c => c.ErrorId); + foreach (var check in sortedChecks) + { + IRow row = excelWorkSheet.CreateRow(rowCount); + + row.CreateCell(0).SetCellValue(check.FullId); + row.CreateCell(1).SetCellValue(check.Category.ToString()); + row.CreateCell(2).SetCellValue(SimplifyNamespace(check)); + row.CreateCell(3).SetCellValue(check.CheckName); + row.CreateCell(4).SetCellValue(check.Name); + + string description; + try + { + description = check.Description; + for (int i = 0; i < check.Parameters.Count; i++) + { + var param = check.Parameters[i]; + string oldValue = String.Format("{{{0}}}", i); + + string newValue; + if (String.IsNullOrWhiteSpace(param.Value)) + { + newValue = String.Format("{{{0}}}", param.Text); + } + else + { + // No need to add the braces as it's a hard-coded value anyway. + newValue = String.Format("{0}", param.Value); + } + + description = description.Replace(oldValue, newValue); + } + } + catch (IndexOutOfRangeException) + { + description = check.Description; + } + + row.CreateCell(5).SetCellValue(description); + row.CreateCell(6).SetCellValue(check.Severity.ToString()); + row.CreateCell(7).SetCellValue(check.Certainty.ToString()); + row.CreateCell(8).SetCellValue(check.FixImpact.ToString()); + row.CreateCell(9).SetCellValue(check.HasCodeFix); + row.CreateCell(10).SetCellValue(check.Source.ToString()); + row.CreateCell(11).SetCellValue(check.Details); + row.CreateCell(12).SetCellValue(check.ExampleCode); + row.CreateCell(13).SetCellValue(check.HowToFix); + + rowCount++; + } + + excelWorkSheet.AutoSizeColumn(0); + excelWorkSheet.AutoSizeColumn(1); + excelWorkSheet.AutoSizeColumn(2); + excelWorkSheet.AutoSizeColumn(3); + excelWorkSheet.AutoSizeColumn(4); + excelWorkSheet.AutoSizeColumn(5); + excelWorkSheet.AutoSizeColumn(6); + excelWorkSheet.AutoSizeColumn(7); + excelWorkSheet.AutoSizeColumn(8); + excelWorkSheet.AutoSizeColumn(9); + excelWorkSheet.AutoSizeColumn(10); + excelWorkSheet.AutoSizeColumn(11); + excelWorkSheet.AutoSizeColumn(12); + excelWorkSheet.AutoSizeColumn(13); + + excelWorkSheet.SetAutoFilter(new CellRangeAddress(0, rowCount, 0, 13)); + } + catch (Exception e) + { + Console.Error.WriteLine(e.Message); + Console.Error.WriteLine(e.StackTrace); + } + } + + private static string SimplifyNamespace(Check check) + { + List nsParts = check.Namespace.Split('.').ToList(); + + List simpleNs = new List(); + bool found = false; + string catString = check.Category.ToString(); + foreach (string item in nsParts) + { + string partToCheck = item; + if (found) + { + simpleNs.Add(partToCheck); + continue; + } + + if (check.Category == Category.ParameterGroup && String.Equals(partToCheck, "ParameterGroups")) + { + partToCheck = "ParameterGroup"; + } + + if (String.Equals(catString, partToCheck, StringComparison.OrdinalIgnoreCase)) + { + found = true; + } + } + + string ns; + if (simpleNs.Count == 0) + { + ns = nsParts.Last(); + } + else + { + ns = String.Join(".", simpleNs); + } + + return ns; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Common/Settings.cs b/Validator Management Tool/Common/Settings.cs new file mode 100644 index 00000000..387c75f6 --- /dev/null +++ b/Validator Management Tool/Common/Settings.cs @@ -0,0 +1,179 @@ +namespace Validator_Management_Tool.Common +{ + using System.Collections.Immutable; + + /// + /// Static class that holds the settings. + /// + public static class Settings + { + // The default paths + public const string DefaultErrorMessagesPath = "../../../Protocol/"; + public const string DefaultTestPath = "../../../Protocol/"; + public const string DefaultUnitTestPath = "../../../ProtocolTests/"; + public const string DefaultXmlPath = "../../../Protocol/ErrorMessages.xml"; + public const string DefaultExportPath = ""; + public const string DefaultExportFile = "Validator Error Messages"; + + public const bool DefaultErrorClassesInOneFile = false; + + // The path specified in the settings menu + public static string ErrorMessagesPath = "../../../Protocol/"; + public static string TestPath = "../../../Protocol/"; + public static string UnitTestPath = "../../../ProtocolTests/"; + public static string XmlPath = "../../../Protocol/ErrorMessages.xml"; + public static string ExportPath = ""; + public static string ExportFile = "Validator Error Messages"; + + public static bool ErrorClassesInOneFile = false; + + public static readonly ImmutableList ForbiddenStrings = ImmutableList.Create( + "namespace", + "Namespace", + "class", + "public", + "private", + "param", + "string", + "int", + "bool", + "uint", + "internal", + "protected", + "static", + "readonly", + "const", + "short", + "long", + "float", + "double", + "if", + "else", + "for", + "foreach", + "default", + "case", + "switch", + "break", + "return", + "try", + "catch", + "while", + "throw", + "do", + "finally", + "as", + "in", + "enum", + "equals", + "true", + "false", + "lock", + "nameof", + "typeof", + "new", + "null", + "object", + "byte", + "sbyte", + "sizeof", + "struct", + "ulong", + "unsafe", + "volatile", + "base", + "abstract", + "char", + "checked", + "continue", + "decimal", + "delegate", + "event", + "explicit", + "extern", + "fixed", + "goto", + "implicit", + "interface", + "operator", + "out", + "override", + "params", + "ref", + "sealed", + "stackalloc", + "this", + "unchecked", + "unsafe", + "ushort", + "using static", + "virtual", + "Error", + "ErrorId", + "Validator", + "Fix", + "test", + "node", + "ExportManager", + "Settings", + "TestGenerator", + "XMLParser", + "MyCommand", + "MyTCommand", + "Check", + "Serializer", + "XmlClasses", + "Check", + "InputParameter", + "InputParameters", + "DescriptionTemplate", + "DescriptionTemplates", + "ErrorMessage", + "ErrorMessages", + "Checks", + "Category", + "Categories", + "Severity", + "Certainty", + "ValidationChecks", + "ErrorMessagesClass", + "TestClass", + "UnitTestClass", + "EmptyStringToBooleanConvert", + "InverterConverter", + "CertaintyConverter", + "CategoryConverter", + "ArrayToStringConverter", + "CheckIdConverter", + "CheckBubbelUpConverter", + "NamespaceBubbelUpConverter", + "CategoryBubbelUpConverter", + "AddCheckView", + "AddCheckViewModel", + "CheckEditView", + "CheckEditViewModel", + "CheckViewModel", + "CheckView", + "MainWindow", + "MainWindowViewModel", + "NewCheckView", + "NewCheckViewModel", + "NewNamespaceView", + "NewNamespaceViewModel", + "SettingsView", + "SettingsViewModel", + "ViewModelLocator", + "BindableBase", + "CodeFixContent", + "TestAttribute", + "TestCategory", + "TestDescription", + "ValidationResult", + "ValidatorContext", + "ProtocolAdditions", + "ICodeFix", + "ITest", + "IValidationResult", + "IValidator" ); + } +} \ No newline at end of file diff --git a/Validator Management Tool/Common/TestGenerator.cs b/Validator Management Tool/Common/TestGenerator.cs new file mode 100644 index 00000000..68725769 --- /dev/null +++ b/Validator Management Tool/Common/TestGenerator.cs @@ -0,0 +1,290 @@ +namespace Validator_Management_Tool.Common +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.Text; + using Validator_Management_Tool.Model; + using Validator_Management_Tool.Templates.Error_Messages; + using Validator_Management_Tool.Templates.Tests; + using Validator_Management_Tool.Templates.Unit_Tests; + + /// + /// Static class that contains all the methods to generate the classes and write them to files. + /// + public static class TestGenerator + { + /// + /// String builder that holds all the code from the different classes, when the single file option for the error message classes is checked. + /// + private static StringBuilder singleFile = new StringBuilder(); + + /// + /// Generates test, error messages and unit test classes. + /// + /// All the checks for which the classes needs to be generated. + public static void GenerateFiles(ObservableCollection checks) + { + DeletePreviousErrorMessageClasses(); + + // Generate the Test classes + List namespaces = new List(); + foreach (var check in checks) + { + if (!namespaces.Contains(check.FullNamespace)) + { + namespaces.Add(check.FullNamespace); + CreateTestClass(check); + } + } + + // Generate the error message classes and unit test classes + foreach (var ns in namespaces) + { + List currentChecks = new List(); + foreach (var check in checks) + { + if (check.FullNamespace == ns) + { + currentChecks.Add(check); + } + } + + CreateErrorClass(currentChecks); + CreateUnitTestClass(currentChecks); + } + + if (Settings.ErrorClassesInOneFile) + { + CreateFile("ErrorMessages", singleFile.ToString(), Settings.ErrorMessagesPath, "Error Messages/", "Error Messages\\", true, false); + } + + } + + /// + /// Deletes the already existing directory with the error messages. + /// + private static void DeletePreviousErrorMessageClasses() + { + if (!Directory.Exists(Settings.ErrorMessagesPath + "Error Messages/")) + { + return; + } + + Directory.Delete(Settings.ErrorMessagesPath + "Error Messages/", true); + } + + /// + /// Creates a .cs file and makes the correct folder structure. + /// + /// The whole namespace of the file. + /// The code that has to be added to the file. + /// The base of the path where the file has to be added. + /// The first folder within the directory path were the file has to be added. + /// The base of the path to add the file to the .csproj file, split by backslashes. + /// If true, the existing file will be overwritten. + /// If true, the file is a unit test class and a different folder structure will be created. + private static void CreateFile(string @namespace, string generatedCode, string baseDirectoryPath, string baseDirectorySuffix, string baseConfigFileName, bool overwrite, bool unitTest, List checks = null) + { + var splitNamespace = @namespace.Split(new string[] { "." }, StringSplitOptions.None); + + StringBuilder directoryPath = new StringBuilder(baseDirectoryPath + baseDirectorySuffix); + StringBuilder configFileName = new StringBuilder(baseConfigFileName); + + string className = splitNamespace[splitNamespace.Length - 1]; + + for (int i = 0; i < splitNamespace.Length - 1; ++i) + { + directoryPath.Append(splitNamespace[i] + "/"); + configFileName.Append(splitNamespace[i] + "\\"); + } + + string filePath; + if (unitTest) + { + directoryPath.Append(className + "/"); + configFileName.Append(className + "\\"); + + Directory.CreateDirectory(directoryPath.ToString()); + filePath = directoryPath + className + ".cs"; + + var codefixDir = Directory.CreateDirectory(directoryPath.ToString() + "Samples/Codefix/"); + + var invalidCompareDir = Directory.CreateDirectory(directoryPath.ToString() + "Samples/Compare/Invalid/"); + var validCompareDir = Directory.CreateDirectory(directoryPath.ToString() + "Samples/Compare/Valid/"); + + var invalidValidateDir = Directory.CreateDirectory(directoryPath.ToString() + "Samples/Validate/Invalid/"); + var validValidateDir = Directory.CreateDirectory(directoryPath.ToString() + "Samples/Validate/Valid/"); + + if (checks != null) + { + // Create default xml file + foreach (var check in checks) + { + if (check.Name.EndsWith("_Sub")) + { + // Ignore the ones that are specifically for SubErrors + continue; + } + + string[] asNamespace = check.Namespace.Split('.'); + StringBuilder sb = new StringBuilder(); + int index = 0; + for (int i = 0; i < asNamespace.Length; i++, index++) + { + for (int j = 0; j < index; j++) + { + sb.Append("\t"); + } + + if (String.Equals(asNamespace[i], "Protocol")) + { + sb.AppendLine("<" + asNamespace[i] + " xmlns=\"http://www.skyline.be/validatorProtocolUnitTest\">"); + } + else + { + sb.AppendLine("<" + asNamespace[i] + ">"); + } + } + + for (int i = asNamespace.Length; i > 0; i--, index--) + { + for (int j = 1; j < index; j++) + { + sb.Append("\t"); + } + + sb.AppendLine(""); + } + + string tempXml = sb.ToString().Trim(); + + string tempPath; + if (check.Source == Skyline.DataMiner.CICD.Validators.Common.Model.Source.Validator) + { + // Create Valid Validate file + tempPath = Path.Combine(validValidateDir.FullName, "Valid.xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + + // Create Invalid Validate files + tempPath = Path.Combine(invalidValidateDir.FullName, check.Name + ".xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + + // Create CodeFix files + if (check.HasCodeFix) + { + tempPath = Path.Combine(codefixDir.FullName, check.Name + ".xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + } + } + else if (check.Source == Skyline.DataMiner.CICD.Validators.Common.Model.Source.MajorChangeChecker) + { + // Create Valid Compare files + tempPath = Path.Combine(validCompareDir.FullName, "Valid_Old.xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + + tempPath = Path.Combine(validCompareDir.FullName, "Valid_New.xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + + // Create Invalid Compare files + tempPath = Path.Combine(invalidCompareDir.FullName, check.Name + "_Old.xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + + tempPath = Path.Combine(invalidCompareDir.FullName, check.Name + "_New.xml"); + if (!File.Exists(tempPath)) + { + File.WriteAllText(tempPath, tempXml, Encoding.UTF8); + } + } + } + } + + } + else + { + Directory.CreateDirectory(directoryPath.ToString()); + filePath = directoryPath + className + ".cs"; + } + + configFileName.Append(splitNamespace[splitNamespace.Length - 1] + ".cs"); + + if (!File.Exists(filePath.ToString()) || overwrite) + { + // Create a file to write to. + File.WriteAllText(filePath, generatedCode); + + Console.WriteLine("File: " + configFileName + " was generated!"); + } + } + + /// + /// Creates a unit test class for the corresponding checks. + /// + /// A list with all the checks within a namespace. + private static void CreateUnitTestClass(List checks) + { + var splitNamespace = checks[0].FullNamespace.Split(new string[] { "." }, StringSplitOptions.None); + var className = splitNamespace[splitNamespace.Length - 1]; + + UnitTestClass unitTestClass = new UnitTestClass(checks, className); + var generatedCode = unitTestClass.TransformText(); + + CreateFile(checks[0].FullNamespace, generatedCode, Settings.UnitTestPath, String.Empty, String.Empty, false, true, checks); + } + + /// + /// Creates an error message class for the corresponding checks. + /// + /// A list with all the checks within a namespace. + private static void CreateErrorClass(List checks) + { + ErrorMessagesClass errorMessagesClass = new ErrorMessagesClass(checks); + var generatedCode = errorMessagesClass.TransformText(); + + if (Settings.ErrorClassesInOneFile) + { + singleFile.Append(generatedCode); + singleFile.Append(Environment.NewLine); + singleFile.Append(Environment.NewLine); + } + else + { + CreateFile(checks[0].FullNamespace, generatedCode, Settings.ErrorMessagesPath, "Error Messages/", "Error Messages\\", true, false); + } + } + + /// + /// Creates a test class for the corresponding check. + /// + /// A check that represents a group of checks within a category and namespace. + private static void CreateTestClass(Check check) + { + var splitNamespace = check.FullNamespace.Split(new string[] { "." }, StringSplitOptions.None); + var className = splitNamespace[splitNamespace.Length - 1]; + + TestsClass checkClass = new TestsClass(className, check.FullNamespace, check.Category); + var generatedCode = checkClass.TransformText(); + + CreateFile(check.FullNamespace, generatedCode, Settings.TestPath, "Tests/", "Tests\\", false, false); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Common/XMLParser.cs b/Validator Management Tool/Common/XMLParser.cs new file mode 100644 index 00000000..5546fc2f --- /dev/null +++ b/Validator Management Tool/Common/XMLParser.cs @@ -0,0 +1,30 @@ +namespace Validator_Management_Tool.Common +{ + using System; + using System.Collections.Generic; + using Skyline.DataMiner.CICD.Validators.Common.Model; + + /// + /// The old implementation to convert the XML file into check objects. + /// This class could only read them and not write them back to the XML file. + /// It is not used in the current application. + /// + public static class XMLParser + { + /// + /// Gets the different data types that are used in a dictionary, to switch between them. + /// + public static Dictionary Types { get; } = new Dictionary + { + { typeof(string), 0 }, + { typeof(uint), 1 }, + { typeof(bool), 2 }, + { typeof(Severity), 3 }, + { typeof(Certainty), 4 }, + { typeof(Category), 5 }, + { typeof(int), 6 }, + { typeof(Source), 7 }, + { typeof(FixImpact), 8 }, + }; + } +} \ No newline at end of file diff --git a/Validator Management Tool/Interfaces/MyCommand.cs b/Validator Management Tool/Interfaces/MyCommand.cs new file mode 100644 index 00000000..d72cda2a --- /dev/null +++ b/Validator Management Tool/Interfaces/MyCommand.cs @@ -0,0 +1,52 @@ +namespace Validator_Management_Tool.Interfaces +{ + using System; + using System.Windows.Input; + + public class MyCommand : ICommand + { + private readonly Action _TargetExecuteMethod; + private readonly Func _TargetCanExecuteMethod; + + public MyCommand(Action executeMethod) + { + _TargetExecuteMethod = executeMethod; + } + + public MyCommand(Action executeMethod, Func canExecuteMethod) + { + _TargetExecuteMethod = executeMethod; + _TargetCanExecuteMethod = canExecuteMethod; + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged(this, EventArgs.Empty); + } + + bool ICommand.CanExecute(object parameter) + { + if (_TargetCanExecuteMethod != null) + { + return _TargetCanExecuteMethod(); + } + + if (_TargetExecuteMethod != null) + { + return true; + } + + return false; + } + + // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command + + // Prism commands solve this in their implementation + public event EventHandler CanExecuteChanged = delegate { }; + + void ICommand.Execute(object parameter) + { + _TargetExecuteMethod?.Invoke(); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Interfaces/MyTCommand.cs b/Validator Management Tool/Interfaces/MyTCommand.cs new file mode 100644 index 00000000..fb8f844a --- /dev/null +++ b/Validator Management Tool/Interfaces/MyTCommand.cs @@ -0,0 +1,58 @@ +namespace Validator_Management_Tool.Interfaces +{ + using System; + using System.Windows.Input; + + public class MyTCommand : ICommand + { + private readonly Action _TargetExecuteMethod; + private readonly Func _TargetCanExecuteMethod; + + public MyTCommand(Action executeMethod) + { + _TargetExecuteMethod = executeMethod; + } + + public MyTCommand(Action executeMethod, Func canExecuteMethod) + { + _TargetExecuteMethod = executeMethod; + _TargetCanExecuteMethod = canExecuteMethod; + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged(this, EventArgs.Empty); + } + + #region ICommand Members + + bool ICommand.CanExecute(object parameter) + { + if (_TargetCanExecuteMethod != null) + { + T tparm = (T)parameter; + return _TargetCanExecuteMethod(tparm); + } + + if (_TargetExecuteMethod != null) + { + return true; + } + + return false; + } + + // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command + + // Prism commands solve this in their implementation + + public event EventHandler CanExecuteChanged = delegate { }; + + void ICommand.Execute(object parameter) + { + _TargetExecuteMethod?.Invoke((T)parameter); + } + + #endregion ICommand Members + } +} \ No newline at end of file diff --git a/Validator Management Tool/Model/CheckModel.cs b/Validator Management Tool/Model/CheckModel.cs new file mode 100644 index 00000000..5cd159ad --- /dev/null +++ b/Validator Management Tool/Model/CheckModel.cs @@ -0,0 +1,1474 @@ +namespace Validator_Management_Tool.Model +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Linq; + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Interfaces; + using Validator_Management_Tool.Views; + + /// + /// The Check Model that represents an error message. + /// + public class Check : INotifyPropertyChanged, IValidationResult + { + private uint checkId; + private uint errorId; + private string checkName; + private string name; + private string @namespace; + private Certainty certainty; + private FixImpact fixImpact; + private string groupDescription; + private string description; + private bool fromTemplate; + private uint templateId; + private string howToFix; + private string exampleCode; + private string details; + private Category category; + private Source source; + private uint categoryId; + private Severity severity; + private bool hasCodeFix; + private int line; + private string descriptionFormat; + private HashSet settedProperties; + private Dictionary errorMessages; + private Dictionary propertyHasError; + private bool error; + private bool hasChanges; + private ObservableCollection parameters; + private List<(string Message, bool AutoFixPopup)> autoFixWarnings; + + /// + /// Initializes a new instance of the class. + /// + public Check() + { + SettedProperties = new HashSet(); + ErrorMessages = new Dictionary(); + Parameters = new ObservableCollection(); + parameters.CollectionChanged += Parameters_CollectionChanged; + + PropertyHasError = new Dictionary(); + foreach (var property in typeof(Check).GetProperties()) + { + PropertyHasError.Add(property.Name, false); + } + + autoFixWarnings = new List<(string, bool)>(); + } + + /// + /// Initializes a new instance of the class. + /// The copy constructor will copy all the properties of the parameter object. + /// + /// Object from which the new object has to copy his properties. + public Check(Check check) + { + Copy(check); + } + + /// + /// Handles when a property calls an property changed event, to notify the UI. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets or sets the ID of the check to which to error message belongs. + /// + public uint CheckId + { + get + { + return checkId; + } + + set + { + if (checkId != value) + { + checkId = value; + RaisePropertyChanged("CheckCollectionId"); + RaisePropertyChanged("FullId"); + } + + SettedProperties.Add("CheckCollectionId"); + SettedProperties.Add("FullId"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the ID of the error message. + /// + public uint ErrorId + { + get + { + return errorId; + } + + set + { + if (errorId != value) + { + errorId = value; + RaisePropertyChanged("ErrorId"); + RaisePropertyChanged("FullId"); + } + + SettedProperties.Add("ErrorId"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the name of the check to which the error message belongs. + /// There is logic that will check if the name is acceptable. + /// + public string CheckName + { + get + { + return checkName; + } + + set + { + if (value != null) + { + if (ErrorMessages.ContainsKey("CheckName")) + { + PropertyHasError["CheckName"] = false; + ErrorMessages.Remove("CheckName"); + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + if (value == String.Empty) + { + AddError("CheckName", "Choose a correct check name!"); + } + else if (Settings.ForbiddenStrings.Contains(value)) + { + string errorString = String.Format( + "The checkName '{0}' is a forbidden string!", + value); + AddError("CheckName", errorString); + } + else if (!Char.IsUpper(value[0])) + { + string errorString = String.Format( + "The checkName '{0}' has to start with a capital!", + value); + AddError("CheckName", errorString); + } + else if (value.IndexOf(" ") != -1) + { + string errorString = String.Format( + "The checkName '{0}' can't contain a space!", + value); + AddError("CheckName", errorString); + } + else if (value.Any(c => !Char.IsLetter(c))) + { + string errorString = String.Format( + "The checkName '{0}' can't contain symbols!", + value); + AddError("CheckName", errorString); + } + + checkName = value; + RaisePropertyChanged("CheckName"); + } + else + { + AddError("CheckName", "Choose a correct check name!"); + checkName = String.Empty; + RaisePropertyChanged("CheckName"); + } + + SettedProperties.Add("CheckName"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the name of the error message. + /// There is logic that will check if the name is acceptable. + /// + public string Name + { + get + { + return name; + } + + set + { + if (name != value) + { + if (ErrorMessages.ContainsKey("Name")) + { + PropertyHasError["Name"] = false; + ErrorMessages.Remove("Name"); + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + + if (value == String.Empty) + { + string errorString = "The name can't be empty!"; + AddError("Name", errorString); + } + else if (Settings.ForbiddenStrings.Contains(value)) + { + string errorString = String.Format( + "The name '{0}' is a forbidden string!", + value); + AddError("Name", errorString); + } + else if (!Char.IsUpper(value[0])) + { + string errorString = String.Format( + "The name '{0}' has to start with a capital!", + value); + AddError("Name", errorString); + } + else if (value.IndexOf(" ") != -1) + { + string errorString = String.Format( + "The name '{0}' can't contain a space!", + value); + AddError("Name", errorString); + } + else if (Char.IsNumber(value.First())) + { + string errorString = String.Format( + "The name '{0}' can't start with a number!", + value); + AddError("Name", errorString); + } + else if (value.Any(c => !(Char.IsLetter(c) || Char.IsNumber(c) || Char.Equals(c, '_')))) + { + string errorString = String.Format( + "The name '{0}' can't contain symbols!", + value); + AddError("Name", errorString); + } + + name = value; + RaisePropertyChanged("Name"); + } + + SettedProperties.Add("Name"); + + HasChanges = true; + } + } + + /// + /// Gets the concatenation of the namespace and the check name. + /// + public string FullNamespace + { + get + { + return @namespace + "." + checkName; + } + } + + /// + /// Gets the full Id of the error message. + /// + public string FullId + { + get + { + return categoryId.ToString() + "." + checkId.ToString() + "." + errorId.ToString(); + } + } + + /// + /// Gets or sets the namespace to which the check belongs. + /// There is logic that will check if the namespace is acceptable. + /// + public string Namespace + { + get + { + return @namespace; + } + + set + { + if (@namespace != value) + { + if (ErrorMessages.ContainsKey("Namespace")) + { + ErrorMessages.Remove("Namespace"); + PropertyHasError["Namespace"] = false; + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + if (String.IsNullOrEmpty(value)) + { + string errorString = "The namespace can't be empty!"; + AddError("Namespace", errorString); + } + else + { + var splitNamespace = value.Split(new string[] { "." }, StringSplitOptions.None); + foreach (var namespacePart in splitNamespace) + { + if (namespacePart != String.Empty) + { + if (Settings.ForbiddenStrings.Contains(namespacePart)) + { + string errorString = String.Format( + "The namespace-part '{0}' is a forbidden string!", + namespacePart); + AddError("Namespace", errorString); + } + else if (!Char.IsUpper(namespacePart[0])) + { + string errorString = String.Format( + "The namespace-part '{0}' has to start with a capital!", + namespacePart); + AddError("Namespace", errorString); + } + else if (namespacePart.IndexOf(" ") != -1) + { + string errorString = String.Format( + "The namespace-part '{0}' can't contain a space!", + namespacePart); + AddError("Namespace", errorString); + } + else if (namespacePart.Any(c => !Char.IsLetter(c) && c != '.')) + { + string errorString = String.Format( + "The namespace-part '{0}' can't contain a symbol!", + namespacePart); + AddError("Namespace", errorString); + } + } + else + { + string errorString = "A part of the namespace can't be empty!"; + AddError("Namespace", errorString); + } + } + } + + @namespace = value; + RaisePropertyChanged("Namespace"); + } + + SettedProperties.Add("Namespace"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the certainty for the error message. + /// + public Certainty Certainty + { + get + { + return certainty; + } + + set + { + certainty = value; + RaisePropertyChanged("Certainty"); + RaisePropertyChanged("CertaintyEnabled"); + + SettedProperties.Add("Certainty"); + + HasChanges = true; + } + } + + /// + /// Gets or sets a value indicating whether a error message is breaking change or not. + /// + public FixImpact FixImpact + { + get + { + return fixImpact; + } + + set + { + fixImpact = value; + RaisePropertyChanged("FixImpact"); + RaisePropertyChanged("FixImpactEnabled"); + + SettedProperties.Add("FixImpact"); + + HasChanges = true; + } + } + + /// + /// Generic description to be used when grouping multiple instances of an error message. + /// + public string GroupDescription + { + get + { + return groupDescription; + } + + set + { + groupDescription = value ?? ""; + + SettedProperties.Add("GroupDescription"); + } + } + + /// + /// Gets or sets the description string that describes the error message. + /// There is logic that will check if the description is acceptable. + /// + public string Description + { + get + { + return description; + } + + set + { + if (ErrorMessages.ContainsKey("Description")) + { + ErrorMessages.Remove("Description"); + PropertyHasError["Description"] = false; + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + if (value != String.Empty) + { + List parameterIndexes = new List(); + + // Get all the positions of the parameters + for (int i = value.IndexOf('{'); i > -1; i = value.IndexOf('{', i + 1)) + { + if (i + 2 < value.Length && value[i + 2] == '}' && Int32.TryParse(value[i + 1].ToString(), out int number)) + { + bool exist = false; + foreach (var item in parameterIndexes) + { + if (value[item] == value[i + 1]) + { + exist = true; + } + } + + if (!exist) + { + parameterIndexes.Add(i + 1); + } + } + } + + int amountOfParameters = parameterIndexes.Count; + + // Check if the numbers of the indexes make sense + bool incorrect = false; + int count = 0; + while (!incorrect && count < amountOfParameters) + { + char index = value[parameterIndexes[count]]; + if (Int32.Parse(index.ToString()) > amountOfParameters - 1) + { + incorrect = true; + } + + count++; + } + + if (incorrect) + { + string errorString = String.Format( + "The parameter indexes in the description : {0} are incorrect!", + value); + AddError("Description", errorString); + } + else + { + if (Parameters == null) + { + if (parameterIndexes.Count > 0) + { + ObservableCollection parameterCollection = new ObservableCollection(); + for (int i = 0; i < parameterIndexes.Count; i++) + { + parameterCollection.Add(new Serialization.InputParameter { Id = i.ToString(), Text = "newParameter" }); + } + + Parameters = parameterCollection; + } + } + else + { + // Check if the amount of parameters matches with the format string + if (Parameters.Count < parameterIndexes.Count) + { + ObservableCollection parameterCollection = new ObservableCollection(); + int paramCount = 0; + foreach (var param in Parameters) + { + parameterCollection.Add(new Serialization.InputParameter { Id = paramCount.ToString(), Text = param.Text }); + paramCount++; + } + + int difference = parameterIndexes.Count - parameterCollection.Count; + + for (int i = 0; i < difference; i++) + { + parameterCollection.Add(new Serialization.InputParameter { Id = (paramCount + i).ToString(), Text = "newParameter" }); + } + + Parameters = parameterCollection; + } + else if (Parameters.Count > parameterIndexes.Count) + { + ObservableCollection parameterCollection = new ObservableCollection(); + foreach (var param in Parameters) + { + parameterCollection.Add(new Serialization.InputParameter { Id = param.Id, Text = param.Text }); + } + + int difference = parameterCollection.Count - parameterIndexes.Count; + for (int i = 0; i < difference; i++) + { + parameterCollection.RemoveAt(parameterCollection.Count - 1); + } + + Parameters = parameterCollection; + } + } + } + } + else + { + AddError("Description", "The description can't be empty!"); + } + + description = value; + + SettedProperties.Add("Description"); + + CheckDescriptionParameters(); + RaisePropertyChanged("Description"); + HasChanges = true; + } + } + + /// + /// Gets or sets a value indicating whether the description is from a description template or not. + /// + public bool FromTemplate + { + get + { + return fromTemplate; + } + + set + { + if (value != fromTemplate) + { + fromTemplate = value; + RaisePropertyChanged("FromTemplate"); + CheckDescriptionParameters(); + } + } + } + + /// + /// Gets or sets the template id if the description is from a description template. + /// + public uint TemplateId + { + get + { + return templateId; + } + + set + { + if (value != templateId) + { + templateId = value; + RaisePropertyChanged("TemplateId"); + } + } + } + + /// + /// Gets or sets the description of how to fix the error message. + /// + public string HowToFix + { + get + { + return howToFix; + } + + set + { + if (howToFix != value) + { + howToFix = value; + RaisePropertyChanged("HowToFix"); + RaisePropertyChanged("HowToFixEnabled"); + + if (value == null) + { + SettedProperties.Remove("HowToFix"); + } + else + { + SettedProperties.Add("HowToFix"); + } + + HasChanges = true; + } + } + } + + /// + /// Gets or sets a piece of example code to fix the error. + /// + public string ExampleCode + { + get + { + return exampleCode; + } + + set + { + if (exampleCode != value) + { + exampleCode = value; + RaisePropertyChanged("ExampleCodeEnabled"); + RaisePropertyChanged("ExampleCode"); + } + + if (value == null) + { + SettedProperties.Remove("ExampleCode"); + } + else + { + SettedProperties.Add("ExampleCode"); + } + + HasChanges = true; + } + } + + /// + /// Gets or sets some details about the error message. + /// + public string Details + { + get + { + return details; + } + + set + { + if (details != value) + { + details = value; + RaisePropertyChanged("DetailsEnabled"); + RaisePropertyChanged("Details"); + } + + if (value == null) + { + SettedProperties.Remove("Details"); + } + else + { + SettedProperties.Add("Details"); + } + + HasChanges = true; + } + } + + /// + /// Gets or sets the category of the error message. + /// + public Category Category + { + get + { + return category; + } + + set + { + if (category != value) + { + if (ErrorMessages.ContainsKey("Category")) + { + PropertyHasError["Category"] = false; + ErrorMessages.Remove("Category"); + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + if (value == Category.Undefined) + { + AddError("Category", "Choose a correct Category!"); + } + + category = value; + RaisePropertyChanged("Category"); + } + + SettedProperties.Add("Category"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the source of the error message. + /// + public Source Source + { + get + { + return source; + } + + set + { + if (source != value) + { + if (ErrorMessages.ContainsKey("Source")) + { + PropertyHasError["Source"] = false; + ErrorMessages.Remove("Source"); + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + if (value == Source.Undefined) + { + AddError("Source", "Choose a correct Source!"); + } + + source = value; + RaisePropertyChanged("Source"); + } + + SettedProperties.Add("Source"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the ID of the category to which the error message belongs. + /// + public uint CategoryId + { + get + { + return categoryId; + } + + set + { + if (value != categoryId) + { + categoryId = value; + RaisePropertyChanged("CategoryId"); + RaisePropertyChanged("FullId"); + } + } + } + + /// + /// Gets or sets the severity of the error message. + /// + public Severity Severity + { + get + { + return severity; + } + + set + { + severity = value; + RaisePropertyChanged("Severity"); + RaisePropertyChanged("SeverityEnabled"); + + SettedProperties.Add("Severity"); + + HasChanges = true; + } + } + + /// + /// Gets or sets a value indicating whether the error message has a code fix or not. + /// + public bool HasCodeFix + { + get + { + return hasCodeFix; + } + + set + { + if (hasCodeFix != value) + { + hasCodeFix = value; + RaisePropertyChanged("HasCodeFix"); + } + + SettedProperties.Add("HasCodeFix"); + + HasChanges = true; + } + } + + /// + /// Gets or sets the line number. + /// + public int Line + { + get + { + return line; + } + + set + { + if (line != value) + { + line = value; + RaisePropertyChanged("Line"); + } + } + } + + /// + /// Gets or sets the format string for the description of the error message. + /// + public string DescriptionFormat + { + get + { + return descriptionFormat; + } + + set + { + if (descriptionFormat != value) + { + descriptionFormat = value; + RaisePropertyChanged("DescriptionFormat"); + } + } + } + + /// + /// Gets or sets a collection of serialization input parameters that is used for dynamic binding to the View. + /// + public ObservableCollection Parameters + { + get + { + return parameters; + } + + set + { + parameters = value; + parameters.CollectionChanged += Parameters_CollectionChanged; + HasChanges = true; + RaisePropertyChanged("Parameters"); + } + } + + /// + /// Gets or sets a list that holds the names of the properties that are set while parsing the XML file + /// or when editing the error messages in the UI. This list is used to know which properties + /// needs to be asked as a parameter in the generated classes and also to determine whether a + /// tag needs to be written to the XML. + /// + public HashSet SettedProperties + { + get + { + return settedProperties; + } + + set + { + if (settedProperties != value) + { + settedProperties = value; + RaisePropertyChanged("SettedProperties"); + } + } + } + + /// + /// Gets or sets a dictionary that contains the property name as key and the error message(s) as value if there is an error. + /// + public Dictionary ErrorMessages + { + get + { + return errorMessages; + } + + set + { + if (errorMessages != value) + { + errorMessages = value; + RaisePropertyChanged("ErrorMessage"); + } + } + } + + /// + /// Gets or sets a value indicating whether the error message has an error or not. + /// + public bool Error + { + get + { + return error; + } + + set + { + if (error != value) + { + error = value; + RaisePropertyChanged("Error"); + } + } + } + + /// + /// Gets or sets a value indicating whether the error message has changes or not. + /// + public bool HasChanges + { + get + { + return hasChanges; + } + + set + { + if (value != hasChanges) + { + hasChanges = value; + RaisePropertyChanged("HasChanges"); + } + } + } + + /// + /// Gets or sets a dictionary that has all the properties as key and for each property has a boolean + /// as value that decides whether a property has an error. + /// + public Dictionary PropertyHasError + { + get + { + return propertyHasError; + } + + set + { + if (propertyHasError != value) + { + propertyHasError = value; + RaisePropertyChanged("PropertyHasError"); + } + } + } + + /// + /// Gets the command that opens a new detail view window when called from the View. + /// + public MyCommand EditCommand + { + get + { + return new MyCommand(OnEdit); + } + } + + /// + /// Gets or sets a value indicating whether the example code tag should be included in the XML or not. + /// + public bool ExampleCodeEnabled + { + get + { + return SettedProperties.Contains("ExampleCode"); + } + + set + { + if (value) + { + if (ExampleCode == null) + { + ExampleCode = String.Empty; + } + + SettedProperties.Add("ExampleCode"); + } + else + { + ExampleCode = null; + SettedProperties.Remove("ExampleCode"); + } + + RaisePropertyChanged("ExampleCodeEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the how to fix tag should be included in the XML or not. + /// + public bool HowToFixEnabled + { + get + { + return SettedProperties.Contains("HowToFix"); + } + + set + { + if (value) + { + if (HowToFix == null) + { + HowToFix = String.Empty; + } + + SettedProperties.Add("HowToFix"); + } + else + { + HowToFix = null; + SettedProperties.Remove("HowToFix"); + } + + RaisePropertyChanged("HowToFixEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the details tag should be included in the XML or not. + /// + public bool DetailsEnabled + { + get + { + return SettedProperties.Contains("Details"); + } + + set + { + if (value) + { + if (Details == null) + { + Details = String.Empty; + } + + SettedProperties.Add("Details"); + } + else + { + Details = null; + SettedProperties.Remove("Details"); + } + + RaisePropertyChanged("DetailsEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the certainty tag should be included in the XML or not. + /// + public bool CertaintyEnabled + { + get + { + return SettedProperties.Contains("Certainty"); + } + + set + { + if (value) + { + SettedProperties.Add("Certainty"); + } + else + { + SettedProperties.Remove("Certainty"); + } + + RaisePropertyChanged("CertaintyEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the severity tag should be included in the XML or not. + /// + public bool SeverityEnabled + { + get + { + return SettedProperties.Contains("Severity"); + } + + set + { + if (value) + { + SettedProperties.Add("Severity"); + } + else + { + SettedProperties.Remove("Severity"); + } + + RaisePropertyChanged("SeverityEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the has code fix tag should be included in the XML or not. + /// + public bool HasCodeFixEnabled + { + get + { + return SettedProperties.Contains("HasCodeFix"); + } + + set + { + if (value) + { + SettedProperties.Add("HasCodeFix"); + } + else + { + SettedProperties.Remove("HasCodeFix"); + } + + RaisePropertyChanged("HasCodeFixEnabled"); + } + } + + /// + /// Gets or sets a value indicating whether the is breaking change tag should be included in the XML or not. + /// + public bool FixImpactEnabled + { + get + { + return SettedProperties.Contains("FixImpact"); + } + + set + { + if (value) + { + SettedProperties.Add("FixImpact"); + } + else + { + SettedProperties.Remove("FixImpact"); + } + + RaisePropertyChanged("FixImpactEnabled"); + } + } + + public IReadable ReferenceNode => null; + + public IReadable PositionNode => null; + + public int Position => -1; + + public List SubResults => null; + + public object[] DescriptionParameters => throw new NotImplementedException(); + + public List<(string Message, bool AutoFixPopup)> AutoFixWarnings + { + get => autoFixWarnings; + + set + { + autoFixWarnings = value; + RaisePropertyChanged("AutoFixWarnings"); + + SettedProperties.Add("AutoFixWarnings"); + + HasChanges = true; + } + } + + public (int TablePid, string Name)? DveExport => throw new NotImplementedException(); + + /// + /// Copies all the properties of the parameter object. + /// + /// Object from which the called object has to copy his properties. + public void Copy(Check check) + { + ErrorMessages = new Dictionary(check.ErrorMessages); + Error = check.Error; + SettedProperties = new HashSet(check.SettedProperties); + PropertyHasError = new Dictionary(check.PropertyHasError); + HasChanges = check.HasChanges; + + Parameters = new ObservableCollection(); + foreach (var item in check.Parameters) + { + // Creation of a new InputParameter to stop the sharing of data by reference + Parameters.Add(new Serialization.InputParameter { Id = item.Id, Text = item.Text, Value = item.Value }); + } + + var checkProperties = typeof(Check).GetProperties(); + foreach (var property in checkProperties) + { + if (check.SettedProperties.Contains(property.Name) && property.GetSetMethod() != null) + { + property.SetValue(this, property.GetValue(check)); + } + } + + CategoryId = check.CategoryId; + CheckId = check.CheckId; + Line = check.Line; + DescriptionFormat = check.DescriptionFormat; + FromTemplate = check.FromTemplate; + TemplateId = check.TemplateId; + AutoFixWarnings = new List<(string, bool)>(check.AutoFixWarnings ?? new List<(string, bool)>()); + } + + /// + /// Checks if the parameters meet the requirements. + /// If not, errors are generated. + /// Is called whenever the description parameters are changed. + /// + public void CheckDescriptionParameters() + { + if (ErrorMessages.ContainsKey("Parameters")) + { + ErrorMessages.Remove("Parameters"); + PropertyHasError["Parameters"] = false; + if (ErrorMessages.Count == 0) + { + Error = false; + } + + NotifyErrorUI(); + } + + List parameterList = new List(); + foreach (var parameter in Parameters) + { + if (parameter.Text == String.Empty) + { + AddError("Parameters", "The description parameter can't be empty!"); + continue; + } + + if (parameterList.Contains(parameter.Text)) + { + AddError("Parameters", $"The error message {Name} has duplicate description parameters: {parameter.Text}!"); + } + else if (!Char.IsLower(parameter.Text[0])) + { + AddError("Parameters", $"The description parameter '{parameter.Text}' has to start with a lowercase!"); + } + else if (parameter.Text.IndexOf(" ") != -1) + { + AddError("Parameters", $"The description parameter '{parameter.Text}' can't contain a space!"); + } + else if (Settings.ForbiddenStrings.Contains(parameter.Text)) + { + AddError("Parameters", $"The description parameter '{parameter.Text}' contains a forbidden string!"); + } + else if (parameter.Text.Any(c => !(Char.IsLetter(c) || Char.IsNumber(c))) && !Char.IsNumber(parameter.Text[0])) + { + string errorString = $"The description parameter '{parameter.Text}' can't contain symbols!"; + AddError("Parameters", errorString); + } + else if (parameter.Text == "newParameter") + { + AddError("Parameters", $"The description parameter {parameter.Id} has to be named!"); + } + + parameterList.Add(parameter.Text); + } + } + + /// + /// Refreshes the error for the description parameters. + /// + public void RefreshParameters() + { + CheckDescriptionParameters(); + } + + /// + /// Notifies the UI and is called whenever a error from a property is changed/added. + /// + private void NotifyErrorUI() + { + RaisePropertyChanged("PropertyHasError"); + } + + /// + /// Adds a error to the error message. It will set and edit all the required + /// values and list so that the error will be shown in the View. + /// + /// The name of the property that has an error. + /// The error message that describes the error for the user. + private void AddError(string propertyName, string errorMessage) + { + if (ErrorMessages.TryGetValue(propertyName, out string previousError)) + { + ErrorMessages.Remove(propertyName); + PropertyHasError[propertyName] = true; + ErrorMessages.Add(propertyName, previousError + "; " + errorMessage); + NotifyErrorUI(); + RaisePropertyChanged("ErrorMessages"); + } + else + { + ErrorMessages.Add(propertyName, errorMessage); + PropertyHasError[propertyName] = true; + Error = true; + NotifyErrorUI(); + RaisePropertyChanged("ErrorMessages"); + } + } + + /// + /// Is added to the parameter list to notify the UI when the list is changed. + /// + private void Parameters_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + CheckDescriptionParameters(); + if (e.NewItems != null) + { + foreach (object parameter in e.NewItems) + { + (parameter as INotifyPropertyChanged).PropertyChanged + += new PropertyChangedEventHandler(Parameter_PropertyChanged); + } + } + + if (e.OldItems != null) + { + foreach (object parameter in e.OldItems) + { + (parameter as INotifyPropertyChanged).PropertyChanged + -= new PropertyChangedEventHandler(Parameter_PropertyChanged); + } + } + } + + /// + /// Is added to the parameter list to get notified when a property of a member of the list is changed. + /// + private void Parameter_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + CheckDescriptionParameters(); + } + + /// + /// Notifies the UI that the given property is changed. + /// + /// The name of the property that is changed. + private void RaisePropertyChanged(string property) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + + /// + /// Is executed when the EditCommand is called through the View. + /// Will open a new edit view window for this error message. + /// + private void OnEdit() + { + CheckEditView checkEditView = new CheckEditView(this); + checkEditView.ShowDialog(); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Properties/Annotations.cs b/Validator Management Tool/Properties/Annotations.cs new file mode 100644 index 00000000..c1c64144 --- /dev/null +++ b/Validator Management Tool/Properties/Annotations.cs @@ -0,0 +1,1078 @@ +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +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. */ + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Validator_Management_Tool.Properties +{ + using System; + + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] + public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = true)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for method output + /// means that the methos doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by R# analysis.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] + public string Contract { get; private set; } + + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] + public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] + public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + public sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + + public PathReferenceAttribute([NotNull, PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] + public string BasePath { get; private set; } + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// > + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RegexPatternAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + public sealed class NoReorderAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] + public string TagName { get; private set; } + + [NotNull] + public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] + public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] + public string Type { get; private set; } + + [NotNull] + public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] + public string Directive { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute { } +} \ No newline at end of file diff --git a/Validator Management Tool/Properties/AssemblyInfo.cs b/Validator Management Tool/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..9b00f3dd --- /dev/null +++ b/Validator Management Tool/Properties/AssemblyInfo.cs @@ -0,0 +1,57 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DIS Validator Management Tool")] +[assembly: AssemblyDescription("")] +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif +[assembly: AssemblyCompany("Skyline Communications")] +[assembly: AssemblyProduct("DataMiner Integration Studio")] +[assembly: AssemblyCopyright("Copyright © 2023 Skyline Communications")] +[assembly: AssemblyTrademark("Skyline Communications")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Validator Management Tool/Resources/skyline.ico b/Validator Management Tool/Resources/skyline.ico new file mode 100644 index 00000000..e204f1e7 Binary files /dev/null and b/Validator Management Tool/Resources/skyline.ico differ diff --git a/Validator Management Tool/Serialization/Serializer.cs b/Validator Management Tool/Serialization/Serializer.cs new file mode 100644 index 00000000..c5e88db7 --- /dev/null +++ b/Validator Management Tool/Serialization/Serializer.cs @@ -0,0 +1,1168 @@ +namespace Validator_Management_Tool.Serialization +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.Linq; + using System.Text; + using System.Windows; + using System.Xml; + using System.Xml.Serialization; + using Skyline.DataMiner.CICD.Validators.Common.Model; + + /// + /// Static class that contains all the methods to read and write to and from the XML file. + /// Also the methods to convert the XML class hierarchy to a list of check objects, is located in here. + /// + public static class Serializer + { + /// + /// List that holds the error messages from the errors that occur while parsing. + /// + public static List ParsingErrors = new List(); + + /// + /// Static object that holds all the data from the XML file. + /// + public static Validator xmlValidator; + + /// + /// Gets the different data types that are used in a dictionary, to switch between them. + /// + public static Dictionary Types { get; } = new Dictionary + { + { typeof(string), 0 }, + { typeof(uint), 1 }, + { typeof(bool), 2 }, + { typeof(Severity), 3 }, + { typeof(Certainty), 4 }, + { typeof(Category), 5 }, + { typeof(int), 6 }, + { typeof(Source), 7 }, + { typeof(FixImpact), 8 }, + }; + + /// + /// Reads the XML file and converts the content in a object. + /// + /// The relative or absolute path to the XML file. + /// All the data from the XML file in the corresponding class hierarchy. + public static Validator ReadXml(string filePath) + { + Validator validator = null; + ParsingErrors.Clear(); + XmlSerializer serializer = new XmlSerializer(typeof(Validator)); + + serializer.UnknownAttribute += Serializer_UnknownAttribute; + serializer.UnknownElement += Serializer_UnknownElement; + + using (StreamReader reader = new StreamReader(filePath)) + { + validator = (Validator)serializer.Deserialize(reader); + reader.Close(); + } + + ValidateErrorMessage(validator); + + if (ParsingErrors.Count > 0) + { + StringBuilder sb = new StringBuilder(); + ParsingErrors.Sort(); + foreach (var error in ParsingErrors) + { + sb.AppendLine(error); + } + + Console.Error.WriteLine(sb.ToString()); + } + + xmlValidator = validator; + + return validator; + } + + private static void ValidateErrorMessage(Validator validator) + { + foreach (var cat in validator.ValidationChecks.Categories.Category) + { + foreach (var check in cat.Checks.Check) + { + foreach (var error in check.ErrorMessages.ErrorMessage) + { + if (error.Description.TemplateId != null && error.Description.Format != null) + { + string fullId = $"{cat.Id}.{check.Id}.{error.Id}"; + + ParsingErrors.Add($"[{fullId}] Description@TemplateId and Description\\Format can't be used together!"); + } + } + } + } + } + + /// + /// Writes the static object to a XML file. + /// + /// The relative or absolute path to the XML file. + public static void WriteXml(string filePath) + { + XmlSerializer serializer = new XmlSerializer(typeof(Validator)); + + using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8)) + using (XmlWriter xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true, IndentChars = "\t" })) + { + serializer.Serialize(xmlWriter, xmlValidator); + } + } + + /// + /// Converts a list of error messages from the UI to a object, and saves it in the static object. + /// + /// List of s that contains the different error messages. + /// List of s that contains the different templates that are used in the error messages. + /// List of that are currently used in the error messages, the categories also contain the corresponding checks that are used. + /// A object that contains all the content in a XML class hierarchy. + public static Validator SetChecks(ObservableCollection checks, List templates, List categoryListing) + { + var validator = new Validator(); + var categories = new Categories(); + List categoryList = new List(); + + var lookupTemplates = templates.ToDictionary(key => key.Id); + + // Loop through all the existing categories and checks and add them to the new Categories object + foreach (var category in categoryListing) + { + Category newCategory = new Category() + { + Name = category.Name, + Id = category.Id, + Checks = new Checks() + }; + + List checkList = new List(); + foreach (var check in category.Checks.Check) + { + checkList.Add(new Check() + { + Id = check.Id, + Name = check.Name, + ErrorMessages = new ErrorMessages() + { + ErrorMessage = new List() + } + }); + } + + newCategory.Checks.Check = checkList; + categoryList.Add(newCategory); + } + + foreach (var errorMessage in checks) + { + foreach (var category in categoryList) + { + foreach (var check in category.Checks.Check) + { + if (errorMessage.Category.ToString() == category.Name && errorMessage.CheckName == check.Name.Text && errorMessage.Namespace == check.Name.Namespace) + { + // Namespace from the check + check.Name.Namespace = errorMessage.Namespace; + + // Name + ErrorMessage newError = new ErrorMessage + { + Name = new Name + { + Text = errorMessage.Name + } + }; + + // ErrorId + newError.Id = errorMessage.ErrorId.ToString(); + + // Description + if (errorMessage.FromTemplate) + { + // If it is from a template + newError.Description = new Description + { + TemplateId = errorMessage.TemplateId.ToString() + }; + + if (errorMessage.Parameters.Any(x => !String.IsNullOrWhiteSpace(x.Value))) + { + // Some have overridden values + newError.Description.InputParameters = new InputParameters + { + InputParameter = new List(errorMessage.Parameters.Count) + }; + + // Get DescriptionTemplate to check if they are all there...? Or only on code generation + DescriptionTemplate descriptionTemplate = lookupTemplates[errorMessage.TemplateId]; + + List coveredIds = new List(errorMessage.Parameters.Count); + foreach (var item in errorMessage.Parameters) + { + coveredIds.Add(item.Id); + newError.Description.InputParameters.InputParameter.Add(item); + } + + if (descriptionTemplate.TemplateInputParameters.InputParameter.Count != coveredIds.Count) + { + foreach (var item in descriptionTemplate.TemplateInputParameters.InputParameter) + { + // Only for the ones that aren't covered + if (coveredIds.Contains(item.Id)) + { + continue; + } + + // Add remaining parameters + newError.Description.InputParameters.InputParameter.Add(item); + } + } + } + } + else + { + // If it is a hard coded description + newError.Description = new Description + { + Format = errorMessage.Description, + InputParameters = new InputParameters(), + }; + newError.Description.InputParameters.InputParameter = new List(); + + if (errorMessage.Parameters != null) + { + newError.Description.InputParameters.InputParameter = new List(errorMessage.Parameters); + } + } + + // AutoFixWarnings + if (errorMessage.AutoFixWarnings.Count != 0) + { + newError.AutoFixWarnings = new AutoFixWarnings + { + AutoFixWarning = errorMessage.AutoFixWarnings.Select(x => new AutoFixWarning { AutoFixPopup = x.AutoFixPopup, Text = x.Message }).ToList() + }; + } + + var checkProperties = typeof(Model.Check).GetProperties(); + var newCheckProperties = typeof(ErrorMessage).GetProperties(); + + foreach (var property in checkProperties) + { + if (property.Name == "Description" || property.Name == "Name" || property.Name == "AutoFixWarnings" || + !errorMessage.SettedProperties.Contains(property.Name)) + { + continue; + } + + foreach (var newProperty in newCheckProperties) + { + if (property.Name != newProperty.Name) + { + continue; + } + + if (newProperty.PropertyType == typeof(XmlCDataSection)) + { + if (property.GetValue(errorMessage) != null) + { + XmlDocument xdoc = new XmlDocument(); + newProperty.SetValue(newError, xdoc.CreateCDataSection(property.GetValue(errorMessage).ToString().Replace(Environment.NewLine, "[NewLine]"))); + } + } + else + { + newProperty.SetValue(newError, property.GetValue(errorMessage).ToString()); + } + } + } + + check.ErrorMessages.ErrorMessage.Add(newError); + } + } + } + } + + validator.ValidationChecks = new ValidationChecks(); + categories.Category = categoryList; + validator.ValidationChecks.Categories = categories; + validator.ValidationChecks.DescriptionTemplates = new DescriptionTemplates + { + DescriptionTemplate = templates.OrderBy(x => x.Id).ToList() + }; + + xmlValidator = validator; + + return validator; + } + + /// + /// Gets the categories that are currently used. + /// + /// A list of . + public static List GetCategories() + { + return xmlValidator.ValidationChecks.Categories.Category; + } + + /// + /// Gets the templates that are currently used. + /// + /// A list of . + public static List GetTemplates() + { + return xmlValidator.ValidationChecks.DescriptionTemplates.DescriptionTemplate; + } + + /// + /// Converts the XML class hierarchy into a collection of error messages that can be used to generate code. + /// + /// A list of . + public static ObservableCollection GetChecks() + { + ObservableCollection checks = new ObservableCollection(); + + // Get all the properties of the interface with Reflection + var checkProperties = typeof(Model.Check).GetProperties(); + + // Check for duplicate templateID's + HasDuplicateTemplateIDs(xmlValidator); + + List namespaces = new List(); + + // List with all the category Id's to prevent duplicates + List categoryIds = new List(); + + // Loop through all the categories + foreach (var category in xmlValidator.ValidationChecks.Categories.Category) + { + // List with all the check Id's within a category to prevent duplicates + List checkIds = new List(); + bool categoryIdError = false; + + if (category?.Id != null && UInt32.TryParse(category.Id, out uint id)) + { + if (!categoryIds.Contains(id)) + { + categoryIds.Add(id); + } + else + { + categoryIdError = true; + } + } + + if (category?.Checks == null) + { + continue; + } + + // Loop through all the checks within a category + foreach (var check in category.Checks.Check) + { + bool checkIdError = false; + if (check.Id != null && UInt32.TryParse(check.Id, out uint checkId)) + { + if (!checkIds.Contains(checkId)) + { + checkIds.Add(checkId); + } + else + { + checkIdError = true; + } + } + + // List with all the error Id's within a check to prevent duplicates + List errorIds = new List(); + + if (check.ErrorMessages == null) + { + continue; + } + + // Loop through all the error messages within a check + foreach (var errorMessage in check.ErrorMessages.ErrorMessage) + { + Model.Check validationCheck = new Model.Check + { + CheckId = UInt32.Parse(check.Id) + }; + + if (categoryIdError) + { + string errorString = String.Format( + "The Category {0} has a duplicate ID: {1}, please edit the XML file manually!", + category.Name, + category.Id.ToString()); + AddError(validationCheck, "CheckId", errorString); + } + + if (checkIdError) + { + string errorString = String.Format( + "The Check {0} has a duplicate ID: {1}, please edit the XML file manually!", + check.Name.Text, + check.Id.ToString()); + AddError(validationCheck, "CheckId", errorString); + } + + if (check.Name != null) + { + if (check.Name.Text != null) + { + validationCheck.CheckName = check.Name.Text; + } + else + { + validationCheck.CheckName = String.Empty; + } + + if (check.Name.Namespace != null) + { + string @namespace = check.Name.Namespace; + + validationCheck.Namespace = @namespace; + + if (!namespaces.Contains(@namespace)) + { + namespaces.Add(@namespace); + } + } + else + { + validationCheck.Namespace = String.Empty; + string errorString = String.Format( + "There is no Namespace attribute present in check {0}, a namespace is mandatory!", + check.Name.Text); + AddError(validationCheck, "Namespace", errorString); + } + } + else + { + validationCheck.CheckName = String.Empty; + validationCheck.Namespace = String.Empty; + string errorString = String.Format( + "There is no Name tag present for check {0}, a name is mandatory!", + check.Id); + AddError(validationCheck, "Name", errorString); + } + + // Add the Category + if (category != null) + { + if (category.Id != null && category.Name != null) + { + if (UInt32.TryParse(category.Id, out uint catId)) + { + validationCheck.CategoryId = catId; + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + category.Id, + "Category ID", + "UInt32"); + + AddError(validationCheck, "Category", errorString); + } + + if (Enum.TryParse(category.Name.ToString(), out Skyline.DataMiner.CICD.Validators.Common.Model.Category categoryName)) + { + validationCheck.Category = categoryName; + } + else + { + validationCheck.Category = Skyline.DataMiner.CICD.Validators.Common.Model.Category.Undefined; + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + category.Name, + "Category", + "Category"); + + AddError(validationCheck, "Category", errorString); + } + } + else + { + validationCheck.Category = Skyline.DataMiner.CICD.Validators.Common.Model.Category.Undefined; + string errorString = String.Format( + "The {0} id or Name tag is missing!", + "Category"); + + AddError(validationCheck, "Category", errorString); + } + } + else + { + validationCheck.Category = Skyline.DataMiner.CICD.Validators.Common.Model.Category.Undefined; + string errorString = String.Format("The {0} tag is missing!", "Category"); + + AddError(validationCheck, "Category", errorString); + } + + // Add the Source + if (errorMessage.Source != null) + { + // Check if the Source is not empty + if (errorMessage.Source != String.Empty) + { + if (Enum.TryParse(errorMessage.Source.ToString(), out Source sourceName)) + { + if (sourceName != Source.Undefined) + { + validationCheck.Source = sourceName; + } + else + { + validationCheck.Source = Source.Undefined; + string errorString = "A correct Source is mandatory!"; + + AddError(validationCheck, "Source", errorString); + } + } + else + { + validationCheck.Source = Source.Undefined; + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + errorMessage.Source, + "Category", + "Category"); + + AddError(validationCheck, "Source", errorString); + } + } + else + { + validationCheck.Source = Source.Undefined; + string errorString = "A correct Source is mandatory!"; + + AddError(validationCheck, "Source", errorString); + } + } + else + { + validationCheck.Source = Source.Undefined; + string errorString = String.Format("The {0} tag is missing!", "Source"); + + AddError(validationCheck, "Source", errorString); + } + + // Add the ErrorId + if (errorMessage.Id != null) + { + // Check if the id is not empty + if (errorMessage.Id != String.Empty) + { + // Check if Id is correct type + if (UInt32.TryParse(errorMessage.Id, out uint errorId)) + { + // Check if the Id is unique + if (errorIds.Contains(errorId)) + { + // Duplicate Id + string errorString = String.Format("The error id {0} is a duplicate!", errorId); + + AddError(validationCheck, "ErrorId", errorString); + } + + validationCheck.ErrorId = errorId; + errorIds.Add(errorId); + } + else + { + validationCheck.ErrorId = 0; + string errorString = String.Format("The type of value {0} for {1} is not supported, expected type: {2}", errorMessage.Id, "id", "UInt32"); + + AddError(validationCheck, "ErrorId", errorString); + } + } + else + { + validationCheck.ErrorId = 0; + string errorString = "A correct error ID is mandatory!"; + + AddError(validationCheck, "ErrorId", errorString); + } + } + else + { + validationCheck.ErrorId = 0; + string errorString = "There is no ID attribute present, ID is mandatory!"; + + AddError(validationCheck, "CheckId", errorString); + } + + // Check if a name tag exists + if (errorMessage.Name != null) + { + // Check if name is not null + if (errorMessage.Name.Text != null) + { + validationCheck.Name = errorMessage.Name.Text; + } + else + { + validationCheck.Name = String.Empty; + string errorString = String.Format( + "There is no Name tag present in error {0}, a name is mandatory!", + validationCheck.ErrorId); + AddError(validationCheck, "Name", errorString); + } + } + else + { + validationCheck.Name = String.Empty; + validationCheck.Namespace = String.Empty; + string errorString = String.Format( + "There is no Name tag present in error {0}, a name is mandatory!", + validationCheck.ErrorId); + AddError(validationCheck, "Name", errorString); + } + + // Check if a description is present, a description is mandatory! + if (errorMessage.Description != null) + { + // Check if description is a template or a new description + if (errorMessage.Description.TemplateId != null) + { + // Existing Template + if (UInt32.TryParse(errorMessage.Description.TemplateId, out uint templateId)) + { + validationCheck.TemplateId = templateId; + validationCheck.FromTemplate = true; + + // Find the corresponding template and add the description and the input parameters to the ValidationCheck + DescriptionTemplate template = null; + int count = 0; + while (template == null && count < xmlValidator.ValidationChecks.DescriptionTemplates.DescriptionTemplate.Count) + { + if (xmlValidator.ValidationChecks.DescriptionTemplates.DescriptionTemplate[count].Id == templateId) + { + template = xmlValidator.ValidationChecks.DescriptionTemplates.DescriptionTemplate[count]; + } + + count++; + } + + if (template != null) + { + if (errorMessage.Description.InputParameters == null) + { + IsCorrectDescription(template, validationCheck); + } + else + { + IsCorrectDescription(template, validationCheck, errorMessage.Description); + } + } + else + { + string errorString = String.Format("The template with the templateId {0} was not found!", templateId.ToString()); + AddError(validationCheck, "Description", errorString); + validationCheck.Description = String.Empty; + validationCheck.Parameters = new ObservableCollection(); + if (!ParsingErrors.Contains(errorString)) + { + ParsingErrors.Add(errorString); + } + } + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + errorMessage.Description.TemplateId, + "templateID", + "UInt32"); + AddError(validationCheck, "Description", errorString); + + validationCheck.Description = String.Empty; + validationCheck.Parameters = new ObservableCollection(); + if (!ParsingErrors.Contains(errorString)) + { + ParsingErrors.Add(errorString); + } + } + } + else + { + // Create a new description + validationCheck.FromTemplate = false; + IsCorrectDescription(errorMessage.Description, validationCheck); + } + } + else + { + // No description tag + string errorString = String.Format( + "A description is mandatory in the check with ID {0}!", + validationCheck.ErrorId); + AddError(validationCheck, "Description", errorString); + + validationCheck.Description = String.Empty; + validationCheck.Parameters = new ObservableCollection(); + } + + // Check the warnings + if (errorMessage.AutoFixWarnings != null) + { + foreach (var warning in errorMessage.AutoFixWarnings.AutoFixWarning) + { + validationCheck.AutoFixWarnings.Add((warning.Text, warning.AutoFixPopup)); + } + } + else + { + validationCheck.AutoFixWarnings = new List<(string, bool)>(); + } + + // List with all the properties in the Serialization.Check class + var serializationProperties = typeof(ErrorMessage).GetProperties(); + + // Loop through all the properties that are obtained by Reflection on the interface + foreach (var prop in checkProperties) + { + // Checks if the property is already set + if (validationCheck.SettedProperties.Contains(prop.Name) || !Types.ContainsKey(prop.PropertyType)) + { + continue; + } + + bool foundMatch = false; + int count = 0; + while (!foundMatch && count < serializationProperties.Count()) + { + if (serializationProperties[count].Name == prop.Name) + { + foundMatch = true; + if (serializationProperties[count].GetValue(errorMessage) != null) + { + if (serializationProperties[count].PropertyType == typeof(XmlCDataSection)) + { + var data = (XmlCDataSection)serializationProperties[count].GetValue(errorMessage); + prop.SetValue(validationCheck, data.Value.Replace("[NewLine]", Environment.NewLine)); + } + else + { + // Determines the data type and choose the correct action + switch (Types[prop.PropertyType]) + { + // string + case 0: + // Check if XML contains the corresponding Element + prop.SetValue(validationCheck, serializationProperties[count].GetValue(errorMessage).ToString()); + break; + + // uint + case 1: + if (UInt32.TryParse(serializationProperties[count].GetValue(errorMessage).ToString(), out uint uintResult)) + { + prop.SetValue(validationCheck, uintResult); + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + serializationProperties[count].GetValue(errorMessage).ToString(), + prop.Name, + "UInt32"); + AddError(validationCheck, prop.Name, errorString); + } + + break; + + // bool + case 2: + if (Boolean.TryParse(serializationProperties[count].GetValue(errorMessage).ToString(), out bool boolResult)) + { + prop.SetValue(validationCheck, boolResult); + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + serializationProperties[count].GetValue(errorMessage).ToString(), + prop.Name, + "Boolean"); + AddError(validationCheck, prop.Name, errorString); + } + + break; + + // Severity + case 3: + if (Enum.TryParse(serializationProperties[count].GetValue(errorMessage).ToString(), out Severity severityResult)) + { + prop.SetValue(validationCheck, severityResult); + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + serializationProperties[count].GetValue(errorMessage).ToString(), + prop.Name, + "Severity"); + AddError(validationCheck, prop.Name, errorString); + } + + break; + + // Certainty + case 4: + if (Enum.TryParse(serializationProperties[count].GetValue(errorMessage).ToString(), out Certainty certaintyResult)) + { + prop.SetValue(validationCheck, certaintyResult); + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + serializationProperties[count].GetValue(errorMessage).ToString(), + prop.Name, + "Certainty"); + AddError(validationCheck, prop.Name, errorString); + } + + break; + + // FixImpact + case 8: + if (Enum.TryParse(serializationProperties[count].GetValue(errorMessage).ToString(), out FixImpact fixImpactResult)) + { + prop.SetValue(validationCheck, fixImpactResult); + } + else + { + string errorString = String.Format( + "The type of value {0} for {1} is not supported, expected type: {2}", + serializationProperties[count].GetValue(errorMessage).ToString(), + prop.Name, + "FixImpact"); + AddError(validationCheck, prop.Name, errorString); + } + + break; + + default: + MessageBox.Show($"Default: {prop.PropertyType.Name}"); + break; + } + } + } + } + + count++; + } + } + + checks.Add(validationCheck); + } + } + } + + HasDuplicates(checks, namespaces); + return checks; + } + + /// + /// Adds an error message to the check object. + /// + /// The check object to which to error has to be added. + /// The name of the property of the check where the error occurred. + /// The error message that describes the error. + private static void AddError(Model.Check check, string propertyName, string errorMessage) + { + if (check.ErrorMessages.TryGetValue(propertyName, out string previousError)) + { + check.ErrorMessages.Remove(propertyName); + check.PropertyHasError[propertyName] = false; + check.ErrorMessages.Add(propertyName, previousError + "; " + errorMessage); + } + else + { + check.ErrorMessages.Add(propertyName, errorMessage); + check.PropertyHasError[propertyName] = true; + check.Error = true; + } + } + + /// + /// Checks if a description is complete and if the parameters correspond to the format. + /// + /// The XElement that contains the Description tag from the XML file. + /// The check object to which the description applies. + private static void IsCorrectDescription(Description description, Model.Check validationCheck) + { + string formatDescription = String.Empty; + + // Check if description contains the format tag + if (description.Format != null) + { + formatDescription = description.Format; + } + else + { + string errorString = String.Format( + "The description of check {0}, does not contain a Format tag", + validationCheck.ErrorId); + AddError(validationCheck, "Description", errorString); + } + + // Check if Inputparameters tag exists + if (description.InputParameters != null) + { + // Add the parameters + validationCheck.Parameters = new ObservableCollection(description.InputParameters.InputParameter); + } + else + { + string errorString = String.Format( + "The description of check {0}, does not contain a InputParameters tag", + validationCheck.ErrorId); + AddError(validationCheck, "Description", errorString); + } + + validationCheck.Description = formatDescription; + } + + /// + /// Checks if a description is complete and if the parameters correspond to the format. + /// + /// The XElement that contains the Description tag from the XML file. + /// The check object to which the description applies. + private static void IsCorrectDescription(DescriptionTemplate description, Model.Check validationCheck, Description originalDescription = null) + { + string formatDescription = String.Empty; + + // Check if description contains the format tag + if (description.Format != null) + { + formatDescription = description.Format; + } + else + { + string errorString = String.Format( + "The description of check {0}, does not contain a Format tag", + validationCheck.ErrorId); + AddError(validationCheck, "Description", errorString); + } + + // Check if Inputparameters tag exists + if (description.TemplateInputParameters != null) + { + // Add the parameters + if (originalDescription == null) + { + validationCheck.Parameters = new ObservableCollection(description.TemplateInputParameters.InputParameter); + } + else + { + validationCheck.Parameters = new ObservableCollection(originalDescription.InputParameters.InputParameter); + + IEnumerable coveredIds = originalDescription.InputParameters.InputParameter.Select(x => x.Id); + if (description.TemplateInputParameters.InputParameter.Count != originalDescription.InputParameters.InputParameter.Count) + { + foreach (var item in description.TemplateInputParameters.InputParameter) + { + // Only for the ones that aren't covered + if (coveredIds.Contains(item.Id)) + { + continue; + } + + // Add remaining parameters + validationCheck.Parameters.Add(item); + } + } + } + } + else + { + string errorString = String.Format( + "The description of check {0}, does not contain a InputParameters tag", + validationCheck.ErrorId); + AddError(validationCheck, "Description", errorString); + } + + validationCheck.Description = formatDescription; + } + + /// + /// Checks if there are duplicate name's or duplicate descriptions within a namespace. + /// + /// A list that contains all the checks as check object. + /// A list that contains all the different namespace's as a string. + private static void HasDuplicates(ObservableCollection checks, List namespaces) + { + foreach (var ns in namespaces) + { + var checkNamesInNamespace = new Dictionary Ids, Model.Check CheckItself)>(); + var checksWithErrorMessages = new Dictionary<(string CheckName, uint CheckId), (Dictionary Names, Dictionary Descriptions)>(); + foreach (var check in checks) + { + if (check.Namespace == ns) + { + var checkCredentials = (check.CheckName, check.CheckId); + + if (!checksWithErrorMessages.ContainsKey(checkCredentials)) + { + checksWithErrorMessages.Add(checkCredentials, (new Dictionary(), new Dictionary())); + } + + if (!checkNamesInNamespace.ContainsKey(check.CheckName)) + { + checkNamesInNamespace.Add(check.CheckName, (new HashSet(), check)); + } + + checkNamesInNamespace[check.CheckName].Ids.Add(check.CheckId); + + // Duplicate error names + if (!checksWithErrorMessages[checkCredentials].Names.Keys.Contains(check.Name)) + { + checksWithErrorMessages[checkCredentials].Names.Add(check.Name, check); + } + else + { + string errorString = String.Format( + "The name : {0}, is a duplicate in the namespace {1}", + check.Name, + ns); + + // Current check + AddError(check, "Name", errorString); + foreach (var item in checksWithErrorMessages[checkCredentials].Names) + { + if (String.Equals(item.Key, check.Name, StringComparison.Ordinal)) + { + // Other checks + AddError(item.Value, "Name", errorString); + } + } + + ParsingErrors.Add(errorString); + } + + // Duplicate descriptions + + // Create filled in description to check (with the hard-coded values) + string description; + try + { + description = check.Description; + for (int i = 0; i < check.Parameters.Count; i++) + { + var param = check.Parameters[i]; + string oldValue = String.Format("{{{0}}}", i); + + string newValue; + if (String.IsNullOrWhiteSpace(param.Value)) + { + newValue = String.Format("{{{0}}}", param.Text); + } + else + { + // No need to add the braces as it's a hard-coded value anyway. + newValue = String.Format("{0}", param.Value); + } + + description = description.Replace(oldValue, newValue); + } + } + catch (IndexOutOfRangeException) + { + description = check.Description; + } + + if (!checksWithErrorMessages[checkCredentials].Descriptions.Keys.Contains(description)) + { + checksWithErrorMessages[checkCredentials].Descriptions.Add(description, check); + } + else + { + string errorString = String.Format( + "The description : {0}, is a duplicate in the namespace {1}", + check.Description, + ns); + + // Current check + AddError(check, "Description", errorString); + foreach (var item in checksWithErrorMessages[checkCredentials].Descriptions) + { + if (String.Equals(item.Key, description, StringComparison.Ordinal)) + { + // Other checks + AddError(item.Value, "Description", errorString); + } + } + + ParsingErrors.Add(errorString); + } + } + } + + foreach (var item in checkNamesInNamespace) + { + if (item.Value.Ids.Count > 1) + { + string errorString = String.Format( + "The name : {0}, is a duplicate in the namespace {1}", + item.Key, + ns); + AddError(item.Value.CheckItself, "CheckName", errorString); + ParsingErrors.Add(errorString); + } + } + } + } + + /// + /// Checks if there are duplicate templateId's in the XML file. + /// + /// The deserialized XML file as a Validator object + private static void HasDuplicateTemplateIDs(Validator validator) + { + List templateIds = new List(); + if (validator.ValidationChecks.DescriptionTemplates.DescriptionTemplate == null) + { + return; + } + + foreach (var template in validator.ValidationChecks.DescriptionTemplates.DescriptionTemplate) + { + if (!templateIds.Contains(template.Id)) + { + templateIds.Add(template.Id); + } + else + { + ParsingErrors.Add($"The template ID {template.Id} is a duplicate, please edit the XML file manually!"); + } + } + } + + /// + /// Adds an error message when there are parsing errors with attributes from the XML. + /// + private static void Serializer_UnknownAttribute(object sender, XmlAttributeEventArgs e) + { + ParsingErrors.Add($"[{e.LineNumber}] XML Attribute {e.Attr.Name}, not found in the interface"); + } + + /// + /// Adds an error message when there are parsing errors with elements from the XML. + /// + private static void Serializer_UnknownElement(object sender, XmlElementEventArgs e) + { + ParsingErrors.Add($"[{e.LineNumber}] XML Element {e.Element.Name}, not found in the interface"); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Serialization/XmlClasses.cs b/Validator Management Tool/Serialization/XmlClasses.cs new file mode 100644 index 00000000..8f68e39b --- /dev/null +++ b/Validator Management Tool/Serialization/XmlClasses.cs @@ -0,0 +1,326 @@ +namespace Validator_Management_Tool.Serialization +{ + using System; + using System.Collections.Generic; + using System.Xml; + using System.Xml.Serialization; + + [XmlRoot(ElementName = "InputParameter")] + public class InputParameter : BindableBase + { + private string id; + private string valueAttr; + private string text; + + [XmlAttribute(AttributeName = "id")] + public string Id + { + get + { + return id; + } + + set + { + if (value != id) + { + id = value; + OnPropertyChanged("Id"); + } + } + } + + [XmlAttribute(AttributeName = "value")] + public string Value + { + get + { + return valueAttr; + } + + set + { + if (value != valueAttr) + { + valueAttr = value; + OnPropertyChanged("Value"); + } + } + } + + [XmlText] + public string Text + { + get + { + return text; + } + + set + { + if (value != text) + { + text = value; + OnPropertyChanged("Text"); + } + } + } + } + + [XmlRoot(ElementName = "InputParameters")] + public class InputParameters + { + [XmlElement(ElementName = "InputParameter")] + public List InputParameter { get; set; } + } + + [XmlRoot(ElementName = "DescriptionTemplate")] + public class DescriptionTemplate + { + [XmlElement(ElementName = "Name")] + public string Name { get; set; } + + [XmlElement(ElementName = "Format")] + public string Format { get; set; } + + [XmlElement(ElementName = "InputParameters")] + public InputParameters TemplateInputParameters { get; set; } + + [XmlAttribute(AttributeName = "id")] + public uint Id { get; set; } + } + + [XmlRoot(ElementName = "DescriptionTemplates")] + public class DescriptionTemplates + { + [XmlElement(ElementName = "DescriptionTemplate")] + public List DescriptionTemplate { get; set; } + } + + [XmlRoot(ElementName = "Name")] + public class Name + { + [XmlAttribute(AttributeName = "namespace")] + public string Namespace { get; set; } + + [XmlText] + public string Text { get; set; } + } + + [XmlRoot(ElementName = "Description")] + public class Description + { + [XmlAttribute(AttributeName = "templateId")] + public string TemplateId { get; set; } + + [XmlElement(ElementName = "Format")] + public string Format { get; set; } + + [XmlElement(ElementName = "InputParameters")] + public InputParameters InputParameters { get; set; } + } + + [XmlRoot(ElementName = "ErrorMessage")] + public class ErrorMessage + { + private string _howToFix = null; + private string _exampleCode = null; + private string _details = null; + private string _groupDescription = String.Empty; + + [XmlElement(ElementName = "Name")] + public Name Name { get; set; } + + [XmlElement(ElementName = "GroupDescription", IsNullable = false)] + public string GroupDescription + { + get + { + return _groupDescription ?? String.Empty; + } + set + { + _groupDescription = value ?? String.Empty; + } + } + + [XmlElement(ElementName = "Description")] + public Description Description { get; set; } + + [XmlElement(ElementName = "Severity")] + public string Severity { get; set; } + + [XmlElement(ElementName = "Certainty")] + public string Certainty { get; set; } + + [XmlElement(ElementName = "Source")] + public string Source { get; set; } + + [XmlElement(ElementName = "FixImpact")] + public string FixImpact { get; set; } + + [XmlElement(ElementName = "HasCodeFix")] + public string HasCodeFix { get; set; } + + [XmlElement(ElementName = "HowToFix")] + public XmlCDataSection HowToFix + { + get + { + if (_howToFix != null) + { + XmlDocument doc = new XmlDocument(); + return doc.CreateCDataSection(_howToFix); + } + else + { + return null; + } + } + + set + { + if (value != null) + { + _howToFix = value.Value; + } + } + } + + [XmlElement(ElementName = "ExampleCode")] + public XmlCDataSection ExampleCode + { + get + { + if (_exampleCode != null) + { + XmlDocument doc = new XmlDocument(); + return doc.CreateCDataSection(_exampleCode); + } + else + { + return null; + } + } + + set + { + if (value != null) + { + _exampleCode = value.Value; + } + } + } + + [XmlElement(ElementName = "Details")] + public XmlCDataSection Details + { + get + { + if (_details != null) + { + XmlDocument doc = new XmlDocument(); + return doc.CreateCDataSection(_details); + } + else + { + return null; + } + } + + set + { + if (value != null) + { + _details = value.Value; + } + } + } + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlElement(ElementName = "AutoFixWarnings")] + public AutoFixWarnings AutoFixWarnings { get; set; } + } + + [XmlRoot(ElementName = "AutoFixWarning")] + public class AutoFixWarning + { + [XmlAttribute(AttributeName = "autoFixPopup")] + public bool AutoFixPopup { get; set; } + + [XmlText] + public string Text { get; set; } + } + + [XmlRoot(ElementName = "AutoFixWarnings")] + public class AutoFixWarnings + { + [XmlElement(ElementName = "AutoFixWarning")] + public List AutoFixWarning { get; set; } + } + + [XmlRoot(ElementName = "ErrorMessages")] + public class ErrorMessages + { + [XmlElement(ElementName = "ErrorMessage")] + public List ErrorMessage { get; set; } + } + + [XmlRoot(ElementName = "Check")] + public class Check + { + [XmlElement(ElementName = "Name")] + public Name Name { get; set; } + + [XmlElement(ElementName = "ErrorMessages")] + public ErrorMessages ErrorMessages { get; set; } + + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + } + + [XmlRoot(ElementName = "Checks")] + public class Checks + { + [XmlElement(ElementName = "Check")] + public List Check { get; set; } + } + + [XmlRoot(ElementName = "Category")] + public class Category + { + [XmlElement(ElementName = "Name")] + public string Name { get; set; } + + [XmlElement(ElementName = "Checks")] + public Checks Checks { get; set; } + + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + } + + [XmlRoot(ElementName = "Categories")] + public class Categories + { + [XmlElement(ElementName = "Category")] + public List Category { get; set; } + } + + [XmlRoot(ElementName = "ValidationChecks")] + public class ValidationChecks + { + [XmlElement(ElementName = "DescriptionTemplates")] + public DescriptionTemplates DescriptionTemplates { get; set; } + + [XmlElement(ElementName = "Categories")] + public Categories Categories { get; set; } + } + + [XmlRoot(ElementName = "Validator")] + public class Validator + { + [XmlElement(ElementName = "ValidationChecks")] + public ValidationChecks ValidationChecks { get; set; } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.cs b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.cs new file mode 100644 index 00000000..0c32d5ce --- /dev/null +++ b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.cs @@ -0,0 +1,400 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Validator_Management_Tool.Templates.Error_Messages +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class ErrorMessagesClass : ErrorMessagesClassBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("namespace Skyline.DataMiner.CICD.Validators.Protocol.Tests."); + this.Write(this.ToStringHelper.ToStringWithCulture(@namespace)); + this.Write(@" +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + +"); + if(checks.Count > 0){ + this.Write(" internal static class Error\r\n {"); + foreach(var check in checks) { + this.Write("\r\n public static IValidationResult "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(IValidate test, IReadable referenceNode, IReadable positionNode"); + foreach(var argument in argumentLists[check.Name]) { + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(argument.Value)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(argument.Key)); + } + this.Write(")\r\n {\r\n return new ValidationResult\r\n {\r\n " + + " Test = test,\r\n"); + foreach(var property in propertyLists[check.Name]) +{ + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Key)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Value)); + this.Write(",\r\n"); + } + this.Write("\r\n PositionNode = positionNode,\r\n ReferenceNode = r" + + "eferenceNode,\r\n"); + if (check.AutoFixWarnings.Count != 0) +{ + this.Write("\r\n AutoFixWarnings = new List<(string, bool)>\r\n {\r\n" + + ""); + foreach(var warning in warningLists[check.Name]) +{ + this.Write(" (\""); + this.Write(this.ToStringHelper.ToStringWithCulture(warning.Key)); + this.Write("\", "); + this.Write(this.ToStringHelper.ToStringWithCulture(warning.Value)); + this.Write("),\r\n"); + } + this.Write(" }\r\n"); + } + this.Write(" };\r\n }\r\n"); + } + this.Write(" }\r\n"); + } + this.Write("\r\n"); + if(compares.Count > 0){ + this.Write(" internal static class ErrorCompare\r\n {"); + foreach(var compare in compares) { + this.Write("\r\n public static IValidationResult "); + this.Write(this.ToStringHelper.ToStringWithCulture(compare.Name)); + this.Write("(IReadable referenceNode, IReadable positionNode"); + foreach(var argument in argumentLists[compare.Name]) { + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(argument.Value)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(argument.Key)); + } + this.Write(")\r\n {\r\n return new ValidationResult\r\n {\r\n " + + " Test = null,\r\n"); + foreach(var property in propertyLists[compare.Name]) +{ + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Key)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Value)); + this.Write(",\r\n"); + } + this.Write("\r\n PositionNode = positionNode,\r\n ReferenceNode = r" + + "eferenceNode,\r\n };\r\n }\r\n"); + } + this.Write(" }\r\n\r\n"); + } + this.Write(" internal static class ErrorIds\r\n {\r\n"); + foreach(var check in allChecks) { + this.Write(" public const uint "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.ErrorId)); + this.Write(";\r\n"); +} + this.Write(" }\r\n\r\n /// \r\n /// Contains the identifiers of the checks.\r\n " + + "/// \r\n public static class CheckId\r\n {\r\n /// \r\n " + + " /// The check identifier.\r\n /// \r\n public const u" + + "int "); + this.Write(this.ToStringHelper.ToStringWithCulture(allChecks[0].CheckName)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(allChecks[0].CheckId)); + this.Write(";\r\n }\r\n}"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class ErrorMessagesClassBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.tt b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.tt new file mode 100644 index 00000000..a573cd91 --- /dev/null +++ b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClass.tt @@ -0,0 +1,85 @@ +<#@ template language="C#" linePragmas="false" #> +namespace Skyline.DataMiner.CICD.Validators.Protocol.Tests.<#= @namespace #> +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Models.Protocol.Read; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + +<# if(checks.Count > 0){ #> + internal static class Error + {<# foreach(var check in checks) {#> + + public static IValidationResult <#= check.Name #>(IValidate test, IReadable referenceNode, IReadable positionNode<# foreach(var argument in argumentLists[check.Name]) { #>, <#= argument.Value #> <#= argument.Key #><# } #>) + { + return new ValidationResult + { + Test = test, +<# foreach(var property in propertyLists[check.Name]) +{ #> + <#= property.Key #> = <#= property.Value #>, +<# } #> + + PositionNode = positionNode, + ReferenceNode = referenceNode, +<# if (check.AutoFixWarnings.Count != 0) +{ #> + + AutoFixWarnings = new List<(string, bool)> + { +<# foreach(var warning in warningLists[check.Name]) +{ #> + ("<#= warning.Key #>", <#= warning.Value #>), +<# } #> + } +<# } #> + }; + } +<# } #> + } +<# } #> + +<# if(compares.Count > 0){ #> + internal static class ErrorCompare + {<# foreach(var compare in compares) {#> + + public static IValidationResult <#= compare.Name #>(IReadable referenceNode, IReadable positionNode<# foreach(var argument in argumentLists[compare.Name]) { #>, <#= argument.Value #> <#= argument.Key #><# } #>) + { + return new ValidationResult + { + Test = null, +<# foreach(var property in propertyLists[compare.Name]) +{ #> + <#= property.Key #> = <#= property.Value #>, +<# } #> + + PositionNode = positionNode, + ReferenceNode = referenceNode, + }; + } +<# } #> + } + +<# } #> + internal static class ErrorIds + { +<# foreach(var check in allChecks) { #> + public const uint <#= check.Name #> = <#= check.ErrorId #>; +<#}#> + } + + /// + /// Contains the identifiers of the checks. + /// + public static class CheckId + { + /// + /// The check identifier. + /// + public const uint <#= allChecks[0].CheckName #> = <#= allChecks[0].CheckId #>; + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Error Messages/ErrorMessagesClassCode.cs b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClassCode.cs new file mode 100644 index 00000000..a4046617 --- /dev/null +++ b/Validator Management Tool/Templates/Error Messages/ErrorMessagesClassCode.cs @@ -0,0 +1,48 @@ +namespace Validator_Management_Tool.Templates.Error_Messages +{ + using System.Collections.Generic; + using System.Linq; + using Validator_Management_Tool.Model; + + public partial class ErrorMessagesClass + { + private List allChecks; + + private List checks; + private List compares; + + /// + /// Contains a key for each check name (string) in the checklist. + /// The value is another dictionary whose keys are the argument names and the values are the argument types (string). + /// + private Dictionary> argumentLists; + + /// + /// Contains a key for each check name (string) in the checklist. + /// The value is another dictionary whose keys are the property names and the values are the property values (string). + /// + private Dictionary> propertyLists; + + /// + /// Contains a key for each check name (string) in the checklist. + /// The value is another dictionary whose keys are the property names and the values are the property values (string). + /// + private Dictionary> warningLists; + + private string @namespace; + + public ErrorMessagesClass(List checks) + { + this.allChecks = checks; + + this.checks = checks.Where(x => x.Source != Skyline.DataMiner.CICD.Validators.Common.Model.Source.MajorChangeChecker).ToList(); + this.compares = checks.Where(x => x.Source == Skyline.DataMiner.CICD.Validators.Common.Model.Source.MajorChangeChecker).ToList(); + + var (Arguments, Properties) = TemplateHelper.MakeLists(allChecks); + argumentLists = Arguments; + propertyLists = Properties; + warningLists = TemplateHelper.GetWarnings(allChecks); + @namespace = checks[0].FullNamespace; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/TemplateHelper.cs b/Validator Management Tool/Templates/TemplateHelper.cs new file mode 100644 index 00000000..97e403d8 --- /dev/null +++ b/Validator Management Tool/Templates/TemplateHelper.cs @@ -0,0 +1,218 @@ +namespace Validator_Management_Tool.Templates +{ + using System; + using System.Collections.Generic; + using System.Text; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Model; + + public static class TemplateHelper + { + /// + /// Makes a list of arguments for the method and a list of properties that are assigned in the method body. + /// This method will use the SettedProperties list of the object to decide wither or not + /// a property has to be asked as argument. + /// + internal static (Dictionary> Arguments, Dictionary> Properties) MakeLists(IEnumerable allChecks) + { + var argumentLists = new Dictionary>(); + var propertyLists = new Dictionary>(); + + var interfaceProperties = typeof(IValidationResult).GetProperties(); + + foreach (var check in allChecks) + { + Dictionary argumentList = new Dictionary(); + Dictionary propertyList = new Dictionary(); + + foreach (var interfaceProperty in interfaceProperties) + { + // Add the description with the correct parameters + if (interfaceProperty.Name == "Description") + { + if (check.Parameters == null || check.Parameters.Count == 0) + { + // The description needs no parameters + propertyList.Add("Description", "\"" + MakeCSharpString(check.Description) + "\""); + } + else + { + // Parameters exists + StringBuilder description = new StringBuilder("String.Format(\""); + description.Append(MakeCSharpString(check.Description) + "\""); + foreach (var parameter in check.Parameters) + { + if (String.IsNullOrWhiteSpace(parameter.Value)) + { + description.Append(", " + parameter.Text); + argumentList.Add(parameter.Text, "string"); + } + else + { + string stringified = String.Format("\"{0}\"", MakeCSharpString(parameter.Value)); + description.Append(", " + stringified); + } + } + + description.Append(")"); + propertyList.Add("Description", description.ToString()); + } + + continue; + } + + if (interfaceProperty.Name == "ErrorId") + { + // If the property is the ID, then it has to be added to the ErrorsIds static class + propertyList.Add(interfaceProperty.Name, "ErrorIds." + check.Name); + continue; + } + + if (interfaceProperty.Name == "CheckId") + { + propertyList.Add(interfaceProperty.Name, "CheckId." + check.CheckName); + continue; + } + + if (check.SettedProperties.Contains(interfaceProperty.Name) && XMLParser.Types.ContainsKey(interfaceProperty.PropertyType) && interfaceProperty.GetValue(check) != null) + { + // If it is a property that is set when parsing the XML, the correct value is given to the property + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + propertyList.Add(interfaceProperty.Name, ("\"" + MakeCSharpString(interfaceProperty.GetValue(check).ToString()) + "\"").Replace(Environment.NewLine, "\" + Environment.NewLine + \"")); + break; + + // uint + // int + case 1: + case 6: + propertyList.Add(interfaceProperty.Name, interfaceProperty.GetValue(check)); + break; + + // bool + case 2: + propertyList.Add(interfaceProperty.Name, interfaceProperty.GetValue(check).ToString().ToLower()); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + propertyList.Add(interfaceProperty.Name, interfaceProperty.Name + "." + interfaceProperty.GetValue(check).ToString()); + break; + } + + continue; + } + + if (interfaceProperty.Name == "GroupDescription") + { + propertyList.Add(interfaceProperty.Name, $"\"{MakeCSharpString(interfaceProperty.GetValue(check)?.ToString())}\""); + continue; + } + + if (interfaceProperty.Name != "Line" && interfaceProperty.Name != "DescriptionFormat" && + interfaceProperty.Name != "DescriptionParameters" && interfaceProperty.Name != "ReferenceNode" && + interfaceProperty.Name != "PositionNode" && interfaceProperty.Name != "Position" && + interfaceProperty.Name != "SubResults" && interfaceProperty.Name != "AutoFixWarnings" && + interfaceProperty.Name != "DveExport") + { + // If the property was not set, it will be added to the argument list of the method + string lowerCaseObject = MakeCSharpString(Char.ToLowerInvariant(interfaceProperty.Name[0]) + interfaceProperty.Name.Substring(1)); + propertyList.Add(interfaceProperty.Name, lowerCaseObject); + + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + argumentList.Add(lowerCaseObject, "string"); + break; + + // uint + case 1: + argumentList.Add(lowerCaseObject, "uint"); + break; + + // bool + case 2: + argumentList.Add(lowerCaseObject, "bool"); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + argumentList.Add(lowerCaseObject, interfaceProperty.Name); + break; + + // int + case 6: + argumentList.Add(lowerCaseObject, "int"); + break; + } + } + } + + argumentLists.Add(check.Name, argumentList); + propertyLists.Add(check.Name, propertyList); + } + + return (argumentLists, propertyLists); + } + + internal static Dictionary> GetWarnings(IEnumerable checks) + { + Dictionary> allWarningsPerCheck = new Dictionary>(); + + foreach (Check check in checks) + { + Dictionary warnings = new Dictionary(); + + if (check.AutoFixWarnings != null) + { + foreach ((string message, bool autoFixPopup) in check.AutoFixWarnings) + { + warnings.Add(MakeCSharpString(message), autoFixPopup.ToString().ToLowerInvariant()); + } + } + + allWarningsPerCheck.Add(check.Name, warnings); + } + + return allWarningsPerCheck; + } + + /// + /// Converts the input string to a C# proof string. + /// + /// Input string that needs to be converted. + /// The converted C# proof string + internal static string MakeCSharpString(string input) + { + if (input == null) + { + return null; + } + + input = input.Replace("\\", "\\" + "\\"); + int previousIndex = 0; + while (input.IndexOf('"', previousIndex) != -1) + { + int index = input.IndexOf('"', previousIndex); + input = input.Insert(index, @"\"); + previousIndex = index + 2; + } + + return input; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Tests/TestsClass.cs b/Validator Management Tool/Templates/Tests/TestsClass.cs new file mode 100644 index 00000000..a00b270f --- /dev/null +++ b/Validator Management Tool/Templates/Tests/TestsClass.cs @@ -0,0 +1,354 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Validator_Management_Tool.Templates.Tests +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class TestsClass : TestsClassBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("namespace Skyline.DataMiner.CICD.Validators.Protocol.Tests."); + this.Write(this.ToStringHelper.ToStringWithCulture(@namespace)); + this.Write(@" +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Common.Attributes; + using Skyline.DataMiner.CICD.Validators.Protocol.Common.Extensions; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + + //[Test(CheckId."); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write(", Category."); + this.Write(this.ToStringHelper.ToStringWithCulture(category)); + this.Write(")]\r\n internal class "); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write(@" : IValidate, ICodeFix, ICompare + { + // Please comment out the interfaces that aren't used together with the respective methods. + + public List Validate(ValidatorContext context) + { + List results = new List(); + + return results; + } + + public ICodeFixResult Fix(CodeFixContext context) + { + CodeFixResult result = new CodeFixResult(); + + switch (context.Result.ErrorId) + { + + default: + result.Message = $""This error ({context.Result.ErrorId}) isn't implemented.""; + break; + } + + return result; + } + + public List Compare(MajorChangeCheckContext context) + { + List results = new List(); + + return results; + } + } +}"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class TestsClassBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Validator Management Tool/Templates/Tests/TestsClass.tt b/Validator Management Tool/Templates/Tests/TestsClass.tt new file mode 100644 index 00000000..84ab4a7b --- /dev/null +++ b/Validator Management Tool/Templates/Tests/TestsClass.tt @@ -0,0 +1,48 @@ +<#@ template language="C#" linePragmas="false" #> +namespace Skyline.DataMiner.CICD.Validators.Protocol.Tests.<#= @namespace #> +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Common.Attributes; + using Skyline.DataMiner.CICD.Validators.Protocol.Common.Extensions; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + + //[Test(CheckId.<#= className #>, Category.<#= category #>)] + internal class <#= className #> : IValidate, ICodeFix, ICompare + { + // Please comment out the interfaces that aren't used together with the respective methods. + + public List Validate(ValidatorContext context) + { + List results = new List(); + + return results; + } + + public ICodeFixResult Fix(CodeFixContext context) + { + CodeFixResult result = new CodeFixResult(); + + switch (context.Result.ErrorId) + { + + default: + result.Message = $"This error ({context.Result.ErrorId}) isn't implemented."; + break; + } + + return result; + } + + public List Compare(MajorChangeCheckContext context) + { + List results = new List(); + + return results; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Tests/TestsClassCode.cs b/Validator Management Tool/Templates/Tests/TestsClassCode.cs new file mode 100644 index 00000000..c0f2cb03 --- /dev/null +++ b/Validator Management Tool/Templates/Tests/TestsClassCode.cs @@ -0,0 +1,18 @@ +namespace Validator_Management_Tool.Templates.Tests +{ + using Skyline.DataMiner.CICD.Validators.Common.Model; + + public partial class TestsClass + { + private string className; + private string @namespace; + private Category category; + + public TestsClass(string className, string @namespace, Category category) + { + this.className = className; + this.@namespace = @namespace; + this.category = category; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Unit Tests/UnitTestClass.cs b/Validator Management Tool/Templates/Unit Tests/UnitTestClass.cs new file mode 100644 index 00000000..ce65e39e --- /dev/null +++ b/Validator Management Tool/Templates/Unit Tests/UnitTestClass.cs @@ -0,0 +1,546 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Validator_Management_Tool.Templates.Unit_Tests +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class UnitTestClass : UnitTestClassBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("namespace ProtocolTests."); + this.Write(this.ToStringHelper.ToStringWithCulture(@namespace)); + this.Write(@" +{ + using System; + using System.Collections.Generic; + + using FluentAssertions; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + using Skyline.DataMiner.CICD.Validators.Protocol.Tests."); + this.Write(this.ToStringHelper.ToStringWithCulture(@namespace)); + this.Write(";\r\n\r\n"); + if(validateChecks.Count > 0){ + this.Write(" [TestClass]\r\n public class Validate\r\n {\r\n private readonly IVali" + + "date check = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("();\r\n\r\n #region Valid Checks\r\n\r\n [TestMethod]\r\n [Ignore]\r\n " + + " public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(validateChecks[0].check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write(@"_Valid() + { + Generic.ValidateData data = new Generic.ValidateData + { + TestType = Generic.TestType.Valid, + FileName = ""Valid"", + ExpectedResults = new List() + }; + + Generic.Validate(check, data); + } + + #endregion + + #region Invalid Checks +"); + foreach (var (hasArgs, args, _, check) in validateChecks) { + this.Write("\r\n [TestMethod]\r\n [Ignore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("()\r\n {\r\n Generic.ValidateData data = new Generic.ValidateData\r\n" + + " {\r\n TestType = Generic.TestType.Invalid,\r\n " + + " FileName = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("\",\r\n ExpectedResults = new List\r\n " + + " {\r\n"); + if (hasArgs) { + this.Write(" //Error."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, null, "); + this.Write(this.ToStringHelper.ToStringWithCulture(args)); + this.Write("),\r\n"); + } else { + this.Write(" //Error."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, null),\r\n"); + } + this.Write(" }\r\n };\r\n\r\n Generic.Validate(check, data);\r\n" + + " }\r\n"); + } + this.Write("\r\n #endregion\r\n }\r\n\r\n"); + } + if(compareChecks.Count > 0){ + this.Write(" [TestClass]\r\n public class Compare\r\n {\r\n private readonly ICompa" + + "re check = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("();\r\n \r\n #region Valid Checks\r\n\r\n [TestMethod]\r\n [Ign" + + "ore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(compareChecks[0].check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write(@"_Valid() + { + Generic.CompareData data = new Generic.CompareData + { + TestType = Generic.TestType.Valid, + FileNameBase = ""Valid"", + ExpectedResults = new List() + }; + + Generic.Compare(check, data); + } + + #endregion + + #region Invalid Checks +"); + foreach (var (hasArgs, args, _, check) in compareChecks) { + this.Write("\r\n [TestMethod]\r\n [Ignore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("()\r\n {\r\n Generic.CompareData data = new Generic.CompareData\r\n " + + " {\r\n TestType = Generic.TestType.Invalid,\r\n " + + " FileNameBase = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("\",\r\n ExpectedResults = new List\r\n " + + " {\r\n"); + if (hasArgs) { + this.Write(" //ErrorCompare."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, "); + this.Write(this.ToStringHelper.ToStringWithCulture(args)); + this.Write("),\r\n"); + } else { + this.Write(" //ErrorCompare."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null),\r\n"); + } + this.Write(" }\r\n };\r\n\r\n Generic.Compare(check, data);\r\n " + + " }\r\n"); + } + this.Write("\r\n #endregion\r\n }\r\n\r\n"); + } + if(codefixes.Count > 0){ + this.Write(" [TestClass]\r\n public class CodeFix\r\n {\r\n private readonly ICodeF" + + "ix check = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("();\r\n"); + foreach (var check in codefixes) { + this.Write("\r\n [TestMethod]\r\n [Ignore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(codefixes[0].Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("()\r\n {\r\n Generic.FixData data = new Generic.FixData\r\n " + + " {\r\n FileNameBase = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("\",\r\n };\r\n\r\n Generic.Fix(check, data);\r\n }\r\n"); + } + this.Write(" }\r\n\r\n"); + } + if(allChecks.Count > 0){ + this.Write(" [TestClass]\r\n public class ErrorMessages\r\n {\r\n"); + for (int i = 0; i < validateChecks.Count;i++) { +var (hasArgs, args, props, check) = validateChecks[i]; +if (i != 0){ + + this.Write("\r\n"); + +} + + this.Write(" [TestMethod]\r\n [Ignore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("()\r\n {\r\n // Create ErrorMessage\r\n "); + if (hasArgs) { + this.Write("var message = Error."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, null, "); + this.Write(this.ToStringHelper.ToStringWithCulture(args)); + this.Write(");\r\n "); + } else { + this.Write("var message = Error."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, null);\r\n "); + } + this.Write(" \r\n var expected = new ValidationResult\r\n {\r\n"); + foreach(var property in props) +{ + if (ExcludedProperties.Contains(property.Key)) continue; + + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Key)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Value)); + this.Write(",\r\n"); + } + this.Write(" };\r\n\r\n // Assert\r\n message.Should().BeEquivalen" + + "tTo(expected, Generic.ExcludePropertiesForErrorMessages);\r\n }\r\n"); + } + for (int i = 0; i < compareChecks.Count;i++) { +var (hasArgs, args, props, check) = compareChecks[i]; +if (i != 0 || validateChecks.Count > 0){ + + this.Write("\r\n"); + +} + + this.Write(" [TestMethod]\r\n [Ignore]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("()\r\n {\r\n // Create ErrorMessage\r\n "); + if (hasArgs) { + this.Write("var message = ErrorCompare."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null, "); + this.Write(this.ToStringHelper.ToStringWithCulture(args)); + this.Write(");\r\n "); + } else { + this.Write("var message = ErrorCompare."); + this.Write(this.ToStringHelper.ToStringWithCulture(check.Name)); + this.Write("(null, null);\r\n "); + } + this.Write(" \r\n var expected = new ValidationResult()\r\n {\r\n"); + foreach(var property in props) +{ + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Key)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(property.Value)); + this.Write(",\r\n"); + } + this.Write(" };\r\n\r\n // Assert\r\n message.Should().BeEquivalen" + + "tTo(expected, Generic.ExcludePropertiesForErrorMessages);\r\n }\r\n"); + } + this.Write(" }\r\n\r\n"); + } + if(allChecks.Count > 0){ + this.Write(" [TestClass]\r\n [Ignore]\r\n public class Attribute\r\n {\r\n private" + + " readonly IRoot check = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("();\r\n\r\n [TestMethod]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(allChecks[0].Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_CheckCategory() => Generic.CheckCategory(check, Category."); + this.Write(this.ToStringHelper.ToStringWithCulture(allChecks[0].Category)); + this.Write(");\r\n\r\n [TestMethod]\r\n public void "); + this.Write(this.ToStringHelper.ToStringWithCulture(allChecks[0].Category)); + this.Write("_"); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write("_CheckId() => Generic.CheckId(check, CheckId."); + this.Write(this.ToStringHelper.ToStringWithCulture(className)); + this.Write(");\r\n }\r\n"); + } + this.Write("}"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class UnitTestClassBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Validator Management Tool/Templates/Unit Tests/UnitTestClass.tt b/Validator Management Tool/Templates/Unit Tests/UnitTestClass.tt new file mode 100644 index 00000000..6d6adf74 --- /dev/null +++ b/Validator Management Tool/Templates/Unit Tests/UnitTestClass.tt @@ -0,0 +1,226 @@ +<#@ template language="C#" linePragmas="false" #> +namespace ProtocolTests.<#= @namespace #> +{ + using System; + using System.Collections.Generic; + + using FluentAssertions; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Skyline.DataMiner.CICD.Validators.Protocol.Common; + using Skyline.DataMiner.CICD.Validators.Protocol.Interfaces; + using Skyline.DataMiner.CICD.Validators.Protocol.Tests.<#= @namespace #>; + +<# if(validateChecks.Count > 0){ #> + [TestClass] + public class Validate + { + private readonly IValidate check = new <#= className #>(); + + #region Valid Checks + + [TestMethod] + [Ignore] + public void <#= validateChecks[0].check.Category #>_<#= className #>_Valid() + { + Generic.ValidateData data = new Generic.ValidateData + { + TestType = Generic.TestType.Valid, + FileName = "Valid", + ExpectedResults = new List() + }; + + Generic.Validate(check, data); + } + + #endregion + + #region Invalid Checks +<# foreach (var (hasArgs, args, _, check) in validateChecks) { #> + + [TestMethod] + [Ignore] + public void <#= check.Category #>_<#= className #>_<#= check.Name #>() + { + Generic.ValidateData data = new Generic.ValidateData + { + TestType = Generic.TestType.Invalid, + FileName = "<#= check.Name #>", + ExpectedResults = new List + { +<# if (hasArgs) {#> + //Error.<#= check.Name #>(null, null, null, <#= args #>), +<# } else { #> + //Error.<#= check.Name #>(null, null, null), +<# } #> + } + }; + + Generic.Validate(check, data); + } +<# } #> + + #endregion + } + +<# } #> +<# if(compareChecks.Count > 0){ #> + [TestClass] + public class Compare + { + private readonly ICompare check = new <#= className #>(); + + #region Valid Checks + + [TestMethod] + [Ignore] + public void <#= compareChecks[0].check.Category #>_<#= className #>_Valid() + { + Generic.CompareData data = new Generic.CompareData + { + TestType = Generic.TestType.Valid, + FileNameBase = "Valid", + ExpectedResults = new List() + }; + + Generic.Compare(check, data); + } + + #endregion + + #region Invalid Checks +<# foreach (var (hasArgs, args, _, check) in compareChecks) { #> + + [TestMethod] + [Ignore] + public void <#= check.Category #>_<#= className #>_<#= check.Name #>() + { + Generic.CompareData data = new Generic.CompareData + { + TestType = Generic.TestType.Invalid, + FileNameBase = "<#= check.Name #>", + ExpectedResults = new List + { +<# if (hasArgs) {#> + //ErrorCompare.<#= check.Name #>(null, null, <#= args #>), +<# } else { #> + //ErrorCompare.<#= check.Name #>(null, null), +<# } #> + } + }; + + Generic.Compare(check, data); + } +<# } #> + + #endregion + } + +<# } #> +<# if(codefixes.Count > 0){ #> + [TestClass] + public class CodeFix + { + private readonly ICodeFix check = new <#= className #>(); +<# foreach (var check in codefixes) { #> + + [TestMethod] + [Ignore] + public void <#= codefixes[0].Category #>_<#= className #>_<#= check.Name #>() + { + Generic.FixData data = new Generic.FixData + { + FileNameBase = "<#= check.Name #>", + }; + + Generic.Fix(check, data); + } +<# } #> + } + +<# } #> +<# if(allChecks.Count > 0){ #> + [TestClass] + public class ErrorMessages + { +<# for (int i = 0; i < validateChecks.Count;i++) { +var (hasArgs, args, props, check) = validateChecks[i]; +if (i != 0){ +#> + +<# +} +#> + [TestMethod] + [Ignore] + public void <#= check.Category #>_<#= className #>_<#= check.Name #>() + { + // Create ErrorMessage + <# if (hasArgs) {#>var message = Error.<#= check.Name #>(null, null, null, <#= args #>); + <# } else { #>var message = Error.<#= check.Name #>(null, null, null); + <# } #> + + var expected = new ValidationResult + { +<# foreach(var property in props) +{ + if (ExcludedProperties.Contains(property.Key)) continue; +#> + <#= property.Key #> = <#= property.Value #>, +<# } #> + }; + + // Assert + message.Should().BeEquivalentTo(expected, Generic.ExcludePropertiesForErrorMessages); + } +<# } #> +<# for (int i = 0; i < compareChecks.Count;i++) { +var (hasArgs, args, props, check) = compareChecks[i]; +if (i != 0 || validateChecks.Count > 0){ +#> + +<# +} +#> + [TestMethod] + [Ignore] + public void <#= check.Category #>_<#= className #>_<#= check.Name #>() + { + // Create ErrorMessage + <# if (hasArgs) {#>var message = ErrorCompare.<#= check.Name #>(null, null, <#= args #>); + <# } else { #>var message = ErrorCompare.<#= check.Name #>(null, null); + <# } #> + + var expected = new ValidationResult() + { +<# foreach(var property in props) +{ #> + <#= property.Key #> = <#= property.Value #>, +<# } #> + }; + + // Assert + message.Should().BeEquivalentTo(expected, Generic.ExcludePropertiesForErrorMessages); + } +<# } #> + } + +<# } #> +<# if(allChecks.Count > 0){ #> + [TestClass] + [Ignore] + public class Attribute + { + private readonly IRoot check = new <#= className #>(); + + [TestMethod] + public void <#= allChecks[0].Category #>_<#= className #>_CheckCategory() => Generic.CheckCategory(check, Category.<#= allChecks[0].Category #>); + + [TestMethod] + public void <#= allChecks[0].Category #>_<#= className #>_CheckId() => Generic.CheckId(check, CheckId.<#= className #>); + } +<# } #> +} \ No newline at end of file diff --git a/Validator Management Tool/Templates/Unit Tests/UnitTestClassCode.cs b/Validator Management Tool/Templates/Unit Tests/UnitTestClassCode.cs new file mode 100644 index 00000000..4175a3cf --- /dev/null +++ b/Validator Management Tool/Templates/Unit Tests/UnitTestClassCode.cs @@ -0,0 +1,276 @@ +namespace Validator_Management_Tool.Templates.Unit_Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Skyline.DataMiner.CICD.Validators.Common.Interfaces; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Model; + + partial class UnitTestClass + { + // Properties to be excluded from the error messages unit tests. + private static readonly List ExcludedProperties = new List + { + // These shouldn't change normally. + "ErrorId", + "FullId", + "Category", + "Source", + + // These are almost never used. + "HowToFix", + "ExampleCode" + }; + + private readonly List allChecks; + private readonly List compares; + private readonly List codefixes; + private readonly List checks; + private readonly string className; + private readonly string @namespace; + + private readonly List<(bool hasArguments, string args, Dictionary props, Check check)> validateChecks = new List<(bool hasArguments, string args, Dictionary props, Check check)>(); + private readonly List<(bool hasArguments, string args, Dictionary props, Check check)> compareChecks = new List<(bool hasArguments, string args, Dictionary props, Check check)>(); + + public UnitTestClass(List checks, string className) + { + this.allChecks = checks; + this.checks = checks.Where(x => x.Source != Source.MajorChangeChecker).OrderBy(x => x.Name).ToList(); + this.compares = checks.Where(x => x.Source == Source.MajorChangeChecker).OrderBy(x => x.Name).ToList(); + this.codefixes = checks.Where(x => x.Source == Source.Validator && x.HasCodeFix && !x.Name.EndsWith("_Sub")).OrderBy(x => x.Name).ToList(); + this.className = className; + this.@namespace = checks[0].FullNamespace; + + MakeLists(); + + // Need to happen after MakeLists + checks = checks.Where(x => !x.Name.EndsWith("_Sub")).ToList(); + compares = compares.Where(x => !x.Name.EndsWith("_Sub")).ToList(); + } + + private void MakeLists() + { + var interfaceProperties = typeof(IValidationResult).GetProperties(); + + foreach (var check in this.checks) + { + List args = new List(); + Dictionary properties = new Dictionary(); + + foreach (var interfaceProperty in interfaceProperties) + { + // Add the description with the correct parameters + if (interfaceProperty.Name == "Description") + { + string desc = TemplateHelper.MakeCSharpString(check.Description); + if (check.Parameters?.Count > 0) + { + // Parameters exists + int argCount = 0; + foreach (var parameter in check.Parameters) + { + if (String.IsNullOrWhiteSpace(parameter.Value)) + { + string argName = parameter.Text; + args.Add($"\"{argName}\""); + desc = desc.Replace($"{{{argCount}}}", $"{argName}"); + } + else + { + desc = desc.Replace($"{{{argCount}}}", $"{parameter.Value}"); + } + + argCount++; + } + } + + properties.Add("Description", "\"" + desc + "\""); + } + else if (check.SettedProperties.Contains(interfaceProperty.Name) && XMLParser.Types.ContainsKey(interfaceProperty.PropertyType) && interfaceProperty.GetValue(check) != null) + { + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + properties.Add(interfaceProperty.Name, ("\"" + TemplateHelper.MakeCSharpString(interfaceProperty.GetValue(check).ToString()) + "\"").Replace(Environment.NewLine, "\" + Environment.NewLine + \"")); + break; + + // uint + case 1: + case 6: + properties.Add(interfaceProperty.Name, interfaceProperty.GetValue(check)); + break; + + // bool + case 2: + properties.Add(interfaceProperty.Name, interfaceProperty.GetValue(check).ToString().ToLower()); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + properties.Add(interfaceProperty.Name, interfaceProperty.Name + "." + interfaceProperty.GetValue(check).ToString()); + break; + } + } + else if (interfaceProperty.Name != "Line" && interfaceProperty.Name != "DescriptionFormat" && + interfaceProperty.Name != "ErrorId" && interfaceProperty.Name != "CheckId" && + interfaceProperty.Name != "DescriptionParameters" && interfaceProperty.Name != "ReferenceNode" && + interfaceProperty.Name != "PositionNode" && interfaceProperty.Name != "Position" && + interfaceProperty.Name != "SubResults" && interfaceProperty.Name != "AutoFixWarnings" && + interfaceProperty.Name != "DveExport") + { + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + string argName = interfaceProperty.Name; + args.Add($"\"{argName}\""); + properties.Add(interfaceProperty.Name, $"\"{argName}\""); + break; + + // uint + case 1: + case 6: + args.Add("0"); + properties.Add(interfaceProperty.Name, "0"); + break; + + // bool + case 2: + args.Add("false"); + properties.Add(interfaceProperty.Name, "false"); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + args.Add($"{interfaceProperty.Name}.Undefined"); + properties.Add(interfaceProperty.Name, $"{interfaceProperty.Name}.Undefined"); + break; + } + } + } + + validateChecks.Add((args.Count != 0, String.Join(", ", args), properties, check)); + } + + foreach (var check in this.compares) + { + List args = new List(); + Dictionary properties = new Dictionary(); + + foreach (var interfaceProperty in interfaceProperties) + { + // Add the description with the correct parameters + if (interfaceProperty.Name == "Description") + { + string desc = TemplateHelper.MakeCSharpString(check.Description); + if (check.Parameters?.Count > 0) + { + // Parameters exists + int argCount = 0; + foreach (var parameter in check.Parameters) + { + if (String.IsNullOrWhiteSpace(parameter.Value)) + { + string argName = parameter.Text; + args.Add($"\"{argName}\""); + desc = desc.Replace($"{{{argCount}}}", $"{argName}"); + } + else + { + desc = desc.Replace($"{{{argCount}}}", $"{parameter.Value}"); + } + + argCount++; + } + } + + properties.Add("Description", "\"" + desc + "\""); + } + else if (check.SettedProperties.Contains(interfaceProperty.Name) && XMLParser.Types.ContainsKey(interfaceProperty.PropertyType) && interfaceProperty.GetValue(check) != null) + { + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + properties.Add(interfaceProperty.Name, ("\"" + TemplateHelper.MakeCSharpString(interfaceProperty.GetValue(check).ToString()) + "\"").Replace(Environment.NewLine, "\" + Environment.NewLine + \"")); + break; + + // uint + case 1: + case 6: + properties.Add(interfaceProperty.Name, interfaceProperty.GetValue(check)); + break; + + // bool + case 2: + properties.Add(interfaceProperty.Name, interfaceProperty.GetValue(check).ToString().ToLower()); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + properties.Add(interfaceProperty.Name, interfaceProperty.Name + "." + interfaceProperty.GetValue(check).ToString()); + break; + } + } + else if (interfaceProperty.Name != "Line" && interfaceProperty.Name != "DescriptionFormat" && + interfaceProperty.Name != "ErrorId" && interfaceProperty.Name != "CheckId" && + interfaceProperty.Name != "DescriptionParameters" && interfaceProperty.Name != "ReferenceNode" && + interfaceProperty.Name != "PositionNode" && interfaceProperty.Name != "Position" && + interfaceProperty.Name != "SubResults" && interfaceProperty.Name != "AutoFixWarnings" && + interfaceProperty.Name != "DveExport") + { + switch (XMLParser.Types[interfaceProperty.PropertyType]) + { + // string + case 0: + string argName = interfaceProperty.Name; + args.Add($"\"{argName}\""); + properties.Add(interfaceProperty.Name, $"\"{argName}\""); + break; + + // uint + case 1: + case 6: + args.Add("0"); + properties.Add(interfaceProperty.Name, "0"); + break; + + // bool + case 2: + args.Add("false"); + properties.Add(interfaceProperty.Name, "false"); + break; + + // Enums + case 3: + case 4: + case 5: + case 7: + case 8: + args.Add($"{interfaceProperty.Name}.Undefined"); + properties.Add(interfaceProperty.Name, $"{interfaceProperty.Name}.Undefined"); + break; + } + } + } + + compareChecks.Add((args.Count != 0, String.Join(", ", args), properties, check)); + } + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Tools/Converters.cs b/Validator Management Tool/Tools/Converters.cs new file mode 100644 index 00000000..dee77606 --- /dev/null +++ b/Validator Management Tool/Tools/Converters.cs @@ -0,0 +1,264 @@ +namespace Validator_Management_Tool.Tools +{ + using System; + using System.Collections.ObjectModel; + using System.Globalization; + using System.Text; + using System.Windows.Data; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Model; + + [ValueConversion(typeof(string), typeof(bool))] + public class EmptyStringToBooleanConvert : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return String.IsNullOrEmpty((string)value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return String.IsNullOrEmpty((string)value); + } + } + + [ValueConversion(typeof(bool), typeof(bool))] + public class InverterConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return !(bool)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return !(bool)value; + } + } + + [ValueConversion(typeof(uint), typeof(Certainty))] + public class CertaintyConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is uint) + { + return (Certainty)Int32.Parse(value.ToString()); + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Certainty) + { + return UInt32.Parse(((int)value).ToString()); + } + return value; + } + } + + [ValueConversion(typeof(uint), typeof(FixImpact))] + public class FixImpactConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is uint) + { + return (FixImpact)Int32.Parse(value.ToString()); + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is FixImpact) + { + return UInt32.Parse(((int)value).ToString()); + } + return value; + } + } + + [ValueConversion(typeof(Category), typeof(int))] + public class CategoryConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Category) + { + return (int)value; + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int) + { + return (Category)Enum.Parse(typeof(Category), value.ToString()); + } + return value; + } + } + + [ValueConversion(typeof(Source), typeof(uint))] + public class SourceConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Source) + { + return (int)value; + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is uint) + { + return (Source)Enum.Parse(typeof(Source), value.ToString()); + } + return value; + } + } + + [ValueConversion(typeof(object), typeof(string))] + public class ArrayToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + ReadOnlyObservableCollection collection = (ReadOnlyObservableCollection)value; + StringBuilder total = new StringBuilder(); + int count = 0; + foreach (CollectionViewGroup group in collection) + { + Check check = (Check)group.Items[0]; + if (count == 0) + { + total.Append(check.CheckId); + } + else + { + total.Append(", ").Append(check.CheckId); + } + + count++; + } + + return total.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } + } + + [ValueConversion(typeof(object), typeof(string))] + public class CheckIdConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + ReadOnlyObservableCollection collection = (ReadOnlyObservableCollection)value; + + if (collection.Count != 0) + { + var check = (Check)collection[0]; + return check.CheckId.ToString(); + } + + return String.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return String.Empty; + } + } + + [ValueConversion(typeof(object), typeof(bool))] + public class CheckBubbelUpConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + ReadOnlyObservableCollection collection = (ReadOnlyObservableCollection)value; + bool error = false; + + foreach (Check check in collection) + { + if (check.Error) + { + error = true; + } + } + + return error; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } + } + + [ValueConversion(typeof(object), typeof(bool))] + public class NamespaceBubbelUpConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + ReadOnlyObservableCollection collection = (ReadOnlyObservableCollection)value; + bool error = false; + + foreach (CollectionViewGroup group in collection) + { + foreach (Check check in group.Items) + { + if (check.Error) + { + error = true; + } + } + } + + return error; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } + } + + [ValueConversion(typeof(object), typeof(bool))] + public class CategoryBubbelUpConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + ReadOnlyObservableCollection collection = (ReadOnlyObservableCollection)value; + bool error = false; + + foreach (CollectionViewGroup group1 in collection) + { + foreach (CollectionViewGroup group2 in group1.Items) + { + foreach (Check check in group2.Items) + { + if (check.Error) + { + error = true; + } + } + } + } + + return error; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value; + } + } +} diff --git a/Validator Management Tool/VML/ViewModelLocator.cs b/Validator Management Tool/VML/ViewModelLocator.cs new file mode 100644 index 00000000..a986a92e --- /dev/null +++ b/Validator Management Tool/VML/ViewModelLocator.cs @@ -0,0 +1,48 @@ +namespace Validator_Management_Tool.VML +{ + using System; + using System.ComponentModel; + using System.Windows; + + public static class ViewModelLocator + { + public static bool GetAutoHookedUpViewModel(DependencyObject obj) + { + return (bool)obj.GetValue(AutoHookedUpViewModelProperty); + } + + public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) + { + obj.SetValue(AutoHookedUpViewModelProperty, value); + } + + // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. + // This enables animation, styling, binding, etc... + + public static readonly DependencyProperty AutoHookedUpViewModelProperty = + DependencyProperty.RegisterAttached("AutoHookedUpViewModel", + typeof(bool), + typeof(ViewModelLocator), + new PropertyMetadata(false, AutoHookedUpViewModelChanged)); + + private static void AutoHookedUpViewModelChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) + { + if (DesignerProperties.GetIsInDesignMode(d)) + { + return; + } + + var viewType = d.GetType(); + + string str = viewType.FullName; + str = str.Replace(".Views.", ".ViewModel."); + + var viewTypeName = str; + var viewModelTypeName = viewTypeName + "Model"; + var viewModelType = Type.GetType(viewModelTypeName); + var viewModel = Activator.CreateInstance(viewModelType); + + ((FrameworkElement)d).DataContext = viewModel; + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Validator Management Tool.csproj b/Validator Management Tool/Validator Management Tool.csproj new file mode 100644 index 00000000..b2547499 --- /dev/null +++ b/Validator Management Tool/Validator Management Tool.csproj @@ -0,0 +1,220 @@ + + + + + Debug + AnyCPU + {82F5E0F7-A112-489E-9480-D4ECD9FACB4E} + WinExe + Validator_Management_Tool + Validator Management Tool + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + false + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + false + + + + MSBuild:Compile + Designer + + + + + + + + + + + + + TextTemplatingFilePreprocessor + ErrorMessagesClass.cs + + + + True + True + ErrorMessagesClass.tt + + + + + TestsClass.tt + True + True + + + + True + True + UnitTestClass.tt + + + + + + + + + False + + + + + AddCheckView.xaml + + + CheckEditView.xaml + + + CheckView.xaml + + + NewCheckView.xaml + + + NewNamespaceView.xaml + + + SettingsView.xaml + + + + TextTemplatingFilePreprocessor + UnitTestClass.cs + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + + Code + + + + + + + + TextTemplatingFilePreprocessor + TestsClass.cs + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + + + + + + + 2.6.2 + + + 1.3.3 + + + 1.0.8-a + + + + + {6c83f73e-1130-46ef-bb3c-67573f987afe} + Common + + + + \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/AddCheckViewModel.cs b/Validator Management Tool/ViewModel/AddCheckViewModel.cs new file mode 100644 index 00000000..4ff874dc --- /dev/null +++ b/Validator Management Tool/ViewModel/AddCheckViewModel.cs @@ -0,0 +1,596 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Interfaces; + using Validator_Management_Tool.Model; + using Validator_Management_Tool.Views; + + /// + /// The Viewmodel for the Add Error Message window. + /// + public class AddCheckViewModel : BindableBase + { + private Check check; + private List severities; + private List certainties; + private List fixImpacts; + private List categories; + private List sources; + private List selectedNamespaces; + + /// + /// Initializes a new instance of the class. + /// A new check object is created and the necessary default values are set. + /// + public AddCheckViewModel() + { + // Setting up a new check object + Check = new Check + { + Name = String.Empty, + CheckName = String.Empty, + Description = String.Empty, + Namespace = String.Empty, + Details = String.Empty, + HowToFix = String.Empty, + ExampleCode = String.Empty, + Severity = Severity.Undefined, + Certainty = Certainty.Undefined, + Category = Category.Undefined, + CategoryId = 1, + FixImpact = FixImpact.Undefined, + HasCodeFix = false, + Source = Source.Undefined, + GroupDescription = String.Empty + }; + + CancelCommand = new MyCommand(OnCancel); + AddCheckCommand = new MyCommand(OnAddCheck); + RefreshParametersCommand = new MyCommand(OnRefreshParameters); + AddNewCheckCommand = new MyTCommand(OnAddNewCheck); + AddNewNamespaceCommand = new MyCommand(OnAddNewNamespace); + + severities = new List(Enum.GetValues(typeof(Severity)).Cast().ToList()); + certainties = new List(Enum.GetValues(typeof(Certainty)).Cast().ToList()); + fixImpacts = new List(Enum.GetValues(typeof(FixImpact)).Cast().ToList()); + categories = new List(Enum.GetValues(typeof(Category)).Cast().ToList()); + sources = new List(Enum.GetValues(typeof(Source)).Cast().ToList()); + + GetExistingNamespace(); + CalculateNextIds(); + } + + public List SelectedNamespaces + { + get + { + selectedNamespaces.Sort(); + return selectedNamespaces; + } + + set + { + if (value != selectedNamespaces) + { + selectedNamespaces = value; + selectedNamespaces.Sort(); + OnPropertyChanged("SelectedNamespaces"); + } + } + } + + public string SelectedNamespace + { + get + { + return Check.Namespace; + } + + set + { + if (value != null && value != Check.Namespace) + { + Check.Namespace = value; + + OnPropertyChanged("SelectedNamespace"); + OnPropertyChanged("SelectedCheck"); + OnPropertyChanged("SelectedChecks"); + } + } + } + + /// + /// Gets or sets the command to cancel the add check window. + /// + public MyCommand CancelCommand { get; set; } + + /// + /// Gets or sets the command to add a new check to the list. + /// + public MyCommand AddCheckCommand { get; set; } + + /// + /// Gets or sets the command to create a new Check name within a certain namespace. + /// + public MyTCommand AddNewCheckCommand { get; set; } + + /// + /// Gets or sets the command to refresh the errors on the description parameters. + /// + public MyCommand RefreshParametersCommand { get; set; } + + /// + /// Gets or sets the command to create a new namespace within a certain category. + /// + public MyCommand AddNewNamespaceCommand { get; set; } + + /// + /// Gets or sets the check that is being made. + /// + public Check Check + { + get + { + return check; + } + + set + { + if (value != check) + { + check = value; + OnPropertyChanged("Check"); + } + } + } + + /// + /// Gets ors sets the currently selected checks that needs to appear in the combobox. + /// + public List SelectedChecks + { + get + { + if (Check.CategoryId != 0) + { + if (CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()) == null) + { + AddNewCategory(SelectedCategory); + } + + return CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()).Checks.Check.Where(e => e.Name.Namespace == SelectedNamespace).ToList(); + } + else + { + return new List(); + } + } + } + + /// + /// Gets or sets the currently selected category from the combobox. + /// + public Category SelectedCategory + { + get + { + return Check.Category; + } + + set + { + if (value.ToString() != Check.Category.ToString()) + { + Check.Category = value; + + if ((int)value == -1) + { + Check.CategoryId = 0; + } + else + { + Check.CategoryId = (uint)(int)value; + } + + GetExistingNamespace(); + + OnPropertyChanged("SelectedCategory"); + OnPropertyChanged("SelectedNamespace"); + } + } + } + + /// + /// Gets or sets the currently selected source from the combobox. + /// + public Source SelectedSource + { + get + { + return Check.Source; + } + + set + { + if (value.ToString() != Check.Source.ToString()) + { + Check.Source = value; + + OnPropertyChanged("SelectedSource"); + } + } + } + + /// + /// Gets or sets the currently selected check from the combobox. + /// + public Serialization.Check SelectedCheck + { + get + { + return new Serialization.Check() + { + Name = new Serialization.Name() + { + Text = Check.CheckName + }, + Id = Check.ErrorId.ToString() + }; + } + + set + { + if (value != null && value.Name.Text != Check.CheckName) + { + Check.CheckName = value.Name.Text; + Check.CheckId = UInt32.Parse(value.Id); + Check.ErrorId = NextIds[key: Check.CategoryId][key: Check.CheckId]; + + OnPropertyChanged("SelectedCheck"); + } + } + } + + /// + /// Gets or sets a value indicating whether a check is using a description template or not. + /// + public bool FromTemplate + { + get + { + return Check.FromTemplate; + } + + set + { + if (value != Check.FromTemplate) + { + if (value) + { + SelectedTemplate = Templates[0]; + } + + Check.FromTemplate = value; + OnPropertyChanged("FromTemplate"); + } + } + } + + /// + /// Gets or sets the currently selected description template from the combobox. + /// + public Serialization.DescriptionTemplate SelectedTemplate + { + get => + new Serialization.DescriptionTemplate + { + Id = Check.TemplateId, + Format = Check.Description, + TemplateInputParameters = new Serialization.InputParameters() + }; + + set + { + if (value == null) + { + return; + } + + Check.Parameters = new ObservableCollection(); + foreach (var inputParameter in value.TemplateInputParameters.InputParameter) + { + check.Parameters.Add(new Serialization.InputParameter + { + Id = inputParameter.Id, + Text = inputParameter.Text, + }); + } + + Check.Description = value.Format; + Check.TemplateId = value.Id; + OnPropertyChanged("SelectedTemplate"); + } + } + + /// + /// Gets a dictionary with the next error Id within a certain check and category. + /// + public Dictionary> NextIds { get; private set; } + + /// + /// Gets the collection of the categories that are currently used. + /// + public List CategoriesCollection + { + get + { + return CheckViewModel.Categories; + } + } + + /// + /// Gets or sets the list of categories that needs to appear in the combobox. + /// + public List Categories + { + get + { + return categories; + } + + set + { + if (value != categories) + { + categories = value; + OnPropertyChanged("Categories"); + } + } + } + + /// + /// Gets or sets the list of sources that needs to appear in the combobox. + /// + public List Sources + { + get + { + return sources; + } + + set + { + if (value != sources) + { + sources = value; + OnPropertyChanged("Sources"); + } + } + } + + /// + /// Gets or sets the list of severities that needs to appear in the combobox. + /// + public List Severities + { + get + { + return severities; + } + + set + { + if (value != severities) + { + severities = value; + OnPropertyChanged("Severities"); + } + } + } + + /// + /// Gets or sets the list of certainties that needs to appear in the combobox. + /// + public List Certainties + { + get + { + return certainties; + } + + set + { + if (value != certainties) + { + certainties = value; + OnPropertyChanged("Certainties"); + } + } + } + + /// + /// Gets or sets the list of breakingchanges that needs to appear in the combobox. + /// + public List FixImpacts + { + get + { + return fixImpacts; + } + + set + { + if (value != fixImpacts) + { + fixImpacts = value; + OnPropertyChanged("FixImpacts"); + } + } + } + + /// + /// Gets the list of description templates that needs to appear in the combobox. + /// + public List Templates + { + get + { + return CheckViewModel.Templates; + } + } + + /// + /// Gets or sets the close action. + /// + public Action CloseAction { get; set; } + + private void GetExistingNamespace() + { + if (Check.CategoryId != 0) + { + if (CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()) == null) + { + AddNewCategory(SelectedCategory); + SelectedNamespaces = new List(); + } + else + { + List namespaceList = new List(); + foreach (var checkIterator in CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()).Checks.Check) + { + if (!namespaceList.Contains(checkIterator.Name.Namespace)) + { + namespaceList.Add(checkIterator.Name.Namespace); + } + } + + SelectedNamespaces = namespaceList; + } + } + else + { + SelectedNamespaces = new List(); + } + } + + /// + /// Opens the window to create a new namespace within a certain category. + /// + /// The category where the new check has to be created. + private void OnAddNewNamespace() + { + int amountOfNamespacesBefore = SelectedNamespaces.Count; + var window = new NewNamespaceView(SelectedCategory, SelectedNamespaces); + window.ShowDialog(); + + if (amountOfNamespacesBefore < SelectedNamespaces.Count) + { + OnPropertyChanged("SelectedNamespace"); + SelectedNamespace = SelectedNamespaces[0]; + SelectedNamespaces = new List(SelectedNamespaces); // Needed for event + } + } + + /// + /// Closes the window without saving. + /// + private void OnCancel() + { + CloseAction(); + } + + /// + /// Closes the window and adds the new error message to the list. + /// + private void OnAddCheck() + { + Check.CheckDescriptionParameters(); + CheckViewModel.Checks.Add(Check); + CheckViewModel.Checks = new ObservableCollection( + CheckViewModel.Checks.OrderBy(c => c.CategoryId).ThenBy(x => x.Namespace).ThenBy(c => c.CheckId).ThenBy(c => c.ErrorId).ToList()); + CloseAction(); + } + + /// + /// Refreshes the errors from the description parameters. + /// + private void OnRefreshParameters() + { + Check.RefreshParameters(); + } + + /// + /// Opens the window to create a new check within a certain category. + /// + /// The category where the new check has to be created. + private void OnAddNewCheck(string @namespace) + { + int amountOfChecksBefore = CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count; + var window = new NewCheckView(SelectedCategory, @namespace); + window.ShowDialog(); + + if (amountOfChecksBefore != CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count) + { + CalculateNextIds(); + OnPropertyChanged("SelectedChecks"); + if (CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count != 0) + { + SelectedCheck = CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Last(); + } + } + } + + /// + /// Calculates the next id for each check in each category. + /// + private void CalculateNextIds() + { + // > + NextIds = new Dictionary>(); + foreach (var category in CategoriesCollection) + { + Dictionary nextErrorIds = new Dictionary(); + foreach (var idCheck in category.Checks.Check) + { + uint maxErrorId = 0; + + foreach (var errorMessage in CheckViewModel.Checks) + { + if (errorMessage.Category.ToString() == category.Name + && errorMessage.CheckName == idCheck.Name.Text + && errorMessage.Namespace == idCheck.Name.Namespace + && errorMessage.ErrorId > maxErrorId) + { + maxErrorId = errorMessage.ErrorId; + } + } + + nextErrorIds.Add(UInt32.Parse(idCheck.Id), maxErrorId + 1); + } + + NextIds.Add(UInt32.Parse(category.Id), nextErrorIds); + } + } + + /// + /// Adds a new category to the list of categories. + /// + /// The category that needs to be added. + private void AddNewCategory(Category category) + { + CheckViewModel.Categories.Add(new Serialization.Category() + { + Id = ((int)category).ToString(), + Name = category.ToString(), + Checks = new Serialization.Checks() + { + Check = new List() + } + }); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/CheckEditViewModel.cs b/Validator Management Tool/ViewModel/CheckEditViewModel.cs new file mode 100644 index 00000000..6153bddd --- /dev/null +++ b/Validator Management Tool/ViewModel/CheckEditViewModel.cs @@ -0,0 +1,621 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Interfaces; + using Validator_Management_Tool.Model; + using Validator_Management_Tool.Views; + + /// + /// The Viewmodel for the Edit Error Message window. + /// + public class CheckEditViewModel : BindableBase + { + private readonly bool originalFromTemplate; + private readonly Check newCheck; + private Check check; + private List severities; + private List certainties; + private List fixImpacts; + private List categories; + private List sources; + private List selectedNamespaces; + + /// + /// Initializes a new instance of the class. + /// + /// The check that has to be edited. + public CheckEditViewModel(Check check) + { + newCheck = check; + this.check = new Check(check); + CancelCommand = new MyCommand(OnCancel); + SaveCommand = new MyCommand(OnSave); + RefreshParametersCommand = new MyCommand(OnRefreshParameters); + AddNewCheckCommand = new MyTCommand(OnAddNewCheck); + AddNewNamespaceCommand = new MyCommand(OnAddNewNamespace); + + severities = new List(Enum.GetValues(typeof(Severity)).Cast().ToList()); + certainties = new List(Enum.GetValues(typeof(Certainty)).Cast().ToList()); + fixImpacts = new List(Enum.GetValues(typeof(FixImpact)).Cast().ToList()); + categories = new List(Enum.GetValues(typeof(Category)).Cast().ToList()); + sources = new List(Enum.GetValues(typeof(Source)).Cast().ToList()); + + GetExistingNamespace(); + CalculateNextIds(); + + originalFromTemplate = Check.FromTemplate; + } + + public List SelectedNamespaces + { + get + { + selectedNamespaces.Sort(); + return selectedNamespaces; + } + + set + { + if (value != selectedNamespaces) + { + selectedNamespaces = value; + selectedNamespaces.Sort(); + OnPropertyChanged("SelectedNamespaces"); + } + } + } + + public string SelectedNamespace + { + get + { + return Check.Namespace; + } + + set + { + if (value != null && value != Check.Namespace) + { + Check.Namespace = value; + + OnPropertyChanged("SelectedNamespace"); + OnPropertyChanged("SelectedCheck"); + OnPropertyChanged("SelectedChecks"); + } + + if (value == newCheck.Namespace) + { + Check.Namespace = newCheck.Namespace; + } + } + } + + /// + /// Gets or sets the check that is being edited. + /// + public Check Check + { + get + { + return check; + } + + set + { + check = value; + OnPropertyChanged("Check"); + } + } + + /// + /// Gets ors sets the currently selected checks that needs to appear in the combobox. + /// + public List SelectedChecks + { + get + { + if (Check.CategoryId != 0) + { + if (CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()) == null) + { + AddNewCategory(SelectedCategory); + return new List(); + } + + return CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()).Checks.Check.Where(e => e.Name.Namespace == SelectedNamespace).ToList(); + } + else + { + return new List(); + } + } + } + + /// + /// Gets or sets the currently selected category from the combobox. + /// + public Category SelectedCategory + { + get + { + return Check.Category; + } + + set + { + if (value.ToString() != Check.Category.ToString()) + { + Check.Category = value; + + if ((int)value == -1) + { + Check.CategoryId = 0; + } + else + { + Check.CategoryId = (uint)(int)value; + } + + GetExistingNamespace(); + + OnPropertyChanged("SelectedCategory"); + OnPropertyChanged("SelectedNamespace"); + } + + if (value.ToString() == newCheck.Category.ToString()) + { + Check.CheckName = newCheck.CheckName; + } + } + } + + /// + /// Gets or sets the currently selected source from the combobox. + /// + public Source SelectedSource + { + get + { + return Check.Source; + } + + set + { + if (value.ToString() != Check.Source.ToString()) + { + Check.Source = value; + + OnPropertyChanged("SelectedSource"); + } + } + } + + /// + /// Gets or sets the currently selected check from the combobox. + /// + public Serialization.Check SelectedCheck + { + get + { + return new Serialization.Check() + { + Name = new Serialization.Name() + { + Text = Check.CheckName + }, + Id = Check.ErrorId.ToString() + }; + } + + set + { + if (value != null && value.Name.Text != Check.CheckName) + { + Check.CheckName = value.Name.Text; + Check.CheckId = UInt32.Parse(value.Id); + + // Change the Id to the next available ID for that category + if (value.Name.Text == newCheck.CheckName.ToString() && SelectedCategory.ToString() == newCheck.Category.ToString()) + { + Check.ErrorId = newCheck.ErrorId; + } + else + { + if (NextIds.ContainsKey(Check.CategoryId)) + { + Check.ErrorId = NextIds[key: Check.CategoryId][key: Check.CheckId]; + } + else + { + AddNewCategory(SelectedCategory); + } + } + + OnPropertyChanged("SelectedCheck"); + } + } + } + + /// + /// Gets or sets a value indicating whether a check is using a description template or not. + /// + public bool FromTemplate + { + get + { + return Check.FromTemplate; + } + + set + { + if (value != Check.FromTemplate) + { + if (value) + { + if (!originalFromTemplate) + { + SelectedTemplate = Templates[0]; + } + else + { + SelectedTemplate = Templates[(int)Check.TemplateId]; + } + } + + Check.FromTemplate = value; + OnPropertyChanged("FromTemplate"); + } + } + } + + /// + /// Gets or sets the currently selected description template from the combobox. + /// + public Serialization.DescriptionTemplate SelectedTemplate + { + get => + new Serialization.DescriptionTemplate + { + Id = Check.TemplateId, + Format = Check.Description, + TemplateInputParameters = new Serialization.InputParameters() + }; + + set + { + if (value == null) + { + return; + } + + if (Check.Parameters == null) + { + Check.Parameters = new ObservableCollection(); + foreach (var inputParameter in value.TemplateInputParameters.InputParameter) + { + check.Parameters.Add(new Serialization.InputParameter + { + Id = inputParameter.Id, + Text = inputParameter.Text, + }); + } + } + + Check.Description = value.Format; + Check.TemplateId = value.Id; + OnPropertyChanged("SelectedTemplate"); + } + } + + /// + /// Gets a dictionary with the next error Id within a certain check and category. + /// + public Dictionary> NextIds { get; private set; } + + /// + /// Gets the collection of the categories that are currently used. + /// + public List CategoriesCollection + { + get + { + return CheckViewModel.Categories; + } + } + + /// + /// Gets or sets the list of severities that needs to appear in the combobox. + /// + public List Severities + { + get + { + return severities; + } + + set + { + if (value != severities) + { + severities = value; + OnPropertyChanged("Severities"); + } + } + } + + /// + /// Gets or sets the list of categories that needs to appear in the combobox. + /// + public List Categories + { + get + { + return categories; + } + + set + { + if (value != categories) + { + categories = value; + OnPropertyChanged("Categories"); + } + } + } + + /// + /// Gets or sets the list of sources that needs to appear in the combobox. + /// + public List Sources + { + get + { + return sources; + } + + set + { + if (value != sources) + { + sources = value; + OnPropertyChanged("Sources"); + } + } + } + + /// + /// Gets or sets the list of certainties that needs to appear in the combobox. + /// + public List Certainties + { + get + { + return certainties; + } + + set + { + if (value != certainties) + { + certainties = value; + OnPropertyChanged("Certainties"); + } + } + } + + /// + /// Gets or sets the list of breakingchanges that needs to appear in the combobox. + /// + public List FixImpacts + { + get + { + return fixImpacts; + } + + set + { + if (value != fixImpacts) + { + fixImpacts = value; + OnPropertyChanged("FixImpacts"); + } + } + } + + /// + /// Gets the list of description templates that needs to appear in the combobox. + /// + public List Templates + { + get + { + return CheckViewModel.Templates; + } + } + + /// + /// Gets or sets the command to cancel the add check window. + /// + public MyCommand CancelCommand { get; set; } + + /// + /// Gets or sets the command to save the changes that are made to the list. + /// + public MyCommand SaveCommand { get; set; } + + /// + /// Gets or sets the command to refresh the errors on the description parameters. + /// + public MyCommand RefreshParametersCommand { get; set; } + + /// + /// Gets or sets the command to create a new namespace within a certain category. + /// + public MyCommand AddNewNamespaceCommand { get; set; } + + /// + /// Gets or sets the command to create a new Check name within a certain namespace. + /// + public MyTCommand AddNewCheckCommand { get; set; } + + /// + /// Gets or sets the close action. + /// + public Action CloseAction { get; set; } + + private void GetExistingNamespace() + { + if (Check.CategoryId != 0) + { + if (CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()) == null) + { + AddNewCategory(SelectedCategory); + SelectedNamespaces = new List(); + } + else + { + List namespaceList = new List(); + foreach (var checkIterator in CategoriesCollection.FirstOrDefault(c => c.Id.ToString() == Check.CategoryId.ToString()).Checks.Check) + { + if (!namespaceList.Contains(checkIterator.Name.Namespace)) + { + namespaceList.Add(checkIterator.Name.Namespace); + } + } + + SelectedNamespaces = namespaceList; + } + } + else + { + SelectedNamespaces = new List(); + } + } + + /// + /// Adds a new category to the list of categories. + /// + /// The category that needs to be added. + private void AddNewCategory(Category category) + { + CheckViewModel.Categories.Add(new Serialization.Category() + { + Id = ((int)category).ToString(), + Name = category.ToString(), + Checks = new Serialization.Checks() + { + Check = new List() + } + }); + } + + /// + /// Opens the window to create a new check within a certain namespace. + /// + /// The namespace where the new check has to be created. + private void OnAddNewCheck(string @namespace) + { + int amountOfChecksBefore = CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count; + var window = new NewCheckView(SelectedCategory, @namespace); + window.ShowDialog(); + + if (amountOfChecksBefore != CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count) + { + CalculateNextIds(); + OnPropertyChanged("SelectedChecks"); + if (CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Count != 0) + { + SelectedCheck = CategoriesCollection.SingleOrDefault(e => e.Name == check.Category.ToString()).Checks.Check.Last(); + } + } + } + + /// + /// Opens the window to create a new namespace within a certain category. + /// + /// The category where the new check has to be created. + private void OnAddNewNamespace() + { + int amountOfNamespacesBefore = SelectedNamespaces.Count; + var window = new NewNamespaceView(SelectedCategory, SelectedNamespaces); + window.ShowDialog(); + + if (amountOfNamespacesBefore < SelectedNamespaces.Count) + { + OnPropertyChanged("SelectedNamespace"); + SelectedNamespace = SelectedNamespaces[0]; + SelectedNamespaces = new List(SelectedNamespaces); + } + } + + /// + /// Closes the window without saving the changes. + /// + private void OnCancel() + { + CloseAction(); + } + + /// + /// Saves the changes that are made to the list. + /// + private void OnSave() + { + Check.CheckDescriptionParameters(); + newCheck.Copy(Check); + CheckViewModel.Checks = new ObservableCollection( + CheckViewModel.Checks.OrderBy(c => c.CategoryId).ThenBy(x => x.Namespace).ThenBy(c => c.CheckId).ThenBy(c => c.ErrorId).ToList()); + CloseAction(); + } + + /// + /// Refreshes the errors from the description parameters. + /// + private void OnRefreshParameters() + { + Check.RefreshParameters(); + } + + /// + /// Calculates the next id for each check in each category. + /// + private void CalculateNextIds() + { + NextIds = new Dictionary>(); + foreach (var category in CategoriesCollection) + { + Dictionary nextErrorIds = new Dictionary(); + foreach (var idCheck in category.Checks.Check) + { + uint maxErrorId = 0; + + foreach (var errorMessage in CheckViewModel.Checks) + { + if (errorMessage.Category.ToString() == category.Name + && errorMessage.CheckName == idCheck.Name.Text + && errorMessage.Namespace == idCheck.Name.Namespace + && errorMessage.ErrorId > maxErrorId) + { + maxErrorId = errorMessage.ErrorId; + } + } + + if (!nextErrorIds.ContainsKey(UInt32.Parse(idCheck.Id))) + { + nextErrorIds.Add(UInt32.Parse(idCheck.Id), maxErrorId + 1); + } + } + + if (!NextIds.ContainsKey(UInt32.Parse(category.Id))) + { + NextIds.Add(UInt32.Parse(category.Id), nextErrorIds); + } + } + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/CheckViewModel.cs b/Validator Management Tool/ViewModel/CheckViewModel.cs new file mode 100644 index 00000000..c24cbe4e --- /dev/null +++ b/Validator Management Tool/ViewModel/CheckViewModel.cs @@ -0,0 +1,882 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Linq; + using System.Text; + using System.Windows; + using System.Windows.Data; + using System.Windows.Threading; + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Interfaces; + using Validator_Management_Tool.Model; + using Validator_Management_Tool.Views; + + /// + /// The Viewmodel for the main check overview. + /// + public class CheckViewModel : BindableBase + { + private static ObservableCollection checks; + private bool hasErrors; + private bool categoryExpanderToggle = true; + private bool namespaceExpanderToggle = true; + private bool checkNameExpanderToggle = true; + private bool hasChanges = false; + private int currentErrorIndex = 0; + private string searchString; + private bool isCollapsed = false; + + /// + /// Initializes a new instance of the class. + /// Loads the checks with the static methods. + /// + public CheckViewModel() + { + checks = new ObservableCollection(); + checks.CollectionChanged += Checks_CollectionChanged; + + ErrorChecks = new List(); + + this.LoadChecks(); + this.GenerateFilesCommand = new MyCommand(this.OnGenerateFiles); + this.ExpandCommand = new MyCommand(this.OnExpand); + this.RefreshCommand = new MyCommand(this.OnRefresh); + this.SaveCommand = new MyCommand(OnSave); + this.AddCheckCommand = new MyCommand(this.OnAddCheck); + this.DeleteCommand = new MyTCommand(this.OnDelete); + this.OpenCheckCommand = new MyCommand(this.OnOpenCheck); + this.UpArrowCommand = new MyCommand(this.OnUpArrow); + this.DownArrowCommand = new MyCommand(this.OnDownArrow); + this.GenerateExcelCommand = new MyCommand(this.OnGenerateExcel); + + Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing); + + View = CollectionViewSource.GetDefaultView(Checks); + View.GroupDescriptions.Add(new PropertyGroupDescription("Category")); + View.GroupDescriptions.Add(new PropertyGroupDescription("Namespace")); + View.GroupDescriptions.Add(new PropertyGroupDescription("CheckName")); + View.Filter = Filter; + } + + public ICollectionView View; + + public string CollapseButtonString + { + get + { + if (isCollapsed) + { + return "Expand All"; + } + else + { + return "Collapse All"; + } + } + } + + public string SearchString + { + get + { + return searchString; + } + + set + { + if (value != searchString) + { + searchString = value; + OnPropertyChanged("SearchString"); + View.Refresh(); + } + } + } + + /// + /// Gets or sets the collection that holds all the error messages. + /// + public static ObservableCollection Checks + { + get + { + return checks; + } + + set + { + checks.Clear(); + foreach (var check in value) + { + checks.Add(check); + } + } + } + + /// + /// Gets or sets the collection that holds all the description templates. + /// + public static List Templates { get; set; } + + /// + /// Gets or sets the collection that holds all the categories and checks. + /// + public static List Categories { get; set; } + + /// + /// Gets or sets the command to refresh the view. + /// + public MyCommand RefreshCommand { get; set; } + + /// + /// Gets or sets the command to generate the classes in files. + /// + public MyCommand GenerateFilesCommand { get; set; } + + /// + /// Gets or sets the command to collapse the tree-view. + /// + public MyCommand ExpandCommand { get; set; } + + /// + /// Gets or sets the command to save the checks. + /// + public MyCommand SaveCommand { get; set; } + + /// + /// Gets or sets the command to add a new error message + /// + public MyCommand AddCheckCommand { get; set; } + + /// + /// Gets or sets the command to delete a error message from the list. + /// + public MyTCommand DeleteCommand { get; set; } + + /// + /// Gets or sets the command to open the edit window for a error message. + /// + public MyCommand OpenCheckCommand { get; set; } + + /// + /// Gets or sets the command to go to the next error. + /// + public MyCommand UpArrowCommand { get; set; } + + /// + /// Gets or sets the command to go to the previous error. + /// + public MyCommand DownArrowCommand { get; set; } + + /// + /// Gets or sets the command to generate an excel worksheet. + /// + public MyCommand GenerateExcelCommand { get; set; } + + /// + /// Gets or sets a value indicating whether one of the checks has changes or not. + /// + public bool HasChanges + { + get + { + return hasChanges; + } + + set + { + if (value != hasChanges) + { + hasChanges = value; + OnPropertyChanged("HasChanges"); + } + } + } + + /// + /// Gets or sets a value indicating whether the category expanders are open or not. + /// + public bool CategoryExpanderToggle + { + get + { + return categoryExpanderToggle; + } + + set + { + categoryExpanderToggle = value; + OnPropertyChanged("CategoryExpanderToggle"); + } + } + + /// + /// Gets or sets a value indicating whether the namespace expanders are open or not. + /// + public bool NamespaceExpanderToggle + { + get + { + return namespaceExpanderToggle; + } + + set + { + namespaceExpanderToggle = value; + OnPropertyChanged("NamespaceExpanderToggle"); + } + } + + /// + /// Gets or sets a value indicating whether the check name expanders are open or not. + /// + public bool CheckNameExpanderToggle + { + get + { + return checkNameExpanderToggle; + } + + set + { + checkNameExpanderToggle = value; + OnPropertyChanged("CheckNameExpanderToggle"); + } + } + + /// + /// Gets the currently selected check with an error. + /// + public Check SelectedErrorCheck + { + get + { + if (ErrorChecks.Count > 0) + { + return ErrorChecks[currentErrorIndex]; + } + + return new Check(); + } + } + + /// + /// Gets or sets the list with all the checks that have an error. + /// + public List ErrorChecks { get; set; } + + /// + /// Gets a value indicating whether the up arrow on the error navigation is enabled or not. + /// + public bool UpArrowEnabled + { + get + { + return currentErrorIndex < ErrorChecks.Count - 1; + } + } + + /// + /// Gets a value indicating whether the down arrow on the error navigation is enabled or not. + /// + public bool DownArrowEnabled + { + get + { + return currentErrorIndex > 0; + } + } + + /// + /// Gets or sets a value indicating whether one of the checks has an error or not. + /// + public bool HasErrors + { + get + { + return hasErrors; + } + + set + { + if (value != hasErrors) + { + hasErrors = value; + OnPropertyChanged("HasErrors"); + } + } + } + + /// + /// Reads the checks from the XML and updates the display. + /// + public void LoadChecks() + { + Serialization.Serializer.ReadXml(Settings.XmlPath); + + foreach (var check in Serialization.Serializer.GetChecks()) + { + Checks.Add(check); + } + + Checks = new ObservableCollection( + Checks.OrderBy(c => c.CategoryId).ThenBy(x => x.Namespace).ThenBy(c => c.CheckId).ThenBy(c => c.ErrorId).ToList()); + + if (Serialization.Serializer.ParsingErrors.Count > 0) + { + StringBuilder error = new StringBuilder(); + foreach (var parsingError in Serialization.Serializer.ParsingErrors) + { + error.Append(parsingError + "\n"); + } + + Application.Current.Dispatcher.BeginInvoke( + DispatcherPriority.Normal, + (Action)(() => { MessageBox.Show(error.ToString(), "Alert", MessageBoxButton.OK, MessageBoxImage.Information); })); + } + + foreach (var check in Checks) + { + check.HasChanges = false; + } + + Templates = Serialization.Serializer.GetTemplates().OrderBy(template => template.Id).ToList(); + Categories = Serialization.Serializer.GetCategories(); + + GetErrorsAndChanges(); + } + + /// + /// Checks if the checks have errors or changes. + /// + public void GetErrorsAndChanges() + { + List errorList = new List(); + foreach (var check in Checks) + { + if (check.Error) + { + errorList.Add(check); + } + } + + ErrorChecks = errorList; + if (ErrorChecks.Count != 0) + { + HasErrors = true; + currentErrorIndex = 0; + UpdateErrorNavigation(); + } + else + { + HasErrors = false; + } + + bool changes = false; + int count = 0; + while (!changes && count < Checks.Count) + { + if (Checks[count].HasChanges) + { + changes = true; + } + + count++; + } + + HasChanges = changes; + } + + /// + /// Is executed on the main window closing event. + /// + public void MainWindow_Closing(object sender, CancelEventArgs e) + { + if (HasChanges) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + "There are unsaved changed. Would you like to save them before we close the application?", + "Close Confirmation", + MessageBoxButton.YesNoCancel, + MessageBoxImage.Exclamation); + if (messageBoxResult == MessageBoxResult.Yes) + { + OnSave(); + } + else if (messageBoxResult == MessageBoxResult.Cancel) + { + e.Cancel = true; + } + } + } + + /// + /// Is executed when the checks collection changes. + /// + private void Checks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + GetErrorsAndChanges(); + if (e.NewItems != null) + { + foreach (object check in e.NewItems) + { + (check as INotifyPropertyChanged).PropertyChanged + += new PropertyChangedEventHandler(Check_PropertyChanged); + } + } + + if (e.OldItems != null) + { + foreach (object check in e.OldItems) + { + (check as INotifyPropertyChanged).PropertyChanged + -= new PropertyChangedEventHandler(Check_PropertyChanged); + } + } + } + + /// + /// Is executed when a property of a check in the collection is changed. + /// + private void Check_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + GetErrorsAndChanges(); + OnPropertyChanged("Checks"); + } + + /// + /// Notifies the View if the errors in the checks are changed. + /// + private void UpdateErrorNavigation() + { + OnPropertyChanged("UpArrowEnabled"); + OnPropertyChanged("DownArrowEnabled"); + OnPropertyChanged("SelectedErrorCheck"); + } + + /// + /// Generates the classes into files. + /// + private void OnGenerateFiles() + { + if (HasChanges) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + "There are unsaved checks, do you want to Save first and Generate afterwards?", + "Unsaved Checks", + MessageBoxButton.YesNo, + MessageBoxImage.Information); + if (messageBoxResult == MessageBoxResult.Yes) + { + OnSave(); + TestGenerator.GenerateFiles(Checks); + MessageBox.Show("Generating Done!", "Generation Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + else + { + TestGenerator.GenerateFiles(Checks); + MessageBox.Show("Generating Done!", "Generation Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + /// + /// Generates an excel worksheet with all the error messages. + /// + private void OnGenerateExcel() + { + if (HasChanges) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + "There are unsaved checks, do you want to Save first and Generate afterwards?", + "Unsaved Checks", + MessageBoxButton.YesNo, + MessageBoxImage.Information); + if (messageBoxResult == MessageBoxResult.Yes) + { + OnSave(); + ExportManager.ExportToExcel(Checks); + MessageBox.Show("Exporting Done!", "Export Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + else + { + ExportManager.ExportToExcel(Checks); + MessageBox.Show("Exporting Done!", "Export Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + /// + /// Collapses or expands the grouping headers. + /// + private void OnExpand() + { + if (isCollapsed) + { + CategoryExpanderToggle = true; + NamespaceExpanderToggle = true; + CheckNameExpanderToggle = true; + + isCollapsed = false; + OnPropertyChanged("CollapseButtonString"); + } + else + { + CategoryExpanderToggle = false; + NamespaceExpanderToggle = false; + CheckNameExpanderToggle = false; + + isCollapsed = true; + OnPropertyChanged("CollapseButtonString"); + } + } + + /// + /// Refreshes the view and reads the XML again. + /// + private void OnRefresh() + { + if (MessageBox.Show("Any unsaved changes will be removed and the file will be refreshed with the items from the XML.", "Warning", MessageBoxButton.OKCancel) == MessageBoxResult.OK) + { + Checks.Clear(); + LoadChecks(); + } + } + + private void CheckDuplicates() + { + List parsingErrors = new List(); + + List namespaces = Checks.Select(x => x.Namespace).Distinct().ToList(); + + foreach (var ns in namespaces) + { + var checkNamesInNamespace = new Dictionary Ids, Check CheckItself)>(); + var checksWithErrorMessages = new Dictionary<(string CheckName, uint CheckId), (Dictionary Names, Dictionary Descriptions)>(); + foreach (var check in Checks) + { + if (check.Namespace == ns) + { + var checkCredentials = (check.CheckName, check.CheckId); + + if (!checksWithErrorMessages.ContainsKey(checkCredentials)) + { + checksWithErrorMessages.Add(checkCredentials, (new Dictionary(), new Dictionary())); + } + + if (!checkNamesInNamespace.ContainsKey(check.CheckName)) + { + checkNamesInNamespace.Add(check.CheckName, (new HashSet(), check)); + } + + checkNamesInNamespace[check.CheckName].Ids.Add(check.CheckId); + + // Duplicate error names + if (!checksWithErrorMessages[checkCredentials].Names.Keys.Contains(check.Name)) + { + checksWithErrorMessages[checkCredentials].Names.Add(check.Name, check); + } + else + { + string errorString = String.Format( + "The name : {0}, is a duplicate in the namespace {1}", + check.Name, + ns); + + // Current check + AddError(check, "Name", errorString); + foreach (var item in checksWithErrorMessages[checkCredentials].Names) + { + if (String.Equals(item.Key, check.Name, StringComparison.Ordinal)) + { + // Other checks + AddError(item.Value, "Name", errorString); + } + } + + parsingErrors.Add(errorString); + } + + // Duplicate descriptions + + // Create filled in description to check (with the hard-coded values) + string description; + try + { + description = check.Description; + for (int i = 0; i < check.Parameters.Count; i++) + { + var param = check.Parameters[i]; + string oldValue = String.Format("{{{0}}}", i); + + string newValue; + if (String.IsNullOrWhiteSpace(param.Value)) + { + newValue = String.Format("{{{0}}}", param.Text); + } + else + { + // No need to add the braces as it's a hard-coded value anyway. + newValue = String.Format("{0}", param.Value); + } + + description = description.Replace(oldValue, newValue); + } + } + catch (IndexOutOfRangeException) + { + description = check.Description; + } + + if (!checksWithErrorMessages[checkCredentials].Descriptions.Keys.Contains(description)) + { + checksWithErrorMessages[checkCredentials].Descriptions.Add(description, check); + } + else + { + string errorString = String.Format( + "The description : {0}, is a duplicate in the namespace {1}", + check.Description, + ns); + + // Current check + AddError(check, "Description", errorString); + foreach (var item in checksWithErrorMessages[checkCredentials].Descriptions) + { + if (String.Equals(item.Key, description, StringComparison.Ordinal)) + { + // Other checks + AddError(item.Value, "Description", errorString); + } + } + + parsingErrors.Add(errorString); + } + } + } + + foreach (var item in checkNamesInNamespace) + { + if (item.Value.Ids.Count > 1) + { + string errorString = String.Format( + "The name : {0}, is a duplicate in the namespace {1}", + item.Key, + ns); + AddError(item.Value.CheckItself, "CheckName", errorString); + parsingErrors.Add(errorString); + } + } + } + + if (parsingErrors.Count > 0) + { + StringBuilder error = new StringBuilder(); + foreach (var parsingError in parsingErrors) + { + error.Append(parsingError + "\n"); + } + + Application.Current.Dispatcher.BeginInvoke( + DispatcherPriority.Normal, + (Action)(() => { MessageBox.Show(error.ToString(), "Alert", MessageBoxButton.OK, MessageBoxImage.Information); })); + } + } + + private static void AddError(Check check, string propertyName, string errorMessage) + { + if (check.ErrorMessages.TryGetValue(propertyName, out string previousError)) + { + check.ErrorMessages.Remove(propertyName); + check.PropertyHasError[propertyName] = false; + check.ErrorMessages.Add(propertyName, previousError + "; " + errorMessage); + } + else + { + check.ErrorMessages.Add(propertyName, errorMessage); + check.PropertyHasError[propertyName] = true; + check.Error = true; + } + } + + /// + /// Deletes a check from the list. + /// + /// The check that has to be deleted. + private void OnDelete(Check check) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + "Are you sure?", + "Delete Confirmation", + MessageBoxButton.YesNo, + MessageBoxImage.Exclamation); + if (messageBoxResult == MessageBoxResult.Yes) + { + Checks.Remove(check); + GetErrorsAndChanges(); + HasChanges = true; + } + } + + /// + /// Saves the current check list to the XML file. + /// + private void OnSave() + { + if (HasErrors) + { + MessageBoxResult messageBoxResult = MessageBox.Show( + "There are some issues detected, are you sure you want to save anyway?", + "Save Confirmation", + MessageBoxButton.YesNo, + MessageBoxImage.Exclamation); + if (messageBoxResult == MessageBoxResult.Yes) + { + Serialization.Serializer.SetChecks(Checks, Templates, Categories); + Serialization.Serializer.WriteXml(Settings.XmlPath); + foreach (var check in Checks) + { + check.HasChanges = false; + } + + GetErrorsAndChanges(); + MessageBox.Show("Saving Done!", "Saving Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + else + { + Serialization.Serializer.SetChecks(Checks, Templates, Categories); + Serialization.Serializer.WriteXml(Settings.XmlPath); + foreach (var check in Checks) + { + check.HasChanges = false; + } + + GetErrorsAndChanges(); + MessageBox.Show("Saving Done!", "Saving Confirmation", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + /// + /// Opens the window to add a new check. + /// + private void OnAddCheck() + { + AddCheckView addCheckView = new AddCheckView(); + addCheckView.ShowDialog(); + GetErrorsAndChanges(); + CheckDuplicates(); + } + + /// + /// Opens the edit window for the check that has an error and is selected. + /// + private void OnOpenCheck() + { + CheckEditView checkEditView = new CheckEditView(SelectedErrorCheck); + checkEditView.ShowDialog(); + } + + /// + /// Show the next check with an error. + /// + private void OnUpArrow() + { + if (currentErrorIndex < ErrorChecks.Count - 1) + { + currentErrorIndex++; + UpdateErrorNavigation(); + } + } + + /// + /// Show the previous check with an error. + /// + private void OnDownArrow() + { + if (currentErrorIndex > 0) + { + currentErrorIndex--; + UpdateErrorNavigation(); + } + } + + private bool Filter(object item) + { + if (String.IsNullOrEmpty(SearchString)) + { + return true; + } + else + { + List filterWords = new List(); + if (SearchString.StartsWith("\"") && SearchString.EndsWith("\"")) + { + // One group of filter words + filterWords.Add(SearchString.Split(new char[] { '\"' })[1]); + } + else + { + // Different filter words + var splitFilter = SearchString.Split(new char[] { ' ' }); + foreach (var word in splitFilter) + { + filterWords.Add(word); + } + } + + + Check checkItem = item as Check; + + List stringProperties = new List(); + var checkProperties = typeof(Check).GetProperties(); + + // List all the properties as a string + foreach (var property in checkProperties) + { + if (property.Name == "DescriptionParameters" && checkItem.SettedProperties.Contains(property.Name)) + { + foreach (string parameter in (object[])property.GetValue(checkItem)) + { + stringProperties.Add(parameter); + } + } + else if (checkItem.SettedProperties.Contains(property.Name)) + { + stringProperties.Add(property.GetValue(checkItem).ToString()); + } + } + + int matchCount = 0; + + // Find a match for each filter word + foreach (var filterWord in filterWords) + { + bool match = false; + int stringCount = 0; + while (!match && stringCount < stringProperties.Count) + { + if (stringProperties[stringCount].IndexOf(filterWord, StringComparison.OrdinalIgnoreCase) >= 0) + { + match = true; + } + + stringCount++; + } + + if (match) + { + matchCount++; + } + } + + // Check if each filter word has a match in the check, only then the check can be displayed + return matchCount >= filterWords.Count; + } + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/MainWindowModel.cs b/Validator Management Tool/ViewModel/MainWindowModel.cs new file mode 100644 index 00000000..954f96fa --- /dev/null +++ b/Validator Management Tool/ViewModel/MainWindowModel.cs @@ -0,0 +1,46 @@ +namespace Validator_Management_Tool.ViewModel +{ + using Validator_Management_Tool.Interfaces; + + /// + /// The main window view model. + /// This window contains the menu buttons to switch between the check overview and the settings view. + /// + public class MainWindowModel : BindableBase + { + public MainWindowModel() + { + NavCommand = new MyTCommand(OnNav); + CurrentViewModel = checkViewModel; + } + + private readonly CheckViewModel checkViewModel = new CheckViewModel(); + + private readonly SettingsViewModel settingsViewModel = new SettingsViewModel(); + + private BindableBase _CurrentViewModel; + + public BindableBase CurrentViewModel + { + get { return _CurrentViewModel; } + set { SetProperty(ref _CurrentViewModel, value); } + } + + public MyTCommand NavCommand { get; private set; } + + private void OnNav(string destination) + { + switch (destination) + { + case "settings": + CurrentViewModel = settingsViewModel; + break; + + case "checks": + default: + CurrentViewModel = checkViewModel; + break; + } + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/NewCheckViewModel.cs b/Validator Management Tool/ViewModel/NewCheckViewModel.cs new file mode 100644 index 00000000..aa4bc420 --- /dev/null +++ b/Validator Management Tool/ViewModel/NewCheckViewModel.cs @@ -0,0 +1,206 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Interfaces; + + /// + /// The Viewmodel for the Create new check window. + /// + public class NewCheckViewModel : BindableBase + { + private readonly Category category; + private readonly List existingChecks; + private string errorMessage = String.Empty; + private bool hasError; + private string checkName; + private readonly string @namespace; + + /// + /// Initializes a new instance of the class. + /// + /// The category to which the new check needs to be added. + public NewCheckViewModel(Category category, string @namespace) + { + CategoryId = (int)category; + this.category = category; + CheckName = String.Empty; + this.@namespace = @namespace; + + CalculateNextCheckId(); + existingChecks = CheckViewModel.Categories.SingleOrDefault(e => e.Name == category.ToString()).Checks.Check.Where(e => e.Name.Namespace == @namespace).ToList(); + + AddCheckCommand = new MyCommand(OnAddCheck); + CancelCommand = new MyCommand(OnCancel); + } + + /// + /// Gets or sets the name for the new check. + /// It contains logic to check whether the name is acceptable or not. + /// + public string CheckName + { + get + { + return checkName; + } + + set + { + if (checkName != value) + { + if (value == String.Empty) + { + ErrorMessage = "The name can't be empty!"; + HasError = true; + } + else if (Settings.ForbiddenStrings.Contains(value)) + { + ErrorMessage = "The name is a forbidden string!"; + HasError = true; + } + else if (!Char.IsUpper(value[0])) + { + ErrorMessage = "The name has to start with a capital!"; + HasError = true; + } + else if (value.IndexOf(" ") != -1) + { + ErrorMessage = "The name can't contain a space!"; + HasError = true; + } + else if (value.Any(c => !Char.IsLetter(c))) + { + ErrorMessage = "The name can't contain symbols!"; + HasError = true; + } + else if (existingChecks.Any(e => e.Name.Text == value)) + { + ErrorMessage = "The name already exists in the category!"; + HasError = true; + } + else + { + HasError = false; + ErrorMessage = String.Empty; + } + + checkName = value; + OnPropertyChanged("CheckName"); + } + } + } + + /// + /// Gets or sets the error message that is displayed when there is an error. + /// + public string ErrorMessage + { + get + { + return errorMessage; + } + + set + { + if (value != errorMessage) + { + errorMessage = value; + OnPropertyChanged("ErrorMessage"); + } + } + } + + /// + /// Gets or sets a value indicating whether there is an error in the check name or not. + /// + public bool HasError + { + get + { + return hasError; + } + + set + { + if (value != hasError) + { + hasError = value; + OnPropertyChanged("HasError"); + } + } + } + + /// + /// Gets the category id from the category to which to check is added. + /// + public int CategoryId { get; } + + /// + /// Gets the next check id that can be used. + /// + public int NextCheckId { get; private set; } + + /// + /// Gets or sets the command to add the new check. + /// + public MyCommand AddCheckCommand { get; set; } + + /// + /// Gets or sets the command to cancel and close the window. + /// + public MyCommand CancelCommand { get; set; } + + /// + /// Gets or sets the close action. + /// + public Action CloseAction { get; set; } + + /// + /// Calculates the next check id within an category. + /// + private void CalculateNextCheckId() + { + List checks = CheckViewModel.Categories.SingleOrDefault(e => e.Name == category.ToString()).Checks.Check; + int maxId = 0; + foreach (var check in checks) + { + if (Int32.Parse(check.Id) > maxId) + { + maxId = Int32.Parse(check.Id); + } + } + + NextCheckId = maxId + 1; + } + + /// + /// Closes the window. + /// + private void OnCancel() + { + CloseAction(); + } + + /// + /// Adds the new check to the category and closes the window. + /// + private void OnAddCheck() + { + CheckViewModel.Categories.SingleOrDefault(e => e.Name == category.ToString()).Checks.Check.Add(new Serialization.Check() + { + Name = new Serialization.Name() + { + Text = CheckName, + Namespace = @namespace + }, + Id = NextCheckId.ToString() + }); + + CloseAction(); + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/ViewModel/NewNamespaceViewModel.cs b/Validator Management Tool/ViewModel/NewNamespaceViewModel.cs new file mode 100644 index 00000000..e5ac7cc0 --- /dev/null +++ b/Validator Management Tool/ViewModel/NewNamespaceViewModel.cs @@ -0,0 +1,186 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Skyline.DataMiner.CICD.Validators.Common.Model; + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Interfaces; + + public class NewNamespaceViewModel : BindableBase + { + private readonly Category category; + private readonly List existingNamespaces; + private string errorMessage = String.Empty; + private bool hasError; + private string @namespace; + + /// + /// Initializes a new instance of the class. + /// + /// The category to which the new check needs to be added. + public NewNamespaceViewModel(Category category, List namespaceList) + { + this.category = category; + Namespace = String.Empty; + + existingNamespaces = namespaceList; + + AddNamespaceCommand = new MyCommand(OnAddNamespace); + CancelCommand = new MyCommand(OnCancel); + } + + + /// + /// Gets or sets the name for the new check. + /// It contains logic to check whether the name is acceptable or not. + /// + public string Namespace + { + get + { + return @namespace; + } + + set + { + if (value == String.Empty || value == null) + { + ErrorMessage = "The namespace can't be empty!"; + HasError = true; + } + else if (existingNamespaces.Contains(value)) + { + ErrorMessage = String.Format( + "The namespace {0} already exists in this category!", + value); + HasError = true; + } + else + { + var splitNamespace = value.Split(new string[] { "." }, StringSplitOptions.None); + foreach (var namespacePart in splitNamespace) + { + if (namespacePart != String.Empty) + { + if (Settings.ForbiddenStrings.Contains(namespacePart)) + { + ErrorMessage = String.Format( + "The namespace-part '{0}' is a forbidden string!", + namespacePart); + HasError = true; + } + else if (!Char.IsUpper(namespacePart[0])) + { + ErrorMessage = String.Format( + "The namespace-part '{0}' has to start with a capital!", + namespacePart); + HasError = true; + } + else if (namespacePart.IndexOf(" ") != -1) + { + ErrorMessage = String.Format( + "The namespace-part '{0}' can't contain a space!", + namespacePart); + HasError = true; + } + else if (namespacePart.Any(c => !Char.IsLetter(c) && c != '.')) + { + ErrorMessage = String.Format( + "The namespace-part '{0}' can't contain a symbol!", + namespacePart); + HasError = true; + } + else + { + HasError = false; + ErrorMessage = String.Empty; + } + } + else + { + ErrorMessage = "A part of the namespace can't be empty!"; + HasError = true; + } + } + } + + @namespace = value; + OnPropertyChanged("Namespace"); + } + } + + /// + /// Gets or sets the error message that is displayed when there is an error. + /// + public string ErrorMessage + { + get + { + return errorMessage; + } + + set + { + if (value != errorMessage) + { + errorMessage = value; + OnPropertyChanged("ErrorMessage"); + } + } + } + + /// + /// Gets or sets a value indicating whether there is an error in the check name or not. + /// + public bool HasError + { + get + { + return hasError; + } + + set + { + if (value != hasError) + { + hasError = value; + OnPropertyChanged("HasError"); + } + } + } + + /// + /// Gets or sets the command to add the new check. + /// + public MyCommand AddNamespaceCommand { get; set; } + + /// + /// Gets or sets the command to cancel and close the window. + /// + public MyCommand CancelCommand { get; set; } + + /// + /// Gets or sets the close action. + /// + public Action CloseAction { get; set; } + + /// + /// Closes the window. + /// + private void OnCancel() + { + CloseAction(); + } + + /// + /// Adds the new check to the category and closes the window. + /// + private void OnAddNamespace() + { + existingNamespaces.Add(Namespace); + + CloseAction(); + } + } +} diff --git a/Validator Management Tool/ViewModel/SettingsViewModel.cs b/Validator Management Tool/ViewModel/SettingsViewModel.cs new file mode 100644 index 00000000..9d59ba21 --- /dev/null +++ b/Validator Management Tool/ViewModel/SettingsViewModel.cs @@ -0,0 +1,227 @@ +namespace Validator_Management_Tool.ViewModel +{ + using System.Windows.Forms; + using Validator_Management_Tool.Common; + using Validator_Management_Tool.Interfaces; + + /// + /// The Viewmodel for the settings view. + /// + public class SettingsViewModel : BindableBase + { + /// + /// Initializes a new instance of the class. + /// + public SettingsViewModel() + { + BrowseFolderCommand = new MyTCommand(OnBrowseFolder); + BrowseFileCommand = new MyCommand(OnBrowseFile); + DefaultPathCommand = new MyTCommand(OnDefaultPath); + DefaultAllCommand = new MyCommand(OnDefaultAll); + } + + /// + /// Gets or sets the command to browse for a folder. + /// + public MyTCommand BrowseFolderCommand { get; set; } + + /// + /// Gets or sets the command to browse for a file. + /// + public MyCommand BrowseFileCommand { get; set; } + + /// + /// Gets or sets the command to restore a path to default. + /// + public MyTCommand DefaultPathCommand { get; set; } + + /// + /// Gets or sets the command to restore all settings to default. + /// + public MyCommand DefaultAllCommand { get; set; } + + /// + /// Gets or sets the path where the error message classes will be generated. + /// + public string ErrorPath + { + get + { + return Settings.ErrorMessagesPath; + } + + set + { + if (value != Settings.ErrorMessagesPath) + { + Settings.ErrorMessagesPath = value; + OnPropertyChanged("ErrorPath"); + } + } + } + + /// + /// Gets or sets the path where the test classes will be generated. + /// + public string TestPath + { + get + { + return Settings.TestPath; + } + + set + { + if (value != Settings.TestPath) + { + Settings.TestPath = value; + OnPropertyChanged("TestPath"); + } + } + } + + /// + /// Gets or sets the path where the unit test classes will be generated. + /// + public string UnitTestPath + { + get + { + return Settings.UnitTestPath; + } + + set + { + if (value != Settings.UnitTestPath) + { + Settings.UnitTestPath = value; + OnPropertyChanged("UnitTestPath"); + } + } + } + + /// + /// Gets or sets the path where the XML file is located. + /// + public string XmlPath + { + get + { + return Settings.XmlPath; + } + + set + { + if (value != Settings.XmlPath) + { + Settings.XmlPath = value; + OnPropertyChanged("XmlPath"); + } + } + } + + /// + /// Gets or sets a value indicating whether the error message classes are generated in one single file or not. + /// + public bool AllClassesInOneFile + { + get + { + return Settings.ErrorClassesInOneFile; + } + + set + { + if (value != Settings.ErrorClassesInOneFile) + { + Settings.ErrorClassesInOneFile = value; + OnPropertyChanged("AllClassesInOneFile"); + } + } + } + + /// + /// Opens a folder browse dialog to select the path for a certain browser. + /// + /// The path that is being changed. + private void OnBrowseFolder(string index) + { + using (var dialog = new FolderBrowserDialog()) + { + if (dialog.ShowDialog() == DialogResult.OK) + { + string path = dialog.SelectedPath.Replace("\\", "/") + "/"; + switch (index) + { + case "TEST": + TestPath = path; + break; + case "ERROR": + ErrorPath = path; + break; + case "UNIT": + UnitTestPath = path; + break; + default: + break; + } + } + } + } + + /// + /// Restores a certain path to the default path. + /// + /// The path that is being restored. + private void OnDefaultPath(string index) + { + switch (index) + { + case "TEST": + TestPath = Settings.DefaultTestPath; + break; + case "ERROR": + ErrorPath = Settings.DefaultErrorMessagesPath; + break; + case "UNIT": + UnitTestPath = Settings.DefaultUnitTestPath; + break; + case "XML": + XmlPath = Settings.DefaultXmlPath; + break; + default: + break; + } + } + + /// + /// Restores all settings to default. + /// + private void OnDefaultAll() + { + TestPath = Settings.DefaultTestPath; + ErrorPath = Settings.DefaultErrorMessagesPath; + UnitTestPath = Settings.DefaultUnitTestPath; + XmlPath = Settings.DefaultXmlPath; + AllClassesInOneFile = Settings.DefaultErrorClassesInOneFile; + } + + /// + /// Opens a file browser dialog to select the location of the XML file. + /// + private void OnBrowseFile() + { + var dialog = new Microsoft.Win32.OpenFileDialog + { + DefaultExt = ".xml", + Filter = "XML Files (*.xml)|*.xml" + }; + + if (dialog.ShowDialog() == true) + { + string path = dialog.FileName.Replace("\\", "/"); + XmlPath = path; + } + } + } +} \ No newline at end of file diff --git a/Validator Management Tool/Views/AddCheckView.xaml b/Validator Management Tool/Views/AddCheckView.xaml new file mode 100644 index 00000000..c449d5d4 --- /dev/null +++ b/Validator Management Tool/Views/AddCheckView.xaml @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +