From 898e1ffd20b6b3b31033faa5b5cd72feefecbe0c Mon Sep 17 00:00:00 2001 From: ATrefzer <36333177+ATrefzer@users.noreply.github.com> Date: Thu, 16 Jul 2026 23:19:30 +0200 Subject: [PATCH 01/13] Added dsmsuite (experiment) --- CLAUDE.md | 13 + CSharpCodeAnalyst.sln | 109 ++ CSharpCodeAnalyst/App.xaml | 22 +- CSharpCodeAnalyst/CSharpCodeAnalyst.csproj | 4 + .../DsmMatrix/CodeGraphToDsmModelBuilder.cs | 94 ++ .../Features/DsmMatrix/DsmMatrixView.xaml | 19 + .../Features/DsmMatrix/DsmMatrixView.xaml.cs | 38 + CSharpCodeAnalyst/MainViewModel.cs | 7 + CSharpCodeAnalyst/MainWindow.xaml | 5 + CSharpCodeAnalyst/MainWindow.xaml.cs | 1 + README.md | 4 + .../CodeGraphToDsmModelBuilderTests.cs | 150 +++ ThirdParty/DsmSuite/Directory.Build.props | 22 + .../Core/DsiElement.cs | 24 + .../Core/DsiElementModel.cs | 144 +++ .../DsmSuite.Analyzer.Model/Core/DsiModel.cs | 216 ++++ .../Core/DsiRelation.cs | 26 + .../Core/DsiRelationModel.cs | 251 ++++ .../DsmSuite.Analyzer.Model.csproj | 14 + .../Interface/IDsiElement.cs | 11 + .../Interface/IDsiModel.cs | 53 + .../Interface/IDsiRelation.cs | 12 + .../Persistency/DsiModelFile.cs | 398 +++++++ .../IDsiElementModelFileCallback.cs | 13 + .../IDsiRelationModelFileCallback.cs | 12 + .../Core/MetaDataItem.cs | 17 + .../Core/MetaDataModel.cs | 109 ++ .../DsmSuite.Common.Model.csproj | 13 + .../Interface/IMetaDataItem.cs | 9 + .../Persistency/IMetaDataModelFileCallback.cs | 14 + .../DsmSuite.Common.Util/CompressedFile.cs | 110 ++ .../DsmSuite.Common.Util/ConsoleActionBase.cs | 142 +++ .../DsmSuite.Common.Util.csproj | 18 + .../DsmSuite.Common.Util/ElementName.cs | 61 + .../DsmSuite/DsmSuite.Common.Util/FilePath.cs | 60 + .../DsmSuite/DsmSuite.Common.Util/LogLevel.cs | 17 + .../DsmSuite/DsmSuite.Common.Util/Logger.cs | 217 ++++ .../DsmSuite.Common.Util/ProgressInfo.cs | 13 + .../DsmSuite.Common.Util/StringExtensions.cs | 13 + .../DsmSuite.Common.Util/SystemInfo.cs | 22 + .../Actions/Base/ActionAttributes.cs | 94 ++ .../Actions/Base/ActionReadOnlyAttributes.cs | 136 +++ .../Element/ElementChangeNameAction.cs | 77 ++ .../Element/ElementChangeParentAction.cs | 111 ++ .../Element/ElementChangeTypeAction.cs | 76 ++ .../Actions/Element/ElementCopyAction.cs | 59 + .../Actions/Element/ElementCreateAction.cs | 94 ++ .../Actions/Element/ElementCutAction.cs | 59 + .../Actions/Element/ElementDeleteAction.cs | 68 ++ .../Actions/Element/ElementMoveDownAction.cs | 73 ++ .../Actions/Element/ElementMoveUpAction.cs | 73 ++ .../Actions/Element/ElementPasteAction.cs | 93 ++ .../Actions/Element/ElementSortAction.cs | 87 ++ .../Element/ElementSortRecursiveAction.cs | 97 ++ .../Filtering/ShowElementContextAction.cs | 91 ++ .../Filtering/ShowElementDetailAction.cs | 94 ++ .../Actions/Management/ActionContext.cs | 30 + .../Actions/Management/ActionManager.cs | 179 +++ .../Actions/Management/ActionStore.cs | 134 +++ .../Actions/Management/IActionContext.cs | 13 + .../Actions/Management/IActionManager.cs | 15 + .../Actions/Management/MultiActionDTO.cs | 34 + .../Relation/RelationChangeTypeAction.cs | 79 ++ .../Relation/RelationChangeWeightAction.cs | 80 ++ .../Actions/Relation/RelationCreateAction.cs | 83 ++ .../Actions/Relation/RelationDeleteAction.cs | 74 ++ .../Actions/Snapshot/MakeSnapshotAction.cs | 68 ++ .../ApplicationClasses.cd | 98 ++ .../Core/DsmApplication.cs | 440 +++++++ .../DsmSuite.DsmViewer.Application.csproj | 17 + .../Import/Common/ImporterBase.cs | 73 ++ .../Import/Dsi/DsiImporter.cs | 151 +++ .../Interfaces/ActionType.cs | 33 + .../Interfaces/IAction.cs | 61 + .../Interfaces/IDsmApplication.cs | 86 ++ .../Interfaces/IMultiAction.cs | 17 + .../Interfaces/WeightedElement.cs | 7 + .../Metrics/DsmMetrics.cs | 25 + .../Queries/DsmQueries.cs | 145 +++ .../Sorting/AlphabeticalSortAlgorithm.cs | 43 + .../Sorting/ISortAlgorithm.cs | 9 + .../Sorting/PartitioningCalculation.cs | 278 +++++ .../Sorting/PartitioningSortAlgorithm.cs | 64 + .../Sorting/Permutation.cs | 59 + .../Sorting/SortAlgorithmFactory.cs | 46 + .../Sorting/SortResult.cs | 102 ++ .../Sorting/WeightsMatrix.cs | 56 + .../Core/DsmAction.cs | 31 + .../Core/DsmActionModel.cs | 72 ++ .../Core/DsmDependencies.cs | 160 +++ .../Core/DsmElement.cs | 241 ++++ .../Core/DsmElementModel.cs | 535 +++++++++ .../DsmSuite.DsmViewer.Model/Core/DsmModel.cs | 347 ++++++ .../Core/DsmRelation.cs | 58 + .../Core/DsmRelationModel.cs | 528 +++++++++ .../Core/NameRegistration.cs | 48 + .../DsmSuite.DsmViewer.Model.csproj | 16 + .../Interfaces/CycleType.cs | 11 + .../Interfaces/IDsmAction.cs | 17 + .../Interfaces/IDsmElement.cs | 96 ++ .../Interfaces/IDsmModel.cs | 91 ++ .../Interfaces/IDsmRelation.cs | 39 + .../Interfaces/ISortResult.cs | 10 + .../Interfaces/RelationDirection.cs | 10 + .../Interfaces/RelationScope.cs | 10 + .../DsmSuite.DsmViewer.Model/ModelClasses.cd | 163 +++ .../Persistency/DsmModelFile.cs | 592 ++++++++++ .../IDsmActionModelFileCallback.cs | 24 + .../IDsmElementModelFileCallback.cs | 13 + .../IDsmRelationModelFileCallback.cs | 13 + .../DsmSuite.DsmViewer.View.csproj | 28 + .../Editing/ElementEditDialog.xaml | 33 + .../Editing/ElementEditDialog.xaml.cs | 21 + .../Editing/RelationEditDialog.xaml | 106 ++ .../Editing/RelationEditDialog.xaml.cs | 29 + .../Editing/SnapshotCreateDialog.xaml | 33 + .../Editing/SnapshotCreateDialog.xaml.cs | 21 + .../Lists/ActionListView.xaml | 39 + .../Lists/ActionListView.xaml.cs | 25 + .../Lists/ElementListView.xaml | 36 + .../Lists/ElementListView.xaml.cs | 24 + .../Lists/RelationListView.xaml | 49 + .../Lists/RelationListView.xaml.cs | 40 + .../Matrix/MatrixCellsView.cs | 151 +++ .../Matrix/MatrixColumnHeaderView.cs | 115 ++ .../Matrix/MatrixFrameworkElement.cs | 98 ++ .../Matrix/MatrixMetricsSelectorView.xaml | 13 + .../Matrix/MatrixMetricsSelectorView.xaml.cs | 14 + .../Matrix/MatrixRowHeaderItemView.cs | 267 +++++ .../Matrix/MatrixRowHeaderView.cs | 191 +++ .../Matrix/MatrixRowMetricsView.cs | 113 ++ .../Matrix/MatrixTheme.cs | 151 +++ .../Matrix/MatrixTopCornerView.xaml | 12 + .../Matrix/MatrixTopCornerView.xaml.cs | 33 + .../Matrix/MatrixView.xaml | 219 ++++ .../Matrix/MatrixView.xaml.cs | 47 + .../Properties/Resources.Designer.cs | 64 + .../Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 27 + .../Properties/Settings.settings | 7 + .../Resources/ControlTemplates.xaml | 40 + .../Resources/Converters.xaml | 21 + .../DsmSuite.DsmViewer.View/Resources/DSM.ico | Bin 0 -> 101074 bytes .../Resources/Icons.xaml | 328 ++++++ .../Resources/Style.xaml | 455 ++++++++ .../Resources/Themes/DarkTheme.xaml | 35 + .../Resources/Themes/LightTheme.xaml | 35 + .../Resources/Themes/PastelTheme.xaml | 35 + .../Themes/ThemeResourceDictionary.cs | 76 ++ .../Settings/SettingsDialog.xaml | 36 + .../Settings/SettingsDialog.xaml.cs | 21 + .../UserControls/ElementSearchBox.xaml | 28 + .../UserControls/ElementSearchBox.xaml.cs | 16 + .../UserControls/LegendView.xaml | 46 + .../UserControls/LegendView.xaml.cs | 25 + .../UserControls/ToolView.xaml | 67 ++ .../UserControls/ToolView.xaml.cs | 81 ++ .../ElementNameMustBeNonEmptyRule.cs | 14 + .../WeightMustBePositiveNumberRule.cs | 23 + ...kmarkIndicatorModeToVisibilityConverter.cs | 22 + ...faultIndicatorModeToVisibilityConverter.cs | 22 + .../MatrixSizeToVisbilityConverter.cs | 21 + ...MetricsExpandedToButtonContentConverter.cs | 24 + .../MetricsExpandedToViewWidthConverter.cs | 23 + ...SearchElementFoundToVisibilityConverter.cs | 22 + ...rchElementNotFoundToVisibilityConverter.cs | 22 + ...earchIndicatorModeToVisibilityConverter.cs | 22 + .../SearchStateNotOffToVisibilityConverter.cs | 22 + .../SearchStateOffToVisibilityConverter.cs | 22 + .../SearchStateToColorConverter.cs | 42 + .../Common/ViewModelBase.cs | 44 + .../DsmSuite.DsmViewer.ViewModel.csproj | 19 + .../Editing/Element/ElementEditViewModel.cs | 143 +++ .../Element/ElementEditViewModelType.cs | 12 + .../Editing/Relation/RelationEditViewModel.cs | 179 +++ .../Relation/RelationEditViewModelType.cs | 9 + .../Editing/Snapshot/SnapshotMakeViewModel.cs | 51 + .../Lists/ActionListItemViewModel.cs | 22 + .../Lists/ActionListViewModel.cs | 86 ++ .../Lists/ElementListItemViewModel.cs | 48 + .../Lists/ElementListViewModel.cs | 124 ++ .../Lists/ElementListViewModelType.cs | 12 + .../Lists/RelationListItemViewModel.cs | 80 ++ .../Lists/RelationListViewModel.cs | 235 ++++ .../Lists/RelationsListViewModelType.cs | 11 + .../Main/ElementSearchViewModel.cs | 191 +++ .../Main/IMainViewModel.cs | 36 + .../Main/IndicatorViewMode.cs | 10 + .../Main/MainViewModel.cs | 650 +++++++++++ .../Main/ProgressViewModel.cs | 66 ++ .../Main/SearchState.cs | 12 + .../Matrix/CellToolTipViewModel.cs | 33 + .../Matrix/ElementToolTipViewModel.cs | 31 + .../Matrix/ElementTreeItemViewModel.cs | 140 +++ .../Matrix/IMatrixViewModel.cs | 17 + .../Matrix/LegendColor.cs | 12 + .../Matrix/LegendViewModel.cs | 17 + .../Matrix/MatrixColor.cs | 13 + .../Matrix/MatrixColorConverter.cs | 30 + .../Matrix/MatrixViewModel.cs | 988 ++++++++++++++++ .../Matrix/MatrixViewModelCoordinate.cs | 37 + .../Matrix/MetricType.cs | 52 + .../Properties/Annotations.cs | 1038 +++++++++++++++++ .../Settings/SettingsViewModel.cs | 84 ++ .../Settings/Theme.cs | 11 + .../Settings/ViewerSetting.cs | 51 + .../Settings/ViewerSettingsData.cs | 88 ++ ThirdParty/DsmSuite/LICENSE | 621 ++++++++++ ThirdParty/DsmSuite/LICENSE-jmuijsenberg | 21 + ThirdParty/DsmSuite/README.md | 49 + .../GPL-3.0-LICENSED-LIBRARIES.txt | 26 + 211 files changed, 18769 insertions(+), 2 deletions(-) create mode 100644 CSharpCodeAnalyst/Features/DsmMatrix/CodeGraphToDsmModelBuilder.cs create mode 100644 CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml create mode 100644 CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml.cs create mode 100644 Tests/UnitTests/DsmMatrix/CodeGraphToDsmModelBuilderTests.cs create mode 100644 ThirdParty/DsmSuite/Directory.Build.props create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElementModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelationModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/DsmSuite.Analyzer.Model.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiRelation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/DsiModelFile.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiElementModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiRelationModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataItem.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Model/DsmSuite.Common.Model.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Model/Interface/IMetaDataItem.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Model/Persistency/IMetaDataModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/CompressedFile.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/ConsoleActionBase.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/DsmSuite.Common.Util.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/ElementName.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/FilePath.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/LogLevel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/Logger.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/ProgressInfo.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/StringExtensions.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.Common.Util/SystemInfo.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionAttributes.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionReadOnlyAttributes.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeNameAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeParentAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeTypeAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCopyAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCreateAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCutAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementDeleteAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveDownAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveUpAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementPasteAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortRecursiveAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementContextAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementDetailAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionContext.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionManager.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionStore.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionContext.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionManager.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/MultiActionDTO.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeTypeAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeWeightAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationCreateAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationDeleteAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Snapshot/MakeSnapshotAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/ApplicationClasses.cd create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Core/DsmApplication.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/DsmSuite.DsmViewer.Application.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Common/ImporterBase.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Dsi/DsiImporter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/ActionType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IDsmApplication.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IMultiAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/WeightedElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Metrics/DsmMetrics.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Queries/DsmQueries.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/AlphabeticalSortAlgorithm.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/ISortAlgorithm.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningCalculation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningSortAlgorithm.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/Permutation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortAlgorithmFactory.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortResult.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/WeightsMatrix.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmActionModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmDependencies.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElementModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelationModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/NameRegistration.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/DsmSuite.DsmViewer.Model.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/CycleType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmAction.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmRelation.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/ISortResult.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationDirection.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationScope.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/ModelClasses.cd create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/DsmModelFile.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmActionModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmElementModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmRelationModelFileCallback.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/DsmSuite.DsmViewer.View.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/ElementEditDialog.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/ElementEditDialog.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/RelationEditDialog.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/RelationEditDialog.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/SnapshotCreateDialog.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/SnapshotCreateDialog.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/ActionListView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/ActionListView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/ElementListView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/ElementListView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/RelationListView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Lists/RelationListView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixCellsView.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixColumnHeaderView.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixFrameworkElement.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixMetricsSelectorView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixMetricsSelectorView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixRowHeaderItemView.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixRowHeaderView.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixRowMetricsView.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixTheme.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixTopCornerView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixTopCornerView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Matrix/MatrixView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Properties/Resources.Designer.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Properties/Resources.resx create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Properties/Settings.Designer.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Properties/Settings.settings create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/ControlTemplates.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Converters.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/DSM.ico create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Icons.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Style.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Themes/DarkTheme.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Themes/LightTheme.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Themes/PastelTheme.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Resources/Themes/ThemeResourceDictionary.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Settings/SettingsDialog.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Settings/SettingsDialog.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/ElementSearchBox.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/ElementSearchBox.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/LegendView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/LegendView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/ToolView.xaml create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/UserControls/ToolView.xaml.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Validation/ElementNameMustBeNonEmptyRule.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Validation/WeightMustBePositiveNumberRule.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/BookmarkIndicatorModeToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/DefaultIndicatorModeToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/MatrixSizeToVisbilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/MetricsExpandedToButtonContentConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/MetricsExpandedToViewWidthConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchElementFoundToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchElementNotFoundToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchIndicatorModeToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchStateNotOffToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchStateOffToVisibilityConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/ValueConverters/SearchStateToColorConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Common/ViewModelBase.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/DsmSuite.DsmViewer.ViewModel.csproj create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Editing/Element/ElementEditViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Editing/Element/ElementEditViewModelType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Editing/Relation/RelationEditViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Editing/Relation/RelationEditViewModelType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Editing/Snapshot/SnapshotMakeViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/ActionListItemViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/ActionListViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/ElementListItemViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/ElementListViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/ElementListViewModelType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/RelationListItemViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/RelationListViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Lists/RelationsListViewModelType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/ElementSearchViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/IMainViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/IndicatorViewMode.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/MainViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/ProgressViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Main/SearchState.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/CellToolTipViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/ElementToolTipViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/ElementTreeItemViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/IMatrixViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/LegendColor.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/LegendViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/MatrixColor.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/MatrixColorConverter.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/MatrixViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/MatrixViewModelCoordinate.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Matrix/MetricType.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Properties/Annotations.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Settings/SettingsViewModel.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Settings/Theme.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Settings/ViewerSetting.cs create mode 100644 ThirdParty/DsmSuite/DsmSuite.DsmViewer.ViewModel/Settings/ViewerSettingsData.cs create mode 100644 ThirdParty/DsmSuite/LICENSE create mode 100644 ThirdParty/DsmSuite/LICENSE-jmuijsenberg create mode 100644 ThirdParty/DsmSuite/README.md diff --git a/CLAUDE.md b/CLAUDE.md index d737bd9b..9b29acf0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,8 @@ Five projects wired together in `CSharpCodeAnalyst.sln`: - **`Tests/`** (project name `CodeParserTests`) — NUnit suite. `ApprovalTests/` parses the `TestSuite/` C# solution once per fixture and asserts on the resulting graph; `UnitTests/` covers cycles, exploration, export, search, architectural rules, etc. - **`ApprovalTestTool/`** — standalone console app that clones external repos listed in `Repositories.txt`, parses each at a pinned commit, hashes the graph dump, and diffs against references. Used to catch parser regressions on real codebases; not part of the CI test run. +`ThirdParty/DsmSuite/` holds a vendored subset (7 of ~38 projects) of the GPL-licensed DsmSuite, which provides the matrix view on the DSM tab. It is foreign code with its own rules — see **Vendored DsmSuite** below before touching anything in there. + `TestSuite/` is a handcrafted C# solution used purely as parser input for the approval tests. Do not consume it from production code — it is intentionally full of odd language constructs. `ReferencedAssemblies/` contains the MSAGL DLLs referenced directly by `CSharpCodeAnalyst.csproj` and `Tests.csproj` (MSAGL is not on NuGet for the versions used here). ## Architectural notes worth knowing before editing @@ -112,6 +114,17 @@ Then wire up the edges: `RuleEngine.Execute` for the evaluation, `RuleViolationV ### Refactoring simulation is destructive `Features/Refactoring/` mutates the in-memory `CodeGraph` directly (move / delete elements, cut edges) and there is no undo. Any code path that offers these operations should save or warn first — see existing command handlers before adding new ones. +### Vendored DsmSuite (the DSM tab) +The matrix on the DSM tab is not ours: `ThirdParty/DsmSuite/` is a modified subset of [DsmSuite](https://github.com/ernstaii/dsmsuite.sourcecode), pinned to a commit recorded in `ThirdParty/DsmSuite/README.md`, GPL-3.0-or-later (originally MIT). Our side is `Features/DsmMatrix/`: `CodeGraphToDsmModelBuilder` fills a DsmSuite `IDsmModel` straight from the `TypeGraph` — explicit `parentId`, no DSI file, no name splitting — and `DsmMatrixView` hosts their `MatrixView`. + +**Every change under `ThirdParty/DsmSuite/` costs two things, and neither is optional:** (1) a `Changed for CSharpCodeAnalyst` comment at the site explaining *why*, and (2) a row in the change table in `ThirdParty/DsmSuite/README.md`. GPL §5(a) requires stating what was modified, and that table is the map for ever diffing against upstream again — an undocumented change silently becomes indistinguishable from upstream code. Same rule for the bug list in that README when you find (or fix) a defect in their code. + +Two traps that are already documented there and worth knowing before you write code against their API: +- **`DsmApplication` binds `DsmQueries` to the model it is constructed with and never rebinds.** Populate the model *first*, then construct `DsmApplication`. Calling their `LoadModel` (i.e. the file-based import path) leaves every query running against the previous model. This is why `DsmMatrixView.Show` builds everything from scratch. +- **Their `MainViewModel` is the DataContext**, including its editing commands. Those mutate the DSM model only, never the `CodeGraph`, so they cannot corrupt a parse result — but they are live in the context menus. + +Their resource dictionaries are merged in `App.xaml` at application scope because their controls resolve them via `StaticResource`. Everything in there is keyed; if you pull in more of their XAML, check for implicit (unkeyed) styles first — those would restyle the whole application. + ## Code style - `.editorconfig` is authoritative and ReSharper-tuned: braces required on all `if`/`foreach`/`while`, max line length 199, expression-bodied accessors preferred, `internal` first in modifier order. Analyzer severities default to `none` — do not add warning-as-error enforcement without discussion. diff --git a/CSharpCodeAnalyst.sln b/CSharpCodeAnalyst.sln index 3febaf54..7df2d906 100644 --- a/CSharpCodeAnalyst.sln +++ b/CSharpCodeAnalyst.sln @@ -38,6 +38,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpCodeAnalyst.History", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpCodeAnalyst.Contracts", "CSharpCodeAnalyst.Contracts\CSharpCodeAnalyst.Contracts.csproj", "{9DE77B88-294B-45F2-B1A1-6642CC3B4137}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ThirdParty", "ThirdParty", "{356D9C46-07F5-7F13-4EEF-5DD22B885BE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.Analyzer.Model", "ThirdParty\DsmSuite\DsmSuite.Analyzer.Model\DsmSuite.Analyzer.Model.csproj", "{42652074-77DD-4502-9EF6-D51EBB72A918}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.Common.Model", "ThirdParty\DsmSuite\DsmSuite.Common.Model\DsmSuite.Common.Model.csproj", "{4F8EA064-AF33-4973-A63A-6984CA7C5DF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.Common.Util", "ThirdParty\DsmSuite\DsmSuite.Common.Util\DsmSuite.Common.Util.csproj", "{FE8B79B8-497F-4939-B07A-6546994DC9EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.DsmViewer.Application", "ThirdParty\DsmSuite\DsmSuite.DsmViewer.Application\DsmSuite.DsmViewer.Application.csproj", "{3C96864B-93C5-4D75-87FC-98D00A84E32F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.DsmViewer.Model", "ThirdParty\DsmSuite\DsmSuite.DsmViewer.Model\DsmSuite.DsmViewer.Model.csproj", "{D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.DsmViewer.View", "ThirdParty\DsmSuite\DsmSuite.DsmViewer.View\DsmSuite.DsmViewer.View.csproj", "{9421A097-A4E6-496B-B782-DAB1E819DD0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DsmSuite.DsmViewer.ViewModel", "ThirdParty\DsmSuite\DsmSuite.DsmViewer.ViewModel\DsmSuite.DsmViewer.ViewModel.csproj", "{A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,10 +184,103 @@ Global {9DE77B88-294B-45F2-B1A1-6642CC3B4137}.Release|x64.Build.0 = Release|Any CPU {9DE77B88-294B-45F2-B1A1-6642CC3B4137}.Release|x86.ActiveCfg = Release|Any CPU {9DE77B88-294B-45F2-B1A1-6642CC3B4137}.Release|x86.Build.0 = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|x64.ActiveCfg = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|x64.Build.0 = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|x86.ActiveCfg = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Debug|x86.Build.0 = Debug|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|Any CPU.Build.0 = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|x64.ActiveCfg = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|x64.Build.0 = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|x86.ActiveCfg = Release|Any CPU + {42652074-77DD-4502-9EF6-D51EBB72A918}.Release|x86.Build.0 = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|x64.Build.0 = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Debug|x86.Build.0 = Debug|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|Any CPU.Build.0 = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|x64.ActiveCfg = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|x64.Build.0 = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|x86.ActiveCfg = Release|Any CPU + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8}.Release|x86.Build.0 = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|x64.Build.0 = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Debug|x86.Build.0 = Debug|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|Any CPU.Build.0 = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|x64.ActiveCfg = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|x64.Build.0 = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|x86.ActiveCfg = Release|Any CPU + {FE8B79B8-497F-4939-B07A-6546994DC9EF}.Release|x86.Build.0 = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|x64.Build.0 = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Debug|x86.Build.0 = Debug|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|Any CPU.Build.0 = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|x64.ActiveCfg = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|x64.Build.0 = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|x86.ActiveCfg = Release|Any CPU + {3C96864B-93C5-4D75-87FC-98D00A84E32F}.Release|x86.Build.0 = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|x64.ActiveCfg = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|x64.Build.0 = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Debug|x86.Build.0 = Debug|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|Any CPU.Build.0 = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|x64.ActiveCfg = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|x64.Build.0 = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|x86.ActiveCfg = Release|Any CPU + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB}.Release|x86.Build.0 = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|x64.ActiveCfg = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|x64.Build.0 = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Debug|x86.Build.0 = Debug|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|Any CPU.Build.0 = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|x64.ActiveCfg = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|x64.Build.0 = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|x86.ActiveCfg = Release|Any CPU + {9421A097-A4E6-496B-B782-DAB1E819DD0D}.Release|x86.Build.0 = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|x64.Build.0 = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Debug|x86.Build.0 = Debug|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|Any CPU.Build.0 = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|x64.ActiveCfg = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|x64.Build.0 = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|x86.ActiveCfg = Release|Any CPU + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {42652074-77DD-4502-9EF6-D51EBB72A918} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {4F8EA064-AF33-4973-A63A-6984CA7C5DF8} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {FE8B79B8-497F-4939-B07A-6546994DC9EF} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {3C96864B-93C5-4D75-87FC-98D00A84E32F} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {D912F0E9-D8C1-43ED-B3DD-F4413AF366AB} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {9421A097-A4E6-496B-B782-DAB1E819DD0D} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + {A9B11DC9-D36F-48DD-87E8-1C6E547EED5E} = {356D9C46-07F5-7F13-4EEF-5DD22B885BE8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2BBCF792-C019-4195-AF08-45CC642B8A18} EndGlobalSection diff --git a/CSharpCodeAnalyst/App.xaml b/CSharpCodeAnalyst/App.xaml index 791663f6..5b4355df 100644 --- a/CSharpCodeAnalyst/App.xaml +++ b/CSharpCodeAnalyst/App.xaml @@ -1,7 +1,25 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:themes="clr-namespace:DsmSuite.DsmViewer.View.Resources.Themes;assembly=DsmSuite.DsmViewer.View"> - + + + + + + + + + + + \ No newline at end of file diff --git a/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj b/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj index 38909381..30b0d00c 100644 --- a/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj +++ b/CSharpCodeAnalyst/CSharpCodeAnalyst.csproj @@ -158,6 +158,10 @@ + + + + diff --git a/CSharpCodeAnalyst/Features/DsmMatrix/CodeGraphToDsmModelBuilder.cs b/CSharpCodeAnalyst/Features/DsmMatrix/CodeGraphToDsmModelBuilder.cs new file mode 100644 index 00000000..8802e183 --- /dev/null +++ b/CSharpCodeAnalyst/Features/DsmMatrix/CodeGraphToDsmModelBuilder.cs @@ -0,0 +1,94 @@ +using CSharpCodeAnalyst.CodeGraph.Algorithms.Metrics; +using CSharpCodeAnalyst.CodeGraph.Graph; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace CSharpCodeAnalyst.Features.DsmMatrix; + +/// +/// Populates a DsmSuite from a , so the +/// embedded matrix view can show the current solution without any file round trip. +/// +/// +/// The topology comes from : one vertex per internal type, dependencies lifted to +/// the containing type and deduplicated, external types and self edges dropped. The hierarchy above the +/// types (namespaces, assemblies) is taken from the code graph's parent chain rather than reconstructed +/// from dotted names, which is what DsmSuite's own DSI import has to do for lack of parent information. +/// +public sealed class CodeGraphToDsmModelBuilder +{ + private const string RelationType = "Dependency"; + + private readonly CodeGraph.Graph.CodeGraph _codeGraph; + private readonly IDsmModel _dsmModel; + + /// Maps a code element id to the DSM element created for it. + private readonly Dictionary _dsmElementsByCodeElementId = []; + + public CodeGraphToDsmModelBuilder(IDsmModel dsmModel, CodeGraph.Graph.CodeGraph codeGraph) + { + _dsmModel = dsmModel; + _codeGraph = codeGraph; + } + + /// + /// Fills the model. Returns the number of types added, so the caller can tell an empty result from a + /// populated one without querying the model. + /// + public int Build() + { + _dsmModel.Clear(); + _dsmElementsByCodeElementId.Clear(); + + var typeGraph = TypeGraph.Build(_codeGraph); + + foreach (var typeId in typeGraph.Vertices) + { + AddWithAncestors(typeId); + } + + AddRelations(typeGraph); + + _dsmModel.AssignElementOrder(); + return typeGraph.VertexCount; + } + + /// + /// Returns the DSM element for a code element, creating it and every missing ancestor first. Walking up + /// before creating keeps a parent's id available by the time the child needs it. + /// + private IDsmElement AddWithAncestors(string codeElementId) + { + if (_dsmElementsByCodeElementId.TryGetValue(codeElementId, out var existing)) + { + return existing; + } + + var codeElement = _codeGraph.Nodes[codeElementId]; + var parent = codeElement.Parent is null ? null : AddWithAncestors(codeElement.Parent.Id); + + var dsmElement = _dsmModel.AddElement( + codeElement.Name, + codeElement.ElementType.ToString(), + parent?.Id, + null, + null); + + _dsmElementsByCodeElementId[codeElementId] = dsmElement; + return dsmElement; + } + + private void AddRelations(TypeGraph typeGraph) + { + foreach (var (consumerId, providerIds) in typeGraph.Out) + { + var consumer = _dsmElementsByCodeElementId[consumerId]; + foreach (var providerId in providerIds) + { + // TypeGraph deduplicates, so every edge carries the same weight. Once it can report how + // many relationships a type level edge stands for, that count belongs here: it is what + // drives the matrix' cell weights. + _dsmModel.AddRelation(consumer, _dsmElementsByCodeElementId[providerId], RelationType, 1, null); + } + } + } +} diff --git a/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml b/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml new file mode 100644 index 00000000..fa015fe2 --- /dev/null +++ b/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml.cs b/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml.cs new file mode 100644 index 00000000..ecdd8a09 --- /dev/null +++ b/CSharpCodeAnalyst/Features/DsmMatrix/DsmMatrixView.xaml.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Windows.Controls; +using DsmSuite.DsmViewer.Application.Core; +using DsmSuite.DsmViewer.Model.Core; + +// Both this application and DsmSuite have a MainViewModel. +using DsmMainViewModel = DsmSuite.DsmViewer.ViewModel.Main.MainViewModel; + +namespace CSharpCodeAnalyst.Features.DsmMatrix; + +/// +/// Hosts DsmSuite's matrix view as a tab. See for how the code +/// graph gets into the DSM model. +/// +public partial class DsmMatrixView : UserControl +{ + public DsmMatrixView() + { + InitializeComponent(); + } + + /// + /// Rebuilds the matrix from the given code graph. Everything is constructed from scratch: DsmSuite's + /// application object binds its query layer to the model instance it is constructed with and does not + /// rebind when the model is swapped, so the model has to be populated before the application exists. + /// + public void Show(CodeGraph.Graph.CodeGraph codeGraph) + { + var model = new DsmModel("CSharpCodeAnalyst", Assembly.GetExecutingAssembly()); + var typeCount = new CodeGraphToDsmModelBuilder(model, codeGraph).Build(); + + var application = new DsmApplication(model); + var mainViewModel = new DsmMainViewModel(application); + mainViewModel.ShowInMemoryModel($"{typeCount} types"); + + DataContext = mainViewModel; + } +} diff --git a/CSharpCodeAnalyst/MainViewModel.cs b/CSharpCodeAnalyst/MainViewModel.cs index 04670be7..77915ebc 100644 --- a/CSharpCodeAnalyst/MainViewModel.cs +++ b/CSharpCodeAnalyst/MainViewModel.cs @@ -861,8 +861,15 @@ private void LoadCodeGraph(CodeGraph.Graph.CodeGraph codeGraph) InfoPanelViewModel?.ClearQuickInfo(); UpdateStatistics(codeGraph); + CodeGraphLoaded?.Invoke(this, codeGraph); } + /// + /// Raised after a new code graph was loaded. Used by views that are not driven by a view model of + /// their own, currently only the DSM matrix tab. + /// + public event EventHandler? CodeGraphLoaded; + private void UpdateStatistics(CodeGraph.Graph.CodeGraph codeGraph) { // Default output: summary of graph diff --git a/CSharpCodeAnalyst/MainWindow.xaml b/CSharpCodeAnalyst/MainWindow.xaml index 5934d01c..b5c3dbd6 100644 --- a/CSharpCodeAnalyst/MainWindow.xaml +++ b/CSharpCodeAnalyst/MainWindow.xaml @@ -8,6 +8,7 @@ mc:Ignorable="d" xmlns:resources="clr-namespace:CSharpCodeAnalyst.Resources" xmlns:dynamicDataGrid1="clr-namespace:CSharpCodeAnalyst.Shared.DynamicDataGrid" + xmlns:dsmMatrix="clr-namespace:CSharpCodeAnalyst.Features.DsmMatrix" xmlns:tree="clr-namespace:CSharpCodeAnalyst.Features.Tree" xmlns:advancedSearch="clr-namespace:CSharpCodeAnalyst.Features.AdvancedSearch" xmlns:info="clr-namespace:CSharpCodeAnalyst.Features.Info" @@ -562,6 +563,10 @@ + + + + + + DsmSuite + + jmuijsenberg, ernstaii and contributors + Copyright (C) DsmSuite contributors. Licensed under GPL-3.0-or-later. + Vendored subset of DsmSuite (https://github.com/ernstaii/dsmsuite.sourcecode). + + diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElement.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElement.cs new file mode 100644 index 00000000..6e05e019 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElement.cs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; + +namespace DsmSuite.Analyzer.Model.Core +{ + /// + /// Represents element of a component. Both the ElementId and Name uniquely identify an element. + /// + public class DsiElement : IDsiElement + { + public DsiElement(int id, string name, string type, IDictionary properties) + { + Id = id; + Name = name; + Type = type; + Properties = properties; + } + + public int Id { get; } + public string Name { get; set; } + public string Type { get; } + public IDictionary Properties { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElementModel.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElementModel.cs new file mode 100644 index 00000000..4418a731 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiElementModel.cs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using DsmSuite.Analyzer.Model.Interface; +using DsmSuite.Analyzer.Model.Persistency; +using DsmSuite.Common.Util; + +namespace DsmSuite.Analyzer.Model.Core +{ + public class DsiElementModel : IDsiElementModelFileCallback + { + private readonly Dictionary _elementsByName; + private readonly Dictionary _elementsById; + private readonly Dictionary _elementTypeCount; + + public event EventHandler ElementRemoved; + + public DsiElementModel() + { + _elementsByName = new Dictionary(); + _elementsById = new Dictionary(); + _elementTypeCount = new Dictionary(); + } + + public void Clear() + { + _elementsByName.Clear(); + _elementsById.Clear(); + _elementTypeCount.Clear(); + } + + public IDsiElement ImportElement(int id, string name, string type, IDictionary properties) + { + Logger.LogDataModelMessage($"Import element id={id} name={name} type={type}"); + + DsiElement element = new DsiElement(id, name, type, properties); + string key = CreateKey(element.Name); + _elementsByName[key] = element; + _elementsById[element.Id] = element; + IncrementElementTypeCount(element.Type); + return element; + } + + public IDsiElement AddElement(string name, string type, IDictionary properties) + { + Logger.LogDataModelMessage($"Add element name={name} type={type}"); + + string key = CreateKey(name); + if (!_elementsByName.ContainsKey(key)) + { + IncrementElementTypeCount(type); + int id = _elementsByName.Count + 1; + DsiElement element = new DsiElement(id, name, type, properties); + _elementsByName[key] = element; + _elementsById[id] = element; + return element; + } + else + { + return null; + } + } + + public void IgnoreElement(string name, string type) + { + Logger.LogDataModelMessage($"Ignore element name={name} type={type}"); + } + + public void RemoveElement(IDsiElement element) + { + Logger.LogDataModelMessage($"Remove element id={element.Id} name={element.Name} type={element.Type}"); + + string key = element.Name.ToLower(); + _elementsByName.Remove(key); + _elementsById.Remove(element.Id); + + ElementRemoved?.Invoke(this, element.Id); + } + + public void RenameElement(IDsiElement element, string newName) + { + Logger.LogDataModelMessage("Rename element id={element.Id} from {element.Name} to {newName}"); + + DsiElement e = element as DsiElement; + if (e != null) + { + string oldKey = CreateKey(e.Name); + _elementsByName.Remove(oldKey); + e.Name = newName; + string newKey = CreateKey(e.Name); + _elementsByName[newKey] = e; + } + } + + public IDsiElement FindElementById(int id) + { + return _elementsById.ContainsKey(id) ? _elementsById[id] : null; + } + + public IDsiElement FindElementByName(string name) + { + string key = name.ToLower(); + return _elementsByName.ContainsKey(key) ? _elementsByName[key] : null; + } + + public IEnumerable GetElements() + { + return _elementsById.Values; + } + + public ICollection GetElementTypes() + { + return _elementTypeCount.Keys; + } + + public int GetElementTypeCount(string type) + { + if (_elementTypeCount.ContainsKey(type)) + { + return _elementTypeCount[type]; + } + else + { + return 0; + } + } + + public int CurrentElementCount => _elementsByName.Values.Count; + + private void IncrementElementTypeCount(string type) + { + if (!_elementTypeCount.ContainsKey(type)) + { + _elementTypeCount[type] = 0; + } + _elementTypeCount[type]++; + } + + private string CreateKey(string name) + { + return name.ToLower(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiModel.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiModel.cs new file mode 100644 index 00000000..91df3409 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiModel.cs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using DsmSuite.Analyzer.Model.Interface; +using DsmSuite.Analyzer.Model.Persistency; +using DsmSuite.Common.Model.Core; +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Util; + +namespace DsmSuite.Analyzer.Model.Core +{ + public class DsiModel : IDsiModel + { + private readonly MetaDataModel _metaDataModel; + private readonly DsiElementModel _elementsDataModel; + private readonly DsiRelationModel _relationsDataModel; + private readonly List _ignoredNames; + + public DsiModel(string processStep, List ignoredNames, Assembly executingAssembly) + { + _metaDataModel = new MetaDataModel(processStep, executingAssembly); + _elementsDataModel = new DsiElementModel(); + _relationsDataModel = new DsiRelationModel(_elementsDataModel); + + _ignoredNames = ignoredNames; + } + + public string Filename { get; private set; } + + public void Load(string dsiFilename, IProgress progress) + { + Logger.LogDataModelMessage($"Load data model file={dsiFilename}"); + + Filename = dsiFilename; + DsiModelFile modelFile = + new DsiModelFile(dsiFilename, _metaDataModel, _elementsDataModel, _relationsDataModel); + modelFile.Load(progress); + } + + public void Save(string dsiFilename, bool compressFile, IProgress progress) + { + Logger.LogDataModelMessage($"Save data model file={dsiFilename} compress={compressFile}"); + + Filename = dsiFilename; + + foreach (string type in GetElementTypes()) + { + _metaDataModel.AddMetaDataItemToDefaultGroup($"- '{type}' elements found", + $"{GetElementTypeCount(type)}"); + } + + _metaDataModel.AddMetaDataItemToDefaultGroup("Total elements found", $"{CurrentElementCount}"); + + foreach (string type in GetRelationTypes()) + { + _metaDataModel.AddMetaDataItemToDefaultGroup($"- '{type}' relations found", + $"{GetRelationTypeCount(type)}"); + } + + _metaDataModel.AddMetaDataItemToDefaultGroup("Total relations found", $"{ImportedRelationCount}"); + _metaDataModel.AddMetaDataItemToDefaultGroup("Total relations resolved", + $"{ResolvedRelationCount} (confidence={ResolvedRelationPercentage:0.000} %)"); + + DsiModelFile modelFile = + new DsiModelFile(dsiFilename, _metaDataModel, _elementsDataModel, _relationsDataModel); + modelFile.Save(compressFile, progress); + } + + public void AddMetaData(string name, string value) + { + _metaDataModel.AddMetaDataItemToDefaultGroup(name, value); + } + + public IEnumerable GetMetaDataGroups() + { + return _metaDataModel.GetExportedMetaDataGroups(); + } + + public IEnumerable GetMetaDataGroupItems(string groupName) + { + return _metaDataModel.GetExportedMetaDataGroupItems(groupName); + } + + public IDsiElement AddElement(string name, string type, IDictionary properties) + { + IDsiElement element = null; + + if (!Ignore(name)) + { + element = _elementsDataModel.AddElement(name, type, properties); + } + else + { + _elementsDataModel.IgnoreElement(name, type); + } + + return element; + } + + public void RemoveElement(IDsiElement element) + { + _elementsDataModel.RemoveElement(element); + } + + public void RenameElement(IDsiElement element, string newName) + { + _elementsDataModel.RenameElement(element, newName); + } + + public IDsiElement FindElementById(int id) + { + return _elementsDataModel.FindElementById(id); + } + + public IDsiElement FindElementByName(string name) + { + return _elementsDataModel.FindElementByName(name); + } + + public IEnumerable GetElements() + { + return _elementsDataModel.GetElements(); + } + + public ICollection GetElementTypes() + { + return _elementsDataModel.GetElementTypes(); + } + + public int GetElementTypeCount(string type) + { + return _elementsDataModel.GetElementTypeCount(type); + } + + public int CurrentElementCount => _elementsDataModel.CurrentElementCount; + + public IDsiRelation AddRelation(string consumerName, string providerName, string type, int weight, IDictionary properties) + { + IDsiRelation relation = null; + + if (!Ignore(consumerName) && !Ignore(providerName)) + { + relation = _relationsDataModel.AddRelation(consumerName, providerName, type, weight, properties); + } + else + { + _relationsDataModel.IgnoreRelation(consumerName, providerName, type, weight); + } + + return relation; + } + + public void SkipRelation(string consumerName, string providerName, string type) + { + _relationsDataModel.SkipRelation(consumerName, providerName, type); + } + + public void AmbiguousRelation(string consumerName, string providerName, string type) + { + _relationsDataModel.AmbiguousRelation(consumerName, providerName, type); + } + + public ICollection GetRelationTypes() + { + return _relationsDataModel.GetRelationTypes(); + } + + public int GetRelationTypeCount(string type) + { + return _relationsDataModel.GetRelationTypeCount(type); + } + + public ICollection GetRelationsOfConsumer(int consumerId) + { + return _relationsDataModel.GetRelationsOfConsumer(consumerId); + } + + public IEnumerable GetRelations() + { + return _relationsDataModel.GetRelations(); + } + + public int CurrentRelationCount => _relationsDataModel.CurrentRelationCount; + + public bool DoesRelationExist(int consumerId, int providerId) + { + return _relationsDataModel.DoesRelationExist(consumerId, providerId); + } + + public int ImportedRelationCount => _relationsDataModel.ImportedRelationCount; + + public int ResolvedRelationCount => _relationsDataModel.ResolvedRelationCount; + + public double ResolvedRelationPercentage => _relationsDataModel.ResolvedRelationPercentage; + + public double AmbiguousRelationPercentage => _relationsDataModel.AmbiguousRelationPercentage; + + private bool Ignore(string name) + { + bool ignore = false; + + foreach (string ignoredName in _ignoredNames) + { + Regex regex = new Regex(ignoredName); + Match match = regex.Match(name); + if (match.Success) + { + ignore = true; + } + } + return ignore; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelation.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelation.cs new file mode 100644 index 00000000..4ad1f00e --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelation.cs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; + +namespace DsmSuite.Analyzer.Model.Core +{ + /// + /// Represents a relation of a specific type between two elements + /// + public class DsiRelation : IDsiRelation + { + public DsiRelation(int consumerId, int providerId, string type, int weight, IDictionary properties) + { + ConsumerId = consumerId; + ProviderId = providerId; + Type = type; + Weight = weight; + Properties = properties; + } + + public int ConsumerId { get; } + public int ProviderId { get; } + public string Type { get; } + public int Weight { get; set; } + public IDictionary Properties { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelationModel.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelationModel.cs new file mode 100644 index 00000000..b599bb72 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Core/DsiRelationModel.cs @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; +using DsmSuite.Analyzer.Model.Persistency; +using DsmSuite.Common.Util; + +namespace DsmSuite.Analyzer.Model.Core +{ + public class DsiRelationModel : IDsiRelationModelFileCallback + { + private readonly DsiElementModel _elementsDataModel; + private readonly Dictionary>>> _relationsByConsumerId; + private readonly Dictionary _relationTypeCount; + private int _importedRelationCount; + private int _resolvedRelationCount; + private int _ambiguousRelationCount; + + public DsiRelationModel(DsiElementModel elementsDataModel) + { + _elementsDataModel = elementsDataModel; + _elementsDataModel.ElementRemoved += OnElementRemoved; + _relationsByConsumerId = new Dictionary>>>(); + _relationTypeCount = new Dictionary(); + } + + public void Clear() + { + _relationsByConsumerId.Clear(); + _relationTypeCount.Clear(); + _importedRelationCount = 0; + _resolvedRelationCount = 0; + _ambiguousRelationCount = 0; + } + + public IDsiRelation ImportRelation(int consumerId, int providerId, string type, int weight, IDictionary properties) + { + Logger.LogDataModelMessage($"Import relation consumerId={consumerId} providerId={providerId} type={type} weight={weight}"); + + _importedRelationCount++; + + DsiRelation relation = null; + + IDsiElement consumer = _elementsDataModel.FindElementById(consumerId); + IDsiElement provider = _elementsDataModel.FindElementById(providerId); + + if ((consumer != null) && (provider != null)) + { + _resolvedRelationCount++; + relation = AddOrUpdateRelation(consumer.Id, provider.Id, type, weight, properties); + } + else + { + Logger.LogErrorDataModelRelationNotResolved(consumerId.ToString(), providerId.ToString()); + } + return relation; + } + + public IDsiRelation AddRelation(string consumerName, string providerName, string type, int weight, IDictionary properties) + { + Logger.LogDataModelMessage($"Add relation consumerName={consumerName} providerName={providerName} type={type} weight={weight}"); + + DsiRelation relation = null; + + _importedRelationCount++; + + IDsiElement consumer = _elementsDataModel.FindElementByName(consumerName); + IDsiElement provider = _elementsDataModel.FindElementByName(providerName); + + if ((consumer != null) && (provider != null)) + { + _resolvedRelationCount++; + relation = AddOrUpdateRelation(consumer.Id, provider.Id, type, weight, properties); + } + else + { + Logger.LogErrorDataModelRelationNotResolved(consumerName, providerName); + } + + return relation; + } + + public void IgnoreRelation(string consumerName, string providerName, string type, int weight) + { + Logger.LogDataModelMessage($"Ignore relation consumerName={consumerName} providerName={providerName} type={type} weight={weight}"); + } + + private DsiRelation AddOrUpdateRelation(int consumerId, int providerId, string type, int weight, IDictionary properties) + { + Dictionary> relations = GetRelations(consumerId, providerId); + + IncrementRelationTypeCount(type); + + if (!relations.ContainsKey(type)) + { + relations[type] = new List(); + } + + DsiRelation relation = new DsiRelation(consumerId, providerId, type, weight, properties); + relations[type].Add(relation); + return relation; + } + + public void IgnoreRelation(string consumerName, string providerName, string type) + { + Logger.LogDataModelMessage($"Ignore relation consumerName={consumerName} providerName={providerName} type={type}"); + } + + public void SkipRelation(string consumerName, string providerName, string type) + { + Logger.LogDataModelMessage($"Skip relation consumerName={consumerName} providerName={providerName} type={type}"); + + Logger.LogErrorDataModelRelationNotResolved(consumerName, providerName); + + _importedRelationCount++; + } + + public void AmbiguousRelation(string consumerName, string providerName, string type) + { + Logger.LogDataModelMessage($"Ambiguous relation consumerName={consumerName} providerName={providerName} type={type}"); + _ambiguousRelationCount++; + } + + public ICollection GetRelationTypes() + { + return _relationTypeCount.Keys; + } + + public int GetRelationTypeCount(string type) + { + if (_relationTypeCount.ContainsKey(type)) + { + return _relationTypeCount[type]; + } + else + { + return 0; + } + } + + public ICollection GetRelationsOfConsumer(int consumerId) + { + List consumerRelations = new List(); + if (_relationsByConsumerId.ContainsKey(consumerId)) + { + foreach (Dictionary> relationsForSpecificConsumer in _relationsByConsumerId[consumerId].Values) + { + foreach (List relation in relationsForSpecificConsumer.Values) + { + consumerRelations.AddRange(relation); + } + } + } + return consumerRelations; + } + + public IEnumerable GetRelations() + { + List allRelations = new List(); + foreach (Dictionary>> consumerRelations in _relationsByConsumerId.Values) + { + foreach (Dictionary> relationsForSpecificConsumer in consumerRelations.Values) + { + foreach (List relation in relationsForSpecificConsumer.Values) + { + allRelations.AddRange(relation); + } + } + } + return allRelations; + } + + public int CurrentRelationCount => GetRelations().Count(); + + public bool DoesRelationExist(int consumerId, int providerId) + { + return _relationsByConsumerId.ContainsKey(consumerId) && + _relationsByConsumerId[consumerId].ContainsKey(providerId); + } + + public int ImportedRelationCount => _importedRelationCount; + + public int ResolvedRelationCount => _resolvedRelationCount; + + public int AmbiguousRelationCount => _ambiguousRelationCount; + + public double ResolvedRelationPercentage + { + get + { + double resolvedRelationPercentage = 0.0; + if (ImportedRelationCount > 0) + { + resolvedRelationPercentage = (ResolvedRelationCount * 100.0) / ImportedRelationCount; + } + return resolvedRelationPercentage; + } + } + + public double AmbiguousRelationPercentage + { + get + { + double ambiguousRelationPercentage = 0.0; + if (ImportedRelationCount > 0) + { + ambiguousRelationPercentage = (AmbiguousRelationCount * 100.0) / ImportedRelationCount; + } + return ambiguousRelationPercentage; + } + } + + private void IncrementRelationTypeCount(string type) + { + if (!_relationTypeCount.ContainsKey(type)) + { + _relationTypeCount[type] = 0; + } + _relationTypeCount[type]++; + } + + private Dictionary> GetRelations(int consumerId, int providerId) + { + if (!_relationsByConsumerId.ContainsKey(consumerId)) + { + _relationsByConsumerId[consumerId] = new Dictionary>>(); + } + + if (!_relationsByConsumerId[consumerId].ContainsKey(providerId)) + { + _relationsByConsumerId[consumerId][providerId] = new Dictionary>(); + } + + return _relationsByConsumerId[consumerId][providerId]; + } + + private void OnElementRemoved(object sender, int elementId) + { + if (_relationsByConsumerId.ContainsKey(elementId)) + { + _relationsByConsumerId.Remove(elementId); + } + + foreach (var relationsByProviderId in _relationsByConsumerId.Values) + { + if (relationsByProviderId.ContainsKey(elementId)) + { + relationsByProviderId.Remove(elementId); + } + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/DsmSuite.Analyzer.Model.csproj b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/DsmSuite.Analyzer.Model.csproj new file mode 100644 index 00000000..348de515 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/DsmSuite.Analyzer.Model.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + Library + enable + + + + + + + + diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiElement.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiElement.cs new file mode 100644 index 00000000..78451fd0 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiElement.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.Analyzer.Model.Interface +{ + public interface IDsiElement + { + int Id { get; } + string Name { get; } + string Type { get; } + IDictionary Properties { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiModel.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiModel.cs new file mode 100644 index 00000000..bea37e0a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiModel.cs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Util; +using System; +using System.Collections.Generic; + +namespace DsmSuite.Analyzer.Model.Interface +{ + /// + /// Interface to the data model. An interface has been introduced to improve testability. + /// + public interface IDsiModel + { + string Filename { get; } + + // Model persistency + void Load(string dsiFilename, IProgress progress); + void Save(string dsiFilename, bool compressFile, IProgress progress); + + // Meta data + void AddMetaData(string name, string value); + IEnumerable GetMetaDataGroups(); + IEnumerable GetMetaDataGroupItems(string groupName); + + // Element editing + IDsiElement AddElement(string name, string type, IDictionary properties); + void RemoveElement(IDsiElement element); + void RenameElement(IDsiElement element, string newName); + + // Element queries + IDsiElement FindElementById(int id); + IDsiElement FindElementByName(string name); + IEnumerable GetElements(); + int CurrentElementCount { get; } + double ResolvedRelationPercentage { get; } + ICollection GetElementTypes(); + int GetElementTypeCount(string type); + + // Relation editing + IDsiRelation AddRelation(string consumerName, string providerName, string type, int weight, IDictionary properties); + void SkipRelation(string consumerName, string providerName, string type); + void AmbiguousRelation(string consumerName, string providerName, string type); + // Relation queries + ICollection GetRelationsOfConsumer(int consumerId); + IEnumerable GetRelations(); + int CurrentRelationCount { get; } + bool DoesRelationExist(int consumerId, int providerId); + int ImportedRelationCount { get; } + int ResolvedRelationCount { get; } + ICollection GetRelationTypes(); + int GetRelationTypeCount(string type); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiRelation.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiRelation.cs new file mode 100644 index 00000000..137b0ab8 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Interface/IDsiRelation.cs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.Analyzer.Model.Interface +{ + public interface IDsiRelation + { + int ConsumerId { get; } + int ProviderId { get; } + string Type { get; } + int Weight { get; } + IDictionary Properties { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/DsiModelFile.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/DsiModelFile.cs new file mode 100644 index 00000000..37175793 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/DsiModelFile.cs @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using DsmSuite.Analyzer.Model.Interface; +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Model.Persistency; +using DsmSuite.Common.Util; + +namespace DsmSuite.Analyzer.Model.Persistency +{ + public class DsiModelFile + { + private const string RootXmlNode = "dsimodel"; + private const string ModelElementCountXmlAttribute = "elementCount"; + private const string ModelRelationCountXmlAttribute = "relationCount"; + + private const string MetaDataGroupXmlNode = "metadatagroup"; + private const string MetaDataGroupNameXmlAttribute = "name"; + private const string MetaDataXmlNode = "metadata"; + private const string MetaDataItemNameXmlAttribute = "name"; + private const string MetaDataItemValueXmlAttribute = "value"; + + private const string ElementGroupXmlNode = "elements"; + + private const string ElementXmlNode = "element"; + private const string ElementIdXmlAttribute = "id"; + private const string ElementNameXmlAttribute = "name"; + private const string ElementTypeXmlAttribute = "type"; + + private const string RelationGroupXmlNode = "relations"; + + private const string RelationXmlNode = "relation"; + private const string RelationFromXmlAttribute = "from"; + private const string RelationToXmlAttribute = "to"; + private const string RelationTypeXmlAttribute = "type"; + private const string RelationWeightXmlAttribute = "weight"; + + private readonly string _filename; + private readonly IMetaDataModelFileCallback _metaDataModelCallback; + private readonly IDsiElementModelFileCallback _elementModelCallback; + private readonly IDsiRelationModelFileCallback _relationModelCallback; + + private int _totalElementCount; + private int _progressedElementCount; + private int _totalRelationCount; + private int _progressedRelationCount; + private int _progressPercentage; + private string _progressActionText; + + public DsiModelFile(string filename, + IMetaDataModelFileCallback metaDataModelCallback, + IDsiElementModelFileCallback elementModelCallback, + IDsiRelationModelFileCallback relationModelCallback) + { + _filename = filename; + _metaDataModelCallback = metaDataModelCallback; + _elementModelCallback = elementModelCallback; + _relationModelCallback = relationModelCallback; + } + + public void Save(bool compressed, IProgress progress) + { + _progressActionText = "Saving dsi model"; + CompressedFile modelFile = new CompressedFile(_filename); + modelFile.WriteFile(WriteDsiXml, progress, compressed); + } + + public void Load(IProgress progress) + { + _progressActionText = "Loading dsi model"; + CompressedFile modelFile = new CompressedFile(_filename); + modelFile.ReadFile(ReadDsiXml, progress); + } + + private void WriteDsiXml(Stream stream, IProgress progress) + { + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + IndentChars = (" ") + }; + + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement(RootXmlNode); + { + WriteModelAttributes(writer); + WriteMetaData(writer); + WriteElements(writer, progress); + WriteRelations(writer, progress); + } + writer.WriteEndDocument(); + } + } + + private void ReadDsiXml(Stream stream, IProgress progress) + { + XmlReader xReader = XmlReader.Create(stream); + while (xReader.Read()) + { + switch (xReader.NodeType) + { + case XmlNodeType.Element: + ReadModelAttributes(xReader); + ReadMetaDataGroup(xReader); + ReadElement(xReader, progress); + ReadRelation(xReader, progress); + break; + case XmlNodeType.Text: + break; + case XmlNodeType.EndElement: + break; + } + } + } + + private void WriteModelAttributes(XmlWriter writer) + { + _totalElementCount = _elementModelCallback.CurrentElementCount; + writer.WriteAttributeString(ModelElementCountXmlAttribute, _totalElementCount.ToString()); + _progressedElementCount = 0; + + _totalRelationCount = _relationModelCallback.CurrentRelationCount; + writer.WriteAttributeString(ModelRelationCountXmlAttribute, _totalRelationCount.ToString()); + _progressedRelationCount = 0; + } + + private void ReadModelAttributes(XmlReader xReader) + { + if (xReader.Name == RootXmlNode) + { + int? elementCount = ParseInt(xReader.GetAttribute(ModelElementCountXmlAttribute)); + int? relationCount = ParseInt(xReader.GetAttribute(ModelRelationCountXmlAttribute)); + + if (elementCount.HasValue && relationCount.HasValue) + { + _totalElementCount = elementCount.Value; + _progressedElementCount = 0; + _totalRelationCount = relationCount.Value; + _progressedRelationCount = 0; + } + } + } + + private void WriteMetaData(XmlWriter writer) + { + foreach (string group in _metaDataModelCallback.GetExportedMetaDataGroups()) + { + WriteMetaDataGroup(writer, group); + } + } + + private void WriteMetaDataGroup(XmlWriter writer, string group) + { + writer.WriteStartElement(MetaDataGroupXmlNode); + writer.WriteAttributeString(MetaDataGroupNameXmlAttribute, group); + + foreach (IMetaDataItem metaDataItem in _metaDataModelCallback.GetExportedMetaDataGroupItems(group)) + { + writer.WriteStartElement(MetaDataXmlNode); + writer.WriteAttributeString(MetaDataItemNameXmlAttribute, metaDataItem.Name); + writer.WriteAttributeString(MetaDataItemValueXmlAttribute, metaDataItem.Value); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + private void ReadMetaDataGroup(XmlReader xReader) + { + if (xReader.Name == MetaDataGroupXmlNode) + { + string group = xReader.GetAttribute(MetaDataGroupNameXmlAttribute); + XmlReader xMetaDataReader = xReader.ReadSubtree(); + while (xMetaDataReader.Read()) + { + if (xMetaDataReader.Name == MetaDataXmlNode) + { + string name = xMetaDataReader.GetAttribute(MetaDataItemNameXmlAttribute); + string value = xMetaDataReader.GetAttribute(MetaDataItemValueXmlAttribute); + if ((name != null) && (value != null)) + { + _metaDataModelCallback.ImportMetaDataItem(group, name, value); + } + } + } + } + } + + private void WriteElements(XmlWriter writer, IProgress progress) + { + writer.WriteStartElement(ElementGroupXmlNode); + foreach (IDsiElement element in _elementModelCallback.GetElements()) + { + WriteElement(writer, element, progress); + } + writer.WriteEndElement(); + } + + private void WriteElement(XmlWriter writer, IDsiElement element, IProgress progress) + { + writer.WriteStartElement(ElementXmlNode); + writer.WriteAttributeString(ElementIdXmlAttribute, element.Id.ToString()); + writer.WriteAttributeString(ElementNameXmlAttribute, element.Name); + writer.WriteAttributeString(ElementTypeXmlAttribute, element.Type); + if (element.Properties != null) + { + foreach (KeyValuePair elementProperty in element.Properties) + { + writer.WriteAttributeString(elementProperty.Key, elementProperty.Value); + } + } + writer.WriteEndElement(); + + _progressedElementCount++; + UpdateProgress(progress); + } + + private void ReadElement(XmlReader xReader, IProgress progress) + { + if (xReader.Name == ElementXmlNode) + { + int? id = null; + string name = ""; + string type = ""; + + Dictionary elementProperties = new Dictionary(); + for (int attInd = 0; attInd < xReader.AttributeCount; attInd++) + { + xReader.MoveToAttribute(attInd); + switch (xReader.Name) + { + case ElementIdXmlAttribute: + id = ParseInt(xReader.Value); + break; + case ElementNameXmlAttribute: + name = xReader.Value; + break; + case ElementTypeXmlAttribute: + type = xReader.Value; + break; + default: + if (!string.IsNullOrEmpty(xReader.Value)) + { + elementProperties[xReader.Name] = xReader.Value; + } + break; + } + } + + if (id.HasValue) + { + if (elementProperties.Count > 0) + { + _elementModelCallback.ImportElement(id.Value, name, type, elementProperties); + } + else + { + _elementModelCallback.ImportElement(id.Value, name, type, null); + } + } + + _progressedElementCount++; + UpdateProgress(progress); + } + } + + private void WriteRelations(XmlWriter writer, IProgress progress) + { + writer.WriteStartElement(RelationGroupXmlNode); + foreach (IDsiRelation relation in _relationModelCallback.GetRelations()) + { + WriteRelation(writer, relation, progress); + } + writer.WriteEndElement(); + } + + private void WriteRelation(XmlWriter writer, IDsiRelation relation, IProgress progress) + { + writer.WriteStartElement(RelationXmlNode); + writer.WriteAttributeString(RelationFromXmlAttribute, relation.ConsumerId.ToString()); + writer.WriteAttributeString(RelationToXmlAttribute, relation.ProviderId.ToString()); + writer.WriteAttributeString(RelationTypeXmlAttribute, relation.Type); + writer.WriteAttributeString(RelationWeightXmlAttribute, relation.Weight.ToString()); + if (relation.Properties != null) + { + foreach (KeyValuePair relationProperty in relation.Properties) + { + writer.WriteAttributeString(relationProperty.Key, relationProperty.Value); + } + } + writer.WriteEndElement(); + + _progressedRelationCount++; + UpdateProgress(progress); + } + + private void ReadRelation(XmlReader xReader, IProgress progress) + { + if (xReader.Name == RelationXmlNode) + { + int? consumerId = null; + int? providerId = null; + string type = ""; + int? weight = null; + + Dictionary relationProperties = new Dictionary(); + for (int attInd = 0; attInd < xReader.AttributeCount; attInd++) + { + xReader.MoveToAttribute(attInd); + switch (xReader.Name) + { + case RelationFromXmlAttribute: + consumerId = ParseInt(xReader.Value); + break; + case RelationToXmlAttribute: + providerId = ParseInt(xReader.Value); + break; + case RelationTypeXmlAttribute: + type = xReader.Value; + break; + case RelationWeightXmlAttribute: + weight = ParseInt(xReader.Value); + break; + default: + if (!string.IsNullOrEmpty(xReader.Value)) + { + relationProperties[xReader.Name] = xReader.Value; + } + break; + } + } + + if (consumerId.HasValue && providerId.HasValue && weight.HasValue) + { + if (relationProperties.Count > 0) + { + _relationModelCallback.ImportRelation(consumerId.Value, providerId.Value, type, weight.Value, relationProperties); + } + else + { + _relationModelCallback.ImportRelation(consumerId.Value, providerId.Value, type, weight.Value, null); + } + } + + _progressedRelationCount++; + UpdateProgress(progress); + } + } + + private void UpdateProgress(IProgress progress) + { + if (progress != null) + { + int totalItemCount = _totalElementCount + _totalRelationCount; + int progressedItemCount = _progressedElementCount + _progressedRelationCount; + + int currentProgressPercentage = 0; + if (totalItemCount > 0) + { + currentProgressPercentage = progressedItemCount * 100 / totalItemCount; + } + + if (_progressPercentage != currentProgressPercentage) + { + _progressPercentage = currentProgressPercentage; + + ProgressInfo progressInfoInfo = new ProgressInfo + { + ActionText = _progressActionText, + Percentage = currentProgressPercentage, + TotalItemCount = totalItemCount, + CurrentItemCount = progressedItemCount, + ItemType = "items", + Done = totalItemCount == progressedItemCount + }; + + progress.Report(progressInfoInfo); + } + } + } + + private int? ParseInt(string value) + { + int? result = null; + + int parsedValued; + if (int.TryParse(value, out parsedValued)) + { + result = parsedValued; + } + return result; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiElementModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiElementModelFileCallback.cs new file mode 100644 index 00000000..fa58c0b9 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiElementModelFileCallback.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; +using System.Collections.Generic; + +namespace DsmSuite.Analyzer.Model.Persistency +{ + public interface IDsiElementModelFileCallback + { + IDsiElement ImportElement(int id, string name, string type, IDictionary properties); + IEnumerable GetElements(); + int CurrentElementCount { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiRelationModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiRelationModelFileCallback.cs new file mode 100644 index 00000000..b8436bd3 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Analyzer.Model/Persistency/IDsiRelationModelFileCallback.cs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; + +namespace DsmSuite.Analyzer.Model.Persistency +{ + public interface IDsiRelationModelFileCallback + { + IDsiRelation ImportRelation(int consumerId, int providerId, string type, int weight, IDictionary properties); + IEnumerable GetRelations(); + int CurrentRelationCount { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataItem.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataItem.cs new file mode 100644 index 00000000..7e850672 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataItem.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Model.Interface; + +namespace DsmSuite.Common.Model.Core +{ + public class MetaDataItem : IMetaDataItem + { + public MetaDataItem(string name, string value) + { + Name = name; + Value = value; + } + + public string Name { get; } + public string Value { get; set; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataModel.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataModel.cs new file mode 100644 index 00000000..869cf4b3 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Core/MetaDataModel.cs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Model.Persistency; +using DsmSuite.Common.Util; + +namespace DsmSuite.Common.Model.Core +{ + public class MetaDataModel : IMetaDataModelFileCallback + { + private readonly string _defaultGroupName; + private readonly Assembly _executingAssembly; + private readonly List _metaDataGroupNames; + private readonly Dictionary> _metaDataGroups; + + public MetaDataModel(string defaultGroupName, Assembly executingAssembly) + { + _defaultGroupName = defaultGroupName; + _executingAssembly = executingAssembly; + + _metaDataGroupNames = new List(); + _metaDataGroups = new Dictionary>(); + + AddDefaultItems(); + } + + public void Clear() + { + _metaDataGroupNames.Clear(); + _metaDataGroups.Clear(); + + AddDefaultItems(); + } + + public IMetaDataItem AddMetaDataItemToDefaultGroup(string name, string value) + { + return AddMetaDataItem(_defaultGroupName, name, value); + } + + public IMetaDataItem AddMetaDataItem(string groupName, string name, string value) + { + Logger.LogDataModelMessage($"Add metadata group={groupName} name={name} value={value}"); + + MetaDataItem item = FindItem(groupName, name); + if (item != null) + { + item.Value = value; + } + else + { + item = new MetaDataItem(name, value); + GetMetaDataGroupItemList(groupName).Add(item); + } + return item; + } + + public IEnumerable GetExportedMetaDataGroups() + { + return _metaDataGroupNames; + } + + public IEnumerable GetExportedMetaDataGroupItems(string groupName) + { + return GetMetaDataGroupItemList(groupName); + } + + private IList GetMetaDataGroupItemList(string groupName) + { + if (!_metaDataGroups.ContainsKey(groupName)) + { + _metaDataGroupNames.Add(groupName); + _metaDataGroups[groupName] = new List(); + } + + return _metaDataGroups[groupName]; + } + + private MetaDataItem FindItem(string groupName, string name) + { + return (from item in GetMetaDataGroupItemList(groupName) + where item.Name == name + select item).FirstOrDefault(); + } + + private void AddDefaultItems() + { + AddMetaDataItemToDefaultGroup("Executable", SystemInfo.GetExecutableInfo(_executingAssembly)); + } + + public IMetaDataItem ImportMetaDataItem(string groupName, string name, string value) + { + Logger.LogDataModelMessage($"Import metadata group={groupName} name={name} value={value}"); + + MetaDataItem item = FindItem(groupName, name); + if (item != null) + { + item.Value = value; + } + else + { + item = new MetaDataItem(name, value); + GetMetaDataGroupItemList(groupName).Add(item); + } + return item; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Model/DsmSuite.Common.Model.csproj b/ThirdParty/DsmSuite/DsmSuite.Common.Model/DsmSuite.Common.Model.csproj new file mode 100644 index 00000000..f8a14986 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Model/DsmSuite.Common.Model.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + Library + enable + + + + + + + \ No newline at end of file diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Model/Interface/IMetaDataItem.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Interface/IMetaDataItem.cs new file mode 100644 index 00000000..eadbaadc --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Interface/IMetaDataItem.cs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.Common.Model.Interface +{ + public interface IMetaDataItem + { + string Name { get; } + string Value { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Model/Persistency/IMetaDataModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Persistency/IMetaDataModelFileCallback.cs new file mode 100644 index 00000000..7aa1b886 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Model/Persistency/IMetaDataModelFileCallback.cs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Collections.Generic; +using DsmSuite.Common.Model.Interface; + +namespace DsmSuite.Common.Model.Persistency +{ + public interface IMetaDataModelFileCallback + { + IMetaDataItem ImportMetaDataItem(string group, string name, string value); + + IEnumerable GetExportedMetaDataGroups(); + IEnumerable GetExportedMetaDataGroupItems(string group); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/CompressedFile.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/CompressedFile.cs new file mode 100644 index 00000000..c6b7feea --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/CompressedFile.cs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.IO; +using System.IO.Compression; + +namespace DsmSuite.Common.Util +{ + /// + public class CompressedFile + { + private readonly string _filename; + private const int ZipLeadBytes = 0x04034b50; + + public delegate void ReadContent(Stream stream, IProgress progress); + public delegate void WriteContent(Stream stream, IProgress progress); + + public CompressedFile(string filename) + { + _filename = filename; + } + + public void ReadFile(ReadContent readContent, IProgress progress) + { + FileInfo fileInfo = new FileInfo(_filename); + if (fileInfo.Exists) + { + if (IsCompressed) + { + using (ZipArchive archive = ZipFile.OpenRead(fileInfo.FullName)) + { + if (archive.Entries.Count == 1) + { + using (Stream entryStream = archive.Entries[0].Open()) + { + readContent(entryStream, progress); + } + } + } + } + else + { + using (FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read)) + { + readContent(stream, progress); + } + } + } + } + + public void WriteFile(WriteContent writeContent, IProgress progress, bool compressed) + { + FileInfo fileInfo = new FileInfo(_filename); + if (compressed) + { + using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Create)) + { + using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create, false)) + { + ZipArchiveEntry entry = archive.CreateEntry(fileInfo.Name); + using (Stream entryStream = entry.Open()) + { + writeContent(entryStream, progress); + } + } + } + } + else + { + using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Create)) + { + writeContent(fileStream, progress); + } + } + } + + public bool FileExists + { + get + { + FileInfo fileInfo = new FileInfo(_filename); + return fileInfo.Exists; + } + } + + public bool IsCompressed + { + get + { + bool isCompressedFile = false; + + FileInfo fileInfo = new FileInfo(_filename); + + if (fileInfo.Exists) + { + using (FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read)) + { + byte[] bytes = new byte[4]; + stream.Read(bytes, 0, 4); + if (BitConverter.ToInt32(bytes, 0) == ZipLeadBytes) + { + isCompressedFile = true; + } + } + } + + return isCompressedFile; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/ConsoleActionBase.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ConsoleActionBase.cs new file mode 100644 index 00000000..10ddb122 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ConsoleActionBase.cs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Diagnostics; +using System.Text; + +namespace DsmSuite.Common.Util +{ + public abstract class ConsoleActionBase : IProgress + { + private const string InputParameters = "Input parameters"; + private const string Processing = "Processing"; + private const string OutputParameters = "Ouput parameters"; + private const string Performance = "Performance"; + + private readonly string _title; + private readonly Stopwatch _stopWatch; + private string _currentText = string.Empty; + + protected ConsoleActionBase(string title) + { + _title = title; + _stopWatch = new Stopwatch(); + } + + public void Execute() + { + LogTitle(); + EmptyLine(); + + Logger.LogAssemblyInfo(); + EmptyLine(); + + if (CheckPrecondition()) + { + StartTimer(); + + LogSubTitle(InputParameters); + LogInputParameters(); + EmptyLine(); + + LogSubTitle(Processing); + Action(); + EmptyLine(); + + LogSubTitle(OutputParameters); + LogOutputParameters(); + EmptyLine(); + + LogSubTitle(Performance); + StopTimer(); + Logger.LogUserMessage($"Total elapsed time={_stopWatch.Elapsed}"); + Logger.LogResourceUsage(); + EmptyLine(); + Logger.LogLogDirectory(); + } + } + + public void Report(ProgressInfo progress) + { + WriteToConsole( + progress.Percentage.HasValue + ? $"{progress.ActionText} {progress.CurrentItemCount}/{progress.TotalItemCount} {progress.ItemType} {progress.Percentage.Value}%" + : $"{progress.ActionText} {progress.CurrentItemCount} {progress.ItemType}", true, progress.Done); + } + + protected abstract bool CheckPrecondition(); + protected abstract void LogInputParameters(); + protected abstract void Action(); + protected abstract void LogOutputParameters(); + + private void StartTimer() + { + _stopWatch.Start(); + } + + private void StopTimer() + { + _stopWatch.Stop(); + } + + private void LogTitle() + { + Logger.LogUserMessage(_title); + Underline('=', _title.Length); + } + + private void LogSubTitle(string text) + { + Logger.LogUserMessage(text); + Underline('-', text.Length); + } + + private void Underline(char character, int length) + { + Logger.LogUserMessage(new String(character, length)); + } + + private void EmptyLine() + { + Logger.LogUserMessage(""); + } + + private void WriteToConsole(string text, bool overwrite, bool endOfLine) + { + if (!Console.IsOutputRedirected) + { + StringBuilder outputBuilder = new StringBuilder(); + + if (overwrite) + { + outputBuilder.Append("\r"); + outputBuilder.Append(text); + int overlapCount = _currentText.Length - text.Length; + if (overlapCount > 0) + { + outputBuilder.Append(' ', overlapCount); + outputBuilder.Append('\b', overlapCount); + } + + Console.Write(outputBuilder); + } + + outputBuilder.Clear(); + + if (overwrite) + { + outputBuilder.Append("\r"); + } + outputBuilder.Append(text); + Console.Write(outputBuilder); + + if (endOfLine) + { + outputBuilder.Append("\n"); + Logger.LogUserMessage(outputBuilder.ToString()); + } + + _currentText = outputBuilder.ToString(); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/DsmSuite.Common.Util.csproj b/ThirdParty/DsmSuite/DsmSuite.Common.Util/DsmSuite.Common.Util.csproj new file mode 100644 index 00000000..aeeea636 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/DsmSuite.Common.Util.csproj @@ -0,0 +1,18 @@ + + + net10.0 + Library + enable + + + + github + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/ElementName.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ElementName.cs new file mode 100644 index 00000000..c2f36604 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ElementName.cs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.Common.Util +{ + + public class ElementName + { + /// + /// Periods are used as path separator. This is the suggested character to use as a + /// non-path separating period. + /// + public const char NOPERIOD = '\u00b7'; // unicode middle dot + + public ElementName() + { + FullName = ""; + } + + public ElementName(string fullname) + { + FullName = fullname; + } + + public ElementName(string parentName, string name) + { + FullName = parentName; + AddNamePart(name); + } + + public void AddNamePart(string name) + { + if (FullName.Length > 0) + { + FullName += "."; + } + FullName += name; + } + + public string FullName { get; private set; } + + public string[] NameParts => FullName.Split('.'); + + public int NamePartCount => NameParts.Length; + + public string LastNamePart => NameParts[NameParts.Length - 1]; + + public string ParentName + { + get + { + string parentName = ""; + int beginIndex = 0; + int endIndex = FullName.Length - LastNamePart.Length - 1; + if (endIndex > 0) + { + parentName = FullName.Substring(beginIndex, endIndex); + } + return parentName; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/FilePath.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/FilePath.cs new file mode 100644 index 00000000..5571517a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/FilePath.cs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.IO; +using System.Collections.Generic; + +namespace DsmSuite.Common.Util +{ + public static class FilePath + { + public static string ResolveFile(string path, string filename) + { + return Resolve(path, filename); + } + + public static List ResolveFiles(string path, IEnumerable filenames) + { + List resolvedFiles = new List(); + foreach (string filename in filenames) + { + resolvedFiles.Add(Resolve(path, filename)); + } + + return resolvedFiles; + } + + private static string Resolve_old(string path, string filename) + { + string absoluteFilename = ""; + string result = ""; + string[] assemblyFolders = filename.Split(';'); + int size = assemblyFolders.Length; + foreach (string folder in assemblyFolders) + { + absoluteFilename = Path.GetFullPath(folder); + if (!File.Exists(absoluteFilename)) + { + absoluteFilename = Path.GetFullPath(Path.Combine(path, folder)); + } + result += absoluteFilename + ";"; + Logger.LogInfo("Resolve file: path=" + path + " file=" + filename + " as file=" + absoluteFilename); + } + if (result.Length > 0) + { + result = result.Substring(0, result.Length - 1); + } + return result; + } + + private static string Resolve(string path, string filename) + { + string absoluteFilename = Path.GetFullPath(filename); + if (!File.Exists(absoluteFilename)) + { + absoluteFilename = Path.GetFullPath(Path.Combine(path ?? "", filename)); + } + + Logger.LogInfo("Resolve file: path=" + path + " file=" + filename + " as file=" + absoluteFilename); + return absoluteFilename; + } + } +} \ No newline at end of file diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/LogLevel.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/LogLevel.cs new file mode 100644 index 00000000..e8260bae --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/LogLevel.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; + +namespace DsmSuite.Common.Util +{ + [Serializable] + public enum LogLevel + { + None, + User, + Error, + Warning, + Info, + Data, + All + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/Logger.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/Logger.cs new file mode 100644 index 00000000..67197e61 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/Logger.cs @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace DsmSuite.Common.Util +{ + /// + /// Provides logging to be used for diagnostic purposes + /// + public class Logger + { + private static Assembly _assembly; + private static string _logPath; + private static readonly Dictionary> DataModelRelationNotResolvedLogMessages; + + public static DirectoryInfo LogDirectory { get; private set; } + + static Logger() + { + DataModelRelationNotResolvedLogMessages = new Dictionary>(); + } + + public static void Init(Assembly assembly, bool logInCurrentDirectory) + { + _assembly = assembly; + _logPath = logInCurrentDirectory ? Directory.GetCurrentDirectory() : System.IO.Path.GetTempPath() + @"DsmSuiteLogging\"; + + LogLevel = LogLevel.None; + } + + public static LogLevel LogLevel { get; set; } + + + + public static void LogResourceUsage() + { + Process currentProcess = Process.GetCurrentProcess(); + const long million = 1000000; + long peakPagedMemMb = currentProcess.PeakPagedMemorySize64 / million; + long peakVirtualMemMb = currentProcess.PeakVirtualMemorySize64 / million; + long peakWorkingSetMb = currentProcess.PeakWorkingSet64 / million; + LogUserMessage($"Peak physical memory usage {peakWorkingSetMb:0.000}MB"); + LogUserMessage($"Peak paged memory usage {peakPagedMemMb:0.000}MB"); + LogUserMessage($"Peak virtual memory usage {peakVirtualMemMb:0.000}MB"); + } + + public static void LogAssemblyInfo() + { + string name = _assembly.GetName().Name; + string version = _assembly.GetName().Version.ToString(); + DateTime buildDate = new FileInfo(_assembly.Location).LastWriteTime; + LogUserMessage($"{SystemInfo.VersionLong}"); + LogUserMessage("Assembly: " + name + " version =" + version + " built on " + buildDate); + } + + public static void LogLogDirectory() { + LogUserMessage($"Log level {LogLevel}, location: {LogDirectory?.FullName}"); + } + + public static void LogUserMessage(string message, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + Console.WriteLine(message); + LogToFile(LogLevel.User, "userMessages.log", message); + } + + public static void LogError(string message, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + LogToFile(LogLevel.Error, "errorMessages.log", FormatLine(sourceFile, method, lineNumber, "error", message)); + } + + public static void LogException(string message, Exception e, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + LogToFile(LogLevel.Error, "exceptions.log", FormatLine(sourceFile, method, lineNumber, message, e.Message)); + LogToFile(LogLevel.Error, "exceptions.log", e.StackTrace); + LogToFile(LogLevel.Error, "exceptions.log", ""); + } + + public static void LogWarning(string message, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + LogToFile(LogLevel.Warning, "warningMessages.log", FormatLine(sourceFile, method, lineNumber, "error", message)); + } + + public static void LogInfo(string message, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + LogToFile(LogLevel.Info, "infoMessages.log", FormatLine(sourceFile, method, lineNumber, "info", message)); + } + + public static void LogDataModelMessage(string message, + [CallerFilePath] string sourceFile = "", + [CallerMemberName] string method = "", + [CallerLineNumber] int lineNumber = 0) + { + LogToFile(LogLevel.Data, "dataModelMessages.log", FormatLine(sourceFile, method, lineNumber, "info", message)); + } + + public static void LogErrorDataModelRelationNotResolved(string consumerName, string providerName) + { + string key = providerName; + string message = " From " + consumerName; + if (!DataModelRelationNotResolvedLogMessages.ContainsKey(key)) + { + DataModelRelationNotResolvedLogMessages[key] = new HashSet(); + } + DataModelRelationNotResolvedLogMessages[key].Add(message); + } + + public static void Flush() + { + Flush(LogLevel.Error, DataModelRelationNotResolvedLogMessages, "Relations not resolved", "dataModelRelationsNotResolved", 0); + } + + public static void LogToFile(LogLevel logLevel, string logFilename, string line) + { + if (LogLevel >= logLevel) + { + string path = GetLogFullPath(logFilename); + FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write); + using (StreamWriter writer = new StreamWriter(fs)) + { + writer.WriteLine(line); + } + } + } + + private static void Flush(LogLevel logLevel, Dictionary> messages, string title, string filename, int minCount) + { + string overviewFilename = filename + "Overview.txt"; + string detailsFilename = filename + "Details.txt"; + + int totalOccurrences = 0; + + List keys = messages.Keys.ToList(); + keys.Sort(); + + if (keys.Count > 0) + { + Logger.LogToFile(logLevel, overviewFilename, title); + Logger.LogToFile(logLevel, detailsFilename, title); + + Logger.LogToFile(logLevel, overviewFilename, "--------------------------------------------"); + Logger.LogToFile(logLevel, detailsFilename, "---------------------------------------------"); + } + + foreach (string key in keys) + { + int occurrences = messages[key].Count; + + if (occurrences > minCount) + { + totalOccurrences += occurrences; + Logger.LogToFile(logLevel, overviewFilename, $"{key} {occurrences} occurrences"); + Logger.LogToFile(logLevel, detailsFilename, $"{key} {occurrences} occurrences"); + foreach (string message in messages[key]) + { + Logger.LogToFile(logLevel, detailsFilename, " " + message); + } + } + } + + if (keys.Count > 0) + { + Logger.LogToFile(logLevel, overviewFilename, $"{keys.Count} items found in {totalOccurrences} occurrences"); + Logger.LogToFile(logLevel, detailsFilename, $"{keys.Count} items found in {totalOccurrences} occurrences"); + } + } + + private static DirectoryInfo CreateLogDirectory() + { + DateTime now = DateTime.Now; + string timestamp = $"{now.Year:0000}-{now.Month:00}-{now.Day:00}-{now.Hour:00}-{now.Minute:00}-{now.Second:00}"; + string assemblyName = _assembly.GetName().Name; + return Directory.CreateDirectory($@"{_logPath}\{assemblyName}_{timestamp}\"); + } + + private static string GetLogFullPath(string logFilename) + { + if (LogDirectory == null) + { + LogDirectory = CreateLogDirectory(); + } + + return Path.GetFullPath(Path.Combine(LogDirectory.FullName, logFilename)); + } + + private static string FormatLine(string sourceFile, string method, int lineNumber, string category, string text) + { + return StripPath(sourceFile) + " " + method + "() line=" + lineNumber + " " + category + "=" + text; + } + + private static string StripPath(string sourceFile) + { + char[] separators = { '\\' }; + string[] parts = sourceFile.Split(separators); + return parts[parts.Length - 1]; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/ProgressInfo.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ProgressInfo.cs new file mode 100644 index 00000000..89d99cb9 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/ProgressInfo.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.Common.Util +{ + public class ProgressInfo + { + public string ActionText; + public int TotalItemCount; + public int CurrentItemCount; + public string ItemType; + public int? Percentage; + public bool Done; + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/StringExtensions.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/StringExtensions.cs new file mode 100644 index 00000000..5081e9ce --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/StringExtensions.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Text.RegularExpressions; + +namespace DsmSuite.Common.Util +{ + public static class StringExtensions + { + public static string ReplaceIgnoreCase(this string input, string oldValue, string newValue) + { + return Regex.Replace(input, Regex.Escape(oldValue), newValue.Replace("$", "$$"), RegexOptions.IgnoreCase); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.Common.Util/SystemInfo.cs b/ThirdParty/DsmSuite/DsmSuite.Common.Util/SystemInfo.cs new file mode 100644 index 00000000..76fafd0c --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.Common.Util/SystemInfo.cs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.IO; +using System.Reflection; + +namespace DsmSuite.Common.Util +{ + public class SystemInfo + { + public static string GetExecutableInfo(Assembly assembly) + { + string name = assembly.GetName().Name; + string version = assembly.GetName().Version.ToString(); + DateTime buildDate = new FileInfo(assembly.Location).LastWriteTime; + return $"{name} version={version} build={buildDate}"; + } + + private static string Changes => ThisAssembly.Git.Commits != "0" ? $"-{ThisAssembly.Git.Commits}" : ""; + public static string Version => $"{ThisAssembly.Git.BaseTag}{Changes} {ThisAssembly.Git.Commit}"; + public static string VersionLong => $"DsmSuite version {Version}"; + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionAttributes.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionAttributes.cs new file mode 100644 index 00000000..0fb96598 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionAttributes.cs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Text; + +namespace DsmSuite.DsmViewer.Application.Actions.Base +{ + public class ActionAttributes + { + readonly Dictionary _data; + + public ActionAttributes() + { + _data = new Dictionary(); + } + + public void SetString(string memberName, string memberValue) + { + _data[RemoveUnderscore(memberName)] = memberValue; + } + + public void SetInt(string memberName, int memberValue) + { + _data[RemoveUnderscore(memberName)] = memberValue.ToString(); + } + + public void SetNullableInt(string memberName, int? memberValue) + { + if (memberValue.HasValue) + { + _data[RemoveUnderscore(memberName)] = memberValue.Value.ToString(); + } + } + + public void SetListInt(string memberName, List list) + { + StringBuilder s = new StringBuilder(); + + for (int i = 0; i < list.Count; i++) + { + s.Append(list[i]); + if (i < list.Count - 1) + s.Append(','); + } + + _data[RemoveUnderscore(memberName)] = s.ToString(); + } + + /// + /// Set data for memberName to a compact representation of list. + /// If list is not strictly increasing or contains negative numbers, the results are undefined. + /// + public void SetListIntCompact(string memberName, List list) + { + StringBuilder s = new StringBuilder(); + int startRun, i; + + i = 0; + while (i < list.Count) + { + if (list[i] < 0) // This causes parsing problems for ActionReadOnlyAttributes + throw new ArgumentException("negative element in list"); + //if (i > 0 && list[i] <= list[i - 1]) + //throw new ArgumentException("list must be strictly increasing"); + + if (s.Length > 0) + s.Append(','); + s.Append(list[i]); + startRun = i; + while (i < list.Count-1 && list[i+1] == list[i] + 1) + { + i++; + } + if (i > startRun+1) + { + s.Append('-'); + s.Append(list[i]); + i++; + } + else if (i == startRun) + i++; + } + + _data[RemoveUnderscore(memberName)] = s.ToString(); + } + + public IReadOnlyDictionary Data => _data; + + private static string RemoveUnderscore(string memberName) + { + return memberName.Substring(1); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionReadOnlyAttributes.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionReadOnlyAttributes.cs new file mode 100644 index 00000000..567a5978 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Base/ActionReadOnlyAttributes.cs @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Collections.Generic; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Base +{ + public class ActionReadOnlyAttributes + { + private readonly IDsmModel _model; + private readonly IReadOnlyDictionary _data; + + public ActionReadOnlyAttributes(IDsmModel model, IReadOnlyDictionary data) + { + _data = data; + _model = model; + } + + public string GetString(string memberName) + { + return _data[RemoveUnderscore(memberName)]; + } + + public int GetInt(string memberName) + { + return int.Parse(_data[RemoveUnderscore(memberName)]); + } + + public int? GetNullableInt(string memberName) + { + int? value = null; + + int number; + if (_data.ContainsKey(RemoveUnderscore(memberName)) && + int.TryParse(_data[RemoveUnderscore(memberName)], out number)) + { + value = number; + } + + return value; + } + + public List GetListInt(string memberName) + { + List list = new List(); + string s = _data.GetValueOrDefault(RemoveUnderscore(memberName)); + + foreach (string item in s.Split(',')) + { + int value; + if (int.TryParse(item, out value)) + { + list.Add(value); + } + } + + return list; + } + + + public List GetListIntCompact(string memberName) + { + List list = new List(); + string s = _data.GetValueOrDefault(RemoveUnderscore(memberName)); + + foreach (string item in s.Split(',')) + { + if (item.LastIndexOf('-') > 0) // A range (and not a negative number)? + { + int start, end; + string[] limits = item.Split('-'); + + if (limits.Length > 2 || + !int.TryParse(limits[0], out start) || !int.TryParse(limits[1], out end)) + throw new ArgumentException("malformed data string"); + + for (int i = start; i <= end; i++) + list.Add(i); + } + else + { + int value; + if (int.TryParse(item, out value)) + { + list.Add(value); + } + } + } + + return list; + } + + + public IDsmElement GetElement(string memberName) + { + int id = GetInt(memberName); + return _model.GetElementById(id) ?? + _model.GetDeletedElementById(id); + } + + public IDsmRelation GetRelation(string memberName) + { + int id = GetInt(memberName); + return _model.GetRelationById(id) ?? + _model.GetDeletedRelationById(id); + } + + public IDsmElement GetRelationConsumer(string memberName) + { + IDsmElement consumer = null; + IDsmRelation relation = GetRelation(memberName); + if (relation != null) + { + consumer = _model.GetElementById(relation.Consumer.Id) ?? + _model.GetDeletedElementById(relation.Consumer.Id); + } + return consumer; + } + + public IDsmElement GetRelationProvider(string memberName) + { + IDsmElement provider = null; + IDsmRelation relation = GetRelation(memberName); + if (relation != null) + { + provider = _model.GetElementById(relation.Provider.Id) ?? + _model.GetDeletedElementById(relation.Provider.Id); + } + return provider; + } + + private static string RemoveUnderscore(string memberName) + { + return memberName.Substring(1); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeNameAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeNameAction.cs new file mode 100644 index 00000000..a9016a68 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeNameAction.cs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementChangeNameAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly string _old; + private readonly string _new; + + public const ActionType RegisteredType = ActionType.ElementChangeName; + + public ElementChangeNameAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _old = attributes.GetString(nameof(_old)); + _new = attributes.GetString(nameof(_new)); + } + } + + + public ElementChangeNameAction(IDsmModel model, IDsmElement element, string name) + { + _model = model; + _element = element; + _old = _element.Name; + _new = name; + } + + public ActionType Type => RegisteredType; + public string Title => "Change element name"; + public string Description => $"element={_element.Fullname} name={_old}->{_new}"; + + public object Do() + { + _model.ChangeElementName(_element, _new); + return null; + } + + public void Undo() + { + _model.ChangeElementName(_element, _old); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_old != null) && + (_new != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetString(nameof(_old), _old); + attributes.SetString(nameof(_new), _new); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeParentAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeParentAction.cs new file mode 100644 index 00000000..73987681 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeParentAction.cs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementChangeParentAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly IDsmElement _oldParent; + private readonly int _oldIndex; + private readonly string _oldName; + private readonly IDsmElement _newParent; + private readonly int _newIndex; + private string _newName; + + public const ActionType RegisteredType = ActionType.ElementChangeParent; + + public ElementChangeParentAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _oldParent = attributes.GetElement(nameof(_oldParent)); + _oldIndex = attributes.GetInt(nameof(_oldIndex)); + _oldName = attributes.GetString(nameof(_oldName)); + _newParent = attributes.GetElement(nameof(_newParent)); + _newIndex = attributes.GetInt(nameof(_newIndex)); + _newName = attributes.GetString(nameof(_newName)); + } + } + + public ElementChangeParentAction(IDsmModel model, IDsmElement element, IDsmElement newParent, int index) + { + _model = model; + _element = element; + + _oldParent = element.Parent; + _oldIndex = _oldParent.IndexOfChild(element); + _oldName = element.Name; + + _newParent = newParent; + _newIndex = index; + _newName = element.Name; + } + + public ActionType Type => RegisteredType; + public string Title => "Change element parent"; + public string Description => $"element={_element.Fullname} parent={_oldParent.Fullname}->{_newParent.Fullname}"; + + public object Do() + { + // Rename to avoid duplicate names + if (_newParent.ContainsChildWithName(_oldName)) + { + _newName += " (duplicate)"; + _model.ChangeElementName(_element, _newName); + } + + _model.ChangeElementParent(_element, _newParent, _newIndex); + _model.AssignElementOrder(); + return null; + } + + public void Undo() + { + _model.ChangeElementParent(_element, _oldParent, _oldIndex); + _model.AssignElementOrder(); + + // Restore original name + if (_oldName != _newName) + { + _model.ChangeElementName(_element, _oldName); + } + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_oldParent != null) && + (_oldName != null) && + (_newParent != null) && + (_newName != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetInt(nameof(_oldParent), _oldParent.Id); + attributes.SetString(nameof(_oldName), _oldName); + attributes.SetInt(nameof(_oldIndex), _oldIndex); + attributes.SetInt(nameof(_newParent), _newParent.Id); + attributes.SetString(nameof(_newName), _newName); + attributes.SetInt(nameof(_newIndex), _newIndex); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeTypeAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeTypeAction.cs new file mode 100644 index 00000000..e1c0aeff --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementChangeTypeAction.cs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementChangeTypeAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly string _old; + private readonly string _new; + + public const ActionType RegisteredType = ActionType.ElementChangeType; + + public ElementChangeTypeAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _old = attributes.GetString(nameof(_old)); + _new = attributes.GetString(nameof(_new)); + } + } + + public ElementChangeTypeAction(IDsmModel model, IDsmElement element, string type) + { + _model = model; + _element = element; + _old = _element.Type; + _new = type; + } + + public ActionType Type => RegisteredType; + public string Title => "Change element type"; + public string Description => $"element={_element.Fullname} type={_old}->{_new}"; + + public object Do() + { + _model.ChangeElementType(_element, _new); + return null; + } + + public void Undo() + { + _model.ChangeElementType(_element, _old); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_old != null) && + (_new != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetString(nameof(_old), _old); + attributes.SetString(nameof(_new), _new); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCopyAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCopyAction.cs new file mode 100644 index 00000000..aa037292 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCopyAction.cs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementCopyAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private IDsmElement _elementCopy; + + public const ActionType RegisteredType = ActionType.ElementCopy; + + public ElementCopyAction(IDsmModel model, IDsmElement element, IActionContext actionContext) + { + _model = model; + _element = element; + _actionContext = actionContext; + _elementCopy = null; + } + + public ActionType Type => RegisteredType; + public string Title => "Copy element"; + public string Description => $"element={_element.Fullname}"; + + public object Do() + { + _elementCopy = _model.AddElement(_element.Name, _element.Type, null, null, null); + _actionContext.AddElementToClipboard(_elementCopy); + return null; + } + + public void Undo() + { + _actionContext.RemoveElementFromClipboard(_elementCopy); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetInt(nameof(_elementCopy), _elementCopy.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCreateAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCreateAction.cs new file mode 100644 index 00000000..c22e40bb --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCreateAction.cs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Diagnostics; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementCreateAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private IDsmElement _element; + private readonly string _name; + private readonly string _type; + private readonly IDsmElement _parent; + private readonly int _index; + + public const ActionType RegisteredType = ActionType.ElementCreate; + + public ElementCreateAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _name = attributes.GetString(nameof(_name)); + _type = attributes.GetString(nameof(_type)); + + int? parentId = attributes.GetNullableInt(nameof(_parent)); + if (parentId.HasValue) + { + _parent = _model.GetElementById(parentId.Value); + } + _index = attributes.GetInt(nameof(_index)); + } + } + + public ElementCreateAction(IDsmModel model, string name, string type, IDsmElement parent, int index) + { + _model = model; + _name = name; + _type = type; + _parent = parent; + _index = index; + } + + public ActionType Type => RegisteredType; + public string Title => "Create element"; + public string Description => $"name={_name} type={_type} parent={_parent.Fullname}"; + + public object Do() + { + _element = _model.AddElement(_name, _type, _parent.Id, _index, null); + Debug.Assert(_element != null); + + _model.AssignElementOrder(); + + return _element; + } + + public void Undo() + { + _model.RemoveElement(_element.Id); + _model.AssignElementOrder(); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_type != null) && + (_parent != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetString(nameof(_name), _name); + attributes.SetString(nameof(_type), _type); + attributes.SetNullableInt(nameof(_parent), _parent.Id); + attributes.SetInt(nameof(_index), _index); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCutAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCutAction.cs new file mode 100644 index 00000000..b5907d42 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementCutAction.cs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementCutAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + + public const ActionType RegisteredType = ActionType.ElementCut; + + public ElementCutAction(IDsmModel model, IDsmElement element, IActionContext actionContext) + { + _model = model; + _element = element; + _actionContext = actionContext; + } + + public ActionType Type => RegisteredType; + public string Title => "Cut element"; + public string Description => $"element={_element.Fullname}"; + + public object Do() + { + _actionContext.AddElementToClipboard(_element); + _model.RemoveElement(_element.Id); + _model.AssignElementOrder(); + return null; + } + + public void Undo() + { + _actionContext.RemoveElementFromClipboard(_element); + _model.UnremoveElement(_element.Id); + _model.AssignElementOrder(); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementDeleteAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementDeleteAction.cs new file mode 100644 index 00000000..c97f3881 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementDeleteAction.cs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementDeleteAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + + public const ActionType RegisteredType = ActionType.ElementDelete; + + public ElementDeleteAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + } + } + + public ElementDeleteAction(IDsmModel model, IDsmElement element) + { + _model = model; + _element = element; + } + + public ActionType Type => RegisteredType; + public string Title => "Delete element"; + public string Description => $"element={_element.Fullname}"; + + public object Do() + { + _model.RemoveElement(_element.Id); + _model.AssignElementOrder(); + return null; + } + + public void Undo() + { + _model.UnremoveElement(_element.Id); + _model.AssignElementOrder(); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveDownAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveDownAction.cs new file mode 100644 index 00000000..08fc476b --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveDownAction.cs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementMoveDownAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + + public const ActionType RegisteredType = ActionType.ElementMoveDown; + + public ElementMoveDownAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + _element = new ActionReadOnlyAttributes(_model, data).GetElement(nameof(_element)); + } + + public ElementMoveDownAction(IDsmModel model, IDsmElement element) + { + _model = model; + _element = element; + } + + public ActionType Type => RegisteredType; + public string Title => "Move down element"; + public string Description => $"element={_element.Fullname}"; + + public object Do() + { + IDsmElement nextElement = _model.NextSibling(_element); + if (nextElement != null) + { + _model.Swap(_element, nextElement); + _model.AssignElementOrder(); + } + + return null; + } + + public void Undo() + { + IDsmElement previousElement = _model.PreviousSibling(_element); + if (previousElement != null) + { + _model.Swap(previousElement, _element); + _model.AssignElementOrder(); + } + } + + public bool IsValid() + { + return (_model != null) && + (_element != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveUpAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveUpAction.cs new file mode 100644 index 00000000..2eb769f4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementMoveUpAction.cs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementMoveUpAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + + public const ActionType RegisteredType = ActionType.ElementMoveUp; + + public ElementMoveUpAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + _element = new ActionReadOnlyAttributes(_model, data).GetElement(nameof(_element)); + } + + public ElementMoveUpAction(IDsmModel model, IDsmElement element) + { + _model = model; + _element = element; + } + + public ActionType Type => RegisteredType; + public string Title => "Move up element"; + public string Description => $"element={_element.Fullname}"; + + public object Do() + { + IDsmElement previousElement = _model.PreviousSibling(_element); + if (previousElement != null) + { + _model.Swap(_element, previousElement); + _model.AssignElementOrder(); + } + + return null; + } + + public void Undo() + { + IDsmElement nextElement = _model.NextSibling(_element); + if (nextElement != null) + { + _model.Swap(nextElement, _element); + _model.AssignElementOrder(); + } + } + + public bool IsValid() + { + return (_model != null) && + (_element != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementPasteAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementPasteAction.cs new file mode 100644 index 00000000..d709b9f3 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementPasteAction.cs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementPasteAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly IDsmElement _oldParent; + private readonly int _oldIndex; + private readonly string _oldName; + private readonly IDsmElement _newParent; + private readonly int _newIndex; + private string _newName; + + public const ActionType RegisteredType = ActionType.ElementPaste; + + public ElementPasteAction(IDsmModel model, IDsmElement newParent, int index, IActionContext actionContext) + { + _model = model; + + _actionContext = actionContext; + _element = _actionContext.GetElementOnClipboard(); + + _oldParent = _element.Parent; + _oldIndex = _oldParent.IndexOfChild(_element); + _oldName = _element.Name; + + _newParent = newParent; + _newIndex = index; + _newName = _element.Name; + } + + public ActionType Type => RegisteredType; + public string Title => "Paste element"; + public string Description => $"element={_actionContext.GetElementOnClipboard().Fullname}"; + + public object Do() + { + // Rename to avoid duplicate names + if (_newParent.ContainsChildWithName(_oldName)) + { + _newName += " (duplicate)"; + _model.ChangeElementName(_element, _newName); + } + + _model.ChangeElementParent(_element, _newParent, _newIndex); + _model.AssignElementOrder(); + + _actionContext.RemoveElementFromClipboard(_element); + return null; + } + + public void Undo() + { + _model.ChangeElementParent(_element, _oldParent, _oldIndex); + _model.AssignElementOrder(); + + // Restore original name + if (_oldName != _newName) + { + _model.ChangeElementName(_element, _oldName); + } + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_actionContext != null) && + (_actionContext.IsElementOnClipboard()) && + (_oldParent != null) && + (_oldName != null) && + (_newParent != null) && + (_newName != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortAction.cs new file mode 100644 index 00000000..ad48001c --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortAction.cs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Application.Sorting; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + public class ElementSortAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly string _algorithm; + private List _order; + + public const ActionType RegisteredType = ActionType.ElementSort; + + public ElementSortAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _algorithm = attributes.GetString(nameof(_algorithm)); + _order = attributes.GetListInt(nameof(_order)); + } + } + + public ElementSortAction(IDsmModel model, IDsmElement element, string algorithm) + { + _model = model; + _element = element; + _algorithm = algorithm; + _order = null; + } + + public ActionType Type => RegisteredType; + public string Title => "Partition element"; + public string Description => $"element={_element.Fullname} algorithm={_algorithm}"; + + public object Do() + { + ISortAlgorithm sortAlgorithm = SortAlgorithmFactory.CreateAlgorithm(_model, _element, _algorithm); + SortResult sortResult = sortAlgorithm.Sort(); + _model.ReorderChildren(_element, sortResult); + _order = sortResult.GetOrder(); + + _model.AssignElementOrder(); + + return null; + } + + public void Undo() + { + SortResult sortResult = new SortResult(_order); + sortResult.InvertOrder(); + _model.ReorderChildren(_element, sortResult); + + _model.AssignElementOrder(); + } + + public bool IsValid() + { + return (_model != null) && + (_element != null) && + (_algorithm != null) && + (_order != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetString(nameof(_algorithm), _algorithm); + attributes.SetListInt(nameof(_order), _order); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortRecursiveAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortRecursiveAction.cs new file mode 100644 index 00000000..40b9810a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Element/ElementSortRecursiveAction.cs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Application.Sorting; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Element +{ + /// + /// Recursively sort the subtree rooted at an element. Only sorts elements that are in the tree. + /// + public class ElementSortRecursiveAction : IMultiAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmElement _element; + private readonly string _algorithm; + private List _actions; // The sort actions on descendant elements (possibly empty) + + public const ActionType RegisteredType = ActionType.ElementSortRecursive; + + public ElementSortRecursiveAction(IDsmModel model, IActionContext context, + IReadOnlyDictionary data, IEnumerable actions) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _element = attributes.GetElement(nameof(_element)); + _algorithm = attributes.GetString(nameof(_algorithm)); + _actions = new List(actions); + } + } + + public ElementSortRecursiveAction(IDsmModel model, IDsmElement element, string algorithm) + { + _model = model; + _element = element; + _algorithm = algorithm; + _actions = new(); + } + + public ActionType Type => RegisteredType; + public string Title => "Partition element recursively"; + public string Description => $"element={_element.Fullname} algorithm={_algorithm}"; + + private void doRecursive(IDsmElement element) + { + if (!element.HasChildren) + return; + + Logger.LogInfo(element.Fullname); + + ElementSortAction action = new(_model, element, _algorithm); + action.Do(); + _actions.Add(action); + + foreach (IDsmElement child in element.AllChildren) + doRecursive(child); + } + + public object Do() + { + _actions.Clear(); + doRecursive(_element); + return null; + } + + public void Undo() + { + foreach (IAction action in _actions) + action.Undo(); + } + + public bool IsValid() + { + return _model != null && _element != null && _algorithm != null && _actions != null; + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_element), _element.Id); + attributes.SetString(nameof(_algorithm), _algorithm); + return attributes.Data; + } + } + + public IEnumerable Actions { get { return _actions; } } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementContextAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementContextAction.cs new file mode 100644 index 00000000..ae034c95 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementContextAction.cs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Application.Queries; +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Collections.Generic; +using System.Linq; +using static System.Net.Mime.MediaTypeNames; + +namespace DsmSuite.DsmViewer.Application.Actions.Filtering +{ + public class ShowElementContextAction : IAction + { + private readonly IDsmModel _model; + private readonly IDsmElement _provider; + private readonly IActionContext _actionContext; + private List _before; + + public const ActionType RegisteredType = ActionType.ShowElementContext; + + public ShowElementContextAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + + if (_model != null && data != null) + { + ActionReadOnlyAttributes atts = new(_model, data); + + _provider = atts.GetElement(nameof(_provider)); + _before = atts.GetListIntCompact(nameof(_before)); + } + } + + public ShowElementContextAction(IDsmModel model, IDsmElement provider) + { + _model = model; + _provider = provider; + _before = null; + } + + public ActionType Type => RegisteredType; + public string Title => "ShowElementContext"; + public string Description => $"provider={_provider?.Fullname}"; + + public object Do() + { + DsmQueries queries = new DsmQueries(_model); + + _before = new(); + foreach (IDsmElement e in _model.GetElements()) + if (e.IsIncludedInTree) + _before.Add(e.Id); + _before.Sort(); + + _model.IncludeInTree(_model.RootElement, false); + _model.IncludeInTree(_provider, true); + foreach (WeightedElement consumer in queries.FindConsumersOf(_provider)) + _model.IncludeInTree(consumer.Element, true); + foreach (WeightedElement provider in queries.FindProvidersFor(_provider)) + _model.IncludeInTree(provider.Element, true); + + return null; + } + + public void Undo() + { + _model.IncludeInTree(_model.RootElement, false); + foreach (int i in _before) + _model.GetElementById(i).IsIncludedInTree = true; + } + + public bool IsValid() + { + return _model != null && _provider != null; + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new(); + attributes.SetInt(nameof(_provider), _provider.Id); + attributes.SetListIntCompact(nameof(_before), _before); + return attributes.Data; + } + } + } +} + diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementDetailAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementDetailAction.cs new file mode 100644 index 00000000..2a1fbc6b --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Filtering/ShowElementDetailAction.cs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Collections.Generic; +using System.Linq; + +namespace DsmSuite.DsmViewer.Application.Actions.Filtering +{ + public class ShowElementDetailAction : IAction + { + private readonly IDsmModel _model; + private readonly IDsmElement _provider, _consumer; + private readonly IActionContext _actionContext; + private List _before; + + public const ActionType RegisteredType = ActionType.ShowElementDetail; + + public ShowElementDetailAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + + if (_model != null && data != null) + { + int? id; + ActionReadOnlyAttributes atts = new(_model, data); + + id = atts.GetNullableInt(nameof(_provider)); + if (id.HasValue) + _provider = _model.GetElementById(id.Value); + id = atts.GetNullableInt(nameof(_consumer)); + if (id.HasValue) + _consumer = _model.GetElementById(id.Value); + _before = atts.GetListIntCompact(nameof(_before)); + } + } + + public ShowElementDetailAction(IDsmModel model, IDsmElement provider, IDsmElement consumer) + { + _model = model; + _provider = provider; + _consumer = consumer; + _before = null; + } + + public ActionType Type => RegisteredType; + public string Title => "ShowElementDetail"; + public string Description => $"provider={_provider?.Fullname} consumer={_consumer?.Fullname}"; + + public object Do() + { + _before = new(); + foreach (IDsmElement e in _model.GetElements()) + if (e.IsIncludedInTree) + _before.Add(e.Id); + _before.Sort(); + + _model.IncludeInTree(_model.RootElement, false); + if (_provider != null) + _model.IncludeInTree(_provider, true); + if (_consumer != null) + _model.IncludeInTree(_consumer, true); + return null; + } + + public void Undo() + { + _model.IncludeInTree(_model.RootElement, false); + foreach (int i in _before) + _model.GetElementById(i).IsIncludedInTree = true; + } + + public bool IsValid() + { + return _model != null && (_provider != null || _consumer != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new(); + if (_provider != null) + attributes.SetInt(nameof(_provider), _provider.Id); + if (_consumer != null) + attributes.SetInt(nameof(_consumer), _consumer.Id); + attributes.SetListIntCompact(nameof(_before), _before); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionContext.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionContext.cs new file mode 100644 index 00000000..4b58b46b --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionContext.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + public class ActionContext : IActionContext + { + private IDsmElement _element; + + public void AddElementToClipboard(IDsmElement element) + { + _element = element; + } + + public void RemoveElementFromClipboard(IDsmElement element) + { + _element = null; + } + + public IDsmElement GetElementOnClipboard() + { + return _element; + } + + public bool IsElementOnClipboard() + { + return (_element == null); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionManager.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionManager.cs new file mode 100644 index 00000000..c7f38078 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionManager.cs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + /// + /// Manages user actions. + /// This include executing them, undo/redo and keeping a list of all executed actions. + /// + public class ActionManager : IActionManager + { + private readonly Stack _undoActionStack; + private readonly Stack _redoActionStack; + private readonly ActionContext _actionContext; + + public event EventHandler ActionPerformed; + + public ActionManager() + { + _undoActionStack = new Stack(); + _redoActionStack = new Stack(); + _actionContext = new ActionContext(); + } + + public bool Validate() + { + bool valid = true; + + foreach (var action in _undoActionStack) + { + if (!action.IsValid()) + { + Logger.LogError($"Invalid action: {action.ToString()}"); + valid = false; + } + } + + foreach (var action in _redoActionStack) + { + if (!action.IsValid()) + { + valid = false; + } + } + + return valid; + } + + public object Execute(IAction action) + { + _undoActionStack.Push(action); + _redoActionStack.Clear(); + object result = action.Do(); + ActionPerformed?.Invoke(this, EventArgs.Empty); + return result; + } + + public void Add(IAction action) + { + _undoActionStack.Push(action); + } + + public bool CanUndo() + { + return _undoActionStack.Count > 0; + } + + public IAction GetCurrentUndoAction() + { + IAction action = null; + if (_undoActionStack.Count > 0) + { + action = _undoActionStack.Peek(); + } + return action; + } + + public void Undo() + { + if (_undoActionStack.Count > 0) + { + IAction action = _undoActionStack.Pop(); + if (action != null) + { + _redoActionStack.Push(action); + action.Undo(); + ActionPerformed?.Invoke(this, EventArgs.Empty); + Logger.LogInfo($"Undo :{action.Description}"); + } + } + } + + public bool CanRedo() + { + return _redoActionStack.Count > 0; + } + + public IAction GetCurrentRedoAction() + { + IAction action = null; + if (_redoActionStack.Count > 0) + { + action = _redoActionStack.Peek(); + } + return action; + } + + public void Redo() + { + if (_redoActionStack.Count > 0) + { + IAction action = _redoActionStack.Pop(); + if (action != null) + { + _undoActionStack.Push(action); + action.Do(); + ActionPerformed?.Invoke(this, EventArgs.Empty); + Logger.LogInfo($"Redo :{action.Description}"); + } + } + } + + /// + /// Undo/redo actions until action is at the top of the undo stack. + /// + /// + public void Goto(IAction action) + { + if (_undoActionStack.Contains(action)) + { + while (_undoActionStack.Peek() != action) + { + Undo(); + } + } + else if (_redoActionStack.Contains(action)) + { + do + { + Redo(); + } + while (_undoActionStack.Peek() != action); + } + else + Logger.LogError($"Action {action} not in ActionManager"); + } + + public void Clear() + { + _undoActionStack.Clear(); + _redoActionStack.Clear(); + } + + public IEnumerable GetActionsInReverseChronologicalOrder() + { + return _undoActionStack; + } + + public IEnumerable GetActionsInChronologicalOrder() + { + return _undoActionStack.Reverse(); + } + + /// + /// Return the actions for redo in the order in which they were originally executed. + /// This is also the order in which they will be redone. + /// + public IEnumerable GetRedoActionsInChronologicalOrder() + { + return _redoActionStack; + } + + public IActionContext GetContext() + { + return _actionContext; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionStore.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionStore.cs new file mode 100644 index 00000000..ef6007a0 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/ActionStore.cs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Reflection.Metadata.Ecma335; +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Actions.Element; +using DsmSuite.DsmViewer.Application.Actions.Filtering; +using DsmSuite.DsmViewer.Application.Actions.Relation; +using DsmSuite.DsmViewer.Application.Actions.Snapshot; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + /// + /// Stores/restores user actions to/from the model. + /// + /// + /// The ActionType enum tags are used as the action string for the model. + /// + public class ActionStore + { + // Translate ActionType's to the implementing classes. + // The ActionType enum tags are used as the action string for the model. + // TODO Isn't a string->Type dictionary more suitable? + // TODO Make certain (compile/runtime) all actions are indeed in the table (cut/copy/paste currently aren't) + // otherwise we can't load model that we saved. + private readonly Dictionary _types; + + public ActionStore() + { + _types = new Dictionary(); + RegisterActionTypes(); + } + + + /// + /// Loads all actions from the model into the ActionManager. + /// Unrecognized actions are logged but otherwise ignored. + /// If any action cannot be loaded correctly, the ActionManager is cleared. + /// + public void LoadFromModel(IActionManager actionManager, IDsmModel model) + { + actionManager.Clear(); + foreach (IDsmAction action in model.GetActions()) + { + IAction instance = LoadAction(actionManager, model, action); + if (instance != null) + actionManager.Add(instance); + else + Logger.LogError($"Cannot instantiate action {action.Id} {action.Type}."); + } + + if (!actionManager.Validate()) + { + Logger.LogWarning($"Invalid action found."); + actionManager.Clear(); + } + } + + + private IAction LoadAction(IActionManager actionManager, IDsmModel model, IDsmAction action) + { + ActionType actionType; + Type type; + object[] args; + + if (!ActionType.TryParse(action.Type, out actionType)) + Logger.LogWarning($"Unknown action {action.Type} in model."); + if (!_types.TryGetValue(actionType, out type)) + Logger.LogError($"Action {action.Type} not in table."); + + if (action.Actions == null) + args = [model, actionManager.GetContext(), action.Data]; + else + { + List subactions = new(); + foreach (IDsmAction subaction in action.Actions) + { + IAction a = LoadAction(actionManager, model, subaction); + if (a == null) + return null; + subactions.Add(a); + } + + args = [model, actionManager.GetContext(), action.Data, subactions]; + } + + return Activator.CreateInstance(type, args) as IAction; + } + + + /// + /// Saves all actions in the actionManager to the model. + /// + public void SaveToModel(IActionManager actionManager, IDsmModel model) + { + if (actionManager.Validate()) + { + model.ClearActions(); + foreach (IAction action in actionManager.GetActionsInChronologicalOrder()) + { + model.AddAction(action.Type.ToString(), action.Data, new MultiActionDTO(action).Actions); + } + } + } + + + // Strings here must be kept in sync with MultiAction. + private void RegisterActionTypes() + { + //TODO cut/copy/paste actions not present + _types[ElementChangeNameAction.RegisteredType] = typeof(ElementChangeNameAction); + _types[ElementChangeTypeAction.RegisteredType] = typeof(ElementChangeTypeAction); + _types[ElementChangeParentAction.RegisteredType] = typeof(ElementChangeParentAction); + _types[ElementCreateAction.RegisteredType] = typeof(ElementCreateAction); + _types[ElementDeleteAction.RegisteredType] = typeof(ElementDeleteAction); + _types[ElementMoveDownAction.RegisteredType] = typeof(ElementMoveDownAction); + _types[ElementMoveUpAction.RegisteredType] = typeof(ElementMoveUpAction); + _types[ElementSortAction.RegisteredType] = typeof(ElementSortAction); + _types[ElementSortRecursiveAction.RegisteredType] = typeof(ElementSortRecursiveAction); + + _types[ShowElementDetailAction.RegisteredType] = typeof(ShowElementDetailAction); + _types[ShowElementContextAction.RegisteredType] = typeof(ShowElementContextAction); + + _types[RelationChangeTypeAction.RegisteredType] = typeof(RelationChangeTypeAction); + _types[RelationChangeWeightAction.RegisteredType] = typeof(RelationChangeWeightAction); + _types[RelationCreateAction.RegisteredType] = typeof(RelationCreateAction); + _types[RelationDeleteAction.RegisteredType] = typeof(RelationDeleteAction); + + _types[MakeSnapshotAction.RegisteredType] = typeof(MakeSnapshotAction); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionContext.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionContext.cs new file mode 100644 index 00000000..a68748ad --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionContext.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + public interface IActionContext + { + void AddElementToClipboard(IDsmElement element); + void RemoveElementFromClipboard(IDsmElement element); + IDsmElement GetElementOnClipboard(); + bool IsElementOnClipboard(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionManager.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionManager.cs new file mode 100644 index 00000000..1fa1fc72 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/IActionManager.cs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + public interface IActionManager + { + bool Validate(); + void Clear(); + void Add(IAction action); + object Execute(IAction action); + IEnumerable GetActionsInChronologicalOrder(); + IActionContext GetContext(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/MultiActionDTO.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/MultiActionDTO.cs new file mode 100644 index 00000000..018262ad --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Management/MultiActionDTO.cs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Management +{ + /// + /// A recursive representation of an IMultiAction, used by the ActionStore for saving to the DsmModel. + /// Id is always 0. + /// + public class MultiActionDTO : IDsmAction + { + public int Id { get; } + + public string Type { get; } + + public IReadOnlyDictionary Data { get; } + + public IEnumerable Actions { get; } + + public MultiActionDTO(IAction action) + { + Id = 0; + Type = action.Type.ToString(); // Must be kept in sync with the code in ActionStore + Data = action.Data; + Actions = (action as IMultiAction)?.Actions.Select(a => new MultiActionDTO(a)); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeTypeAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeTypeAction.cs new file mode 100644 index 00000000..8aa852f4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeTypeAction.cs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Relation +{ + public class RelationChangeTypeAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmRelation _relation; + private readonly IDsmElement _consumer; + private readonly IDsmElement _provider; + private readonly string _old; + private readonly string _new; + + public const ActionType RegisteredType = ActionType.RelationChangeType; + + public RelationChangeTypeAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _relation = attributes.GetRelation(nameof(_relation)); + _consumer = attributes.GetRelationConsumer(nameof(_relation)); + _provider = attributes.GetRelationProvider(nameof(_relation)); + _old = attributes.GetString(nameof(_old)); + _new = attributes.GetString(nameof(_new)); + } + } + + public RelationChangeTypeAction(IDsmModel model, IDsmRelation relation, string type) + { + _model = model; + _relation = relation; + _consumer = model.GetElementById(_relation.Consumer.Id); + _provider = model.GetElementById(_relation.Provider.Id); + _old = relation.Type; + _new = type; + } + + public ActionType Type => RegisteredType; + public string Title => "Change relation type"; + public string Description => $"consumer={_consumer.Fullname} provider={_provider.Fullname} type={_old}->{_new}"; + + public object Do() + { + _model.ChangeRelationType(_relation, _new); + return null; + } + + public void Undo() + { + _model.ChangeRelationType(_relation, _old); + } + + public bool IsValid() + { + return (_model != null) && (_relation != null) && (_old != null) && (_new != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_relation), _relation.Id); + attributes.SetString(nameof(_new), _new); + attributes.SetString(nameof(_old), _old); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeWeightAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeWeightAction.cs new file mode 100644 index 00000000..861b0ca4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationChangeWeightAction.cs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Relation +{ + public class RelationChangeWeightAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmRelation _relation; + private readonly IDsmElement _consumer; + private readonly IDsmElement _provider; + private readonly int _old; + private readonly int _new; + + public const ActionType RegisteredType = ActionType.RelationChangeWeight; + + public RelationChangeWeightAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _relation = attributes.GetRelation(nameof(_relation)); + _consumer = attributes.GetRelationConsumer(nameof(_relation)); + _provider = attributes.GetRelationProvider(nameof(_relation)); + _old = attributes.GetInt(nameof(_old)); + _new = attributes.GetInt(nameof(_new)); + } + } + + public RelationChangeWeightAction(IDsmModel model, IDsmRelation relation, int weight) + { + _model = model; + _relation = relation; + _consumer = model.GetElementById(_relation.Consumer.Id); + _provider = model.GetElementById(_relation.Provider.Id); + _old = relation.Weight; + _new = weight; + } + + public ActionType Type => RegisteredType; + public string Title => "Change relation weight"; + public string Description => $"consumer={_consumer.Fullname} provider={_provider.Fullname} type={_relation.Type} weight={_old}->{_new}"; + + public object Do() + { + _model.ChangeRelationWeight(_relation, _new); + return null; + } + + public void Undo() + { + _model.ChangeRelationWeight(_relation, _old); + } + + public bool IsValid() + { + return (_model != null) && + (_relation != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_relation), _relation.Id); + attributes.SetInt(nameof(_old), _old); + attributes.SetInt(nameof(_new), _new); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationCreateAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationCreateAction.cs new file mode 100644 index 00000000..61f49ca2 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationCreateAction.cs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Relation +{ + public class RelationCreateAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private IDsmRelation _relation; + private readonly IDsmElement _consumer; + private readonly IDsmElement _provider; + private readonly string _type; + private readonly int _weight; + + public const ActionType RegisteredType = ActionType.RelationCreate; + + public RelationCreateAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _relation = attributes.GetRelation(nameof(_relation)); + _consumer = attributes.GetRelationConsumer(nameof(_relation)); + _provider = attributes.GetRelationProvider(nameof(_relation)); + _type = attributes.GetString(nameof(_type)); + _weight = attributes.GetInt(nameof(_weight)); + } + } + + public RelationCreateAction(IDsmModel model, int consumerId, int providerId, string type, int weight) + { + _model = model; + _consumer = model.GetElementById(consumerId); + _provider = model.GetElementById(providerId); + _type = type; + _weight = weight; + } + + public ActionType Type => RegisteredType; + public string Title => "Create relation"; + public string Description => $"consumer={_consumer.Fullname} provider={_provider.Fullname} type={_type} weight={_weight}"; + + public object Do() + { + return _model.AddRelation(_consumer, _provider, _type, _weight, null); + } + + public void Undo() + { + _model.RemoveRelation(_relation.Id); + } + + public bool IsValid() + { + return (_model != null) && + (_relation != null) && + (_provider != null) && + (_provider != null) && + (_type != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_relation), _relation.Id); + attributes.SetInt(nameof(_consumer), _consumer.Id); + attributes.SetInt(nameof(_provider), _provider.Id); + attributes.SetString(nameof(_type), _type); + attributes.SetInt(nameof(_weight), _weight); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationDeleteAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationDeleteAction.cs new file mode 100644 index 00000000..62a69183 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Relation/RelationDeleteAction.cs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Relation +{ + public class RelationDeleteAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly IDsmRelation _relation; + private readonly IDsmElement _consumer; + private readonly IDsmElement _provider; + + public const ActionType RegisteredType = ActionType.RelationDelete; + + public RelationDeleteAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + + _relation = attributes.GetRelation(nameof(_relation)); + _consumer = attributes.GetRelationConsumer(nameof(_relation)); + _provider = attributes.GetRelationProvider(nameof(_relation)); + } + } + + public RelationDeleteAction(IDsmModel model, IDsmRelation relation) + { + _model = model; + _relation = relation; + _consumer = model.GetElementById(_relation.Consumer.Id); + _provider = model.GetElementById(_relation.Provider.Id); + } + + public ActionType Type => RegisteredType; + public string Title => "Delete relation"; + public string Description => $"consumer={_consumer.Fullname} provider={_provider.Fullname} type={_relation.Type}"; + + public object Do() + { + _model.RemoveRelation(_relation.Id); + return null; + } + + public void Undo() + { + _model.UnremoveRelation(_relation.Id); + } + + public bool IsValid() + { + return (_model != null) && + (_relation != null) && + (_consumer != null) && + (_provider != null); + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetInt(nameof(_relation), _relation.Id); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Snapshot/MakeSnapshotAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Snapshot/MakeSnapshotAction.cs new file mode 100644 index 00000000..dddd3a24 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Actions/Snapshot/MakeSnapshotAction.cs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Application.Actions.Base; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Actions.Snapshot +{ + public class MakeSnapshotAction : IAction + { + private readonly IDsmModel _model; + private readonly IActionContext _actionContext; + private readonly string _name; + + public const ActionType RegisteredType = ActionType.Snapshot; + + /// + /// MakeSnapshotAction creates a bookmark in the do/undo stacks. + /// Neither Do() nor Undo() has any effect. + /// + public MakeSnapshotAction(IDsmModel model, IActionContext context, IReadOnlyDictionary data) + { + _model = model; + _actionContext = context; + + if (_model != null && data != null) + { + ActionReadOnlyAttributes attributes = new ActionReadOnlyAttributes(_model, data); + _name = attributes.GetString(nameof(_name)); + } + } + + public MakeSnapshotAction(IDsmModel model, string name) + { + _model = model; + _name = name; + } + + public ActionType Type => RegisteredType; + public string Title => "Make snapshot"; + public string Description => $"name={_name}"; + public string Name => _name; + + public object Do() + { + return null; + } + + public void Undo() + { + } + + public bool IsValid() + { + return _model != null && _name != null; + } + + public IReadOnlyDictionary Data + { + get + { + ActionAttributes attributes = new ActionAttributes(); + attributes.SetString(nameof(_name), _name); + return attributes.Data; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/ApplicationClasses.cd b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/ApplicationClasses.cd new file mode 100644 index 00000000..bb7c1436 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/ApplicationClasses.cd @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ApE2RAoAJoyQBAiDABIbREkhoIhohYCFRlAgBIUAQIA= + Core\DsmApplication.cs + + + + + + + + + + + + + + + AAIQAAQABACABASAAIAgEAIBAAAAAAAAECACAAFAAIA= + Actions\Management\ActionManager.cs + + + + + + + AAAAAAAAAAAAAAQAAAAAhAAAEAAAAAAAAAAAAEAAAAA= + Actions\Management\ActionStore.cs + + + + + + AAAAAAIAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA= + Metrics\DsmMetrics.cs + + + + + + + + + + + AAAABAAAAAAAAABAAgAACAAAgAEggACAAAAAAAQAAAA= + Queries\DsmQueries.cs + + + + + + + + + + + + + AAIAAAAAAACAAAQAAIAAAAAAAAAAAAAAACAAAABAAAA= + Actions\Management\IActionManager.cs + + + + + + AAAAAAAAAAAgBEAAAAgAAAAACAAAABAAAQAAAAAAAAA= + Interfaces\IAction.cs + + + + + + + + \ No newline at end of file diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Core/DsmApplication.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Core/DsmApplication.cs new file mode 100644 index 00000000..62b5e8b4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Core/DsmApplication.cs @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Reflection; +using DsmSuite.Analyzer.Model.Core; +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Actions.Element; +using DsmSuite.DsmViewer.Application.Actions.Filtering; +using DsmSuite.DsmViewer.Application.Actions.Management; +using DsmSuite.DsmViewer.Application.Actions.Relation; +using DsmSuite.DsmViewer.Application.Actions.Snapshot; +using DsmSuite.DsmViewer.Application.Import.Common; +using DsmSuite.DsmViewer.Application.Import.Dsi; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Application.Metrics; +using DsmSuite.DsmViewer.Application.Queries; +using DsmSuite.DsmViewer.Application.Sorting; +using DsmSuite.DsmViewer.Model.Core; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Core +{ + public class DsmApplication : IDsmApplication + { + private IDsmModel _dsmModel; + private ActionManager _actionManager; + private readonly ActionStore _actionStore; + private readonly DsmQueries _queries; + private readonly DsmMetrics _metrics; + + public event EventHandler Modified; + public event EventHandler ActionPerformed; + + public DsmApplication(IDsmModel dsmModel) + { + _dsmModel = dsmModel; + _queries = new DsmQueries(dsmModel); + _metrics = new DsmMetrics(); + _actionStore = new ActionStore(); + _actionManager = new ActionManager(); + _actionManager.ActionPerformed += OnActionPerformed; + } + + + /// + /// Set a new model and actionManager. The current model and actionmanager are + /// discarded. + /// + private void LoadModel(IDsmModel model) { + _dsmModel = model; + _actionManager = new ActionManager(); + _actionStore.LoadFromModel(_actionManager, _dsmModel); + _actionManager.ActionPerformed += OnActionPerformed; + IsModified = false; + Modified?.Invoke(this, IsModified); + } + + + private void OnActionPerformed(object sender, EventArgs e) + { + ActionPerformed?.Invoke(this, e); + IsModified = true; + Modified?.Invoke(this, IsModified); + } + + public bool CanUndo() + { + return _actionManager.CanUndo(); + } + + public string GetUndoActionDescription() + { + return _actionManager.GetCurrentUndoAction()?.Description; + } + + public void Undo() + { + _actionManager.Undo(); + } + public void GotoAction(IAction action) + { + _actionManager.Goto(action); + } + + public bool CanRedo() + { + return _actionManager.CanRedo(); + } + + public string GetRedoActionDescription() + { + return _actionManager.GetCurrentRedoAction()?.Description; + } + + public void Redo() + { + _actionManager.Redo(); + } + + public async Task AsyncImportDsiModel(string dsiFilename, string dsmFilename, + bool autoPartition, bool compressDsmFile, IProgress progress) + { + IDsmModel model = await Task.Run( () => + ImportDsiModel(dsiFilename, dsmFilename, autoPartition, compressDsmFile, progress) ); + LoadModel(model); + } + + + /// + /// Create a model by loading data from dsiFilename, save it as dsm to dsmFilename + /// and return the model. + /// + private IDsmModel ImportDsiModel(string dsiFilename, string dsmFilename, + bool autoPartition, bool compressDsmFile, IProgress progress) + { + Assembly assembly = Assembly.GetEntryAssembly(); + + //---- Read file into DsiModel + DsiModel dsiModel = new DsiModel("Builder", new List(), assembly); + dsiModel.Load(dsiFilename, progress); + + //---- Create DsmModel from DsiModel + IDsmModel model = new DsmModel("Viewer", assembly); + DsiImporter importer = new DsiImporter(dsiModel, model, autoPartition); + importer.Import(progress); + + //---- Save DsmModel + model.SaveModel(dsmFilename, compressDsmFile, progress); + + return model; + } + + + // Changed 2026-07 for CSharpCodeAnalyst: AsyncImportSqlModel / ImportSqlModel and the + // SqlImporter they used were removed. The .sql import is not offered by the embedded + // matrix view, and dropping it removes the Dapper and Microsoft.Data.Sqlite dependencies. + + + public async Task AsyncOpenModel(string dsmFilename, Progress progress) + { + IDsmModel model = new DsmModel("Viewer", Assembly.GetExecutingAssembly()); + await Task.Run(() => model.LoadModel(dsmFilename, progress)); + LoadModel(model); + } + + + public async Task AsyncSaveModel(string dsmFilename, Progress progress) + { + _actionStore.SaveToModel(_actionManager, _dsmModel); + await Task.Run(() => _dsmModel.SaveModel(dsmFilename, _dsmModel.IsCompressed, progress)); + IsModified = false; + Modified?.Invoke(this, IsModified); + } + + + public IDsmElement RootElement => _dsmModel.RootElement; + + public bool IsModified { get; private set; } + + public IEnumerable GetElementConsumers(IDsmElement element) + { + return _queries.FindConsumersOf(element); + } + + public IEnumerable GetElementProvidedElements(IDsmElement element) + { + return _queries.FindElementsProvidedBy(element); + } + + public IEnumerable GetElementProviders(IDsmElement element) + { + return _queries.FindProvidersFor(element); + } + + public IEnumerable FindResolvedRelations(IDsmElement consumer, IDsmElement provider) + { + return _queries.FindRelations(consumer, provider); + } + + public IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider) + { + return _dsmModel.FindRelations(consumer, provider); + } + + public int GetRelationCount(IDsmElement consumer, IDsmElement provider) + { + return _dsmModel.GetRelationCount(consumer, provider); + } + + public IEnumerable FindIngoingRelations(IDsmElement element) + { + return _queries.FindConsumingRelations(element); + } + + public IEnumerable FindOutgoingRelations(IDsmElement element) + { + return _queries.FindProvidingRelations(element); + } + + public IEnumerable FindInternalRelations(IDsmElement element) + { + return _queries.FindInternalRelations(element); + } + + public IEnumerable FindExternalRelations(IDsmElement element) + { + return _queries.FindExternalRelations(element); + } + + public IEnumerable GetRelationProviders(IDsmElement consumer, IDsmElement provider) + { + return _queries.FindRelationProviders(consumer, provider); + } + + public IEnumerable GetRelationConsumers(IDsmElement consumer, IDsmElement provider) + { + return _queries.FindRelationConsumers(consumer, provider); + } + + public int GetHierarchicalCycleCount(IDsmElement element) + { + return _dsmModel.GetHierarchicalCycleCount(element); + } + + public int GetSystemCycleCount(IDsmElement element) + { + return _dsmModel.GetSystemCycleCount(element); + } + + public IDsmElement NextSibling(IDsmElement element) + { + return _dsmModel.NextSibling(element); + } + + public IDsmElement PreviousSibling(IDsmElement element) + { + return _dsmModel.PreviousSibling(element); + } + + public bool IsFirstChild(IDsmElement element) + { + return _dsmModel.PreviousSibling(element) == null; + } + + public bool IsLastChild(IDsmElement element) + { + return _dsmModel.NextSibling(element) == null; + } + + public bool HasChildren(IDsmElement element) + { + return element?.Children.Count > 0; + } + + public void Sort(IDsmElement element, string algorithm) + { + ElementSortAction action = new ElementSortAction(_dsmModel, element, algorithm); + _actionManager.Execute(action); + } + + public void SortRecursively(IDsmElement element, string algorithm) + { + IAction action = new ElementSortRecursiveAction(_dsmModel, element, algorithm); + _actionManager.Execute(action); + } + + public IEnumerable GetSupportedSortAlgorithms() + { + return SortAlgorithmFactory.GetSupportedAlgorithms(); + } + + public void MoveUp(IDsmElement element) + { + ElementMoveUpAction action = new ElementMoveUpAction(_dsmModel, element); + _actionManager.Execute(action); + } + + public void MoveDown(IDsmElement element) + { + ElementMoveDownAction action = new ElementMoveDownAction(_dsmModel, element); + _actionManager.Execute(action); + } + + public IEnumerable GetElementTypes() + { + return _dsmModel.GetElementTypes(); + } + + public int GetDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + return _dsmModel.GetDependencyWeight(consumer, provider); + } + + public int GetDirectDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + return _dsmModel.GetDirectDependencyWeight(consumer, provider); + } + + public CycleType IsCyclicDependency(IDsmElement consumer, IDsmElement provider) + { + return _dsmModel.IsCyclicDependency(consumer, provider); + } + + public IList SearchElements(string searchText, IDsmElement searchInElement, bool caseSensitive, string elementTypeFilter, bool markMatchingElements) + { + return _dsmModel.SearchElements(searchText, searchInElement, caseSensitive, elementTypeFilter, markMatchingElements); + } + + public IDsmElement GetElementByFullname(string text) + { + return _dsmModel.GetElementByFullname(text); + } + + public IDsmElement CreateElement(string name, string type, IDsmElement parent, int index) + { + ElementCreateAction action = new ElementCreateAction(_dsmModel, name, type, parent, index); + return _actionManager.Execute(action) as IDsmElement; + } + + public void DeleteElement(IDsmElement element) + { + ElementDeleteAction action = new ElementDeleteAction(_dsmModel, element); + _actionManager.Execute(action); + } + + public void ChangeElementName(IDsmElement element, string name) + { + ElementChangeNameAction action = new ElementChangeNameAction(_dsmModel, element, name); + _actionManager.Execute(action); + } + + public void ChangeElementType(IDsmElement element, string type) + { + ElementChangeTypeAction action = new ElementChangeTypeAction(_dsmModel, element, type); + _actionManager.Execute(action); + } + + public void ChangeElementParent(IDsmElement element, IDsmElement newParent, int index) + { + if (_dsmModel.IsChangeElementParentAllowed(element, newParent)) + { + ElementChangeParentAction action = new ElementChangeParentAction(_dsmModel, element, newParent, index); + _actionManager.Execute(action); + } + } + + public void CutElement(IDsmElement element) + { + ElementCutAction action = new ElementCutAction(_dsmModel, element, _actionManager.GetContext()); + _actionManager.Execute(action); + } + + public void CopyElement(IDsmElement element) + { + ElementCopyAction action = new ElementCopyAction(_dsmModel, element, _actionManager.GetContext()); + _actionManager.Execute(action); + } + + public void PasteElement(IDsmElement newParent, int index) + { + ElementPasteAction action = new ElementPasteAction(_dsmModel, newParent, index, _actionManager.GetContext()); + _actionManager.Execute(action); + } + + + public IDsmRelation CreateRelation(IDsmElement consumer, IDsmElement provider, string type, int weight) + { + RelationCreateAction action = new RelationCreateAction(_dsmModel, consumer.Id, provider.Id, type, weight); + return _actionManager.Execute(action) as IDsmRelation; + } + + public void DeleteRelation(IDsmRelation relation) + { + RelationDeleteAction action = new RelationDeleteAction(_dsmModel, relation); + _actionManager.Execute(action); + } + + public void ChangeRelationType(IDsmRelation relation, string type) + { + RelationChangeTypeAction action = new RelationChangeTypeAction(_dsmModel, relation, type); + _actionManager.Execute(action); + } + + public void ChangeRelationWeight(IDsmRelation relation, int weight) + { + RelationChangeWeightAction action = new RelationChangeWeightAction(_dsmModel, relation, weight); + _actionManager.Execute(action); + } + + public IEnumerable GetRelationTypes() + { + return _dsmModel.GetRelationTypes(); + } + + public void ShowElementDetail(IDsmElement provider, IDsmElement consumer) + { + IAction action = new ShowElementDetailAction(_dsmModel, provider, consumer); + _actionManager.Execute(action); + } + + public void ShowElementContext(IDsmElement provider) + { + IAction action = new ShowElementContextAction(_dsmModel, provider); + _actionManager.Execute(action); + } + + public void MakeSnapshot(string description) + { + MakeSnapshotAction action = new MakeSnapshotAction(_dsmModel, description); + _actionManager.Execute(action); + } + + public IEnumerable GetActions() + { + return _actionManager.GetActionsInReverseChronologicalOrder(); + } + + public IEnumerable GetAllActions() + { + return _actionManager.GetActionsInChronologicalOrder() + .Concat(_actionManager.GetRedoActionsInChronologicalOrder()); + } + + public void ClearActions() + { + _actionManager.Clear(); + _dsmModel.ClearActions(); + IsModified = true; + Modified?.Invoke(this, IsModified); + } + + public int GetElementSize(IDsmElement element) + { + return _metrics.GetElementSize(element); + } + + public int GetElementCount() + { + return _dsmModel.GetElementCount(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/DsmSuite.DsmViewer.Application.csproj b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/DsmSuite.DsmViewer.Application.csproj new file mode 100644 index 00000000..6b75cf5b --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/DsmSuite.DsmViewer.Application.csproj @@ -0,0 +1,17 @@ + + + net10.0-windows + Library + enable + + + + + + + + + + + + diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Common/ImporterBase.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Common/ImporterBase.cs new file mode 100644 index 00000000..bb35cc62 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Common/ImporterBase.cs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Sorting; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Import.Common +{ + public class ImporterBase + { + private readonly IDsmModel _dsmModel; + private int _progressPercentage; + + public ImporterBase(IDsmModel dsmModel) + { + _dsmModel = dsmModel; + _dsmModel.Clear(); + } + + protected void Partition(IProgress progress) + { + int totalElements = _dsmModel.GetElementCount(); + int progressedElements = 0; + Partition(progress, _dsmModel.RootElement, totalElements, ref progressedElements); + } + + protected void Partition(IProgress progress, IDsmElement element, int totalElements, ref int progressedElements) + { + ISortAlgorithm algorithm = SortAlgorithmFactory.CreateAlgorithm(_dsmModel, element, PartitionSortAlgorithm.AlgorithmName); + _dsmModel.ReorderChildren(element, algorithm.Sort()); + progressedElements++; + UpdateProgress(progress, "Partition elements", totalElements, progressedElements); + + foreach (IDsmElement child in element.Children) + { + Partition(progress, child, totalElements, ref progressedElements); + } + } + + protected void FinalizeImport(IProgress progress) + { + _dsmModel.AssignElementOrder(); + } + + protected void UpdateProgress(IProgress progress, string progressActionText, int totalItemCount, int progressedItemCount) + { + if (progress != null) + { + int currentProgressPercentage = 0; + if (totalItemCount > 0) + { + currentProgressPercentage = progressedItemCount * 100 / totalItemCount; + } + + if (_progressPercentage != currentProgressPercentage) + { + _progressPercentage = currentProgressPercentage; + + ProgressInfo progressInfoInfo = new ProgressInfo + { + ActionText = progressActionText, + TotalItemCount = totalItemCount, + CurrentItemCount = progressedItemCount, + ItemType = "items", + Percentage = currentProgressPercentage, + Done = totalItemCount == progressedItemCount + }; + + progress.Report(progressInfoInfo); + } + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Dsi/DsiImporter.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Dsi/DsiImporter.cs new file mode 100644 index 00000000..3ae2c4e5 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Import/Dsi/DsiImporter.cs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Analyzer.Model.Interface; +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Application.Import.Common; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Import.Dsi +{ + public class DsiImporter : ImporterBase + { + private readonly IDsiModel _dsiModel; + private readonly IDsmModel _dsmModel; + private readonly bool _autoPartition; + private readonly Dictionary _dsiToDsmMapping; + private int _totalItemCount; + private int _progressedItemCount; + + public DsiImporter(IDsiModel dsiModel, IDsmModel dsmModel, bool autoPartition) : base(dsmModel) + { + _dsiModel = dsiModel; + _dsmModel = dsmModel; + _autoPartition = autoPartition; + _dsiToDsmMapping = new Dictionary(); + + _totalItemCount = _dsiModel.GetElements().Count() + _dsiModel.GetRelations().Count(); + } + + public void Import(IProgress progress) + { + ImportMetaDataItems(); + ImportElements(progress); + ImportRelations(progress); + + if (_autoPartition) + { + Partition(progress); + } + + FinalizeImport(progress); + } + + private void ImportMetaDataItems() + { + foreach (string groupName in _dsiModel.GetMetaDataGroups()) + { + foreach (IMetaDataItem metaDatItem in _dsiModel.GetMetaDataGroupItems(groupName)) + { + ImportMetaDataItem(groupName, metaDatItem.Name, metaDatItem.Value); + } + } + } + + private void ImportElements(IProgress progress) + { + foreach (IDsiElement dsiElement in _dsiModel.GetElements()) + { + ImportElement(dsiElement); + _progressedItemCount++; + UpdateProgress(progress, "Importing dsi model", _totalItemCount, _progressedItemCount); + } + } + + private void ImportElement(IDsiElement dsiElement) + { + IDsmElement parent = null; + + char[] trimCharacters = { ' ', '.' }; + string dsiElementName = dsiElement.Name.TrimStart(trimCharacters); + ElementName elementName = new ElementName(); + foreach (string name in new ElementName(dsiElementName).NameParts) + { + elementName.AddNamePart(name); + + bool isElementLeaf = (dsiElementName == elementName.FullName); + + if (isElementLeaf) + { + IDsmElement element = ImportElement(elementName.FullName, name, dsiElement.Type, parent, dsiElement.Properties); + parent = element; + _dsiToDsmMapping[dsiElement.Id] = element.Id; + + Logger.LogInfo($"Import leaf element dsiId={dsiElement.Id} dsiName={dsiElement.Name} dsmId={element.Id} dsmName={elementName.FullName}"); + } + else + { + IDsmElement element = ImportElement(elementName.FullName, name, "", parent, null); + parent = element; + + Logger.LogInfo($"Import non leaf element dsiId={dsiElement.Id} dsiName={dsiElement.Name} dsmName={elementName.FullName}"); + } + } + } + + private void ImportRelations(IProgress progress) + { + foreach (IDsiRelation dsiRelation in _dsiModel.GetRelations()) + { + ImportRelation(dsiRelation); + _progressedItemCount++; + UpdateProgress(progress, "Importing dsi model", _totalItemCount, _progressedItemCount); + } + } + + private void ImportRelation(IDsiRelation dsiRelation) + { + Logger.LogInfo($"Import relation consumerId={dsiRelation.ConsumerId} providerId={dsiRelation.ProviderId}"); + + if (_dsiToDsmMapping.ContainsKey(dsiRelation.ConsumerId) && _dsiToDsmMapping.ContainsKey(dsiRelation.ProviderId)) + { + int consumerId = _dsiToDsmMapping[dsiRelation.ConsumerId]; + int providerId = _dsiToDsmMapping[dsiRelation.ProviderId]; + string type = dsiRelation.Type; + int weight = dsiRelation.Weight; + + if (consumerId != providerId) + { + ImportRelation(consumerId, providerId, type, weight, dsiRelation.Properties); + } + } + else + { + Logger.LogError($"Could not find consumer or provider of relation consumer={dsiRelation.ConsumerId} provider={dsiRelation.ProviderId}"); + } + } + + private IMetaDataItem ImportMetaDataItem(string group, string name, string value) + { + return _dsmModel.AddMetaData(group, name, value); + } + + private IDsmElement ImportElement(string fullname, string name, string type, IDsmElement parent, IDictionary properties) + { + IDsmElement element = _dsmModel.GetElementByFullname(fullname); + if (element == null) + { + int? parentId = parent?.Id; + element = _dsmModel.AddElement(name, type, parentId, 0, properties); + } + return element; + } + + private IDsmRelation ImportRelation(int consumerId, int providerId, string type, int weight, IDictionary properties) + { + IDsmElement consumer = _dsmModel.GetElementById(consumerId); + IDsmElement provider = _dsmModel.GetElementById(providerId); + + return _dsmModel.AddRelation(consumer, provider, type, weight, properties); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/ActionType.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/ActionType.cs new file mode 100644 index 00000000..1cd3b314 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/ActionType.cs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Application.Interfaces +{ + /// + /// The enumeration of all available user actions (i.e. IAction implementors). + /// Every enumeration tag should be equal to the name of its implementing class. + /// + public enum ActionType + { + ElementChangeName, + ElementChangeParent, + ElementChangeType, + ElementCreate, + ElementDelete, + ElementMoveUp, + ElementMoveDown, + ElementSort, + ElementSortRecursive, + ElementCopy, + ElementCut, + ElementPaste, + + RelationChangeType, + RelationChangeWeight, + RelationCreate, + RelationDelete, + + ShowElementDetail, + ShowElementContext, + + Snapshot + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IAction.cs new file mode 100644 index 00000000..1e30096a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IAction.cs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Application.Interfaces +{ + /// + /// An action the user can perform in the application. + /// Actions execute on the data in the property. This data is injected + /// into the constructor.
+ /// When the model is saved, the property is saved with the action.
+ /// When loading the model again, the action is re-created using a + /// constructor(IDsmModel m, IActionContext c, IReadOnlyDictionary<string,string> data). + ///
+ public interface IAction + { + /// + /// The unique type for this action. + /// + /// todo only used by ActionStore for impex. Implement locally there and remove here. + ActionType Type { get; } + + /// + /// String that identifies this action in menus and action lists (e.g. undo/redo). + /// This must be the same for all instances of the action. + /// + string Title { get; } + + /// + /// String that describes the details of this particular action for informative purposes + /// (e.g. in undo/redo). + /// This usually returns a different text for different instances of the action, based + /// on the contents of . + /// + string Description { get; } + + /// + /// Perform this action using the data stored in Data.
+ /// This usually returns null, except for actions that are expected by the application to + /// return some useful object, like create actions. + ///
+ /// Some relevant object or null + object Do(); + + /// + /// Undo the result of Do(). + /// + void Undo(); + + /// + /// Return true iff this action has all the data it needs to be done/undone. + /// + bool IsValid(); + + /// + /// A key->value map of the data this action needs to be performed, injected into the + /// constructor. + /// The implementing class can determine the contents and format of Data itself, but + /// ActionAttributes and ActionReadOnlyAttributes provide convenience + /// functions for this. + /// + IReadOnlyDictionary Data { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IDsmApplication.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IDsmApplication.cs new file mode 100644 index 00000000..7d6890e9 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IDsmApplication.cs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Interfaces +{ + public interface IDsmApplication + { + event EventHandler Modified; + event EventHandler ActionPerformed; + + Task AsyncImportDsiModel(string dsiFilename, string dsmFilename, bool autoPartition, bool compressDsmFile, IProgress progress); + // Changed 2026-07 for CSharpCodeAnalyst: AsyncImportSqlModel removed together with SqlImporter. + + Task AsyncOpenModel(string dsmFilename, Progress progress); + Task AsyncSaveModel(string dsmFilename, Progress progress); + bool IsModified { get; } + bool CanUndo(); + string GetUndoActionDescription(); + void Undo(); + void GotoAction(IAction action); + bool CanRedo(); + string GetRedoActionDescription(); + void Redo(); + IDsmElement RootElement { get; } + IEnumerable GetElementConsumers(IDsmElement element); + IEnumerable GetElementProvidedElements(IDsmElement element); + IEnumerable GetElementProviders(IDsmElement element); + IEnumerable GetRelationProviders(IDsmElement consumer, IDsmElement provider); + IEnumerable GetRelationConsumers(IDsmElement consumer, IDsmElement provider); + IEnumerable FindResolvedRelations(IDsmElement consumer, IDsmElement provider); + IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider); + int GetRelationCount(IDsmElement consumer, IDsmElement provider); + IEnumerable FindIngoingRelations(IDsmElement element); + IEnumerable FindOutgoingRelations(IDsmElement element); + IEnumerable FindInternalRelations(IDsmElement element); + IEnumerable FindExternalRelations(IDsmElement element); + int GetHierarchicalCycleCount(IDsmElement element); + int GetSystemCycleCount(IDsmElement element); + IDsmElement NextSibling(IDsmElement element); + IDsmElement PreviousSibling(IDsmElement element); + bool HasChildren(IDsmElement element); + void Sort(IDsmElement element, string algorithm); + void SortRecursively(IDsmElement element, string algorithm); + IEnumerable GetSupportedSortAlgorithms(); + void MoveUp(IDsmElement element); + void MoveDown(IDsmElement element); + IEnumerable GetElementTypes(); + + + int GetDependencyWeight(IDsmElement consumer, IDsmElement provider); + CycleType IsCyclicDependency(IDsmElement consumer, IDsmElement provider); + IList SearchElements(string searchText, IDsmElement searchInElement, bool caseSensitive, string elementTypeFilter, bool markMatchingElements); + IDsmElement GetElementByFullname(string fullname); + IDsmElement CreateElement(string name, string type, IDsmElement parent, int index); + void DeleteElement(IDsmElement element); + void ChangeElementName(IDsmElement element, string name); + void ChangeElementType(IDsmElement element, string type); + void ChangeElementParent(IDsmElement element, IDsmElement newParent, int index); + void CutElement(IDsmElement element); + void CopyElement(IDsmElement element); + void PasteElement(IDsmElement newParent, int index); + + IDsmRelation CreateRelation(IDsmElement consumer, IDsmElement provider, string type, int weight); + void DeleteRelation(IDsmRelation relation); + void ChangeRelationType(IDsmRelation relation, string type); + void ChangeRelationWeight(IDsmRelation relation, int weight); + IEnumerable GetRelationTypes(); + void MakeSnapshot(string name); + + /// + /// Return the undoable actions. + /// + IEnumerable GetActions(); + void ClearActions(); + /// + /// Return the undoable and redoable actions in a single order. + /// + IEnumerable GetAllActions(); + + int GetElementSize(IDsmElement element); + int GetElementCount(); + void ShowElementDetail(IDsmElement consumer, IDsmElement provider); + void ShowElementContext(IDsmElement provider); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IMultiAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IMultiAction.cs new file mode 100644 index 00000000..202a6fd2 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/IMultiAction.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DsmSuite.DsmViewer.Application.Interfaces +{ + /// + /// An action that can contain a sequence of sub-actions. + /// + public interface IMultiAction : IAction + { + IEnumerable Actions { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/WeightedElement.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/WeightedElement.cs new file mode 100644 index 00000000..8c54fb81 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Interfaces/WeightedElement.cs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Interfaces +{ + public record WeightedElement(IDsmElement Element, int weight); +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Metrics/DsmMetrics.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Metrics/DsmMetrics.cs new file mode 100644 index 00000000..ed37dfa7 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Metrics/DsmMetrics.cs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Metrics +{ + public class DsmMetrics + { + public int GetElementSize(IDsmElement element) + { + int count = 0; + CountChildren(element, ref count); + return count; + } + + private void CountChildren(IDsmElement element, ref int count) + { + count++; + + foreach (IDsmElement child in element.Children) + { + CountChildren(child, ref count); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Queries/DsmQueries.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Queries/DsmQueries.cs new file mode 100644 index 00000000..c769fe43 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Queries/DsmQueries.cs @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Collections.Generic; +using System.Linq; +using DsmSuite.DsmViewer.Application.Interfaces; +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Queries +{ + /// + /// Contains some but not all queries the application executes on the model + /// + /// TODO Can this class be made more useful? + /// DsmApplication does some queries as well and dispatches others here. Can we make this class more useful + /// by forwarding once and dispatching here? + public class DsmQueries + { + + private readonly IDsmModel _model; + public DsmQueries(IDsmModel model) + { + _model = model; + } + + /// + /// Return sub-elements of element that are providers for elements outside + /// of element. The result is ordered by provider name. + /// + public IEnumerable FindElementsProvidedBy(IDsmElement element) + { + var elements = _model.FindIngoingRelations(element) + .OrderBy(x => x.Provider.Fullname) + .GroupBy(x => x.Provider.Fullname) + .Select( g => new WeightedElement(g.First().Provider, g.Sum(x => x.Weight)) ) + .ToList(); + + return elements; + } + + /// + /// Return elements outside of element that are providers for elements within + /// element. The result is ordered by provider name. + /// + public IEnumerable FindProvidersFor(IDsmElement element) + { + var elements = _model.FindOutgoingRelations(element) + .OrderBy(x => x.Provider.Fullname) + .GroupBy(x => x.Provider.Fullname) + .Select( g => new WeightedElement(g.First().Provider, g.Sum(x => x.Weight)) ) + .ToList(); + + return elements; + } + + /// + /// Return elements outside of element that are consumer of elements within + /// element. The result is ordered by consumer name. + /// + public IEnumerable FindConsumersOf(IDsmElement element) + { + var elements = _model.FindIngoingRelations(element) + .OrderBy(x => x.Consumer.Fullname) + .GroupBy(x => x.Consumer.Fullname) + .Select( g => new WeightedElement(g.First().Consumer, g.Sum(x => x.Weight)) ) + .ToList(); + + return elements; + } + + /// + /// Return the consumers in the relations between (sub-elements of) consumer and + /// provider. The result is ordered by consumer name. + /// + public IEnumerable FindRelationConsumers(IDsmElement consumer, IDsmElement provider) + { + var elements = _model.FindRelations(consumer, provider) + .OrderBy(x => x.Consumer.Fullname) + .GroupBy(x => x.Consumer.Fullname) + .Select(g => new WeightedElement(g.First().Consumer, g.Sum(x => x.Weight))) + .ToList(); + + return elements; + } + + /// + /// Return the providers in the relations between (sub-elements of) consumer and + /// provider. The result is ordered by provider name. + /// + public IEnumerable FindRelationProviders(IDsmElement consumer, IDsmElement provider) + { + var elements = _model.FindRelations(consumer, provider) + .OrderBy(x => x.Provider.Fullname) + .GroupBy(x => x.Provider.Fullname) + .Select(g => new WeightedElement(g.First().Provider, g.Sum(x => x.Weight))) + .ToList(); + + return elements; + } + + /// + /// Return the relations between (sub-elements of) consumer and + /// provider. The result is ordered by provider name, consumer name. + /// + public IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider) + { + var relations = _model.FindRelations(consumer, provider) + .OrderBy(x => x.Provider.Fullname) + .ThenBy(x => x.Consumer.Fullname) + .ToList(); + return relations; + } + + /// + /// Return the relations consuming (a sub-elements of) element. + /// + public IEnumerable FindConsumingRelations(IDsmElement element) + { + return _model.FindIngoingRelations(element); + } + + /// + /// Return the relations providing to (a sub-elements of) element. + /// + public IEnumerable FindProvidingRelations(IDsmElement element) + { + return _model.FindOutgoingRelations(element); + } + + /// + /// Return the relations between sub-elements of element. + /// + public IEnumerable FindInternalRelations(IDsmElement element) + { + return _model.FindInternalRelations(element); + } + + /// + /// Return the relations between (a sub-element of) element and an element + /// outside of element. + /// + public IEnumerable FindExternalRelations(IDsmElement element) + { + return _model.FindExternalRelations(element); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/AlphabeticalSortAlgorithm.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/AlphabeticalSortAlgorithm.cs new file mode 100644 index 00000000..26f0b81b --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/AlphabeticalSortAlgorithm.cs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Diagnostics; + +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public class AlphabeticalSortAlgorithm : ISortAlgorithm + { + private readonly IDsmModel _model; + private readonly IDsmElement _element; + + public const string AlgorithmName = "Alphabetical"; + + public AlphabeticalSortAlgorithm(object[] args) + { + Debug.Assert(args.Length == 2); + _model = args[0] as IDsmModel; + Debug.Assert(_model != null); + _element = args[1] as IDsmElement; + Debug.Assert(_element != null); + } + + public SortResult Sort() + { + SortResult vector = new SortResult(_element.Children.Count); + + if (_element.Children.Count > 1) + { + List newOrder = _element.Children.OrderBy(x => x.Name).Select(x => x.Id).ToList(); + + for (int i = 0; i < vector.GetNumberOfElements(); i++) + { + int id = _element.Children[i].Id; + vector.SetIndex(newOrder.IndexOf(id), i); + } + } + + return vector; + } + + public string Name => AlgorithmName; + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/ISortAlgorithm.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/ISortAlgorithm.cs new file mode 100644 index 00000000..b8c7a7c4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/ISortAlgorithm.cs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public interface ISortAlgorithm + { + SortResult Sort(); + string Name { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningCalculation.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningCalculation.cs new file mode 100644 index 00000000..98a42094 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningCalculation.cs @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Application.Sorting +{ + /// + /// This class is responsible for the partitioning calculation which for a given square matrix orders the rows + /// as close as possible to the lower block triangular form. The idea being that as many empty cells are pushed + /// into the upper triangle as possible. It is probable that some cells will be non-zero - in this + /// case they are pushed towards the bottom right corner + /// + /// + /// + /// The algorithm first pushes empty rows to the top and complete rows to the bottom. Partially complete rows + /// are then processed using a distance score to push them below but as close as possible to the diagonal + /// The result of the calculation is contained in the vector class which specifies how the new order of row indexes + /// + class PartitioningCalculation + { + readonly WeightsMatrix _sm; + + /// + /// Constructor of calculation on a given n * n matrix + /// + /// + public PartitioningCalculation(WeightsMatrix matrix) + { + _sm = matrix; + } + + /// + /// Run the partition calculation + /// + /// The result in the form of a vector + public SortResult Partition() + { + SortResult vector = new SortResult(_sm.Size); + + DoPartitioning(vector); + + return vector; + } + + /// + /// the main partitioning algorithm. + /// + /// + void DoPartitioning(SortResult vector) + { + // Move all empty rows to the top - an save the index of the first non empty row (start) + int start = MoveZeroRows(vector); + + // Move all the complete rows to the bottom - save the index of the first full row (end) + int end = MoveFullRows(vector, start); + + // For the remaining rows between start and end move to the right empty columns + end = MoveZeroColumns(vector, start, end); + + // Sort the remaining partially complete rows + ToBlockTriangular(vector, start, end); + } + + int MoveZeroRows(SortResult vector) + { + int nextSwapRow = 0; + + // bubble zero rows to the top - starting from the bottom - to leave any rows already moved + // by the user stay in place + + int i = vector.GetNumberOfElements() - 1; + while (i >= nextSwapRow) + { + var allZero = true; + for (int j = 0; j < vector.GetNumberOfElements() && allZero; j++) + { + if (i != j) // the diagonal must obviously be ignored + { + if (TrueMatrixValue(vector, i, j) != 0) + { + allZero = false; + } + } + } + + if (allZero) // swap indexes + { + vector.Swap(nextSwapRow, i); + nextSwapRow++; // points to next swap position + + // don't decrement i as it has not yet been tested - we've changed the order remember !! + } + else + { + i--; // next row + } + } + + return nextSwapRow; + } + + int MoveFullRows(SortResult vector, int start) + { + int nextSwapRow = vector.GetNumberOfElements() - 1; + + // bubble complete rows to the bottom starting 'start' + int i = start; + + while (i <= nextSwapRow) + { + var allNonZero = true; + for (int j = 0; j < vector.GetNumberOfElements() && allNonZero; j++) + { + if (i != j) + { + if (TrueMatrixValue(vector, i, j) == 0) + { + allNonZero = false; + } + } + } + + if (allNonZero) // swap indexes + { + vector.Swap(nextSwapRow, i); + nextSwapRow--; + } + else + { + i++; + } + } + + return nextSwapRow; + } + + int MoveZeroColumns(SortResult vector, int start, int end) + { + int j = start; + int nextSwap = end; //vector.Size - 1; + + while (j <= nextSwap) + { + var allZeros = true; + for (int i = 0; i < vector.GetNumberOfElements() && allZeros; i++) + { + if (i != j) + { + if (TrueMatrixValue(vector, i, j) != 0) + { + allZeros = false; + } + } + } + + if (allZeros) // swap indexes + { + vector.Swap(nextSwap, j); + nextSwap--; + + // stay on new column in position j + } + else + { + j++; + } + } + + return nextSwap; + } + + /// + /// Get the dependency weight from the original square matrix given the current vector and i,j indexes + /// + /// + /// + /// + /// + int TrueMatrixValue(SortResult vector, int i, int j) + { + return _sm.GetWeight(vector.GetIndex(i), vector.GetIndex(j)); + } + + void ToBlockTriangular(SortResult vector, int start, int end) + { + bool doLoop; + + long currentScore = Score(vector); + + // For holding Permutations already examined during one iteration of the outer while loop + IDictionary permMap = + new Dictionary(vector.GetNumberOfElements() * vector.GetNumberOfElements() / 2); + do + { + doLoop = false; + + for (int i = start; i <= end; i++) // i is index on rows + { + for (int j = end; j > i; j--) // cols in upper triangle + { + if (TrueMatrixValue(vector, i, j) != 0) // not zero so we want to possibly move it + { + // now find first zero from the left hand side + + for (int x = start; x <= end; x++) // here x represents the line index + { + for (int y = start; y <= end; y++) + { + if (x != y) // ignore the diagonal + { + if (TrueMatrixValue(vector, x, y) == 0) + { + Permutation p = new Permutation(j, y); + + if (!permMap.ContainsKey(p)) + { + permMap.Add(p, null); + + //check score of potential new vector + vector.Swap(j, y); + + var newScore = Score(vector); + + if (newScore > currentScore) + { + currentScore = newScore; + doLoop = true; // increasing score so continue + } + else + { + vector.Swap(j, y); //swap back to original + } + } + // else permutation already used + } + } + } + } + } + } + } + + permMap.Clear(); + } + while (doLoop); + } + + long Score(SortResult vector) + { + // We're trying to maximize the number of empty cells in the upper triangle + // filled in cells are pushed down - for a set of two orderings the one with the higher + // score should be the most preferable + + + // calculate score for cells in upper triangle (TODO possible optimization between start and end) + long score = 0; + + for (int i = 0; i < vector.GetNumberOfElements() - 1; i++) + { + for (int j = i + 1; j < vector.GetNumberOfElements(); j++) + { + if (TrueMatrixValue(vector, i, j) == 0) + { + score += CellScore(i, j, vector.GetNumberOfElements()); + } + } + } + + return score; + } + + static long CellScore(int i, int j, int size) + { + // a measure of distance from bottom right + int a = (size - i); + int b = j + 1; + + return (a * a * b * b); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningSortAlgorithm.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningSortAlgorithm.cs new file mode 100644 index 00000000..cf420f74 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/PartitioningSortAlgorithm.cs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Diagnostics; + +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public class PartitionSortAlgorithm : ISortAlgorithm + { + private readonly IDsmModel _model; + private readonly IDsmElement _element; + + public const string AlgorithmName = "Partition"; + + public PartitionSortAlgorithm(object[] args) + { + Debug.Assert(args.Length == 2); + _model = args[0] as IDsmModel; + Debug.Assert(_model != null); + _element = args[1] as IDsmElement; + Debug.Assert(_element != null); + } + + public SortResult Sort() + { + SortResult vector = new SortResult(_element.Children.Count); + if (_element.Children.Count > 1) + { + WeightsMatrix matrix = BuildPartitionMatrix(_element.Children); + + PartitioningCalculation algorithm = new PartitioningCalculation(matrix); + + vector = algorithm.Partition(); + } + + return vector; + } + + public string Name => AlgorithmName; + + private WeightsMatrix BuildPartitionMatrix(IList nodes) + { + WeightsMatrix matrix = new WeightsMatrix(nodes.Count); + + for (int i = 0; i < nodes.Count; i++) + { + IDsmElement provider = nodes[i]; + + for (int j = 0; j < nodes.Count; j++) + { + if (j != i) + { + IDsmElement consumer = nodes[j]; + + int weight = _model.GetDependencyWeight(consumer, provider); + + matrix.SetWeight(i, j, weight > 0 ? 1 : 0); + } + } + } + + return matrix; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/Permutation.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/Permutation.cs new file mode 100644 index 00000000..7bacc881 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/Permutation.cs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace DsmSuite.DsmViewer.Application.Sorting +{ + /// + /// Represents a permutation of two values (order not important). + /// + public class Permutation + { + readonly int _first; + readonly int _second; + + /// + /// Constructor + /// + /// + /// + public Permutation(int value1, int value2) + { + // as the order is not important we sort them to make comparing permutations a little easier + if (value1 < value2) + { + _first = value1; + _second = value2; + } + else + { + _first = value2; + _second = value1; + } + } + + /// + /// Equals override so that permutations can be used as a key on a dictionary + /// + /// + /// + public override bool Equals(object obj) + { + Permutation test = obj as Permutation; + if (test != null) + { + return test._first == _first && + test._second == _second; + } + + return false; + } + + /// + /// GetHashCode override so that permutations can be used as a key in a dictionary + /// + /// + public override int GetHashCode() + { + return _first.GetHashCode() ^ _second.GetHashCode(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortAlgorithmFactory.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortAlgorithmFactory.cs new file mode 100644 index 00000000..026cb75c --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortAlgorithmFactory.cs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public class SortAlgorithmFactory + { + private static readonly Dictionary Algorithms; + + static SortAlgorithmFactory() + { + Algorithms = new Dictionary(); + RegisterAlgorithmTypes(); + } + + public static void RegisterAlgorithm(string name, Type algorithm) + { + Algorithms[name] = algorithm; + } + + public static ISortAlgorithm CreateAlgorithm(IDsmModel model, IDsmElement element, string algorithmName) + { + ISortAlgorithm algorithm = null; + + if (Algorithms.ContainsKey(algorithmName)) + { + Type type = Algorithms[algorithmName]; + object[] args = { model, element }; + object argumentList = args; + algorithm = Activator.CreateInstance(type, argumentList) as ISortAlgorithm; + } + return algorithm; + } + + public static IEnumerable GetSupportedAlgorithms() + { + return Algorithms.Keys; + } + + private static void RegisterAlgorithmTypes() + { + RegisterAlgorithm(PartitionSortAlgorithm.AlgorithmName, typeof(PartitionSortAlgorithm)); + RegisterAlgorithm(AlphabeticalSortAlgorithm.AlgorithmName, typeof(AlphabeticalSortAlgorithm)); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortResult.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortResult.cs new file mode 100644 index 00000000..2ab8e4ef --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/SortResult.cs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public class SortResult : ISortResult + { + /// + /// _list contains the ordering as a permutation of indices. If the sorting + /// puts an element on position i that was on position j, then _list[i] == j; + /// + private readonly List _list = new List(); + + public SortResult(List list) + { + _list = new List(list); + } + + public SortResult(int numberOfElements) + { + _list.Clear(); + + for (int i = 0; i < numberOfElements; i++) + { + _list.Add(i); + } + } + + public int GetNumberOfElements() + { + return _list.Count; + } + + public void InvertOrder() + { + List> order = new List>(); + for (int i = 0; i < _list.Count; i++) + { + order.Add(new KeyValuePair(i, _list[i])); + } + + foreach (var v in order) + { + _list[v.Value] = v.Key; + } + } + + public void Swap(int index1, int index2) + { + CheckIndex(index1); + CheckIndex(index2); + int temp = _list[index1]; + _list[index1] = _list[index2]; + _list[index2] = temp; + } + + public void SetIndex(int index, int value) + { + CheckIndex(index); + _list[index] = value; + } + + public int GetIndex(int index) + { + CheckIndex(index); + return _list[index]; + } + + /// + /// Return the sorting order as a permutation of indices. If the sorting + /// puts an element on position i that was on position j, then order[i] == j; + /// + public List GetOrder() + { + return new List(_list); + } + + + public bool IsValid + { + get + { + HashSet set = new HashSet(_list); + + for (int i = 0; i < _list.Count; i++) + { + if (!set.Contains(i)) + return false; + } + return _list.Count > 0; + } + } + + private void CheckIndex(int index) + { + if ((index < 0) || (index >= _list.Count)) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/WeightsMatrix.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/WeightsMatrix.cs new file mode 100644 index 00000000..b128d88f --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Application/Sorting/WeightsMatrix.cs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Application.Sorting +{ + public class WeightsMatrix : ICloneable + { + readonly int[,] _weights; + readonly int _size; + + public WeightsMatrix(int size) + { + _size = size; + _weights = new int[_size, _size]; + } + + public object Clone() + { + WeightsMatrix sm = new WeightsMatrix(Size); + + for (int i = 0; i < Size; i++) + { + for (int j = 0; j < Size; j++) + { + sm.SetWeight(i, j, GetWeight(i, j)); + } + } + + return sm; + } + + public int Size => _size; + + public void SetWeight(int i, int j, int weight) + { + CheckIndex(i); + CheckIndex(i); + + _weights[i, j] = weight; + } + + public int GetWeight(int i, int j) + { + CheckIndex(i); + CheckIndex(i); + + return _weights[i, j]; + } + + private void CheckIndex(int index) + { + if ((index < 0) || (index >= _size)) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmAction.cs new file mode 100644 index 00000000..68b3ab8a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmAction.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Core +{ + public class DsmAction : IDsmAction + { + private List _actions; + + /// + /// Create a new action. For every sub-action, an equivalent DsmAction is created recursively. + /// + public DsmAction(int id, string type, IReadOnlyDictionary data, + IEnumerable actions = null) + { + Id = id; + Type = type; + Data = data; + _actions = actions == null ? null : new List(actions.Select( + a => a is DsmAction ? a : new DsmAction(a.Id, a.Type, a.Data, a.Actions))); + } + + public int Id { get; } + + public string Type { get; } + + public IReadOnlyDictionary Data { get; } + + public IEnumerable Actions { get { return _actions; } } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmActionModel.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmActionModel.cs new file mode 100644 index 00000000..2ea4d685 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmActionModel.cs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; +using DsmSuite.DsmViewer.Model.Persistency; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Stores an ordered list of actions.
+ /// actionIds are assigned in increasing order, if not provided by the caller. + ///
+ /// DsmModel forwards calls here. + public class DsmActionModel : IDsmActionModelFileCallback + { + private readonly List _actions; + private int _lastActionId; + + public DsmActionModel() + { + _actions = new List(); + _lastActionId = 0; + } + + public void Clear() + { + _actions.Clear(); + _lastActionId = 0; + } + + /// + /// Create a new action with the given properties and append it to the list. + /// + /// The new action. + public IDsmAction ImportAction(int id, string type, IReadOnlyDictionary data, + IEnumerable actions) + { + if (id > _lastActionId) + { + _lastActionId = id; + } + + DsmAction action = new DsmAction(id, type, data, actions); + _actions.Add(action); + return action; + } + + + /// + /// Create a new action with the given properties, assigning it a sequential number + /// as id. + /// + /// The new action. + public IDsmAction AddAction(string type, IReadOnlyDictionary data, + IEnumerable actions) + { + _lastActionId++; + DsmAction action = new DsmAction(_lastActionId, type, data, actions); + _actions.Add(action); + return action; + } + + + public IEnumerable GetExportedActions() + { + return _actions; + } + + public int GetExportedActionCount() + { + return _actions.Count; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmDependencies.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmDependencies.cs new file mode 100644 index 00000000..1507b15f --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmDependencies.cs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Core +{ + public class DsmDependencies + { + private readonly Dictionary _directWeights = new Dictionary(); + private readonly Dictionary _derivedWeights = new Dictionary(); + private readonly Dictionary> _outgoingRelations = new Dictionary>(); + private readonly Dictionary> _ingoingRelations = new Dictionary>(); + private readonly IDsmElement _element; + + public DsmDependencies(IDsmElement element) + { + _element = element; + } + + public void AddIngoingRelation(DsmRelation relation) + { + if (!_ingoingRelations.ContainsKey(relation.Consumer.Id)) + { + _ingoingRelations[relation.Consumer.Id] = new List(); + } + + _ingoingRelations[relation.Consumer.Id].Add(relation); + } + + public void RemoveIngoingRelation(DsmRelation relation) + { + if (_ingoingRelations.ContainsKey(relation.Consumer.Id)) + { + _ingoingRelations[relation.Consumer.Id].Remove(relation); + } + } + + public IEnumerable GetIngoingRelations() + { + List relations = new List(); + foreach (List r in _ingoingRelations.Values) + { + relations.AddRange(r); + } + return relations; + } + + public void AddOutgoingRelation(DsmRelation relation) + { + if (!_outgoingRelations.ContainsKey(relation.Provider.Id)) + { + _outgoingRelations[relation.Provider.Id] = new List(); + } + + _outgoingRelations[relation.Provider.Id].Add(relation); + + AddDirectWeight(relation.Provider, relation.Weight); + } + + public void RemoveOutgoingRelation(DsmRelation relation) + { + if (_outgoingRelations.ContainsKey(relation.Provider.Id)) + { + _outgoingRelations[relation.Provider.Id].Remove(relation); + } + + RemoveDirectWeight(relation.Provider, relation.Weight); + } + + public IEnumerable GetOutgoingRelations() + { + List relations = new List(); + foreach (List r in _outgoingRelations.Values) + { + relations.AddRange(r); + } + return relations; + } + + public IEnumerable GetOutgoingRelations(IDsmElement provider) + { + if (_outgoingRelations.ContainsKey(provider.Id)) + { + return _outgoingRelations[provider.Id]; + } + else + { + return new List(); + } + } + + public void AddDerivedWeight(IDsmElement provider, int weight) + { + int currentWeight; + if (_derivedWeights.TryGetValue(provider.Id, out currentWeight)) + { + _derivedWeights[provider.Id] = currentWeight + weight; + } + else + { + _derivedWeights[provider.Id] = weight; + } + } + + public void RemoveDerivedWeight(IDsmElement provider, int weight) + { + int currentWeight; + if (_derivedWeights.TryGetValue(provider.Id, out currentWeight)) + { + if (currentWeight >= weight) + { + _derivedWeights[provider.Id] = currentWeight - weight; + } + else + { + + } + } + } + + public int GetDerivedDependencyWeight(IDsmElement provider) + { + int weight = 0; + if (_element.Id != provider.Id) + { + _derivedWeights.TryGetValue(provider.Id, out weight); + } + return weight; + } + + public int GetDirectDependencyWeight(IDsmElement provider) + { + int weight = 0; + if (_element.Id != provider.Id) + { + _directWeights.TryGetValue(provider.Id, out weight); + } + return weight; + } + + private void AddDirectWeight(IDsmElement provider, int weight) + { + if (_directWeights.ContainsKey(provider.Id)) + { + _directWeights[provider.Id] += weight; + } + else + { + _directWeights[provider.Id] = weight; + } + } + + private void RemoveDirectWeight(IDsmElement provider, int weight) + { + if (_directWeights.ContainsKey(provider.Id)) + { + _directWeights[provider.Id] -= weight; + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElement.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElement.cs new file mode 100644 index 00000000..056fb03d --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElement.cs @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Represent an element in the dsm hierarchy. + /// + public class DsmElement : IDsmElement + { + private char _typeId; + private readonly List _children = new List(); + private DsmElement _parent; + private static readonly NameRegistration ElementTypeNameRegistration = new NameRegistration(); + private static readonly NameRegistration ElementPropertyNameRegistration = new NameRegistration(); + + public DsmElement(int id, string name, string type, IDictionary properties, int order = 0, bool isExpanded = false) + { + Id = id; + Name = name; + _typeId = ElementTypeNameRegistration.RegisterName(type); + Properties = (properties != null) ? properties : new Dictionary(); + Order = order; + IsExpanded = isExpanded; + IsIncludedInTree = true; + Dependencies = new DsmDependencies(this); + + foreach (string key in Properties.Keys) + { + ElementPropertyNameRegistration.RegisterName(key); + } + } + + public DsmDependencies Dependencies { get; } + + public int Id { get; } + + public int Order { get; set; } + + public string Type + { + get { return ElementTypeNameRegistration.GetRegisteredName(_typeId); } + set { _typeId = ElementTypeNameRegistration.RegisterName(value); } + } + + public string Name { get; set; } + + public IDictionary Properties { get; } + + public IEnumerable DiscoveredElementPropertyNames() + { + return ElementPropertyNameRegistration.GetRegisteredNames(); + } + + public bool IsDeleted { get; set; } + + public bool IsBookmarked { get; set; } + + public bool IsRoot => Parent == null; + + public string Fullname + { + get + { + string fullname = Name; + IDsmElement parent = Parent; + while (parent != null) + { + if (parent.Name.Length > 0) + { + fullname = parent.Name + "." + fullname; + } + parent = parent.Parent; + } + return fullname; + } + } + + public string GetRelativeName(IDsmElement element) + { + string fullname = Name; + IDsmElement parent = Parent; + while ((parent != element) && (parent != null)) + { + if (parent.Name.Length > 0) + { + fullname = parent.Name + "." + fullname; + } + parent = parent.Parent; + } + return fullname; + } + + public bool IsExpanded { get; set; } + + public void ExpandRecursively(bool expanded) + { + if (!HasChildren) + return; + + IsExpanded = expanded; + foreach (DsmElement child in Children) + child.ExpandRecursively(expanded); + } + + public bool IsMatch { get; set; } + + public bool IsIncludedInTree { get; set; } + + public IDsmElement Parent => _parent; + + public bool IsRecursiveChildOf(IDsmElement element) + { + bool isRecursiveChildOf = false; + + IDsmElement parent = Parent; + while ((parent != null) && !isRecursiveChildOf) + { + if (parent == element) + { + isRecursiveChildOf = true; + } + + parent = parent.Parent; + } + return isRecursiveChildOf; + } + + public IList Children => _children.Where(child => ((child.IsDeleted == false) && child.IsIncludedInTree)).ToList(); + + public IList AllChildren => _children; + + public int IndexOfChild(IDsmElement child) + { + return _children.IndexOf(child); + } + + public bool ContainsChildWithName(string name) + { + bool containsChildWithName = false; + foreach (IDsmElement child in Children) + { + if (child.Name == name) + { + containsChildWithName = true; + } + } + + return containsChildWithName; + } + + public bool HasChildren => Children.Count > 0; + + public void InsertChildAtEnd(IDsmElement child) + { + _children.Add(child); + DsmElement c = child as DsmElement; + if (c != null) + { + c._parent = this; + } + } + + public void InsertChildAtIndex(IDsmElement child, int index) + { + int rangeLimitedIndex = index; + rangeLimitedIndex = Math.Min(rangeLimitedIndex, _children.Count); + rangeLimitedIndex = Math.Max(rangeLimitedIndex, 0); + _children.Insert(rangeLimitedIndex, child); + DsmElement c = child as DsmElement; + if (c != null) + { + c._parent = this; + } + } + + public void RemoveChild(IDsmElement child) + { + _children.Remove(child); + DsmElement c = child as DsmElement; + if (c != null) + { + c._parent = null; + } + } + + public void RemoveAllChildren() + { + _children.Clear(); + } + + public IDictionary GetElementAndItsChildren() + { + Dictionary elements = new Dictionary(); + GetElementAndItsChildren(this, elements); + return elements; + } + + private void GetElementAndItsChildren(IDsmElement element, Dictionary elements) + { + if (!element.IsDeleted) + { + elements[element.Id] = element as DsmElement; + } + + foreach (IDsmElement child in element.Children) + { + GetElementAndItsChildren(child, elements); + } + } + + public bool Swap(IDsmElement element1, IDsmElement element2) + { + bool swapped = false; + + if (_children.Contains(element1) && _children.Contains(element2)) + { + int index1 = _children.IndexOf(element1); + int index2 = _children.IndexOf(element2); + + _children[index2] = element1; + _children[index1] = element2; + + swapped = true; + } + + return swapped; + } + + public int CompareTo(object obj) + { + DsmElement element = obj as DsmElement; + return Id.CompareTo(element?.Id); + } + + public static IEnumerable GetTypeNames() + { + return ElementTypeNameRegistration.GetRegisteredNames(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElementModel.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElementModel.cs new file mode 100644 index 00000000..af676179 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmElementModel.cs @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Model.Interfaces; +using DsmSuite.DsmViewer.Model.Persistency; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Manages the elements of a model. Only used by DsmModel, which forwards calls here. + /// + public class DsmElementModel : IDsmElementModelFileCallback + { + private readonly Dictionary _elementsById; + private readonly Dictionary _elementsByName; + private readonly Dictionary _deletedElementsById; + private int _lastElementId; + private readonly DsmElement _root; + private readonly DsmRelationModel _relationModel; + + public DsmElementModel(DsmRelationModel relationModel) + { + _relationModel = relationModel; + _elementsById = new Dictionary(); + _elementsByName = new Dictionary(); + _deletedElementsById = new Dictionary(); + _root = new DsmElement(0, "", "", null); + Clear(); + } + + public void Clear() + { + _elementsById.Clear(); + _deletedElementsById.Clear(); + // Changed 2026-07 for CSharpCodeAnalyst: _elementsByName was not cleared here. AddElement + // resolves through FindElementByFullname, so after a Clear it returned stale elements from + // the previous population, which are no longer registered by id nor attached to the root. + // Upstream never hit this because every import runs against a freshly constructed model. + _elementsByName.Clear(); + _root.RemoveAllChildren(); + _lastElementId = 0; + RegisterElement(_root); + } + + public void ClearHistory() + { + _deletedElementsById.Clear(); + } + + public IDsmElement ImportElement(int id, string name, string type, IDictionary properties, int order, bool expanded, int? parentId, bool deleted) + { + Logger.LogDataModelMessage($"Import element id={id} name={name} type={type} order={order} expanded={expanded} parentId={parentId}"); + + if (id > _lastElementId) + { + _lastElementId = id; + } + return AddElement(id, name, type, null, properties, order, expanded, parentId, deleted); + } + + public IDsmElement AddElement(string name, string type, int? parentId, int? index, IDictionary properties) + { + Logger.LogDataModelMessage($"Add element name={name} type={type} parentId={parentId}"); + + string fullname = name; + if (parentId.HasValue) + { + if (_elementsById.ContainsKey(parentId.Value)) + { + ElementName elementName = new ElementName(_elementsById[parentId.Value].Fullname); + elementName.AddNamePart(name); + fullname = elementName.FullName; + } + } + + IDsmElement element = FindElementByFullname(fullname); + if (element == null) + { + _lastElementId++; + element = AddElement(_lastElementId, name, type, index, properties, 0, false, parentId, false); + } + + return element; + } + + public void ChangeElementName(IDsmElement element, string name) + { + DsmElement changedElement = element as DsmElement; + if (changedElement != null) + { + UnregisterElementNameHierarchy(changedElement); + changedElement.Name = name; + RegisterElementNameHierarchy(changedElement); + } + } + + public void ChangeElementType(IDsmElement element, string type) + { + DsmElement changedElement = element as DsmElement; + if (changedElement != null) + { + changedElement.Type = type; + } + } + + public bool IsChangeElementParentAllowed(IDsmElement element, IDsmElement parent) + { + DsmElement changedElement = element as DsmElement; + DsmElement currentParent = element.Parent as DsmElement; + DsmElement newParent = parent as DsmElement; + return ((currentParent != null) && + (newParent != null) && + (currentParent != newParent) && // Do not allow new parent same as current parent + !newParent.IsRecursiveChildOf(changedElement)); // Do not allow new parent being a child of the changed element + } + + public void ChangeElementParent(IDsmElement element, IDsmElement parent, int index) + { + Logger.LogDataModelMessage($"Change element parent name={element.Name} from {element.Parent.Fullname} to {parent.Fullname}"); + + if (IsChangeElementParentAllowed(element, parent)) + { + DsmElement changedElement = element as DsmElement; + DsmElement currentParent = element.Parent as DsmElement; + DsmElement newParent = parent as DsmElement; + + if ((changedElement != null) && (currentParent != null) & (newParent != null)) + { + IEnumerable externalRelations = _relationModel.FindExternalRelations(element).ToList(); + + foreach (IDsmRelation relation in externalRelations) + { + _relationModel.RemoveWeights(relation); + } + + UnregisterElementNameHierarchy(changedElement); + currentParent.RemoveChild(element); + CollapseIfNoChildrenLeft(currentParent); + + newParent.InsertChildAtIndex(element, index); + RegisterElementNameHierarchy(changedElement); + + foreach (IDsmRelation relation in externalRelations) + { + _relationModel.AddWeights(relation); + } + } + } + } + + public void RemoveElement(int elementId) + { + Logger.LogDataModelMessage($"Remove element id={elementId}"); + + if (_elementsById.ContainsKey(elementId)) + { + DsmElement element = _elementsById[elementId]; + UnregisterElement(element); + + CollapseIfNoChildrenLeft(element.Parent); + } + } + + public void UnremoveElement(int elementId) + { + Logger.LogDataModelMessage($"Restore element id={elementId}"); + + if (_deletedElementsById.ContainsKey(elementId)) + { + DsmElement element = _deletedElementsById[elementId]; + ReregisterElement(element); + } + } + + public IEnumerable GetElementTypes() + { + return DsmElement.GetTypeNames(); + } + + public IEnumerable GetElements() + { + return _elementsById.Values; + } + + public int GetElementCount() + { + return _elementsById.Count; + } + + public IDsmElement RootElement + { + get + { + return _root; + } + } + + public int GetExportedElementCount() + { + return _elementsById.Count + _deletedElementsById.Count - 1; // Root not written/read + } + + public void AssignElementOrder() + { + Logger.LogDataModelMessage("AssignElementOrder"); + + int order = 1; + foreach (IDsmElement root in _root.Children) + { + DsmElement rootElement = root as DsmElement; + if (rootElement != null) + { + AssignElementOrder(rootElement, ref order); + } + } + } + + public IDsmElement FindElementById(int elementId) + { + return _elementsById.ContainsKey(elementId) ? _elementsById[elementId] : null; + } + + public IDsmElement FindElementByFullname(string fullname) + { + return _elementsByName.ContainsKey(fullname) ? _elementsByName[fullname] : null; + } + + public IList SearchElements(string searchText, IDsmElement searchInElement, bool caseSensitive, string elementTypeFilter, bool markMatchingElements) + { + List matchingElements = new List(); + if (searchText != null) + { + string fullname = ""; + string text = caseSensitive ? searchText : searchText.ToLower(); + + if (text.Length > 0) + { + RecursiveSearchElements(searchInElement, text, caseSensitive, elementTypeFilter, markMatchingElements, fullname, matchingElements); + } + else + { + ClearMarkElements(_root); + } + } + return matchingElements; + } + + private bool RecursiveSearchElements(IDsmElement element, string searchText, bool caseSensitive, string elementTypeFilter, bool markMatchingElements, string fullname, IList matchingElements) + { + bool isMatch = false; + + if (fullname.Length > 0) + { + fullname += "."; + } + if (caseSensitive) + { + fullname += element.Name; + } + else + { + fullname += element.Name.ToLower(); + } + + if (fullname.Contains(searchText) && IsElementFilterMatch(element, elementTypeFilter) && !element.IsDeleted) + { + // Add to list and mark element as search match + isMatch = true; + matchingElements.Add(element); + } + + foreach (IDsmElement child in element.Children) + { + if (RecursiveSearchElements(child, searchText, caseSensitive, elementTypeFilter, markMatchingElements, fullname, matchingElements)) + { + // Add parent element as match when it contains a matching child + isMatch = true; + } + } + + if (markMatchingElements) + { + element.IsMatch = isMatch; + } + + return isMatch; + } + + private bool IsElementFilterMatch(IDsmElement element, string elementTypeFilter) + { + return string.IsNullOrEmpty(elementTypeFilter) || (elementTypeFilter == element.Type); + } + + private void ClearMarkElements(IDsmElement element) + { + element.IsMatch = false; + + foreach (IDsmElement child in element.Children) + { + ClearMarkElements(child); + } + } + public IDsmElement GetDeletedElementById(int id) + { + return _deletedElementsById.ContainsKey(id) ? _deletedElementsById[id] : null; + } + + public void ReorderChildren(IDsmElement element, ISortResult sortResult) + { + DsmElement parent = element as DsmElement; + if (parent != null) + { + List clonedChildren = new List(parent.Children); + + foreach (IDsmElement child in clonedChildren) + { + parent.RemoveChild(child); + } + + for (int i = 0; i < sortResult.GetNumberOfElements(); i++) + { + parent.InsertChildAtEnd(clonedChildren[sortResult.GetIndex(i)]); + } + } + } + + public bool Swap(IDsmElement element1, IDsmElement element2) + { + bool swapped = false; + + if (element1?.Parent == element2?.Parent) + { + DsmElement parent = element1?.Parent as DsmElement; + if (parent != null) + { + swapped = parent.Swap(element1, element2); + } + } + + return swapped; + } + + public IDsmElement NextSibling(IDsmElement element) + { + IDsmElement nextSibling = null; + DsmElement parent = element?.Parent as DsmElement; + if (parent != null) + { + int index = parent.Children.IndexOf(element); + + if (index < parent.Children.Count - 1) + { + nextSibling = parent.Children[index + 1]; + } + } + return nextSibling; + } + + public IDsmElement PreviousSibling(IDsmElement element) + { + IDsmElement previousSibling = null; + DsmElement parent = element?.Parent as DsmElement; + if (parent != null) + { + int index = parent.Children.IndexOf(element); + + if (index > 0) + { + previousSibling = parent.Children[index - 1]; + } + } + return previousSibling; + } + + public void UpdateChildrenIncludeInTree(IDsmElement element, bool included) + { + element.IsIncludedInTree = included; + + foreach (IDsmElement child in element.AllChildren) + { + UpdateChildrenIncludeInTree(child, included); + } + } + + public void UpdateParentsIncludeInTree(IDsmElement element, bool included) + { + IDsmElement current = element; + do + { + current.IsIncludedInTree = included; + current = current.Parent; + } while (current != null); + } + + + public IDsmElement AddElement(int id, string name, string type, IDictionary properties, int order) + { + DsmElement element = new DsmElement(id, name, type, properties) { Order = order, IsExpanded = false, IsDeleted = false }; + RegisterElement(element); + return element; + } + + private IDsmElement AddElement(int id, string name, string type, int? index, IDictionary properties, int order, bool expanded, int? parentId, bool deleted) + { + DsmElement element = new DsmElement(id, name, type, properties) { Order = order, IsExpanded = expanded, IsDeleted = deleted }; + + if (parentId.HasValue) + { + DsmElement parent = null; + if (_elementsById.ContainsKey(parentId.Value)) + { + parent = _elementsById[parentId.Value]; + } + + if (_deletedElementsById.ContainsKey(parentId.Value)) + { + parent = _deletedElementsById[parentId.Value]; + } + + if (parent != null) + { + if (index.HasValue) + { + parent.InsertChildAtIndex(element, index.Value); + } + else + { + parent.InsertChildAtEnd(element); + } + } + else + { + Logger.LogError($"Parent not found id={id}"); + } + } + else + { + _root.InsertChildAtEnd(element); + _root.IsExpanded = true; + } + + if (deleted) + { + UnregisterElement(element); + } + else + { + RegisterElement(element); + } + + return element; + } + + private void AssignElementOrder(DsmElement element, ref int order) + { + element.Order = order; + order++; + + foreach (IDsmElement child in element.Children) + { + DsmElement childElement = child as DsmElement; + if (childElement != null) + { + AssignElementOrder(childElement, ref order); + } + } + } + + private void CollapseIfNoChildrenLeft(IDsmElement element) + { + DsmElement e = element as DsmElement; + if (e != null) + { + if (element.Children.Count == 0) + { + e.IsExpanded = false; + } + } + } + + private void RegisterElement(DsmElement element) + { + _elementsById[element.Id] = element; + _elementsByName[element.Fullname] = element; + } + + private void UnregisterElement(DsmElement element) + { + _relationModel.UnregisterElementRelations(element); + + element.IsDeleted = true; + _deletedElementsById[element.Id] = element; + _elementsById.Remove(element.Id); + _elementsByName.Remove(element.Fullname); + + foreach (IDsmElement child in element.AllChildren) + { + UnregisterElement(child as DsmElement); + } + } + + private void ReregisterElement(DsmElement element) + { + foreach (IDsmElement child in element.AllChildren) + { + ReregisterElement(child as DsmElement); + } + + _elementsById[element.Id] = element; + _deletedElementsById.Remove(element.Id); + element.IsDeleted = false; + + _relationModel.ReregisterElementRelations(element); + } + + private void UnregisterElementNameHierarchy(DsmElement element) + { + _elementsByName.Remove(element.Fullname); + + foreach (DsmElement child in element.AllChildren) + { + UnregisterElementNameHierarchy(child); + } + } + + private void RegisterElementNameHierarchy(DsmElement element) + { + _elementsByName[element.Fullname] = element; + + foreach (DsmElement child in element.AllChildren) + { + RegisterElementNameHierarchy(child); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmModel.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmModel.cs new file mode 100644 index 00000000..8197edfc --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmModel.cs @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Model.Core; +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Model.Interfaces; +using DsmSuite.DsmViewer.Model.Persistency; +using System.Reflection; + +namespace DsmSuite.DsmViewer.Model.Core +{ + public class DsmModel : IDsmModel + { + private readonly MetaDataModel _metaDataModel; + private readonly DsmElementModel _elementsDataModel; + private readonly DsmRelationModel _relationsDataModel; + private readonly DsmActionModel _actionsDataModel; + + public DsmModel(string processStep, Assembly executingAssembly) + { + _metaDataModel = new MetaDataModel(processStep, executingAssembly); + + _relationsDataModel = new DsmRelationModel(); + _elementsDataModel = new DsmElementModel(_relationsDataModel); + _actionsDataModel = new DsmActionModel(); + } + + public void LoadModel(string dsmFilename, IProgress progress) + { + Logger.LogDataModelMessage($"Load data model file={dsmFilename}"); + + Clear(); + DsmModelFile dsmModelFile = new DsmModelFile(dsmFilename, _metaDataModel, _elementsDataModel, _relationsDataModel, _actionsDataModel); + dsmModelFile.Load(progress); + IsCompressed = dsmModelFile.IsCompressedFile(); + ModelFilename = dsmFilename; + } + + public void SaveModel(string dsmFilename, bool compressFile, IProgress progress) + { + Logger.LogDataModelMessage($"Save data model file={dsmFilename} compress={compressFile}"); + + _metaDataModel.AddMetaDataItemToDefaultGroup("Total elements found", $"{GetExportedElementCount()}"); + + DsmModelFile dsmModelFile = new DsmModelFile(dsmFilename, _metaDataModel, _elementsDataModel, _relationsDataModel, _actionsDataModel); + dsmModelFile.Save(compressFile, progress); + ModelFilename = dsmFilename; + } + + public string ModelFilename { get; private set; } + + public bool IsCompressed { get; private set; } + + public void Clear() + { + _metaDataModel.Clear(); + _elementsDataModel.Clear(); + _relationsDataModel.Clear(); + _actionsDataModel.Clear(); + } + + public IMetaDataItem AddMetaData(string name, string value) + { + return _metaDataModel.AddMetaDataItemToDefaultGroup(name, value); + } + + public IMetaDataItem AddMetaData(string group, string name, string value) + { + return _metaDataModel.AddMetaDataItem(group, name, value); + } + + public IEnumerable GetMetaDataGroups() + { + return _metaDataModel.GetExportedMetaDataGroups(); + } + + public IEnumerable GetMetaDataGroupItems(string groupName) + { + return _metaDataModel.GetExportedMetaDataGroupItems(groupName); + } + + public IDsmAction AddAction(string type, IReadOnlyDictionary data, + IEnumerable actions) + { + return _actionsDataModel.AddAction(type, data, actions); + } + + public void ClearActions() + { + _actionsDataModel.Clear(); + } + + public IEnumerable GetActions() + { + return _actionsDataModel.GetExportedActions(); + } + + public IDsmElement AddElement(int id, string name, string type, IDictionary properties, int order) + { + return _elementsDataModel.AddElement(id, name, type, properties, order); + } + + public IDsmElement AddElement(string name, string type, int? parentId, int? index, IDictionary properties) + { + return _elementsDataModel.AddElement(name, type, parentId, index, properties); + } + + public void ChangeElementName(IDsmElement element, string name) + { + _elementsDataModel.ChangeElementName(element, name); + } + + public void ChangeElementType(IDsmElement element, string type) + { + _elementsDataModel.ChangeElementType(element, type); + } + + public bool IsChangeElementParentAllowed(IDsmElement element, IDsmElement parent) + { + return _elementsDataModel.IsChangeElementParentAllowed(element, parent); + } + + public void ChangeElementParent(IDsmElement element, IDsmElement parent, int index) + { + _elementsDataModel.ChangeElementParent(element, parent, index); + } + + public void RemoveElement(int elementId) + { + _elementsDataModel.RemoveElement(elementId); + } + + public void UnremoveElement(int elementId) + { + _elementsDataModel.UnremoveElement(elementId); + } + + public void IncludeInTree(IDsmElement element, bool included) + { + UpdateChildrenIncludeInTree(element, included); + UpdateParentsIncludeInTree(element, included); + } + + private void UpdateChildrenIncludeInTree(IDsmElement element, bool included) + { + _elementsDataModel.UpdateChildrenIncludeInTree(element, included); + } + + private void UpdateParentsIncludeInTree(IDsmElement element, bool included) + { + _elementsDataModel.UpdateParentsIncludeInTree(element, included); + } + + public IEnumerable GetElementTypes() + { + return _elementsDataModel.GetElementTypes(); + } + + public IEnumerable GetElements() + { + return _elementsDataModel.GetElements(); + } + + public int GetElementCount() + { + return _elementsDataModel.GetElementCount(); + } + + public IDsmElement RootElement + { + get + { + return _elementsDataModel.RootElement; + } + } + + public int GetExportedElementCount() + { + return _elementsDataModel.GetExportedElementCount(); + } + + public void AssignElementOrder() + { + _elementsDataModel.AssignElementOrder(); + } + + public IDsmElement GetElementById(int id) + { + return _elementsDataModel.FindElementById(id); + } + + public IDsmElement GetElementByFullname(string fullname) + { + return _elementsDataModel.FindElementByFullname(fullname); + } + + public IList SearchElements(string searchText, IDsmElement searchInElement, bool caseSensitive, string elementTypeFilter, bool markMatchingElements) + { + return _elementsDataModel.SearchElements(searchText, searchInElement, caseSensitive, elementTypeFilter, markMatchingElements); + } + + public IDsmElement GetDeletedElementById(int id) + { + return _elementsDataModel.GetDeletedElementById(id); + } + + public IDsmRelation AddRelation(IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties) + { + return _relationsDataModel.AddRelation(consumer, provider, type, weight, properties); + } + + public IDsmRelation AddRelation(int id, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties) + { + return _relationsDataModel.AddRelation(id, consumer, provider, type, weight, properties); + } + + + public void ChangeRelationType(IDsmRelation relation, string type) + { + _relationsDataModel.ChangeRelationType(relation, type); + } + + public void ChangeRelationWeight(IDsmRelation relation, int weight) + { + _relationsDataModel.ChangeRelationWeight(relation, weight); + } + + public void RemoveRelation(int relationId) + { + _relationsDataModel.RemoveRelation(relationId); + } + + public void UnremoveRelation(int relationId) + { + _relationsDataModel.UnremoveRelation(relationId); + } + + public IEnumerable GetRelationTypes() + { + return _relationsDataModel.GetRelationTypes(); + } + + public int GetDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + return _relationsDataModel.GetDependencyWeight(consumer, provider); + } + + public int GetDirectDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + return _relationsDataModel.GetDirectDependencyWeight(consumer, provider); + } + + public CycleType IsCyclicDependency(IDsmElement consumer, IDsmElement provider) + { + return _relationsDataModel.IsCyclicDependency(consumer, provider); + } + + public IDsmRelation GetRelationById(int relationId) + { + return _relationsDataModel.GetRelationById(relationId); + } + + public IDsmRelation GetDeletedRelationById(int relationId) + { + return _relationsDataModel.GetDeletedRelationById(relationId); + } + + public IEnumerable GetRelations() + { + return _relationsDataModel.GetRelations(); + } + + public int GetRelationCount() + { + return _relationsDataModel.GetRelationCount(); + } + + public IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider) + { + return _relationsDataModel.FindRelations(consumer, provider); + } + + public int GetRelationCount(IDsmElement consumer, IDsmElement provider) + { + return _relationsDataModel.GetRelationCount(consumer, provider); + } + + public IDsmRelation FindRelation(IDsmElement consumer, IDsmElement provider, string type) + { + return _relationsDataModel.FindRelation(consumer, provider, type); + } + + public IEnumerable FindIngoingRelations(IDsmElement element) + { + return _relationsDataModel.FindIngoingRelations(element); + } + + public IEnumerable FindOutgoingRelations(IDsmElement element) + { + return _relationsDataModel.FindOutgoingRelations(element); + } + + public IEnumerable FindInternalRelations(IDsmElement element) + { + return _relationsDataModel.FindInternalRelations(element); + } + + public IEnumerable FindExternalRelations(IDsmElement element) + { + return _relationsDataModel.FindExternalRelations(element); + } + + public int GetHierarchicalCycleCount(IDsmElement element) + { + return _relationsDataModel.GetHierarchicalCycleCount(element); + } + + public int GetSystemCycleCount(IDsmElement element) + { + return _relationsDataModel.GetSystemCycleCount(element); + } + + public void ReorderChildren(IDsmElement element, ISortResult sortResult) + { + _elementsDataModel.ReorderChildren(element, sortResult); + } + + public bool Swap(IDsmElement element1, IDsmElement element2) + { + return _elementsDataModel.Swap(element1, element2); + } + + public IDsmElement NextSibling(IDsmElement element) + { + return _elementsDataModel.NextSibling(element); + } + + public IDsmElement PreviousSibling(IDsmElement element) + { + return _elementsDataModel.PreviousSibling(element); + } + + public int GetActionCount() + { + return _actionsDataModel.GetExportedActionCount(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelation.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelation.cs new file mode 100644 index 00000000..b4574c59 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelation.cs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Relation between two elements. + /// + public class DsmRelation : IDsmRelation + { + private char _typeId; + private static readonly NameRegistration RelationTypeNameRegistration = new NameRegistration(); + private static readonly NameRegistration RelationPropertyNameRegistration = new NameRegistration(); + + public DsmRelation(int id, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties) + { + Id = id; + Consumer = consumer; + Provider = provider; + _typeId = RelationTypeNameRegistration.RegisterName(type); + Weight = weight; + Properties = (properties != null) ? properties : new Dictionary(); + + foreach (string key in Properties.Keys) + { + RelationPropertyNameRegistration.RegisterName(key); + } + } + + public int Id { get; } + + public IDsmElement Consumer { get; } + + public IDsmElement Provider { get; } + + public string Type + { + get { return RelationTypeNameRegistration.GetRegisteredName(_typeId); } + set { _typeId = RelationTypeNameRegistration.RegisterName(value); } + } + + public int Weight { get; set; } + + public IDictionary Properties { get; } + + public IEnumerable DiscoveredRelationPropertyNames() + { + return RelationPropertyNameRegistration.GetRegisteredNames(); + } + + public bool IsDeleted { get; set; } + + public static IEnumerable GetTypeNames() + { + return RelationTypeNameRegistration.GetRegisteredNames(); + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelationModel.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelationModel.cs new file mode 100644 index 00000000..60710aef --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/DsmRelationModel.cs @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Model.Interfaces; +using DsmSuite.DsmViewer.Model.Persistency; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Manages the relations of a model. Only used by DsmModel, which forwards calls here. + /// + public class DsmRelationModel : IDsmRelationModelFileCallback + { + private readonly Dictionary _relationsById; + private readonly Dictionary _deletedRelationsById; + + private int _lastRelationId; + + public DsmRelationModel() + { + _relationsById = new Dictionary(); + _deletedRelationsById = new Dictionary(); + _lastRelationId = 0; + } + + public void Clear() + { + _relationsById.Clear(); + _deletedRelationsById.Clear(); + + _lastRelationId = 0; + } + + public void ClearHistory() + { + _deletedRelationsById.Clear(); + } + + public IDsmRelation ImportRelation(int relationId, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties, bool deleted) + { + DsmRelation relation = null; + + if ((consumer != null) && (provider != null)) + { + Logger.LogDataModelMessage( + $"Import relation relationId={relationId} consumerId={consumer.Id} providerId={provider.Id} type={type} weight={weight}"); + + if (relationId > _lastRelationId) + { + _lastRelationId = relationId; + } + + + if (consumer.Id != provider.Id) + { + relation = new DsmRelation(relationId, consumer, provider, type, weight, properties) { IsDeleted = deleted }; + if (deleted) + { + UnregisterRelation(relation); + } + else + { + RegisterRelation(relation); + } + } + } + + return relation; + } + + public IDsmRelation AddRelation(IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties) + { + DsmRelation relation = null; + + if ((consumer != null) && (provider != null)) + { + Logger.LogDataModelMessage( + $"Add relation consumerId={consumer.Id} providerId={provider.Id} type={type} weight={weight}"); + + if (consumer.Id != provider.Id) + { + _lastRelationId++; + relation = new DsmRelation(_lastRelationId, consumer, provider, type, weight, properties) { IsDeleted = false }; + RegisterRelation(relation); + } + } + + return relation; + } + + public IDsmRelation AddRelation(int id, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties) + { + DsmRelation relation = null; + + if ((consumer != null) && (provider != null)) + { + Logger.LogDataModelMessage( + $"Add relation consumerId={consumer.Id} providerId={provider.Id} type={type} weight={weight}"); + + if (consumer.Id != provider.Id) + { + relation = new DsmRelation(id, consumer, provider, type, weight, properties) { IsDeleted = false }; + RegisterRelation(relation); + } + } + + return relation; + } + + public void ChangeRelationType(IDsmRelation relation, string type) + { + DsmRelation changedRelation = relation as DsmRelation; + if (changedRelation != null) + { + UnregisterRelation(changedRelation); + + changedRelation.Type = type; + + RegisterRelation(changedRelation); + } + } + + public IEnumerable GetRelationTypes() + { + return DsmRelation.GetTypeNames(); + } + + public void ChangeRelationWeight(IDsmRelation relation, int weight) + { + DsmRelation changedRelation = relation as DsmRelation; + if (changedRelation != null) + { + UnregisterRelation(changedRelation); + + changedRelation.Weight = weight; + + RegisterRelation(changedRelation); + } + } + + public void RemoveRelation(int relationId) + { + if (_relationsById.ContainsKey(relationId)) + { + DsmRelation relation = _relationsById[relationId]; + if (relation != null) + { + UnregisterRelation(relation); + } + } + } + + public void UnremoveRelation(int relationId) + { + if (_deletedRelationsById.ContainsKey(relationId)) + { + DsmRelation relation = _deletedRelationsById[relationId]; + if (relation != null) + { + RegisterRelation(relation); + } + } + } + + public int GetDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + int weight = 0; + DsmElement consumerElement = consumer as DsmElement; + if (consumerElement != null) + { + weight = consumerElement.Dependencies.GetDerivedDependencyWeight(provider); + } + return weight; + } + + public int GetDirectDependencyWeight(IDsmElement consumer, IDsmElement provider) + { + int weight = 0; + DsmElement consumerElement = consumer as DsmElement; + if (consumerElement != null) + { + weight = consumerElement.Dependencies.GetDirectDependencyWeight(provider); + } + return weight; + } + + public CycleType IsCyclicDependency(IDsmElement consumer, IDsmElement provider) + { + if ((GetDirectDependencyWeight(consumer, provider) > 0) && + (GetDirectDependencyWeight(provider, consumer) > 0)) + { + return CycleType.System; + } + else if ((GetDependencyWeight(consumer, provider) > 0) && + (GetDependencyWeight(provider, consumer) > 0)) + { + return CycleType.Hierarchical; + } + else + { + return CycleType.None; + } + } + + public DsmRelation GetRelationById(int id) + { + return _relationsById.ContainsKey(id) ? _relationsById[id] : null; + } + + public DsmRelation GetDeletedRelationById(int id) + { + return _deletedRelationsById.ContainsKey(id) ? _deletedRelationsById[id] : null; + } + + public IDsmRelation FindRelation(IDsmElement consumer, IDsmElement provider, string type) + { + DsmRelation foundRelation = null; + foreach (DsmRelation relation in FindRelations(consumer, provider)) + { + if (relation.Type == type) + { + foundRelation = relation; + } + } + return foundRelation; + } + + public IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider) + { + IList relations = new List(); + + DsmElement consumerDsmElement = consumer as DsmElement; + DsmElement providerDsmElement = provider as DsmElement; + if (consumerDsmElement?.Dependencies != null && providerDsmElement?.Dependencies != null) + { + IDictionary ps = providerDsmElement.GetElementAndItsChildren(); + IDictionary cs = consumerDsmElement.GetElementAndItsChildren(); + + foreach (DsmElement c in cs.Values) + { + foreach (DsmElement p in ps.Values) + { + foreach (DsmRelation relation in c.Dependencies.GetOutgoingRelations(p)) + { + if (!relation.IsDeleted) + { + relations.Add(relation); + } + } + } + } + } + return relations; + } + + public int GetRelationCount(IDsmElement consumer, IDsmElement provider) + { + int count = 0; + + DsmElement consumerDsmElement = consumer as DsmElement; + DsmElement providerDsmElement = provider as DsmElement; + if (consumerDsmElement?.Dependencies != null && providerDsmElement?.Dependencies != null) + { + IDictionary ps = providerDsmElement.GetElementAndItsChildren(); + IDictionary cs = consumerDsmElement.GetElementAndItsChildren(); + + foreach (DsmElement c in cs.Values) + { + foreach (DsmElement p in ps.Values) + { + foreach (DsmRelation relation in c.Dependencies.GetOutgoingRelations(p)) + { + if (!relation.IsDeleted) + { + count++; + } + } + } + } + } + return count; + } + + public IEnumerable FindIngoingRelations(IDsmElement element) + { + return FindRelations(element, RelationDirection.Ingoing, RelationScope.External); + } + + public IEnumerable FindOutgoingRelations(IDsmElement element) + { + return FindRelations(element, RelationDirection.Outgoing, RelationScope.External); + } + + public IEnumerable FindInternalRelations(IDsmElement element) + { + return FindRelations(element, RelationDirection.Outgoing, RelationScope.Internal); + } + + public IEnumerable FindExternalRelations(IDsmElement element) + { + return FindRelations(element, RelationDirection.Both, RelationScope.External); + } + + private IEnumerable FindRelations(IDsmElement element, RelationDirection direction, RelationScope scope) + { + List relations = new List(); + + DsmElement dsmElement = element as DsmElement; + if (dsmElement?.Dependencies != null) + { + IDictionary elements = dsmElement.GetElementAndItsChildren(); + + foreach (DsmElement e in elements.Values) + { + if (direction != RelationDirection.Ingoing) + { + foreach (DsmRelation relation in e.Dependencies.GetOutgoingRelations()) + { + if (HasSelectedScope(scope, relation.Provider, elements) && !relation.IsDeleted) + { + relations.Add(relation); + } + } + } + + if (direction != RelationDirection.Outgoing) + { + foreach (DsmRelation relation in e.Dependencies.GetIngoingRelations()) + { + if (HasSelectedScope(scope, relation.Consumer, elements) && !relation.IsDeleted) + { + relations.Add(relation); + } + } + } + } + } + return relations; + } + + private bool HasSelectedScope(RelationScope scope, IDsmElement element, IDictionary elements) + { + switch (scope) + { + case RelationScope.Internal: + return elements.ContainsKey(element.Id); + case RelationScope.External: + return !elements.ContainsKey(element.Id); + case RelationScope.Both: + return true; + default: + return true; + } + } + + public int GetHierarchicalCycleCount(IDsmElement element) + { + int cyclesCount = 0; + CountCycles(element, CycleType.Hierarchical, ref cyclesCount); + return cyclesCount / 2; + } + + public int GetSystemCycleCount(IDsmElement element) + { + int cyclesCount = 0; + CountCycles(element, CycleType.System, ref cyclesCount); + return cyclesCount / 2; + } + + public IEnumerable GetRelations() + { + return _relationsById.Values; + } + + public int GetRelationCount() + { + return _relationsById.Values.Count; + } + + public IEnumerable GetExportedRelations() + { + List exportedRelations = new List(); + exportedRelations.AddRange(_relationsById.Values); + exportedRelations.AddRange(_deletedRelationsById.Values); + return exportedRelations.OrderBy(x => x.Id); + } + + public int GetExportedRelationCount() + { + return _relationsById.Values.Count + _deletedRelationsById.Values.Count; + } + + public void UnregisterElementRelations(IDsmElement element) + { + List toBeRelationsUnregistered = new List(); + + foreach (DsmRelation relation in _relationsById.Values) + { + if ((element.Id == relation.Consumer.Id) || + (element.Id == relation.Provider.Id)) + { + toBeRelationsUnregistered.Add(relation); + } + } + + foreach (DsmRelation relation in toBeRelationsUnregistered) + { + UnregisterRelation(relation); + } + } + + public void ReregisterElementRelations(IDsmElement element) + { + List toBeRelationsReregistered = new List(); + + foreach (DsmRelation relation in _deletedRelationsById.Values) + { + if ((element.Id == relation.Consumer.Id) || + (element.Id == relation.Provider.Id)) + { + toBeRelationsReregistered.Add(relation); + } + } + + foreach (DsmRelation relation in toBeRelationsReregistered) + { + RegisterRelation(relation); + } + } + + private void RegisterRelation(DsmRelation relation) + { + relation.IsDeleted = false; + _relationsById[relation.Id] = relation; + + if (_deletedRelationsById.ContainsKey(relation.Id)) + { + _deletedRelationsById.Remove(relation.Id); + } + + DsmElement consumer = relation.Consumer as DsmElement; + consumer.Dependencies.AddOutgoingRelation(relation); + + DsmElement provider = relation.Provider as DsmElement; + provider.Dependencies.AddIngoingRelation(relation); + + AddWeights(relation); + } + + private void UnregisterRelation(DsmRelation relation) + { + relation.IsDeleted = true; + _relationsById.Remove(relation.Id); + + _deletedRelationsById[relation.Id] = relation; + + DsmElement consumer = relation.Consumer as DsmElement; + consumer.Dependencies.RemoveOutgoingRelation(relation); + + DsmElement provider = relation.Provider as DsmElement; + provider.Dependencies.RemoveIngoingRelation(relation); + + RemoveWeights(relation); + } + + public void AddWeights(IDsmRelation relation) + { + DsmElement currentConsumer = relation.Consumer as DsmElement; + while (currentConsumer != null) + { + IDsmElement currentProvider = relation.Provider; + while (currentProvider != null) + { + if ((currentConsumer.Id != currentProvider.Id) && + !currentConsumer.IsRoot && + !currentProvider.IsRoot && + !currentConsumer.IsRecursiveChildOf(currentProvider) && + !currentProvider.IsRecursiveChildOf(currentConsumer)) + { + currentConsumer.Dependencies.AddDerivedWeight(currentProvider, relation.Weight); + } + currentProvider = currentProvider.Parent; + } + currentConsumer = currentConsumer.Parent as DsmElement; + } + } + + public void RemoveWeights(IDsmRelation relation) + { + DsmElement currentConsumer = relation.Consumer as DsmElement; + while (currentConsumer != null) + { + IDsmElement currentProvider = relation.Provider; + while (currentProvider != null) + { + if ((currentConsumer.Id != currentProvider.Id) && + !currentConsumer.IsRoot && + !currentProvider.IsRoot && + !currentConsumer.IsRecursiveChildOf(currentProvider) && + !currentProvider.IsRecursiveChildOf(currentConsumer)) + { + currentConsumer.Dependencies.RemoveDerivedWeight(currentProvider, relation.Weight); + } + currentProvider = currentProvider.Parent; + } + currentConsumer = currentConsumer.Parent as DsmElement; + } + } + + private void CountCycles(IDsmElement element, CycleType cycleType, ref int cycleCount) + { + foreach (IDsmElement consumer in element.Children) + { + foreach (IDsmElement provider in element.Children) + { + if (IsCyclicDependency(consumer, provider) == cycleType) + { + cycleCount++; + } + } + } + + foreach (IDsmElement child in element.Children) + { + CountCycles(child, cycleType, ref cycleCount); + } + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/NameRegistration.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/NameRegistration.cs new file mode 100644 index 00000000..0c98af98 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Core/NameRegistration.cs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using System.Diagnostics; + +namespace DsmSuite.DsmViewer.Model.Core +{ + /// + /// Collection of relation or element types. There can be max 256 different types. + /// Introduced to reduce memory usage by avoiding storing type string multiple times in memory. + /// + public class NameRegistration + { + private readonly Dictionary _registeredNames = new Dictionary(); + private readonly Dictionary _nameIds = new Dictionary(); + + public char RegisterName(string typeName) + { + Debug.Assert(_registeredNames.Count < 255); + if (_nameIds.ContainsKey(typeName)) + { + return _nameIds[typeName]; + } + else + { + char id = (char)_registeredNames.Count; + _registeredNames[id] = typeName; + _nameIds[typeName] = id; + return id; + } + } + + public string GetRegisteredName(char typeId) + { + if (_registeredNames.ContainsKey(typeId)) + { + return _registeredNames[typeId]; + } + else + { + return "unknown"; + } + } + + public IEnumerable GetRegisteredNames() + { + return _registeredNames.Values; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/DsmSuite.DsmViewer.Model.csproj b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/DsmSuite.DsmViewer.Model.csproj new file mode 100644 index 00000000..4c95b7b6 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/DsmSuite.DsmViewer.Model.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + Library + enable + + + + + + + + + + diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/CycleType.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/CycleType.cs new file mode 100644 index 00000000..8c41ac91 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/CycleType.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public enum CycleType + { + None, + System, + Hierarchical + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmAction.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmAction.cs new file mode 100644 index 00000000..00874648 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmAction.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public interface IDsmAction + { + int Id { get; } + + string Type { get; } + + IReadOnlyDictionary Data { get; } + + /// + /// Sub-actions or null. + /// + IEnumerable Actions { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmElement.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmElement.cs new file mode 100644 index 00000000..11527bfe --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmElement.cs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public interface IDsmElement : IComparable + { + /// + /// Unique and non-modifiable Number identifying the element. + /// + int Id { get; } + + /// + /// Number identifying sequential order of the element in element tree. + /// + int Order { get; } + + /// + /// Type of element. + /// + string Type { get; } + + /// + /// Name of the element. + /// + string Name { get; } + + // Named properties found for this element + IDictionary Properties { get; } + + // Property names found across all elements + IEnumerable DiscoveredElementPropertyNames(); + + /// + /// Full name of the element based on its position in the element hierarchy + /// + string Fullname { get; } + + string GetRelativeName(IDsmElement element); + + bool IsDeleted { get; } + bool IsBookmarked { get; set; } + bool IsRoot { get; } + + /// + /// Has the element any children that are in the tree (see ). + /// + bool HasChildren { get; } + + /// + /// Tree children of the element (see ). + /// + IList Children { get; } + + IList AllChildren { get; } + + int IndexOfChild(IDsmElement child); + + bool ContainsChildWithName(string name); + + /// + /// Parent of the element. + /// + IDsmElement Parent { get; } + + /// + /// Is the selected element a recursive child of this element. + /// + bool IsRecursiveChildOf(IDsmElement element); + + /// + /// Is the element expanded in the viewer. This is only meaningful for elements with children.
+ /// If an element is expanded, each of its children has a line in the matrix and the element + /// itself hasn't, but is used to vertically group the children.
+ /// If an element is not expanded, its children are not displayed and the element itself has + /// a row in the matrix.
+ /// This property is only relevant and should only be changed for elements that are in the + /// tree (see ). + ///
+ bool IsExpanded { get; set; } + + /// + /// Set or clear the IsExpanded property recursively for elements in the tree. + /// + void ExpandRecursively(bool expanded); + + /// + /// Is the element match in search. + /// + bool IsMatch { get; set; } + + /// + /// Is the element included in the tree. The tree is the set of elements that are in scope + /// for the viewer, i.e. that are not filtered out. + /// + bool IsIncludedInTree { get; set; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmModel.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmModel.cs new file mode 100644 index 00000000..dd28e147 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmModel.cs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Util; + +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + /// + /// Represents a domain structure matrix. A Dsm model consists of a hierarchy of elements + /// , relations between elements and a + /// sequence of actions that were executed on the model. + /// + /// + /// Models can be saved and loaded. Element and relations can have types (strings) assigned + /// to them. Actions are strings as well. The model is agnostic about the meaning of + /// actions and element/relation types. + /// + public interface IDsmModel + { + // Cleanup + void Clear(); + + // Model persistency + string ModelFilename { get; } + bool IsCompressed { get; } + void LoadModel(string dsmFilename, IProgress progress); + void SaveModel(string dsmFilename, bool compressFile, IProgress progress); + + // Meta data + IMetaDataItem AddMetaData(string group, string name, string value); + + // Element editing + IDsmElement AddElement(int id, string name, string type, IDictionary properties, int order); + IDsmElement AddElement(string name, string type, int? parentId, int? index, IDictionary properties); + void RemoveElement(int elementId); + void UnremoveElement(int elementId); + void ChangeElementName(IDsmElement element, string name); + void ChangeElementType(IDsmElement element, string type); + bool IsChangeElementParentAllowed(IDsmElement element, IDsmElement parent); + void ChangeElementParent(IDsmElement element, IDsmElement parent, int index); + void ReorderChildren(IDsmElement element, ISortResult sortResult); + IDsmElement NextSibling(IDsmElement element); + IDsmElement PreviousSibling(IDsmElement element); + bool Swap(IDsmElement first, IDsmElement second); + void AssignElementOrder(); + /// + /// Includes or excludes the children and parents of the given element in the tree. + /// + void IncludeInTree(IDsmElement element, bool included); + + // Element queries + IDsmElement GetElementById(int id); + IDsmElement GetDeletedElementById(int id); + IDsmElement GetElementByFullname(string fullname); + IList SearchElements(string searchText, IDsmElement searchInElement, bool caseSensitive, string elementTypeFilter, bool markMatchingElements); + IDsmElement RootElement { get; } + IEnumerable GetElementTypes(); + IEnumerable GetElements(); + int GetElementCount(); + + // Relation editing + IDsmRelation AddRelation(IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties); + IDsmRelation AddRelation(int id, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties); + void ChangeRelationType(IDsmRelation relation, string type); + void ChangeRelationWeight(IDsmRelation relation, int weight); + void RemoveRelation(int relationId); + void UnremoveRelation(int relationId); + IEnumerable GetRelationTypes(); + + // Relation queries + int GetDependencyWeight(IDsmElement consumer, IDsmElement provider); + int GetDirectDependencyWeight(IDsmElement consumer, IDsmElement provider); + CycleType IsCyclicDependency(IDsmElement consumer, IDsmElement provider); + IDsmRelation GetRelationById(int relationId); + IDsmRelation GetDeletedRelationById(int relationId); + IEnumerable GetRelations(); + IDsmRelation FindRelation(IDsmElement consumer, IDsmElement provider, string type); + IEnumerable FindRelations(IDsmElement consumer, IDsmElement provider); + int GetRelationCount(IDsmElement consumer, IDsmElement provider); + IEnumerable FindIngoingRelations(IDsmElement element); + IEnumerable FindOutgoingRelations(IDsmElement element); + IEnumerable FindInternalRelations(IDsmElement element); + IEnumerable FindExternalRelations(IDsmElement element); + int GetHierarchicalCycleCount(IDsmElement element); + int GetSystemCycleCount(IDsmElement element); + // Actions + IDsmAction AddAction(string type, IReadOnlyDictionary data, + IEnumerable actions); + void ClearActions(); + IEnumerable GetActions(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmRelation.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmRelation.cs new file mode 100644 index 00000000..b93ecab3 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/IDsmRelation.cs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public interface IDsmRelation + { + /// + /// Unique and non-modifiable Number identifying the relation. + /// + int Id { get; } + + /// + /// The consumer element. + /// + IDsmElement Consumer { get; } + + /// + /// The provider element. + /// + IDsmElement Provider { get; } + + /// + /// Type of relation. + /// + string Type { get; } + + /// + /// Strength or weight of the relation + /// + int Weight { get; } + + bool IsDeleted { get; } + + // Named properties found for this relation + IDictionary Properties { get; } + + // Property names found across all relations + IEnumerable DiscoveredRelationPropertyNames(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/ISortResult.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/ISortResult.cs new file mode 100644 index 00000000..43d5d3ee --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/ISortResult.cs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public interface ISortResult + { + int GetIndex(int currentIndex); + int GetNumberOfElements(); + bool IsValid { get; } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationDirection.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationDirection.cs new file mode 100644 index 00000000..5163811c --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationDirection.cs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public enum RelationDirection + { + Ingoing, + Outgoing, + Both + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationScope.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationScope.cs new file mode 100644 index 00000000..07437c4a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Interfaces/RelationScope.cs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +namespace DsmSuite.DsmViewer.Model.Interfaces +{ + public enum RelationScope + { + Internal, + External, + Both + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/ModelClasses.cd b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/ModelClasses.cd new file mode 100644 index 00000000..d7e7079c --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/ModelClasses.cd @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ApAkRACAIJQBAGSAAAILQAEAoIgjJYCExkQgBARgFKA= + Core\DsmModel.cs + + + + + + + + + + + + + + + + + AAMSAQBAAAwAAASAAAgMQEQQEAIBQIAgARAEAQgGAQg= + Core\DsmElement.cs + + + + + + + + + + + + + + + AIAgAICAAIAdAHCAAAIAEAlAIAgDJAIGBEAAIABADIA= + Core\DsmElementModel.cs + + + + + + + + + + IhAERACAAAQAACQBYAAiAQwAgQA4AYCMggBgBAVAAAA= + Core\DsmRelationModel.cs + + + + + + + + + + AgAAAAAAAAAAAAAAMAAAAIAAAAAABAAAAAAAAABgAAA= + Core\DsmActionModel.cs + + + + + + + + + + + + + + + + + + + + AAECAAAAAAAEAAAAAAAAAAAAACBAQAAIAQACAQACAAI= + Core\DsmRelation.cs + + + + + + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAAABAAAQAAAAAAAAA= + Core\DsmAction.cs + + + + + + + AAAAQgQABAAAAABAAAAIBABIABBYAAAAwAgBAAAAAAA= + Core\DsmDependencies.cs + + + + + + ApAkRACAIIQBAGSAAAILQAEAoIgjJYCEhkAgBARgBAA= + Interfaces\IDsmModel.cs + + + + + + AAMCAQBAAAgAAAAAAAAEQEQAEAAAAAAAARAEAQgGAAg= + Interfaces\IDsmElement.cs + + + + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAAABAAAQAAAAAAAAA= + Interfaces\IDsmAction.cs + + + + + + AAACAAAAAAAEAAAAAAAAAAAAACBAAAAAAQACAQACAAA= + Interfaces\IDsmRelation.cs + + + + \ No newline at end of file diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/DsmModelFile.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/DsmModelFile.cs new file mode 100644 index 00000000..694af3e7 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/DsmModelFile.cs @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.Common.Model.Interface; +using DsmSuite.Common.Model.Persistency; +using DsmSuite.Common.Util; +using DsmSuite.DsmViewer.Model.Core; +using DsmSuite.DsmViewer.Model.Interfaces; +using System.Xml; + +namespace DsmSuite.DsmViewer.Model.Persistency +{ + public class DsmModelFile + { + private const string RootXmlNode = "dsmmodel"; + private const string ModelElementCountXmlAttribute = "elementCount"; + private const string ModelRelationCountXmlAttribute = "relationCount"; + private const string ModelActionCountXmlAttribute = "actionCount"; + + private const string MetaDataGroupXmlNode = "metadatagroup"; + private const string MetaDataGroupNameXmlAttribute = "name"; + + private const string MetaDataXmlNode = "metadata"; + private const string MetaDataItemNameXmlAttribute = "name"; + private const string MetaDataItemValueXmlAttribute = "value"; + + private const string ElementGroupXmlNode = "elements"; + + private const string ElementXmlNode = "element"; + private const string ElementIdXmlAttribute = "id"; + private const string ElementOrderXmlAttribute = "order"; + private const string ElementNameXmlAttribute = "name"; + private const string ElementTypeXmlAttribute = "type"; + private const string ElementExpandedXmlAttribute = "expanded"; + private const string ElementParentXmlAttribute = "parent"; + private const string ElementDeletedXmlAttribute = "deleted"; + private const string ElementBookmarkedXmlAttribute = "bookmarked"; + private const string ElementInTreeXmlAttribute = "tree"; + + private const string RelationGroupXmlNode = "relations"; + + private const string RelationXmlNode = "relation"; + private const string RelationIdXmlAttribute = "id"; + private const string RelationFromXmlAttribute = "from"; + private const string RelationToXmlAttribute = "to"; + private const string RelationTypeXmlAttribute = "type"; + private const string RelationWeightXmlAttribute = "weight"; + private const string RelationDeletedXmlAttribute = "deleted"; + + private const string ActionGroupXmlNode = "actions"; + + private const string ActionXmlNode = "action"; + private const string ActionIdXmlAttribute = "id"; + private const string ActionTypeXmlAttribute = "type"; + private const string ActionDataXmlNode = "data"; + + private readonly string _filename; + private readonly IMetaDataModelFileCallback _metaDataModelCallback; + private readonly IDsmElementModelFileCallback _elementModelCallback; + private readonly IDsmRelationModelFileCallback _relationModelCallback; + private readonly IDsmActionModelFileCallback _actionModelCallback; + private int _totalElementCount; + private int _progressedElementCount; + private int _totalRelationCount; + private int _progressedRelationCount; + private int _totalActionCount; + private int _progressedActionCount; + private int _progress; + private string _progressActionText; + + public DsmModelFile(string filename, + IMetaDataModelFileCallback metaDataModelCallback, + IDsmElementModelFileCallback elementModelCallback, + IDsmRelationModelFileCallback relationModelCallback, + IDsmActionModelFileCallback actionModelCallback) + { + _filename = filename; + _metaDataModelCallback = metaDataModelCallback; + _elementModelCallback = elementModelCallback; + _relationModelCallback = relationModelCallback; + _actionModelCallback = actionModelCallback; + } + + public void Save(bool compressed, IProgress progress) + { + _progressActionText = "Saving dsm model"; + CompressedFile modelFile = new CompressedFile(_filename); + modelFile.WriteFile(WriteDsmXml, progress, compressed); + UpdateProgress(progress, true); + } + + public void Load(IProgress progress) + { + _progressActionText = "Loading dsm model"; + CompressedFile modelFile = new CompressedFile(_filename); + modelFile.ReadFile(ReadDsmXml, progress); + UpdateProgress(progress, true); + } + + public bool IsCompressedFile() + { + CompressedFile modelFile = new CompressedFile(_filename); + return modelFile.IsCompressed; + } + + private void WriteDsmXml(Stream stream, IProgress progress) + { + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + IndentChars = (" ") + }; + + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement(RootXmlNode); + { + WriteModelAttributes(writer); + WriteMetaData(writer); + WriteElements(writer, progress); + WriteRelations(writer, progress); + WriteActions(writer, progress); + } + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + } + + private void ReadDsmXml(Stream stream, IProgress progress) + { + using (XmlReader xReader = XmlReader.Create(stream)) + { + while (xReader.Read()) + { + switch (xReader.NodeType) + { + case XmlNodeType.Element: + ReadModelAttributes(xReader); + ReadMetaDataGroup(xReader); + ReadElement(xReader, progress); + ReadRelation(xReader, progress); + ReadActionGroup(xReader, _actionModelCallback, progress); + break; + case XmlNodeType.Text: + break; + case XmlNodeType.EndElement: + break; + } + } + } + } + + private void WriteModelAttributes(XmlWriter writer) + { + _totalElementCount = _elementModelCallback.GetExportedElementCount(); + writer.WriteAttributeString(ModelElementCountXmlAttribute, _totalElementCount.ToString()); + _progressedElementCount = 0; + + _totalRelationCount = _relationModelCallback.GetExportedRelationCount(); + writer.WriteAttributeString(ModelRelationCountXmlAttribute, _totalRelationCount.ToString()); + _progressedRelationCount = 0; + + _totalActionCount = _actionModelCallback.GetExportedActionCount(); + writer.WriteAttributeString(ModelActionCountXmlAttribute, _totalActionCount.ToString()); + _progressedActionCount = 0; + } + + private void ReadModelAttributes(XmlReader xReader) + { + if (xReader.Name == RootXmlNode) + { + int? elementCount = ParseInt(xReader.GetAttribute(ModelElementCountXmlAttribute)); + int? relationCount = ParseInt(xReader.GetAttribute(ModelRelationCountXmlAttribute)); + int? actionCount = ParseInt(xReader.GetAttribute(ModelActionCountXmlAttribute)); + + _totalElementCount = elementCount ?? 0; + _progressedElementCount = 0; + _totalRelationCount = relationCount ?? 0; + _progressedRelationCount = 0; + _totalActionCount = actionCount ?? 0; + _progressedActionCount = 0; + } + } + + private void WriteMetaData(XmlWriter writer) + { + foreach (string group in _metaDataModelCallback.GetExportedMetaDataGroups()) + { + WriteMetaDataGroup(writer, group); + } + } + + private void WriteMetaDataGroup(XmlWriter writer, string group) + { + writer.WriteStartElement(MetaDataGroupXmlNode); + writer.WriteAttributeString(MetaDataGroupNameXmlAttribute, group); + + foreach (IMetaDataItem metaDataItem in _metaDataModelCallback.GetExportedMetaDataGroupItems(group)) + { + writer.WriteStartElement(MetaDataXmlNode); + writer.WriteAttributeString(MetaDataItemNameXmlAttribute, metaDataItem.Name); + writer.WriteAttributeString(MetaDataItemValueXmlAttribute, metaDataItem.Value); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + private void ReadMetaDataGroup(XmlReader xReader) + { + if (xReader.Name == MetaDataGroupXmlNode) + { + string group = xReader.GetAttribute(MetaDataGroupNameXmlAttribute); + XmlReader xMetaDataReader = xReader.ReadSubtree(); + while (xMetaDataReader.Read()) + { + if (xMetaDataReader.Name == MetaDataXmlNode) + { + string name = xMetaDataReader.GetAttribute(MetaDataItemNameXmlAttribute); + string value = xMetaDataReader.GetAttribute(MetaDataItemValueXmlAttribute); + if ((name != null) && (value != null)) + { + _metaDataModelCallback.ImportMetaDataItem(group, name, value); + } + } + } + } + } + + private void WriteElements(XmlWriter writer, IProgress progress) + { + writer.WriteStartElement(ElementGroupXmlNode); + foreach (IDsmElement element in _elementModelCallback.RootElement.AllChildren) + { + WriteElement(writer, element, progress); + } + writer.WriteEndElement(); + } + + private void WriteElement(XmlWriter writer, IDsmElement element, IProgress progress) + { + writer.WriteStartElement(ElementXmlNode); + writer.WriteAttributeString(ElementIdXmlAttribute, element.Id.ToString()); + writer.WriteAttributeString(ElementOrderXmlAttribute, element.Order.ToString()); + writer.WriteAttributeString(ElementNameXmlAttribute, element.Name); + writer.WriteAttributeString(ElementTypeXmlAttribute, element.Type); + writer.WriteAttributeString(ElementExpandedXmlAttribute, element.IsExpanded.ToString()); + if (element.IsDeleted) + { + writer.WriteAttributeString(ElementDeletedXmlAttribute, "true"); + } + if (element.IsBookmarked) + { + writer.WriteAttributeString(ElementBookmarkedXmlAttribute, "true"); + } + writer.WriteAttributeString(ElementInTreeXmlAttribute, element.IsIncludedInTree.ToString()); + if ((element.Parent != null) && (element.Parent.Id > 0)) + { + writer.WriteAttributeString(ElementParentXmlAttribute, element.Parent.Id.ToString()); + } + if (element.Properties != null) + { + foreach (KeyValuePair elementProperty in element.Properties) + { + writer.WriteAttributeString(elementProperty.Key, elementProperty.Value); + } + } + writer.WriteEndElement(); + + _progressedElementCount++; + UpdateProgress(progress, false); + + foreach (IDsmElement child in element.AllChildren) + { + WriteElement(writer, child, progress); + } + } + + private void ReadElement(XmlReader xReader, IProgress progress) + { + if (xReader.Name == ElementXmlNode) + { + int? id = null; + int? order = null; + string name = ""; + string type = ""; + bool expanded = false; + int? parent = null; + bool deleted = false; + bool bookmarked = false; + bool intree = true; + + Dictionary elementProperties = new Dictionary(); + for (int attInd = 0; attInd < xReader.AttributeCount; attInd++) + { + xReader.MoveToAttribute(attInd); + switch (xReader.Name) + { + case ElementIdXmlAttribute: + id = ParseInt(xReader.Value); + break; + case ElementOrderXmlAttribute: + order = ParseInt(xReader.Value); + break; + case ElementNameXmlAttribute: + name = xReader.Value; + break; + case ElementTypeXmlAttribute: + type = xReader.Value; + break; + case ElementExpandedXmlAttribute: + expanded = ParseBool(xReader.Value); + break; + case ElementParentXmlAttribute: + parent = ParseInt(xReader.Value); + break; + case ElementDeletedXmlAttribute: + deleted = ParseBool(xReader.Value); + break; + case ElementBookmarkedXmlAttribute: + bookmarked = ParseBool(xReader.Value); + break; + case ElementInTreeXmlAttribute: + intree = ParseBool(xReader.Value); + break; + default: + if (!string.IsNullOrEmpty(xReader.Value)) + { + elementProperties[xReader.Name] = xReader.Value; + } + break; + } + } + + if (id.HasValue && order.HasValue) + { + IDsmElement element = _elementModelCallback.ImportElement( + id.Value, name, type, + elementProperties.Count > 0 ? elementProperties : null, + order.Value, expanded, parent, deleted + ); + element.IsBookmarked = bookmarked; + element.IsIncludedInTree = intree; + } + + _progressedElementCount++; + UpdateProgress(progress, false); + } + } + + private void WriteRelations(XmlWriter writer, IProgress progress) + { + writer.WriteStartElement(RelationGroupXmlNode); + foreach (IDsmRelation relation in _relationModelCallback.GetExportedRelations()) + { + WriteRelation(writer, relation, progress); + } + writer.WriteEndElement(); + } + + private void WriteRelation(XmlWriter writer, IDsmRelation relation, IProgress progress) + { + writer.WriteStartElement(RelationXmlNode); + writer.WriteAttributeString(RelationIdXmlAttribute, relation.Id.ToString()); + writer.WriteAttributeString(RelationFromXmlAttribute, relation.Consumer.Id.ToString()); + writer.WriteAttributeString(RelationToXmlAttribute, relation.Provider.Id.ToString()); + writer.WriteAttributeString(RelationTypeXmlAttribute, relation.Type); + writer.WriteAttributeString(RelationWeightXmlAttribute, relation.Weight.ToString()); + if (relation.IsDeleted) + { + writer.WriteAttributeString(RelationDeletedXmlAttribute, "true"); + } + if (relation.Properties != null) + { + foreach (KeyValuePair relationProperty in relation.Properties) + { + writer.WriteAttributeString(relationProperty.Key, relationProperty.Value); + } + } + writer.WriteEndElement(); + + _progressedRelationCount++; + UpdateProgress(progress, false); + } + + private void ReadRelation(XmlReader xReader, IProgress progress) + { + if (xReader.Name == RelationXmlNode) + { + int? id = null; + int? consumerId = null; + int? providerId = null; + string type = ""; + int? weight = null; + bool deleted = false; + + Dictionary relationProperties = new Dictionary(); + for (int attInd = 0; attInd < xReader.AttributeCount; attInd++) + { + xReader.MoveToAttribute(attInd); + switch (xReader.Name) + { + case RelationIdXmlAttribute: + id = ParseInt(xReader.Value); + break; + case RelationFromXmlAttribute: + consumerId = ParseInt(xReader.Value); + break; + case RelationToXmlAttribute: + providerId = ParseInt(xReader.Value); + break; + case RelationTypeXmlAttribute: + type = xReader.Value; + break; + case RelationWeightXmlAttribute: + weight = ParseInt(xReader.Value); + break; + case RelationDeletedXmlAttribute: + deleted = ParseBool(xReader.Value); + break; + default: + if (!string.IsNullOrEmpty(xReader.Value)) + { + relationProperties[xReader.Name] = xReader.Value; + } + break; + } + } + + if (id.HasValue && consumerId.HasValue && providerId.HasValue && weight.HasValue) + { + IDsmElement consumer = _elementModelCallback.FindElementById(consumerId.Value); + IDsmElement provider = _elementModelCallback.FindElementById(providerId.Value); + + if (relationProperties.Count > 0) + { + _relationModelCallback.ImportRelation(id.Value, consumer, provider, type, weight.Value, relationProperties, deleted); + } + else + { + _relationModelCallback.ImportRelation(id.Value, consumer, provider, type, weight.Value, null, deleted); + } + } + + _progressedRelationCount++; + UpdateProgress(progress, false); + } + } + + private void WriteActions(XmlWriter writer, IProgress progress) + { + WriteActionGroup(writer, _actionModelCallback.GetExportedActions(), progress); + } + + private void WriteActionGroup(XmlWriter writer, IEnumerable actions, + IProgress progress) + { + writer.WriteStartElement(ActionGroupXmlNode); + foreach (IDsmAction action in actions) + WriteAction(writer, action, progress); + writer.WriteEndElement(); + } + + + private void WriteAction(XmlWriter writer, IDsmAction action, IProgress progress) + { + writer.WriteStartElement(ActionXmlNode); + writer.WriteAttributeString(ActionIdXmlAttribute, action.Id.ToString()); + writer.WriteAttributeString(ActionTypeXmlAttribute, action.Type); + + writer.WriteStartElement(ActionDataXmlNode); + foreach (var d in action.Data) + { + writer.WriteAttributeString(d.Key, d.Value); + } + writer.WriteEndElement(); + + if (action.Actions != null) + WriteActionGroup(writer, action.Actions, progress); + + writer.WriteEndElement(); + + _progressedActionCount++; + UpdateProgress(progress, false); + } + + private void ReadActionGroup(XmlReader xReader, IDsmActionModelFileCallback actionModel, + IProgress progress) + { + if (xReader.Name == ActionGroupXmlNode) + { + XmlReader xActionsReader = xReader.ReadSubtree(); + while (xActionsReader.Read()) + { + if (xActionsReader.Name == ActionXmlNode) + ReadAction(xActionsReader, actionModel, progress); + } + } + } + + private void ReadAction(XmlReader xReader, IDsmActionModelFileCallback actionModel, + IProgress progress) + { + if (xReader.Name == ActionXmlNode) + { + int? id = ParseInt(xReader.GetAttribute(ActionIdXmlAttribute)); + string type = xReader.GetAttribute(ActionTypeXmlAttribute); + Dictionary data = new Dictionary(); + DsmActionModel subactions = null; + + XmlReader xActionDataReader = xReader.ReadSubtree(); + while (xActionDataReader.Read()) + { + switch (xActionDataReader.Name) + { + case ActionDataXmlNode: + while (xActionDataReader.MoveToNextAttribute()) + data[xReader.Name] = xReader.Value; + xActionDataReader.MoveToElement(); + break; + case ActionGroupXmlNode: + subactions = new DsmActionModel(); + ReadActionGroup(xActionDataReader, subactions, progress); + break; + } + } + + if (id.HasValue) + { + actionModel.ImportAction(id.Value, type, data, subactions?.GetExportedActions()); + } + + _progressedActionCount++; + UpdateProgress(progress, false); + } + } + + private void UpdateProgress(IProgress progress, bool done) + { + if (progress != null) + { + int totalItemCount = _totalElementCount + _totalRelationCount + _totalActionCount; + int progressedItemCount = _progressedElementCount + _progressedRelationCount + _progressedActionCount; + + int currentProgress = 0; + if (totalItemCount > 0) + { + currentProgress = progressedItemCount * 100 / totalItemCount; + } + + if ((_progress != currentProgress) || done) + { + _progress = currentProgress; + + ProgressInfo progressInfoInfo = new ProgressInfo + { + ActionText = _progressActionText, + Percentage = _progress, + TotalItemCount = totalItemCount, + CurrentItemCount = progressedItemCount, + ItemType = "items", + Done = done + }; + + progress.Report(progressInfoInfo); + } + } + } + + private int? ParseInt(string value) + { + int? result = null; + + int parsedValued; + if (int.TryParse(value, out parsedValued)) + { + result = parsedValued; + } + return result; + } + + private bool ParseBool(string value) + { + bool result = false; + + bool parsedValued; + if (bool.TryParse(value, out parsedValued)) + { + result = parsedValued; + } + return result; + } + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmActionModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmActionModelFileCallback.cs new file mode 100644 index 00000000..049c189d --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmActionModelFileCallback.cs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Persistency +{ + /// + /// Specifies the methods for adding actions to the model and reading them. + /// DsmModelFile call methods in this to write actions obtained from XML, or to determine which actions + /// to write to XML. + /// + public interface IDsmActionModelFileCallback + { + /// Create a new action with the given properties. + /// The new action. + IDsmAction ImportAction(int id, string type, IReadOnlyDictionary data, + IEnumerable actions); + + /// Return all actions in the model. + IEnumerable GetExportedActions(); + + /// Return the number of actions in the model. + int GetExportedActionCount(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmElementModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmElementModelFileCallback.cs new file mode 100644 index 00000000..a0ea778a --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmElementModelFileCallback.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Persistency +{ + public interface IDsmElementModelFileCallback + { + IDsmElement FindElementById(int elementId); + IDsmElement ImportElement(int id, string name, string type, IDictionary properties, int order, bool expanded, int? parent, bool deleted); + IDsmElement RootElement { get; } + int GetExportedElementCount(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmRelationModelFileCallback.cs b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmRelationModelFileCallback.cs new file mode 100644 index 00000000..d60aa1e4 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.Model/Persistency/IDsmRelationModelFileCallback.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +using DsmSuite.DsmViewer.Model.Interfaces; + +namespace DsmSuite.DsmViewer.Model.Persistency +{ + public interface IDsmRelationModelFileCallback + { + IDsmRelation ImportRelation(int id, IDsmElement consumer, IDsmElement provider, string type, int weight, IDictionary properties, bool deleted); + + IEnumerable GetExportedRelations(); + int GetExportedRelationCount(); + } +} diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/DsmSuite.DsmViewer.View.csproj b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/DsmSuite.DsmViewer.View.csproj new file mode 100644 index 00000000..8d12758e --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/DsmSuite.DsmViewer.View.csproj @@ -0,0 +1,28 @@ + + + + Library + net10.0-windows + enable + true + + + + Resources/DSM.ico + + + + + + + + + + + + + + + + + diff --git a/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/ElementEditDialog.xaml b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/ElementEditDialog.xaml new file mode 100644 index 00000000..af3a6631 --- /dev/null +++ b/ThirdParty/DsmSuite/DsmSuite.DsmViewer.View/Editing/ElementEditDialog.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + +