Skip to content

Commit

Permalink
Adds the ability to bulk create relationships via the !elements key…
Browse files Browse the repository at this point in the history
…word.
  • Loading branch information
simonbrowndotje committed Aug 20, 2024
1 parent d2ccb98 commit aeb71a6
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 47 deletions.
27 changes: 17 additions & 10 deletions structurizr-dsl/src/main/java/com/structurizr/dsl/DslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,25 @@ Element getElement(String identifier, Class<? extends Element> type) {
identifier = identifier.toLowerCase();

if (identifiersRegister.getIdentifierScope() == IdentifierScope.Hierarchical) {
if (this instanceof ModelItemDslContext) {
ModelItemDslContext modelItemDslContext = (ModelItemDslContext)this;
if (modelItemDslContext.getModelItem() instanceof Element) {
Element parent = (Element)modelItemDslContext.getModelItem();
while (parent != null && element == null) {
String parentIdentifier = identifiersRegister.findIdentifier(parent);
ElementDslContext elementDslContext = null;
if (this instanceof ElementDslContext) {
elementDslContext = (ElementDslContext)this;
} else if (this instanceof ElementsDslContext) {
ElementsDslContext elementsDslContext = (ElementsDslContext)this;
if (elementsDslContext.getParentDslContext() instanceof ElementDslContext) {
elementDslContext = (ElementDslContext)elementsDslContext.getParentDslContext();
}
}

element = identifiersRegister.getElement(parentIdentifier + "." + identifier);
parent = parent.getParent();
if (elementDslContext != null) {
Element parent = elementDslContext.getElement();
while (parent != null && element == null) {
String parentIdentifier = identifiersRegister.findIdentifier(parent);

element = checkElementType(element, type);
}
element = identifiersRegister.getElement(parentIdentifier + "." + identifier);
parent = parent.getParent();

element = checkElementType(element, type);
}
} else if (this instanceof DeploymentEnvironmentDslContext) {
DeploymentEnvironmentDslContext deploymentEnvironmentDslContext = (DeploymentEnvironmentDslContext)this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.structurizr.dsl;

import com.structurizr.model.*;
import com.structurizr.model.Element;
import com.structurizr.model.Relationship;

import javax.lang.model.util.Elements;
import java.util.LinkedHashSet;
import java.util.Set;

final class ExplicitRelationshipParser extends AbstractRelationshipParser {

Expand All @@ -24,30 +29,55 @@ Relationship parse(DslContext context, Tokens tokens) {
}

String sourceId = tokens.get(SOURCE_IDENTIFIER_INDEX);
Element sourceElement = findElement(sourceId, context);
if (sourceElement == null) {
throw new RuntimeException("The source element \"" + sourceId + "\" does not exist");
}

String destinationId = tokens.get(DESTINATION_IDENTIFIER_INDEX);
Element destinationElement = findElement(destinationId, context);
if (destinationElement == null) {
throw new RuntimeException("The destination element \"" + destinationId + "\" does not exist");
}

Element sourceElement = context.getElement(sourceId);
Element destinationElement = context.getElement(destinationId);
String description = "";
if (tokens.includes(DESCRIPTION_INDEX)) {
description = tokens.get(DESCRIPTION_INDEX);
}

if (sourceElement == null) {
if (StructurizrDslTokens.THIS_TOKEN.equalsIgnoreCase(sourceId) && context instanceof GroupableElementDslContext) {
GroupableElementDslContext groupableElementDslContext = (GroupableElementDslContext)context;
sourceElement = groupableElementDslContext.getElement();
} else {
throw new RuntimeException("The source element \"" + sourceId + "\" does not exist");
}
String technology = "";
if (tokens.includes(TECHNOLOGY_INDEX)) {
technology = tokens.get(TECHNOLOGY_INDEX);
}

if (destinationElement == null) {
if (StructurizrDslTokens.THIS_TOKEN.equalsIgnoreCase(destinationId) && context instanceof ModelItemDslContext) {
ModelItemDslContext modelItemDslContext = (ModelItemDslContext) context;
if (modelItemDslContext.getModelItem() instanceof Element) {
destinationElement = (Element)modelItemDslContext.getModelItem();
}
}
String[] tags = new String[0];
if (tokens.includes(TAGS_INDEX)) {
tags = tokens.get(TAGS_INDEX).split(",");
}

if (destinationElement == null) {
return createRelationship(sourceElement, description, technology, tags, destinationElement);
}

void parse(ElementsDslContext context, Tokens tokens) {
// <identifier> -> <identifier> [description] [technology] [tags]

if (tokens.hasMoreThan(TAGS_INDEX)) {
throw new RuntimeException("Too many tokens, expected: " + GRAMMAR);
}

if (!tokens.includes(DESTINATION_IDENTIFIER_INDEX)) {
throw new RuntimeException("Expected: " + GRAMMAR);
}

String sourceId = tokens.get(SOURCE_IDENTIFIER_INDEX);
Set<Element> sourceElements = findElements(sourceId, context);
if (sourceElements.isEmpty()) {
throw new RuntimeException("The source element \"" + sourceId + "\" does not exist");
}

String destinationId = tokens.get(DESTINATION_IDENTIFIER_INDEX);
Set<Element> destinationElements = findElements(destinationId, context);
if (destinationElements.isEmpty()) {
throw new RuntimeException("The destination element \"" + destinationId + "\" does not exist");
}

Expand All @@ -66,7 +96,36 @@ Relationship parse(DslContext context, Tokens tokens) {
tags = tokens.get(TAGS_INDEX).split(",");
}

return createRelationship(sourceElement, description, technology, tags, destinationElement);
for (Element sourceElement : sourceElements) {
for (Element destinationElement : destinationElements) {
createRelationship(sourceElement, description, technology, tags, destinationElement);
}
}
}

private Element findElement(String identifier, DslContext context) {
Element element = context.getElement(identifier);

if (element == null && StructurizrDslTokens.THIS_TOKEN.equalsIgnoreCase(identifier) && context instanceof ElementDslContext) {
element = ((ElementDslContext)context).getElement();
}

return element;
}

private Set<Element> findElements(String identifier, ElementsDslContext context) {
Element element = context.getElement(identifier);
Set<Element> elements = new LinkedHashSet<>();

if (element == null) {
if (StructurizrDslTokens.THIS_TOKEN.equalsIgnoreCase(identifier)) {
elements.addAll(context.getElements());
}
} else {
elements.add(element);
}

return elements;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.structurizr.dsl;

import com.structurizr.model.*;
import com.structurizr.model.Element;
import com.structurizr.model.Relationship;

import java.util.Set;

final class ImplicitRelationshipParser extends AbstractRelationshipParser {

Expand All @@ -11,7 +14,7 @@ final class ImplicitRelationshipParser extends AbstractRelationshipParser {
private final static int TECHNOLOGY_INDEX = 3;
private final static int TAGS_INDEX = 4;

Relationship parse(ModelItemDslContext context, Tokens tokens) {
Relationship parse(ElementDslContext context, Tokens tokens) {
// -> <identifier> [description] [technology] [tags]

if (tokens.hasMoreThan(TAGS_INDEX)) {
Expand All @@ -24,9 +27,9 @@ Relationship parse(ModelItemDslContext context, Tokens tokens) {

String destinationId = tokens.get(DESTINATION_IDENTIFIER_INDEX);

Element sourceElement = (Element)context.getModelItem();
Element destinationElement = context.getElement(destinationId);
Element sourceElement = context.getElement();

Element destinationElement = context.getElement(destinationId);
if (destinationElement == null) {
throw new RuntimeException("The destination element \"" + destinationId + "\" does not exist");
}
Expand All @@ -49,4 +52,43 @@ Relationship parse(ModelItemDslContext context, Tokens tokens) {
return createRelationship(sourceElement, description, technology, tags, destinationElement);
}

void parse(ElementsDslContext context, Tokens tokens) {
// -> <identifier> [description] [technology] [tags]

if (tokens.hasMoreThan(TAGS_INDEX)) {
throw new RuntimeException("Too many tokens, expected: " + GRAMMAR);
}

if (!tokens.includes(DESTINATION_IDENTIFIER_INDEX)) {
throw new RuntimeException("Expected: " + GRAMMAR);
}

Set<Element> sourceElements = context.getElements();

String destinationId = tokens.get(DESTINATION_IDENTIFIER_INDEX);
Element destinationElement = context.getElement(destinationId);
if (destinationElement == null) {
throw new RuntimeException("The destination element \"" + destinationId + "\" does not exist");
}

String description = "";
if (tokens.includes(DESCRIPTION_INDEX)) {
description = tokens.get(DESCRIPTION_INDEX);
}

String technology = "";
if (tokens.includes(TECHNOLOGY_INDEX)) {
technology = tokens.get(TECHNOLOGY_INDEX);
}

String[] tags = new String[0];
if (tokens.includes(TAGS_INDEX)) {
tags = tokens.get(TAGS_INDEX).split(",");
}

for (Element sourceElement : sourceElements) {
createRelationship(sourceElement, description, technology, tags, destinationElement);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ void parse(List<String> lines, File dslFile, boolean include) throws Structurizr
} else if (inContext(ExternalScriptDslContext.class)) {
new ScriptParser().parseParameter(getContext(ExternalScriptDslContext.class), tokens);

} else if (tokens.size() > 2 && RELATIONSHIP_TOKEN.equals(tokens.get(1)) && (inContext(ModelDslContext.class) || inContext(CustomElementDslContext.class) || inContext(PersonDslContext.class) || inContext(SoftwareSystemDslContext.class) || inContext(ContainerDslContext.class) || inContext(ComponentDslContext.class) || inContext(DeploymentEnvironmentDslContext.class) || inContext(DeploymentNodeDslContext.class) || inContext(InfrastructureNodeDslContext.class) || inContext(SoftwareSystemInstanceDslContext.class) || inContext(ContainerInstanceDslContext.class))) {
} else if (tokens.size() > 2 && RELATIONSHIP_TOKEN.equals(tokens.get(1)) && (inContext(ModelDslContext.class) || inContext(DeploymentEnvironmentDslContext.class) || inContext(ElementDslContext.class))) {
Relationship relationship = new ExplicitRelationshipParser().parse(getContext(), tokens.withoutContextStartToken());

if (shouldStartContext(tokens)) {
Expand All @@ -304,15 +304,21 @@ void parse(List<String> lines, File dslFile, boolean include) throws Structurizr

registerIdentifier(identifier, relationship);

} else if (tokens.size() >= 2 && RELATIONSHIP_TOKEN.equals(tokens.get(0)) && (inContext(CustomElementDslContext.class) || inContext(PersonDslContext.class) || inContext(SoftwareSystemDslContext.class) || inContext(ContainerDslContext.class) || inContext(ComponentDslContext.class) || inContext(DeploymentNodeDslContext.class) || inContext(InfrastructureNodeDslContext.class) || inContext(SoftwareSystemInstanceDslContext.class) || inContext(ContainerInstanceDslContext.class))) {
Relationship relationship = new ImplicitRelationshipParser().parse(getContext(ModelItemDslContext.class), tokens.withoutContextStartToken());
} else if (tokens.size() >= 2 && RELATIONSHIP_TOKEN.equals(tokens.get(0)) && inContext(ElementDslContext.class)) {
Relationship relationship = new ImplicitRelationshipParser().parse(getContext(ElementDslContext.class), tokens.withoutContextStartToken());

if (shouldStartContext(tokens)) {
startContext(new RelationshipDslContext(relationship));
}

registerIdentifier(identifier, relationship);

} else if (tokens.size() > 2 && RELATIONSHIP_TOKEN.equals(tokens.get(1)) && inContext(ElementsDslContext.class)) {
new ExplicitRelationshipParser().parse(getContext(ElementsDslContext.class), tokens.withoutContextStartToken());

} else if (tokens.size() >= 2 && RELATIONSHIP_TOKEN.equals(tokens.get(0)) && inContext(ElementsDslContext.class)) {
new ImplicitRelationshipParser().parse(getContext(ElementsDslContext.class), tokens.withoutContextStartToken());

} else if ((REF_TOKEN.equalsIgnoreCase(firstToken) || EXTEND_TOKEN.equalsIgnoreCase(firstToken)) && (inContext(ModelDslContext.class))) {
ModelItem modelItem = new RefParser().parse(getContext(), tokens.withoutContextStartToken());

Expand Down Expand Up @@ -348,7 +354,7 @@ void parse(List<String> lines, File dslFile, boolean include) throws Structurizr
}
}

} else if (ELEMENTS_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class) || inContext(ModelItemDslContext.class))) {
} else if (ELEMENTS_TOKEN.equalsIgnoreCase(firstToken) && (inContext(ModelDslContext.class) || inContext(ElementDslContext.class))) {
Set<Element> elements = new ElementsParser().parse(getContext(), tokens.withoutContextStartToken());

if (shouldStartContext(tokens)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1143,4 +1143,13 @@ void test_Var_CannotOverrideConst() {
}
}


@Test
void test_bulkOperations() throws Exception {
File dslFile = new File("src/test/resources/dsl/bulk-operations.dsl");

StructurizrDslParser parser = new StructurizrDslParser();
parser.parse(dslFile);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class ImplicitRelationshipParserTests extends AbstractTests {

private ImplicitRelationshipParser parser = new ImplicitRelationshipParser();

private ModelItemDslContext context(Person person) {
ModelItemDslContext context = new PersonDslContext(person);
private ElementDslContext context(Person person) {
PersonDslContext context = new PersonDslContext(person);
context.setWorkspace(workspace);
model.setImpliedRelationshipsStrategy(new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy());

Expand All @@ -20,7 +20,7 @@ private ModelItemDslContext context(Person person) {
@Test
void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() {
try {
parser.parse(context(null), tokens("->", "destination", "description", "technology", "tags", "extra"));
parser.parse((ElementDslContext)null, tokens("->", "destination", "description", "technology", "tags", "extra"));
fail();
} catch (Exception e) {
assertEquals("Too many tokens, expected: -> <identifier> [description] [technology] [tags]", e.getMessage());
Expand All @@ -30,7 +30,7 @@ void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() {
@Test
void test_parse_ThrowsAnException_WhenTheDestinationIdentifierIsMissing() {
try {
parser.parse(context(null), tokens("->"));
parser.parse((ElementDslContext)null, tokens("->"));
fail();
} catch (Exception e) {
assertEquals("Expected: -> <identifier> [description] [technology] [tags]", e.getMessage());
Expand All @@ -40,7 +40,7 @@ void test_parse_ThrowsAnException_WhenTheDestinationIdentifierIsMissing() {
@Test
void test_parse_ThrowsAnException_WhenTheDestinationElementIsNotDefined() {
Person user = model.addPerson("User", "Description");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);
IdentifiersRegister elements = new IdentifiersRegister();
context.setIdentifierRegister(elements);

Expand All @@ -56,7 +56,7 @@ void test_parse_ThrowsAnException_WhenTheDestinationElementIsNotDefined() {
void test_parse_AddsTheRelationship() {
Person user = model.addPerson("User", "Description");
SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "Description");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);

IdentifiersRegister elements = new IdentifiersRegister();
elements.register("destination", softwareSystem);
Expand All @@ -79,7 +79,7 @@ void test_parse_AddsTheRelationship() {
void test_parse_AddsTheRelationshipWithADescription() {
Person user = model.addPerson("User", "Description");
SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "Description");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);

IdentifiersRegister elements = new IdentifiersRegister();
elements.register("destination", softwareSystem);
Expand All @@ -102,7 +102,7 @@ void test_parse_AddsTheRelationshipWithADescription() {
void test_parse_AddsTheRelationshipWithADescriptionAndTechnology() {
Person user = model.addPerson("User", "Description");
SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "Description");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);

IdentifiersRegister elements = new IdentifiersRegister();
elements.register("destination", softwareSystem);
Expand All @@ -124,7 +124,7 @@ void test_parse_AddsTheRelationshipWithADescriptionAndTechnology() {
void test_parse_AddsTheRelationshipWithADescriptionAndTechnologyAndTags() {
Person user = model.addPerson("User", "Description");
SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "Description");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);

IdentifiersRegister elements = new IdentifiersRegister();
elements.register("destination", softwareSystem);
Expand All @@ -148,7 +148,7 @@ void test_parse_AddsTheRelationshipAndImplicitRelationshipsWithADescriptionAndTe
Person user = model.addPerson("User", "Description");
SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "Description");
Container container = softwareSystem.addContainer("Container", "Description", "Technology");
ModelItemDslContext context = context(user);
ElementDslContext context = context(user);

IdentifiersRegister elements = new IdentifiersRegister();
elements.register("destination", container);
Expand Down
Loading

0 comments on commit aeb71a6

Please sign in to comment.