From ca88ead1d34f838060655a3d706f3dee78ad5244 Mon Sep 17 00:00:00 2001 From: Simon Brown Date: Sat, 20 Jul 2024 17:07:39 +0100 Subject: [PATCH] Adds a way to explicitly specify the order of relationships in dynamic views. --- changelog.md | 1 + .../structurizr/view/RelationshipView.java | 2 +- .../dsl/DynamicViewContentParser.java | 36 ++++++++++++++----- .../main/java/com/structurizr/dsl/Tokens.java | 4 +++ .../java/com/structurizr/dsl/DslTests.java | 16 ++++++--- .../dsl/DynamicViewContentParserTests.java | 4 +-- .../dynamic-view-with-explicit-ordering.dsl | 19 ++++++++++ 7 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 structurizr-dsl/src/test/resources/dsl/dynamic-view-with-explicit-ordering.dsl diff --git a/changelog.md b/changelog.md index 6b9a9fcd..5363e013 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ## unreleased - structurizr-dsl: Fixes https://github.com/structurizr/java/issues/312 (!include doesn't work with files encoded as UTF-8 BOM). +- structurizr-dsl: Adds a way to explicitly specify the order of relationships in dynamic views. ## 2.2.0 (2nd July 2024) diff --git a/structurizr-core/src/main/java/com/structurizr/view/RelationshipView.java b/structurizr-core/src/main/java/com/structurizr/view/RelationshipView.java index 29d96c2e..97a22273 100644 --- a/structurizr-core/src/main/java/com/structurizr/view/RelationshipView.java +++ b/structurizr-core/src/main/java/com/structurizr/view/RelationshipView.java @@ -132,7 +132,7 @@ public String getOrder() { * * @param order the order, as a String */ - void setOrder(String order) { + public void setOrder(String order) { this.order = order; } diff --git a/structurizr-dsl/src/main/java/com/structurizr/dsl/DynamicViewContentParser.java b/structurizr-dsl/src/main/java/com/structurizr/dsl/DynamicViewContentParser.java index e96f6bab..1843c148 100644 --- a/structurizr-dsl/src/main/java/com/structurizr/dsl/DynamicViewContentParser.java +++ b/structurizr-dsl/src/main/java/com/structurizr/dsl/DynamicViewContentParser.java @@ -4,13 +4,16 @@ import com.structurizr.model.Element; import com.structurizr.model.Relationship; import com.structurizr.model.StaticStructureElement; +import com.structurizr.util.StringUtils; import com.structurizr.view.DynamicView; import com.structurizr.view.RelationshipView; final class DynamicViewContentParser extends AbstractParser { - private static final String GRAMMAR_1 = " -> [description] [technology]"; - private static final String GRAMMAR_2 = " [description]"; + private static final String GRAMMAR_1 = "[order:] -> [description] [technology]"; + private static final String GRAMMAR_2 = "[order:] [description]"; + + private static final String ORDER_DELIMITER = ":"; private static final int SOURCE_IDENTIFIER_INDEX = 0; private static final int RELATIONSHIP_TOKEN_INDEX = 1; @@ -22,6 +25,15 @@ final class DynamicViewContentParser extends AbstractParser { RelationshipView parseRelationship(DynamicViewDslContext context, Tokens tokens) { DynamicView view = context.getView(); + RelationshipView relationshipView = null; + String order = null; + + if (tokens.size() > 0 && tokens.get(0).endsWith(ORDER_DELIMITER)) { + // the optional [order:] token + order = tokens.get(0); + order = order.substring(0, order.length()-ORDER_DELIMITER.length()); + tokens.remove(0); + } if (tokens.size() > 1 && StructurizrDslTokens.RELATIONSHIP_TOKEN.equals(tokens.get(RELATIONSHIP_TOKEN_INDEX))) { // -> [description] [technology] @@ -65,16 +77,16 @@ RelationshipView parseRelationship(DynamicViewDslContext context, Tokens tokens) } if (sourceElement instanceof StaticStructureElement && destinationElement instanceof StaticStructureElement) { - return view.add((StaticStructureElement) sourceElement, description, technology, (StaticStructureElement) destinationElement); + relationshipView = view.add((StaticStructureElement) sourceElement, description, technology, (StaticStructureElement) destinationElement); } else if (sourceElement instanceof StaticStructureElement && destinationElement instanceof CustomElement) { - return view.add((StaticStructureElement) sourceElement, description, technology, (CustomElement) destinationElement); + relationshipView = view.add((StaticStructureElement) sourceElement, description, technology, (CustomElement) destinationElement); } else if (sourceElement instanceof CustomElement && destinationElement instanceof StaticStructureElement) { - return view.add((CustomElement) sourceElement, description, technology, (StaticStructureElement) destinationElement); + relationshipView = view.add((CustomElement) sourceElement, description, technology, (StaticStructureElement) destinationElement); } else if (sourceElement instanceof CustomElement && destinationElement instanceof CustomElement) { - return view.add((CustomElement) sourceElement, description, technology, (CustomElement) destinationElement); + relationshipView = view.add((CustomElement) sourceElement, description, technology, (CustomElement) destinationElement); } } else { - // [description] [technology] + // [order] [description] [technology] String relationshipId = tokens.get(RELATIONSHIP_IDENTIFIER_INDEX); Relationship relationship = context.getRelationship(relationshipId); @@ -91,7 +103,15 @@ RelationshipView parseRelationship(DynamicViewDslContext context, Tokens tokens) description = tokens.get(RELATIONSHIP_IDENTIFIER_INDEX+1); } - return view.add(relationship, description); + relationshipView = view.add(relationship, description); + } + + if (relationshipView != null) { + if (!StringUtils.isNullOrEmpty(order)) { + relationshipView.setOrder(order); + } + + return relationshipView; } throw new RuntimeException("The specified relationship could not be added"); diff --git a/structurizr-dsl/src/main/java/com/structurizr/dsl/Tokens.java b/structurizr-dsl/src/main/java/com/structurizr/dsl/Tokens.java index f00c1cfc..ba337b3b 100644 --- a/structurizr-dsl/src/main/java/com/structurizr/dsl/Tokens.java +++ b/structurizr-dsl/src/main/java/com/structurizr/dsl/Tokens.java @@ -14,6 +14,10 @@ String get(int index) { return tokens.get(index).trim().replaceAll("\\\\\"", "\"").trim().replaceAll("\\\\n", "\n"); } + void remove(int index) { + tokens.remove(index); + } + int size() { return tokens.size(); } diff --git a/structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java b/structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java index 45579713..e34e718c 100644 --- a/structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java +++ b/structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java @@ -11,10 +11,7 @@ import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.List; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -866,6 +863,17 @@ void test_dynamicViewWithCustomElements() throws Exception { parser.parse(new File("src/test/resources/dsl/dynamic-view-with-custom-elements.dsl")); } + @Test + void test_dynamicViewWithExplicitOrdering() throws Exception { + StructurizrDslParser parser = new StructurizrDslParser(); + parser.parse(new File("src/test/resources/dsl/dynamic-view-with-explicit-ordering.dsl")); + DynamicView view = parser.getWorkspace().getViews().getDynamicViews().iterator().next(); + Set relationships = view.getRelationships(); + Iterator it = relationships.iterator(); + assertEquals("2", it.next().getOrder()); + assertEquals("3", it.next().getOrder()); + } + @Test void test_workspaceProperties() throws Exception { StructurizrDslParser parser = new StructurizrDslParser(); diff --git a/structurizr-dsl/src/test/java/com/structurizr/dsl/DynamicViewContentParserTests.java b/structurizr-dsl/src/test/java/com/structurizr/dsl/DynamicViewContentParserTests.java index 70bba7b5..a94047a8 100644 --- a/structurizr-dsl/src/test/java/com/structurizr/dsl/DynamicViewContentParserTests.java +++ b/structurizr-dsl/src/test/java/com/structurizr/dsl/DynamicViewContentParserTests.java @@ -19,7 +19,7 @@ void test_parseRelationship_ThrowsAnException_WhenThereAreTooManyTokens() { parser.parseRelationship(new DynamicViewDslContext(null), tokens("source", "->", "destination", "description", "technology", "extra")); fail(); } catch (Exception e) { - assertEquals("Too many tokens, expected: -> [description] [technology]", e.getMessage()); + assertEquals("Too many tokens, expected: [order:] -> [description] [technology]", e.getMessage()); } } @@ -29,7 +29,7 @@ void test_parseRelationship_ThrowsAnException_WhenTheDestinationIdentifierIsMiss parser.parseRelationship(new DynamicViewDslContext(null), tokens("source", "->")); fail(); } catch (Exception e) { - assertEquals("Expected: -> [description] [technology]", e.getMessage()); + assertEquals("Expected: [order:] -> [description] [technology]", e.getMessage()); } } diff --git a/structurizr-dsl/src/test/resources/dsl/dynamic-view-with-explicit-ordering.dsl b/structurizr-dsl/src/test/resources/dsl/dynamic-view-with-explicit-ordering.dsl new file mode 100644 index 00000000..45bed69c --- /dev/null +++ b/structurizr-dsl/src/test/resources/dsl/dynamic-view-with-explicit-ordering.dsl @@ -0,0 +1,19 @@ +workspace { + + model { + a = softwareSystem "A" + b = softwareSystem "B" + c = softwareSystem "C" + + a -> b + b -> c + } + + views { + dynamic * { + 2: a -> b + 3: b -> c + } + } + +} \ No newline at end of file