Skip to content

Commit 34ceefe

Browse files
[Add] QueryInterestingClasses method and refactor Inspect; fixes #88
1 parent 1b9cc7b commit 34ceefe

File tree

3 files changed

+155
-46
lines changed

3 files changed

+155
-46
lines changed

uml4net.Reporting.Tests/Generators/ModelInspectorTestFixture.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public class ModelInspectorTestFixture
4848

4949
private FileInfo reportFileInfo;
5050

51-
[OneTimeSetUp]
52-
public void OneTimeSetUp()
51+
[SetUp]
52+
public void SetUp()
5353
{
5454
Log.Logger = new LoggerConfiguration()
5555
.MinimumLevel.Verbose()
@@ -60,11 +60,7 @@ public void OneTimeSetUp()
6060
{
6161
builder.AddSerilog();
6262
});
63-
}
6463

65-
[SetUp]
66-
public void SetUp()
67-
{
6864
this.modelPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "UML.xmi");
6965
this.modelFileInfo = new FileInfo(modelPath);
7066

@@ -125,5 +121,37 @@ public void Verify_that_inspect_class_returns_expected_result()
125121

126122
Log.Logger.Information(inspectionReport);
127123
}
124+
125+
[Test]
126+
public void Verify_that_QueryInterestingClasses_returns_expected_result()
127+
{
128+
var rootPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData");
129+
130+
var reader = XmiReaderBuilder.Create()
131+
.UsingSettings(x => x.LocalReferenceBasePath = rootPath)
132+
.WithLogger(this.loggerFactory)
133+
.Build();
134+
135+
var xmiReaderResult = reader.Read(Path.Combine(rootPath, "UML.xmi"));
136+
137+
this.modelInspector = new ModelInspector(this.loggerFactory);
138+
139+
var interestingClasses = this.modelInspector.QueryInterestingClasses(xmiReaderResult.Root);
140+
141+
var expectedResult = new List<string>
142+
{
143+
"ActivityGroup", "Association", "Class", "Classifier",
144+
"Connector", "CreateLinkAction", "DurationConstraint", "DurationObservation",
145+
"Element", "Extension", "ExtensionEnd", "InformationFlow",
146+
"LiteralInteger", "LiteralReal", "LiteralUnlimitedNatural", "MultiplicityElement",
147+
"NamedElement", "OpaqueExpression", "Operation", "PackageableElement",
148+
"RedefinableTemplateSignature","Relationship","TimeConstraint","Transition",
149+
"UnmarshallAction"
150+
};
151+
152+
var interestingClassesNames = interestingClasses.Select(x => x.Name);
153+
154+
Assert.That(interestingClassesNames, Is.EquivalentTo(expectedResult));
155+
}
128156
}
129157
}

uml4net.Reporting/Generators/IModelInspector.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020

2121
namespace uml4net.Reporting.Generators
2222
{
23+
using System.Collections.Generic;
24+
25+
using uml4net.Classification;
2326
using uml4net.Packages;
27+
using uml4net.StructuredClassifiers;
2428

2529
/// <summary>
2630
/// The purpose of the <see cref="IModelInspector"/> is to iterate through the model and report on the various kinds of
@@ -40,6 +44,19 @@ public interface IModelInspector : IReportGenerator
4044
/// </returns>
4145
public string Inspect(IPackage package);
4246

47+
/// <summary>
48+
/// Inspect the content of the provided <see cref="IPackage"/> and returns a
49+
/// read-only collection of interesting <see cref="IClass"/>
50+
/// </summary>
51+
/// <param name="package">
52+
/// The <see cref="IPackage"/> that needs to be inspected
53+
/// </param>
54+
/// <returns>
55+
/// A read-only collection of interesting <see cref="IClass"/> that cover the variations
56+
/// of <see cref="IProperty"/>> and <see cref="IOperation"/> variations
57+
/// </returns>
58+
public IReadOnlyCollection<IClass> QueryInterestingClasses(IPackage package);
59+
4360
/// <summary>
4461
/// Inspect the provided <see cref="IClass"/> (by name) that is contained in the <see cref="IPackage"/>
4562
/// and returns the variation of data-types, enums and multiplicity as an Analysis report

uml4net.Reporting/Generators/ModelInspector.cs

Lines changed: 104 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ namespace uml4net.Reporting.Generators
2424
using System.Collections.Generic;
2525
using System.Diagnostics;
2626
using System.IO;
27+
using System.IO.Packaging;
2728
using System.Linq;
2829
using System.Text;
2930

3031
using Microsoft.Extensions.Logging;
3132
using Microsoft.Extensions.Logging.Abstractions;
3233

34+
using uml4net.Classification;
3335
using uml4net.CommonStructure;
3436
using uml4net.Extensions;
3537
using uml4net.Packages;
@@ -80,12 +82,66 @@ public string QueryReportType()
8082
/// Returns a report of the classes of interest in the provided package
8183
/// </returns>
8284
public string Inspect(IPackage package)
85+
{
86+
// Step 1: Map class to property variations
87+
var classPropertyVariations = this.MapClassPropertyVariation(package);
88+
89+
// Step 2: Greedy algorithm to cover all property variations with the fewest classes
90+
var result = this.ReduceClassPropertyVariationToInterestingClasses(classPropertyVariations);
91+
92+
var sb = new StringBuilder();
93+
94+
sb.AppendLine($"----- PACKAGE {package.Name} ANALYSIS ------");
95+
sb.AppendLine("");
96+
97+
sb.AppendLine("");
98+
sb.AppendLine("----- MULTIPLICITY RESULTS ------");
99+
sb.AppendLine("");
100+
101+
var uniqueAndOrderedPropertyVariations = new HashSet<string>(classPropertyVariations.Values.SelectMany(p => p).ToList())
102+
.OrderBy(x => x).ToList();
103+
104+
foreach (var orderedPropertyVariation in uniqueAndOrderedPropertyVariations)
105+
{
106+
sb.AppendLine(orderedPropertyVariation);
107+
}
108+
109+
sb.AppendLine("");
110+
sb.AppendLine("----- INTERESTING CLASSES ------");
111+
sb.AppendLine("");
112+
113+
var orderedClasses = result.OrderBy(x => x.Name).ToList();
114+
115+
foreach (var @class in orderedClasses)
116+
{
117+
var isAbstract = "";
118+
if (@class.IsAbstract)
119+
{
120+
isAbstract = " [Abstract]";
121+
}
122+
123+
sb.AppendLine($"class : {@class.QueryQualifiedName()}{isAbstract}");
124+
}
125+
126+
return sb.ToString();
127+
}
128+
129+
/// <summary>
130+
/// Map class to property variations
131+
/// </summary>
132+
/// <param name="package">
133+
/// The <see cref="IPackage"/> that needs to be inspected
134+
/// </param>
135+
/// <returns>
136+
/// a Dictionary of <see cref="IClass"/> and associated <see cref="HashSet{T}"/> of property
137+
/// variations for that class
138+
/// </returns>
139+
private Dictionary<IClass, HashSet<string>> MapClassPropertyVariation(IPackage package)
83140
{
84141
var classPropertyVariations = new Dictionary<IClass, HashSet<string>>();
85142

86143
var classes = package.QueryPackages().SelectMany(x => x.PackagedElement.OfType<IClass>()).ToList();
87144

88-
// Step 1: Map class to property variations
89145
foreach (var @class in classes)
90146
{
91147
var propertyVariations = new HashSet<string>();
@@ -164,64 +220,72 @@ public string Inspect(IPackage package)
164220
classPropertyVariations.Add(@class, propertyVariations);
165221
}
166222

223+
return classPropertyVariations;
224+
}
225+
226+
/// <summary>
227+
/// Reduces the <see cref="IClass"/>> and property variations in a greedy fashion such
228+
/// that the least amount of classes is returned
229+
/// </summary>
230+
/// <param name="classPropertyVariations">
231+
/// The <see cref="IClass"/>> and property variations in a greedy fashion such that needs to
232+
/// be reduced.
233+
/// </param>
234+
/// <returns>
235+
/// a reduced set of <see cref="IClass"/> and <see cref="IProperty"/> variations.
236+
/// </returns>
237+
private IReadOnlyList<IClass> ReduceClassPropertyVariationToInterestingClasses(Dictionary<IClass, HashSet<string>> classPropertyVariations)
238+
{
239+
var dictionaryClone = new Dictionary<IClass, HashSet<string>>(classPropertyVariations);
240+
167241
// Step 2: Get all unique property variations
168-
var allPropertyVariations = new HashSet<string>(classPropertyVariations.Values.SelectMany(p => p));
242+
var propertyVariations = dictionaryClone.Values.SelectMany(p => p).ToList();
169243

170-
// Step 3: Greedy algorithm to cover all property variations with fewest classes
244+
var uniquePropertyVariations = new HashSet<string>(propertyVariations);
245+
246+
// Step 3: Greedy algorithm to cover all property variations with the fewest classes
171247
var result = new List<IClass>();
172248
var covered = new HashSet<string>();
173249

174-
while (covered.Count < allPropertyVariations.Count)
250+
while (covered.Count < uniquePropertyVariations.Count)
175251
{
176252
// Pick the class that contributes the most uncovered properties
177-
var bestClass = classPropertyVariations
253+
var bestClass = dictionaryClone
178254
.OrderByDescending(kvp => kvp.Value.Count(p => !covered.Contains(p)))
179255
.First().Key;
180256

181257
result.Add(bestClass);
182258

183-
foreach (var prop in classPropertyVariations[bestClass])
259+
foreach (var prop in dictionaryClone[bestClass])
184260
covered.Add(prop);
185261

186-
classPropertyVariations.Remove(bestClass); // avoid reusing the same class
187-
}
188-
189-
var sw = Stopwatch.StartNew();
190-
191-
var sb = new StringBuilder();
192-
193-
sb.AppendLine($"----- PACKAGE {package.Name} ANALYSIS ------");
194-
sb.AppendLine("");
195-
196-
sb.AppendLine("");
197-
sb.AppendLine("----- MULTIPLICITY RESULTS ------");
198-
sb.AppendLine("");
199-
200-
var orderedPropertyVariations = allPropertyVariations.OrderBy(x => x).ToList();
201-
202-
foreach (var orderedPropertyVariation in orderedPropertyVariations)
203-
{
204-
sb.AppendLine(orderedPropertyVariation);
262+
dictionaryClone.Remove(bestClass); // avoid reusing the same class
205263
}
206264

207-
sb.AppendLine("");
208-
sb.AppendLine("----- INTERESTING CLASSES ------");
209-
sb.AppendLine("");
210-
211-
var orderedClasses = result.OrderBy(x => x.Name).ToList();
265+
return result;
266+
}
212267

213-
foreach (var @class in orderedClasses)
214-
{
215-
var isAbstract = "";
216-
if (@class.IsAbstract)
217-
{
218-
isAbstract = " [Abstract]";
219-
}
268+
/// <summary>
269+
/// Inspect the content of the provided <see cref="IPackage"/> and returns a
270+
/// read-only collection of interesting <see cref="IClass"/>
271+
/// </summary>
272+
/// <param name="package">
273+
/// The <see cref="IPackage"/> that needs to be inspected
274+
/// </param>
275+
/// <returns>
276+
/// A read-only collection of interesting <see cref="IClass"/> that cover the variations
277+
/// of <see cref="IProperty"/>> and <see cref="IOperation"/> variations
278+
/// </returns>
279+
public IReadOnlyCollection<IClass> QueryInterestingClasses(IPackage package)
280+
{
281+
// Step 1: Map class to property variations
282+
var classPropertyVariations = this.MapClassPropertyVariation(package);
220283

221-
sb.AppendLine($"class : {@class.QueryQualifiedName()}{isAbstract}");
222-
}
284+
// Step 2: Greedy algorithm to cover all property variations with the fewest classes
285+
var result = this.ReduceClassPropertyVariationToInterestingClasses(classPropertyVariations)
286+
.OrderBy(x => x.Name).ToList();
223287

224-
return sb.ToString();
288+
return result;
225289
}
226290

227291
/// <summary>

0 commit comments

Comments
 (0)