Skip to content

Commit 1bc95cc

Browse files
structurizr-dsl: Adds archetype support for custom elements.
1 parent 7c448e1 commit 1bc95cc

File tree

10 files changed

+105
-13
lines changed

10 files changed

+105
-13
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## v4.0.1 (unreleased)
44

55
- structurizr-dsl: Allows archetypes to be used via workspace extension.
6+
- structurizr-dsl: Adds archetype support for custom elements.
67
- structurizr-dsl: Fixes https://github.com/structurizr/java/issues/392 (SVG not supported in base 64 encoding not mentioned in documentation).
78
- structurizr-export: Adds support for rank and node separation to the StructurizrPlantUMLExporter.
89

structurizr-dsl/src/main/java/com/structurizr/dsl/Archetype.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ final class Archetype implements PropertyHolder, PerspectivesHolder {
1111

1212
private final String name;
1313
private final String type;
14+
private String metadata = "";
1415
private String description = "";
1516
private String technology = "";
1617
private final Set<String> tags = new LinkedHashSet<>();
@@ -35,6 +36,14 @@ String getType() {
3536
return type;
3637
}
3738

39+
String getMetadata() {
40+
return metadata;
41+
}
42+
43+
void setMetadata(String metadata) {
44+
this.metadata = metadata;
45+
}
46+
3847
String getDescription() {
3948
return description;
4049
}

structurizr-dsl/src/main/java/com/structurizr/dsl/ArchetypeParser.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ void parseTags(ArchetypeDslContext context, Tokens tokens) {
3131
}
3232
}
3333

34+
void parseMetadata(ArchetypeDslContext context, Tokens tokens) {
35+
// metadata <metadata>
36+
if (tokens.hasMoreThan(VALUE_INDEX)) {
37+
throw new RuntimeException("Too many tokens, expected: metadata <metadata>");
38+
}
39+
40+
if (!tokens.includes(NAME_INDEX)) {
41+
throw new RuntimeException("Expected: metadata <metadata>");
42+
}
43+
44+
String metadata = tokens.get(VALUE_INDEX);
45+
context.getArchetype().setMetadata(metadata);
46+
}
47+
3448
void parseDescription(ArchetypeDslContext context, Tokens tokens) {
3549
// description <description>
3650
if (tokens.hasMoreThan(VALUE_INDEX)) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.structurizr.dsl;
2+
3+
final class CustomElementArchetypeDslContext extends ElementArchetypeDslContext {
4+
5+
CustomElementArchetypeDslContext(Archetype archetype) {
6+
super(archetype);
7+
}
8+
9+
@Override
10+
protected String[] getPermittedTokens() {
11+
return new String[] {
12+
StructurizrDslTokens.METADATA_TOKEN,
13+
StructurizrDslTokens.DESCRIPTION_TOKEN,
14+
StructurizrDslTokens.TAG_TOKEN,
15+
StructurizrDslTokens.TAGS_TOKEN,
16+
StructurizrDslTokens.PROPERTIES_TOKEN,
17+
StructurizrDslTokens.PERSPECTIVES_TOKEN
18+
};
19+
}
20+
21+
}

structurizr-dsl/src/main/java/com/structurizr/dsl/CustomElementParser.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class CustomElementParser extends AbstractParser {
1313
private final static int DESCRIPTION_INDEX = 3;
1414
private final static int TAGS_INDEX = 4;
1515

16-
CustomElement parse(ModelDslContext context, Tokens tokens) {
16+
CustomElement parse(ModelDslContext context, Tokens tokens, Archetype archetype) {
1717
// element <name> [metadata] [description] [tags]
1818

1919
if (tokens.hasMoreThan(TAGS_INDEX)) {
@@ -26,22 +26,23 @@ CustomElement parse(ModelDslContext context, Tokens tokens) {
2626

2727
String name = tokens.get(NAME_INDEX);
2828

29-
String metadata = "";
29+
String metadata = archetype.getMetadata();
3030
if (tokens.includes(METADATA_INDEX)) {
3131
metadata = tokens.get(METADATA_INDEX);
3232
}
3333

34-
String description = "";
34+
String description = archetype.getDescription();
3535
if (tokens.includes(DESCRIPTION_INDEX)) {
3636
description = tokens.get(DESCRIPTION_INDEX);
3737
}
3838

3939
CustomElement customElement = context.getWorkspace().getModel().addCustomElement(name, metadata, description);
4040

41+
String[] tags = archetype.getTags().toArray(new String[0]);
4142
if (tokens.includes(TAGS_INDEX)) {
42-
String tags = tokens.get(TAGS_INDEX);
43-
customElement.addTags(tags.split(","));
43+
tags = tokens.get(TAGS_INDEX).split(",");
4444
}
45+
customElement.addTags(tags);
4546

4647
if (context.hasGroup()) {
4748
customElement.setGroup(context.getGroup().getName());

structurizr-dsl/src/main/java/com/structurizr/dsl/StructurizrDslParser.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public final class StructurizrDslParser extends StructurizrDslTokens {
4949

5050
private Map<String,Map<String,Archetype>> archetypes = Map.of(
5151
StructurizrDslTokens.GROUP_TOKEN, new HashMap<>(),
52+
StructurizrDslTokens.CUSTOM_ELEMENT_TOKEN, new HashMap<>(),
5253
StructurizrDslTokens.PERSON_TOKEN, new HashMap<>(),
5354
StructurizrDslTokens.SOFTWARE_SYSTEM_TOKEN, new HashMap<>(),
5455
StructurizrDslTokens.CONTAINER_TOKEN, new HashMap<>(),
@@ -421,8 +422,9 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
421422
startContext(new RelationshipsDslContext(getContext(), relationships));
422423
}
423424

424-
} else if (CUSTOM_ELEMENT_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class))) {
425-
CustomElement customElement = new CustomElementParser().parse(getContext(ModelDslContext.class), tokens.withoutContextStartToken());
425+
} else if (isElementKeywordOrArchetype(firstToken, CUSTOM_ELEMENT_TOKEN) && (inContext(ModelDslContext.class))) {
426+
Archetype archetype = getArchetype(CUSTOM_ELEMENT_TOKEN, firstToken);
427+
CustomElement customElement = new CustomElementParser().parse(getContext(ModelDslContext.class), tokens.withoutContextStartToken(), archetype);
426428

427429
if (shouldStartContext(tokens)) {
428430
startContext(new CustomElementDslContext(customElement));
@@ -673,6 +675,15 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
673675
extendArchetype(archetype, firstToken);
674676
addArchetype(archetype);
675677

678+
} else if (isElementKeywordOrArchetype(firstToken, CUSTOM_ELEMENT_TOKEN) && inContext(ArchetypesDslContext.class)) {
679+
Archetype archetype = new Archetype(identifier, CUSTOM_ELEMENT_TOKEN);
680+
extendArchetype(archetype, firstToken);
681+
addArchetype(archetype);
682+
683+
if (shouldStartContext(tokens)) {
684+
startContext(new CustomElementArchetypeDslContext(archetype));
685+
}
686+
676687
} else if (isElementKeywordOrArchetype(firstToken, PERSON_TOKEN) && inContext(ArchetypesDslContext.class)) {
677688
Archetype archetype = new Archetype(identifier, PERSON_TOKEN);
678689
extendArchetype(archetype, firstToken);
@@ -737,6 +748,9 @@ void parse(List<String> lines, File dslFile, boolean fragment, boolean includeIn
737748
startContext(new RelationshipArchetypeDslContext(archetype));
738749
}
739750

751+
} else if (METADATA_TOKEN.equalsIgnoreCase(firstToken) && inContext(CustomElementArchetypeDslContext.class)) {
752+
new ArchetypeParser().parseMetadata(getContext(ArchetypeDslContext.class), tokens);
753+
740754
} else if (DESCRIPTION_TOKEN.equalsIgnoreCase(firstToken) && inContext(ArchetypeDslContext.class)) {
741755
new ArchetypeParser().parseDescription(getContext(ArchetypeDslContext.class), tokens);
742756

@@ -1384,6 +1398,7 @@ private void extendArchetype(Archetype archetype, String archetypeName) {
13841398
archetypeName = archetypeName.toLowerCase();
13851399
Archetype parentArchetype = getArchetype(archetype.getType(), archetypeName);
13861400

1401+
archetype.setMetadata(parentArchetype.getMetadata());
13871402
archetype.setDescription(parentArchetype.getDescription());
13881403
archetype.setTechnology(parentArchetype.getTechnology());
13891404
archetype.addTags(parentArchetype.getTags().toArray(new String[0]));

structurizr-dsl/src/main/java/com/structurizr/dsl/StructurizrDslTokens.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class StructurizrDslTokens {
1515
static final String COMPONENT_TOKEN = "component";
1616
static final String GROUP_TOKEN = "group";
1717
static final String NAME_TOKEN = "name";
18+
static final String METADATA_TOKEN = "metadata";
1819
static final String DESCRIPTION_TOKEN = "description";
1920
static final String TECHNOLOGY_TOKEN = "technology";
2021
static final String INSTANCES_TOKEN = "instances";

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
class CustomElementParserTests extends AbstractTests {
99

1010
private CustomElementParser parser = new CustomElementParser();
11+
private Archetype archetype = new Archetype("name", "type");
1112

1213
@Test
1314
void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() {
1415
try {
15-
parser.parse(context(), tokens("element", "name", "metadata", "description", "tags", "extra"));
16+
parser.parse(context(), tokens("element", "name", "metadata", "description", "tags", "extra"), archetype);
1617
fail();
1718
} catch (Exception e) {
1819
assertEquals("Too many tokens, expected: element <name> [metadata] [description] [tags]", e.getMessage());
@@ -22,7 +23,7 @@ void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() {
2223
@Test
2324
void test_parse_ThrowsAnException_WhenTheNameIsNotSpecified() {
2425
try {
25-
parser.parse(context(), tokens("element"));
26+
parser.parse(context(), tokens("element"), archetype);
2627
fail();
2728
} catch (Exception e) {
2829
assertEquals("Expected: element <name> [metadata] [description] [tags]", e.getMessage());
@@ -31,7 +32,7 @@ void test_parse_ThrowsAnException_WhenTheNameIsNotSpecified() {
3132

3233
@Test
3334
void test_parse_CreatesACustomElement() {
34-
parser.parse(context(), tokens("element", "Name"));
35+
parser.parse(context(), tokens("element", "Name"), archetype);
3536

3637
assertEquals(1, model.getElements().size());
3738
CustomElement element = model.getCustomElementWithName("Name");
@@ -42,7 +43,7 @@ void test_parse_CreatesACustomElement() {
4243

4344
@Test
4445
void test_parse_CreatesACustomElementWithMetadata() {
45-
parser.parse(context(), tokens("element", "Name", "Box"));
46+
parser.parse(context(), tokens("element", "Name", "Box"), archetype);
4647

4748
assertEquals(1, model.getElements().size());
4849
CustomElement element = model.getCustomElementWithName("Name");
@@ -54,7 +55,7 @@ void test_parse_CreatesACustomElementWithMetadata() {
5455

5556
@Test
5657
void test_parse_CreatesACustomElementWithMetadataAndDescription() {
57-
parser.parse(context(), tokens("element", "Name", "Box", "Description"));
58+
parser.parse(context(), tokens("element", "Name", "Box", "Description"), archetype);
5859

5960
assertEquals(1, model.getElements().size());
6061
CustomElement element = model.getCustomElementWithName("Name");
@@ -66,7 +67,7 @@ void test_parse_CreatesACustomElementWithMetadataAndDescription() {
6667

6768
@Test
6869
void test_parse_CreatesACustomElementWithMetadataAndDescriptionAndTags() {
69-
parser.parse(context(), tokens("element", "Name", "Box", "Description", "Tag 1, Tag 2"));
70+
parser.parse(context(), tokens("element", "Name", "Box", "Description", "Tag 1, Tag 2"), archetype);
7071

7172
assertEquals(1, model.getElements().size());
7273
CustomElement element = model.getCustomElementWithName("Name");

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,18 @@ void test_archetypes() throws Exception {
14741474
assertEquals("HTTPS", relationship.getTechnology());
14751475
}
14761476

1477+
@Test
1478+
void test_archetypesForCustomElements() throws Exception {
1479+
File parentDslFile = new File("src/test/resources/dsl/archetypes-for-custom-elements.dsl");
1480+
StructurizrDslParser parser = new StructurizrDslParser();
1481+
parser.parse(parentDslFile);
1482+
Workspace workspace = parser.getWorkspace();
1483+
1484+
CustomElement b = workspace.getModel().getCustomElementWithName("B");
1485+
assertEquals("Hardware System", b.getMetadata());
1486+
assertTrue(b.getTagsAsSet().contains("Hardware System"));
1487+
}
1488+
14771489
@Test
14781490
void test_archetypesForDefaults() throws Exception {
14791491
File parentDslFile = new File("src/test/resources/dsl/archetypes-for-defaults.dsl");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
workspace {
2+
3+
model {
4+
archetypes {
5+
hardwareSystem = element {
6+
metadata "Hardware System"
7+
tag "Hardware System"
8+
}
9+
}
10+
11+
a = softwareSystem "A"
12+
b = hardwareSystem "B"
13+
14+
a -> b "Gets data from"
15+
}
16+
17+
}

0 commit comments

Comments
 (0)