Skip to content

Commit

Permalink
Add relationship arrows for class diagrams
Browse files Browse the repository at this point in the history
  • Loading branch information
eNeRGy164 committed Oct 25, 2023
1 parent 6d1ea9e commit ae7005e
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/PlantUml.Builder/ClassDiagrams/Arrow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
namespace PlantUml.Builder.ClassDiagrams;

public partial class Arrow
{
private readonly string arrowHeads = new(new[] {
ArrowParts.Right,
ArrowParts.Left,
ArrowParts.Generalization,
ArrowParts.Composition,
ArrowParts.Aggregation,
ArrowParts.ProvidedInterface,
ArrowParts.LeftRequiredInterface,
ArrowParts.RightRequiredInterface,
ArrowParts.Note,
ArrowParts.Termination
}
);

public readonly string LeftHead = string.Empty;
public readonly string RightHead = string.Empty;
public readonly bool Dashed = false;

public Arrow(string arrow)
{
ArgumentException.ThrowIfNullOrWhitespace(arrow);

var value = arrow.Trim();

if (value.Length < 2) throw new System.ArgumentException("The arrow type must be at least 2 characters long", nameof(arrow));
if (!value.Contains(ArrowParts.SolidLine) && !value.Contains(ArrowParts.DashedLine)) throw new System.ArgumentException("The arrow must contain at least 1 line character ('-', '.')", nameof(arrow));
if (value.Contains(ArrowParts.SolidLine) && value.Contains(ArrowParts.DashedLine)) throw new System.ArgumentException("The arrow must contain either a dashed line ('.') or a solid line ('-'), but not both.", nameof(arrow));

this.Parse(value, out List<char> left, out List<char> line, out List<char> right);

if (left.Count > 0)
{
this.LeftHead = new string(left.ToArray());
}

if (line.Contains(ArrowParts.DashedLine))
{
this.Dashed = true;
}

if (right.Count > 0)
{
this.RightHead = new string(right.ToArray());
}
}

public override string ToString()
{
return $"{this.LeftHead}{(this.Dashed ? ".." : "--")}{this.RightHead}";
}

public static implicit operator Arrow(string arrow)
{
return new Arrow(arrow);
}

public static implicit operator string(Arrow arrow)
{
return arrow.ToString();
}

private void Parse(string value, out List<char> left, out List<char> line, out List<char> right)
{
left = new List<char>(value.Length);
line = new List<char>(value.Length);
right = new List<char>(value.Length);

var mode = ArrowPart.LeftHead;

for (var i = 0; i < value.Length; i++)
{
if (mode == ArrowPart.LeftHead)
{
if (!this.arrowHeads.Contains(value[i]))
{
mode = ArrowPart.Body;
}
else
{
left.Add(value[i]);
}
}

if (mode == ArrowPart.Body)
{
if (this.arrowHeads.Contains(value[i]))
{
mode = ArrowPart.RightHead;
}
else
{
line.Add(value[i]);
}
}

if (mode == ArrowPart.RightHead)
{
right.Add(value[i]);
}
}
}

private enum ArrowPart
{
LeftHead,
Body,
RightHead
}
}
22 changes: 22 additions & 0 deletions src/PlantUml.Builder/ClassDiagrams/ArrowParts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace PlantUml.Builder.ClassDiagrams;

internal class ArrowParts
{
// Line types
public const char DashedLine = '.';
public const char SolidLine = '-';

// Arrowhead types
public const char Aggregation = 'o';
public const char Composition = '*';
public const char Generalization = '|';
public const char LeftRequiredInterface = '}';
public const char Note = '#';
public const char ProvidedInterface = '+';
public const char RightRequiredInterface = '{';
public const char Termination = 'x';

// Directional arrowheads
public const char Left = '<';
public const char Right = '>';
}
140 changes: 140 additions & 0 deletions src/PlantUml.Builder/ClassDiagrams/Relationship.Defaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
namespace PlantUml.Builder.ClassDiagrams
{
/// <summary>
/// Defines constants for various types of UML relationships.
/// </summary>
public partial class Relationship
{
#pragma warning disable CA2211 // Non-constant fields should not be visible
/// <summary>
/// Represents a simple association between two classes.
/// </summary>
public static Arrow Associates = "--";

/// <summary>
/// Represents a simple association from the perspective of the second class.
/// </summary>
public static Arrow IsAssociatedWith = "--";

/// <summary>
/// Represents a directed association between two classes.
/// </summary>
public static Arrow AssociatesDirectlyWith = "<--";

/// <summary>
/// Represents a directed association from the perspective of the second class.
/// </summary>
public static Arrow IsDirectlyAssociatedWith = "-->";

/// <summary>
/// Represents a strong or special form of association.
/// </summary>
public static Arrow AssociatesStrongly = "--+";

/// <summary>
/// Represents a strong or special form of association from the perspective of the second class.
/// </summary>
public static Arrow IsStronglyAssociatedWith = "+--";

/// <summary>
/// Represents a special form of association denoted by a custom symbol.
/// </summary>
public static Arrow AssociatesSpeciallyWith = "--#";

/// <summary>
/// Represents a special form of association from the perspective of the second class.
/// </summary>
public static Arrow IsSpeciallyAssociatedWith = "#--";

/// <summary>
/// Represents a disallowed or broken relationship.
/// </summary>
public static Arrow Disallows = "--x";

/// <summary>
/// Represents a disallowed or broken relationship from the perspective of the second class.
/// </summary>
public static Arrow IsDisallowedBy = "x--";

/// <summary>
/// Represents an aggregation relationship.
/// </summary>
public static Arrow Aggregates = "--o";

/// <summary>
/// Represents an aggregation relationship from the perspective of the second class.
/// </summary>
public static Arrow IsAggregatedBy = "o--";

/// <summary>
/// Represents a composition relationship.
/// </summary>
public static Arrow Composes = "--*";

/// <summary>
/// Represents a composition relationship from the perspective of the second class.
/// </summary>
public static Arrow IsComposedBy = "*--";

/// <summary>
/// Represents a directed composition relationship.
/// </summary>
public static Arrow ComposesDirectly = "*-->";

/// <summary>
/// Represents a directed composition relationship from the perspective of the second class.
/// </summary>
public static Arrow IsDirectlyComposedBy = "<--*";

/// <summary>
/// Represents an inheritance relationship.
/// </summary>
public static Arrow InheritsFrom = "<|--";

/// <summary>
/// Represents an inheritance relationship from the perspective of the second class.
/// </summary>
public static Arrow IsInheritedBy = "--|>";

/// <summary>
/// Represents a realization or implementation relationship.
/// </summary>
public static Arrow Realizes = "<|..";

/// <summary>
/// Represents a realization or implementation relationship from the perspective of the second class.
/// </summary>
public static Arrow IsRealizedBy = "..|>";

/// <summary>
/// Represents a dependency relationship.
/// </summary>
public static Arrow DependsOn = "..";

/// <summary>
/// Represents a dependency relationship from the perspective of the second class.
/// </summary>
public static Arrow IsDependedUponBy = "..";

/// <summary>
/// Represents a directed dependency relationship.
/// </summary>
public const string DependsDirectlyOn = "..>";

/// <summary>
/// Represents a directed dependency relationship from the perspective of the second class.
/// </summary>
public const string IsDirectlyDependedUponBy = "<..";

/// <summary>
/// Represents a "contains many of" relationship, often used to denote cardinality.
/// </summary>
public const string ContainsManyOf = "}--";

/// <summary>
/// Represents a "is contained by many" relationship, often used to denote cardinality.
/// </summary>
public const string IsContainedBy = "--{";
#pragma warning restore CA2211 // Non-constant fields should not be visible
}
}
Loading

0 comments on commit ae7005e

Please sign in to comment.