Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic PlantUML support for feature request #78 (2nd attempt) #86

Merged
merged 28 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7ec8fda
Merge pull request #1 from TNG/master
studix Oct 20, 2020
8fc9995
Merge remote-tracking branch 'upstream/master'
studix Dec 19, 2020
f73098d
Merge remote-tracking branch 'upstream/master'
studix Jan 30, 2021
7e775d9
Merge remote-tracking branch 'upstream/master'
studix Mar 5, 2021
b3fe1a5
Merge remote-tracking branch 'upstream/master'
studix Mar 29, 2021
40d69d6
Merge remote-tracking branch 'upstream/master'
studix Apr 17, 2021
ee4b2c0
Merge remote-tracking branch 'upstream/main'
studix May 13, 2021
b3432a4
Merge remote-tracking branch 'upstream/main' into main
studix May 22, 2021
e738ff8
port plantuml support from archunit (walking skeleton)
studix Dec 22, 2020
fd6fc5c
port non-parametrized parser tests from archunit
studix Jan 2, 2021
cc56f1c
integrate plant uml parser in type condition definition
studix Jan 30, 2021
f1de06f
move plantuml namespace into domain
studix Jan 30, 2021
f84903d
add unit test for ClassDiagramAssociation
studix Feb 16, 2021
77cf481
replace \\ with @
studix Feb 16, 2021
53de994
suppress compiler warning in test assembly
studix Mar 5, 2021
39fceed
add more diagram tests
studix Mar 29, 2021
8017fce
add parameterized parser tests
studix Apr 18, 2021
d42ec15
add example test for plant uml
studix Apr 18, 2021
80eb775
fix dependency parsing issue
studix May 15, 2021
e90d7e0
rename package with namespace && remove .. syntax in tests
studix May 24, 2021
2172b80
make example test succeed
studix May 24, 2021
a64ec95
apply code review feedback
studix Jun 2, 2021
d3a6ace
parse by stream
studix Jun 8, 2021
f088064
change tests to use streams instead of temporary files
studix Jun 8, 2021
a23f56b
Merge remote-tracking branch 'upstream/main' into main
studix Jun 13, 2021
2493ee5
Merge branch 'main' into pumlv2
studix Jun 13, 2021
a25ce9e
use field for memory stream in test
studix Jun 13, 2021
4d7e9f2
switch back to filestream to stabelize test
studix Jun 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ fabric.properties
list_license.ps1
**/LicenseSources/

.vs/

*.user

# Visual Studio cache/options directory
Expand Down
35 changes: 35 additions & 0 deletions ArchUnitNET/Domain/PlantUml/Alias.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;

namespace ArchUnitNET.Domain.PlantUml
{
internal class Alias
{
private string _value;

public Alias(string value)
{
if (value.Contains("[") || value.Contains("]") || value.Contains(@""""))
{
throw new IllegalDiagramException(string.Format(@"Alias '{0}' should not contain character(s): '[' or ']' or '""'", value));
}
_value = value ?? throw new ArgumentNullException(nameof(value));
}

public override bool Equals(object obj)
{
return obj is Alias alias &&
_value == alias._value;
}

public override int GetHashCode()
{
return -1939223833 + EqualityComparer<string>.Default.GetHashCode(_value);
}

internal string asString()
{
return _value;
}
}
}
146 changes: 146 additions & 0 deletions ArchUnitNET/Domain/PlantUml/ClassDiagramAssociation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using ArchUnitNET.Domain;
using ArchUnitNET.Domain.Extensions;
using ArchUnitNET.Fluent.Conditions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace ArchUnitNET.Domain.PlantUml
{
internal class ClassDiagramAssociation
{
private readonly ImmutableHashSet<AssociatedComponent> _components;

public ClassDiagramAssociation(PlantUmlDiagram diagram)
{
var components = ImmutableHashSet.CreateBuilder<AssociatedComponent>();
ValidateStereotypes(diagram);
foreach (var component in diagram.AllComponents)
{
components.Add(new AssociatedComponent(component));
}
_components = components.ToImmutable();
}

private void ValidateStereotypes(PlantUmlDiagram plantUmlDiagram)
{
ISet<Stereotype> visited = new HashSet<Stereotype>();
foreach (PlantUmlComponent component in plantUmlDiagram.AllComponents)
{
foreach (Stereotype stereotype in component.Stereotypes)
{
if (visited.Contains(stereotype))
{
throw new IllegalDiagramException(string.Format("Stereotype '{0}' should be unique", stereotype.AsString()));
}
visited.Add(stereotype);
}
}
}

public ISet<string> GetTargetNamespaceIdentifiers(IType clazz)
{
var result = ImmutableHashSet.CreateBuilder<string>();
foreach (PlantUmlComponent target in GetComponentOf(clazz).Dependencies)
{
foreach (string identifier in GetNamespaceIdentifiersFromComponentOf(target))
{
result.Add(identifier);
}
}
return result.ToImmutable();
}

public bool Contains(IType clazz)
{
return GetAssociatedComponents(clazz).Count > 0;
}

public ISet<string> GetNamespaceIdentifiersFromComponentOf(IType javaIType)
{
return GetNamespaceIdentifiersFromComponentOf(GetComponentOf(javaIType));
}

private ISet<string> GetNamespaceIdentifiersFromComponentOf(PlantUmlComponent component)
{
var result = ImmutableHashSet.CreateBuilder<string>();
foreach (Stereotype stereotype in component.Stereotypes)
{
result.Add(stereotype.AsString());
}
return result.ToImmutable();
}

private PlantUmlComponent GetComponentOf(IType clazz)
{
ISet<PlantUmlComponent> associatedComponents = GetAssociatedComponents(clazz);

if (associatedComponents.Count > 1)
{
throw new ComponentIntersectionException(
string.Format("Class {0} may not be contained in more than one component, but is contained in [{1}]",
clazz.Name,
string.Join(", ", GetComponentNames(associatedComponents))));
}
else if (associatedComponents.Count == 0)
{
throw new InvalidOperationException(string.Format("Class {0} is not contained in any component", clazz.Name));
}

return associatedComponents.Single();
}

private ISet<string> GetComponentNames(ISet<PlantUmlComponent> associatedComponents)
{
ISet<string> associatedComponentNames = new HashSet<string>();
foreach (PlantUmlComponent associatedComponent in associatedComponents)
{
associatedComponentNames.Add(associatedComponent.ComponentName.AsString());
}
return associatedComponentNames;
}

private ISet<PlantUmlComponent> GetAssociatedComponents(IType clazz)
{
var result = ImmutableHashSet.CreateBuilder<PlantUmlComponent>();
foreach (AssociatedComponent component in _components)
{
if (component.Contains(clazz))
{
result.Add(component.AsPlantUmlComponent);
}
}
return result.ToImmutable();
}

private class AssociatedComponent
{
private readonly PlantUmlComponent _component;

public AssociatedComponent(PlantUmlComponent component)
{
_component = component;
}

public PlantUmlComponent AsPlantUmlComponent
{
get { return _component; }
}

public bool Contains(IType clazz)
{
foreach (Stereotype stereotype in _component.Stereotypes)
{
if (clazz.ResidesInNamespace(stereotype.AsString(), true))
{
return true;
}
}
return false;
}


}
}
}
38 changes: 38 additions & 0 deletions ArchUnitNET/Domain/PlantUml/ComponentIdentifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Generic;

namespace ArchUnitNET.Domain.PlantUml
{
internal class ComponentIdentifier
{
public ComponentIdentifier(ComponentName componentName) : this(componentName, null)
{ }

public ComponentIdentifier(ComponentName componentName, Alias alias)
{
ComponentName = componentName;
Alias = alias;
}

public ComponentName ComponentName { get; private set; }

public Alias Alias { get; private set; }

public override bool Equals(object obj)
{
return obj is ComponentIdentifier identifier &&
EqualityComparer<ComponentName>.Default.Equals(ComponentName, identifier.ComponentName) &&
EqualityComparer<Alias>.Default.Equals(Alias, identifier.Alias);
}

public override int GetHashCode()
{
int hashCode = -1327966062;
hashCode = hashCode * -1521134295 + EqualityComparer<ComponentName>.Default.GetHashCode(ComponentName);
if (Alias != null)
{
hashCode = hashCode * -1521134295 + EqualityComparer<Alias>.Default.GetHashCode(Alias);
}
return hashCode;
}
}
}
25 changes: 25 additions & 0 deletions ArchUnitNET/Domain/PlantUml/ComponentIntersectionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;

namespace ArchUnitNET.Domain.PlantUml
{
[Serializable]
internal class ComponentIntersectionException : Exception
{
public ComponentIntersectionException()
{
}

public ComponentIntersectionException(string message) : base(message)
{
}

public ComponentIntersectionException(string message, Exception innerException) : base(message, innerException)
{
}

protected ComponentIntersectionException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
31 changes: 31 additions & 0 deletions ArchUnitNET/Domain/PlantUml/ComponentName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;

namespace ArchUnitNET.Domain.PlantUml
{
internal class ComponentName
{
private readonly string _value;

public ComponentName(string value)
{
_value = value ?? throw new ArgumentNullException(nameof(value));
}

public string AsString()
{
return _value;
}

public override bool Equals(object obj)
{
return obj is ComponentName name &&
_value == name._value;
}

public override int GetHashCode()
{
return -1939223833 + EqualityComparer<string>.Default.GetHashCode(_value);
}
}
}
32 changes: 32 additions & 0 deletions ArchUnitNET/Domain/PlantUml/IllegalDiagramException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Runtime.Serialization;

namespace ArchUnitNET.Domain.PlantUml
{
[Serializable]
internal class IllegalDiagramException : Exception
{
private object p;

public IllegalDiagramException()
{
}

public IllegalDiagramException(object p)
{
this.p = p;
}

public IllegalDiagramException(string message) : base(message)
{
}

public IllegalDiagramException(string message, Exception innerException) : base(message, innerException)
{
}

protected IllegalDiagramException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
37 changes: 37 additions & 0 deletions ArchUnitNET/Domain/PlantUml/ParsedDependency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;

namespace ArchUnitNET.Domain.PlantUml
{
internal class ParsedDependency
{
public ParsedDependency(ComponentIdentifier origin, ComponentIdentifier target)
{
Origin = origin;
Target = target;
}

public ComponentIdentifier Origin { get; }
public ComponentIdentifier Target { get; }

public override bool Equals(object obj)
{
return obj is ParsedDependency dependency &&
EqualityComparer<ComponentIdentifier>.Default.Equals(Origin, dependency.Origin) &&
EqualityComparer<ComponentIdentifier>.Default.Equals(Target, dependency.Target);
}

public override int GetHashCode()
{
int hashCode = -1013312977;
if (Origin != null)
{
hashCode = hashCode * -1521134295 + EqualityComparer<ComponentIdentifier>.Default.GetHashCode(Origin);
}
if (Target != null)
{
hashCode = hashCode * -1521134295 + EqualityComparer<ComponentIdentifier>.Default.GetHashCode(Target);
}
return hashCode;
}
}
}
Loading