Skip to content

Commit 7d28dd5

Browse files
Adds support for specifying view animation steps via element expressions.
1 parent af225d0 commit 7d28dd5

File tree

11 files changed

+219
-40
lines changed

11 files changed

+219
-40
lines changed

changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/392 (SVG not supported in base 64 encoding not mentioned in documentation).
1010
- structurizr-dsl: Adds support for setting the symbols surrounding element/relationship metadata used when rendering diagrams.
1111
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/408 (Animation steps cannot be added to deployment views via static structure element references).
12-
- structurizr-dsl: Adds support for specifying view animation steps via element expressions (deployment views only; others to follow).
12+
- structurizr-dsl: Adds support for specifying view animation steps via element expressions.
1313
- structurizr-export: Adds support for rank and node separation to the StructurizrPlantUMLExporter.
1414

1515
## v4.0.0 (28th March 2025)

structurizr-core/src/main/java/com/structurizr/view/CustomView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public void addAnimation(CustomElement... elements) {
110110
}
111111
}
112112

113-
if (elementsInThisAnimationStep.size() == 0) {
113+
if (elementsInThisAnimationStep.isEmpty()) {
114114
throw new IllegalArgumentException("None of the specified elements exist in this view.");
115115
}
116116

structurizr-core/src/main/java/com/structurizr/view/StaticView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public void addAnimation(Element... elements) {
218218
}
219219
}
220220

221-
if (elementsInThisAnimationStep.size() == 0) {
221+
if (elementsInThisAnimationStep.isEmpty()) {
222222
throw new IllegalArgumentException("None of the specified elements exist in this view.");
223223
}
224224

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,70 @@
11
package com.structurizr.dsl;
22

3-
import com.structurizr.model.CustomElement;
4-
import com.structurizr.model.Element;
3+
import com.structurizr.model.*;
54
import com.structurizr.view.CustomView;
65

76
import java.util.ArrayList;
87
import java.util.List;
8+
import java.util.Set;
99

1010
final class CustomViewAnimationStepParser extends AbstractParser {
1111

12+
private static final String GRAMMAR = "<identifier|element expression> [identifier|element expression...]";
13+
1214
void parse(CustomViewDslContext context, Tokens tokens) {
13-
// animationStep <identifier> [identifier...]
15+
// animationStep <identifier|element expression> [identifier|element expression...]
1416

1517
if (!tokens.includes(1)) {
16-
throw new RuntimeException("Expected: animationStep <identifier> [identifier...]");
18+
throw new RuntimeException("Expected: animationStep " + GRAMMAR);
1719
}
1820

1921
parse(context, context.getCustomView(), tokens, 1);
2022
}
2123

2224
void parse(CustomViewAnimationDslContext context, Tokens tokens) {
23-
// <identifier> [identifier...]
25+
// <identifier|element expression> [identifier|element expression...]
2426

2527
if (!tokens.includes(0)) {
26-
throw new RuntimeException("Expected: <identifier> [identifier...]");
28+
throw new RuntimeException("Expected: " + GRAMMAR);
2729
}
2830

2931
parse(context, context.getView(), tokens, 0);
3032
}
3133

3234
void parse(DslContext context, CustomView view, Tokens tokens, int startIndex) {
33-
List<CustomElement> elements = new ArrayList<>();
35+
List<CustomElement> customElements = new ArrayList<>();
3436

3537
for (int i = startIndex; i < tokens.size(); i++) {
36-
String elementIdentifier = tokens.get(i);
38+
String token = tokens.get(i);
3739

38-
Element element = context.getElement(elementIdentifier);
39-
if (element == null) {
40-
throw new RuntimeException("The element \"" + elementIdentifier + "\" does not exist");
41-
}
40+
if (ExpressionParser.isExpression(token.toLowerCase())) {
41+
Set<ModelItem> elements = new CustomViewExpressionParser().parseExpression(token, context);
42+
43+
for (ModelItem element : elements) {
44+
if (element instanceof CustomElement) {
45+
customElements.add((CustomElement)element);
46+
}
47+
}
48+
} else {
49+
Set<ModelItem> elements = new CustomViewExpressionParser().parseIdentifier(token, context);
4250

43-
if (element instanceof CustomElement) {
44-
elements.add((CustomElement)element);
51+
if (elements.isEmpty()) {
52+
throw new RuntimeException("The element \"" + token + "\" does not exist");
53+
}
54+
55+
for (ModelItem element : elements) {
56+
if (element instanceof CustomElement) {
57+
customElements.add((CustomElement)element);
58+
}
59+
}
4560
}
4661
}
4762

48-
view.addAnimation(elements.toArray(new CustomElement[0]));
63+
if (!customElements.isEmpty()) {
64+
view.addAnimation(customElements.toArray(new CustomElement[0]));
65+
} else {
66+
throw new RuntimeException("No custom elements were found");
67+
}
4968
}
5069

5170
}
Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,72 @@
11
package com.structurizr.dsl;
22

33
import com.structurizr.model.Element;
4+
import com.structurizr.model.ModelItem;
5+
import com.structurizr.model.StaticStructureElement;
46
import com.structurizr.view.StaticView;
57

68
import java.util.ArrayList;
79
import java.util.List;
10+
import java.util.Set;
811

912
final class StaticViewAnimationStepParser extends AbstractParser {
1013

14+
private static final String GRAMMAR = "<identifier|element expression> [identifier|element expression...]";
15+
1116
void parse(StaticViewDslContext context, Tokens tokens) {
12-
// animationStep <identifier> [identifier...]
17+
// animationStep <identifier|element expression> [identifier|element expression...]
1318

1419
if (!tokens.includes(1)) {
15-
throw new RuntimeException("Expected: animationStep <identifier> [identifier...]");
20+
throw new RuntimeException("Expected: animationStep " + GRAMMAR);
1621
}
1722

1823
parse(context, context.getView(), tokens, 1);
1924
}
2025

2126
void parse(StaticViewAnimationDslContext context, Tokens tokens) {
22-
// <identifier> [identifier...]
27+
// <identifier|element expression> [identifier|element expression...]
2328

2429
if (!tokens.includes(0)) {
25-
throw new RuntimeException("Expected: <identifier> [identifier...]");
30+
throw new RuntimeException("Expected: " + GRAMMAR);
2631
}
2732

2833
parse(context, context.getView(), tokens, 0);
2934
}
3035

31-
void parse(DslContext context, StaticView view, Tokens tokens, int startIndex) {
32-
// <identifier> [identifier...]
33-
34-
List<Element> elements = new ArrayList<>();
36+
private void parse(DslContext context, StaticView view, Tokens tokens, int startIndex) {
37+
List<StaticStructureElement> staticStructureElements = new ArrayList<>();
3538

3639
for (int i = startIndex; i < tokens.size(); i++) {
37-
String elementIdentifier = tokens.get(i);
38-
39-
Element element = context.getElement(elementIdentifier);
40-
if (element == null) {
41-
throw new RuntimeException("The element \"" + elementIdentifier + "\" does not exist");
40+
String token = tokens.get(i);
41+
42+
if (ExpressionParser.isExpression(token.toLowerCase())) {
43+
Set<ModelItem> elements = new StaticViewExpressionParser().parseExpression(token, context);
44+
45+
for (ModelItem element : elements) {
46+
if (element instanceof StaticStructureElement) {
47+
staticStructureElements.add((StaticStructureElement)element);
48+
}
49+
}
50+
} else {
51+
Set<ModelItem> elements = new StaticViewExpressionParser().parseIdentifier(token, context);
52+
53+
if (elements.isEmpty()) {
54+
throw new RuntimeException("The element \"" + token + "\" does not exist");
55+
}
56+
57+
for (ModelItem element : elements) {
58+
if (element instanceof StaticStructureElement) {
59+
staticStructureElements.add((StaticStructureElement)element);
60+
}
61+
}
4262
}
43-
44-
elements.add(element);
4563
}
4664

47-
view.addAnimation(elements.toArray(new Element[0]));
65+
if (!staticStructureElements.isEmpty()) {
66+
view.addAnimation(staticStructureElements.toArray(new Element[0]));
67+
} else {
68+
throw new RuntimeException("No elements were found");
69+
}
4870
}
4971

5072
}

structurizr-dsl/src/test/java/com/structurizr/dsl/CustomViewAnimationStepParserTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.structurizr.dsl;
22

3+
import com.structurizr.view.CustomView;
34
import org.junit.jupiter.api.Test;
45

56
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,7 +16,7 @@ void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() {
1516
parser.parse((CustomViewDslContext)null, tokens("animationStep"));
1617
fail();
1718
} catch (Exception e) {
18-
assertEquals("Expected: animationStep <identifier> [identifier...]", e.getMessage());
19+
assertEquals("Expected: animationStep <identifier|element expression> [identifier|element expression...]", e.getMessage());
1920
}
2021
}
2122

@@ -25,7 +26,23 @@ void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() {
2526
parser.parse((CustomViewAnimationDslContext) null, tokens());
2627
fail();
2728
} catch (Exception e) {
28-
assertEquals("Expected: <identifier> [identifier...]", e.getMessage());
29+
assertEquals("Expected: <identifier|element expression> [identifier|element expression...]", e.getMessage());
30+
}
31+
}
32+
33+
@Test
34+
void test_parse_ThrowsAnException_WhenTheElementDoesNotExist() {
35+
CustomView view = workspace.getViews().createCustomView("key", "Title", "Description");
36+
37+
CustomViewAnimationDslContext context = new CustomViewAnimationDslContext(view);
38+
IdentifiersRegister map = new IdentifiersRegister();
39+
context.setIdentifierRegister(map);
40+
41+
try {
42+
parser.parse(context, tokens("e"));
43+
fail();
44+
} catch (Exception e) {
45+
assertEquals("The element/relationship \"e\" does not exist", e.getMessage());
2946
}
3047
}
3148

structurizr-dsl/src/test/java/com/structurizr/dsl/DslTests.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,9 +1577,49 @@ void test_textBlock() throws Exception {
15771577
}
15781578

15791579
@Test
1580-
void test_deploymentAnimation() throws Exception {
1580+
void test_customViewAnimation() throws Exception {
15811581
StructurizrDslParser parser = new StructurizrDslParser();
1582-
parser.parse(new File("src/test/resources/dsl/deployment-animation.dsl"));
1582+
parser.parse(new File("src/test/resources/dsl/custom-view-animation.dsl"));
1583+
1584+
Workspace workspace = parser.getWorkspace();
1585+
CustomElement a = workspace.getModel().getCustomElementWithName("A");
1586+
CustomElement b = workspace.getModel().getCustomElementWithName("B");
1587+
1588+
for (CustomView view : workspace.getViews().getCustomViews()) {
1589+
assertEquals(2, view.getAnimations().size());
1590+
1591+
// step 1
1592+
assertTrue(view.getAnimations().get(0).getElements().contains(a.getId()));
1593+
1594+
// step 2
1595+
assertTrue(view.getAnimations().get(1).getElements().contains(b.getId()));
1596+
}
1597+
}
1598+
1599+
@Test
1600+
void test_staticViewAnimation() throws Exception {
1601+
StructurizrDslParser parser = new StructurizrDslParser();
1602+
parser.parse(new File("src/test/resources/dsl/static-view-animation.dsl"));
1603+
1604+
Workspace workspace = parser.getWorkspace();
1605+
SoftwareSystem a = workspace.getModel().getSoftwareSystemWithName("A");
1606+
SoftwareSystem b = workspace.getModel().getSoftwareSystemWithName("B");
1607+
1608+
for (SystemLandscapeView view : workspace.getViews().getSystemLandscapeViews()) {
1609+
assertEquals(2, view.getAnimations().size());
1610+
1611+
// step 1
1612+
assertTrue(view.getAnimations().get(0).getElements().contains(a.getId()));
1613+
1614+
// step 2
1615+
assertTrue(view.getAnimations().get(1).getElements().contains(b.getId()));
1616+
}
1617+
}
1618+
1619+
@Test
1620+
void test_deploymentViewAnimation() throws Exception {
1621+
StructurizrDslParser parser = new StructurizrDslParser();
1622+
parser.parse(new File("src/test/resources/dsl/deployment-view-animation.dsl"));
15831623

15841624
Workspace workspace = parser.getWorkspace();
15851625
Container webapp = workspace.getModel().getSoftwareSystemWithName("Software System").getContainerWithName("Web Application");

structurizr-dsl/src/test/java/com/structurizr/dsl/StaticViewAnimationStepParserTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.structurizr.dsl;
22

3+
import com.structurizr.view.SystemLandscapeView;
34
import org.junit.jupiter.api.Test;
45

56
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,7 +16,7 @@ void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() {
1516
parser.parse((StaticViewDslContext)null, tokens("animationStep"));
1617
fail();
1718
} catch (Exception e) {
18-
assertEquals("Expected: animationStep <identifier> [identifier...]", e.getMessage());
19+
assertEquals("Expected: animationStep <identifier|element expression> [identifier|element expression...]", e.getMessage());
1920
}
2021
}
2122

@@ -25,7 +26,23 @@ void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() {
2526
parser.parse((StaticViewAnimationDslContext) null, tokens());
2627
fail();
2728
} catch (Exception e) {
28-
assertEquals("Expected: <identifier> [identifier...]", e.getMessage());
29+
assertEquals("Expected: <identifier|element expression> [identifier|element expression...]", e.getMessage());
30+
}
31+
}
32+
33+
@Test
34+
void test_parse_ThrowsAnException_WhenTheElementDoesNotExist() {
35+
SystemLandscapeView view = workspace.getViews().createSystemLandscapeView("key", "Description");
36+
37+
StaticViewAnimationDslContext context = new StaticViewAnimationDslContext(view);
38+
IdentifiersRegister map = new IdentifiersRegister();
39+
context.setIdentifierRegister(map);
40+
41+
try {
42+
parser.parse(context, tokens("user"));
43+
fail();
44+
} catch (Exception e) {
45+
assertEquals("The element/relationship \"user\" does not exist", e.getMessage());
2946
}
3047
}
3148

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
workspace {
2+
3+
model {
4+
a = element "A"
5+
b = element "B"
6+
7+
a -> b
8+
}
9+
10+
views {
11+
custom {
12+
include *
13+
14+
// add animation steps via element identifiers
15+
animation {
16+
a
17+
b
18+
}
19+
}
20+
21+
custom {
22+
include *
23+
24+
// add animation steps via element expressions
25+
animation {
26+
a
27+
a->
28+
}
29+
}
30+
}
31+
32+
}

0 commit comments

Comments
 (0)