Skip to content

Commit

Permalink
structurizr-dsl: Adds support for element technology expressions (e.g…
Browse files Browse the repository at this point in the history
…. "element.technology==Java"). Also some refactoring.
  • Loading branch information
simonbrowndotje committed Aug 18, 2024
1 parent 4730125 commit 3d7e92b
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 17 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- structurizr-dsl: Adds name-value properties to dynamic view relationship views.
- 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.
- structurizr-dsl: Adds support for element technology expressions (e.g. "element.technology==Java").

## 2.2.0 (2nd July 2024)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import static com.structurizr.dsl.StructurizrDslExpressions.ELEMENT_TYPE_EQUALS_EXPRESSION;

final class CustomViewExpressionParser extends AbstractExpressionParser {
final class CustomViewExpressionParser extends ExpressionParser {

@Override
protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import static com.structurizr.dsl.StructurizrDslExpressions.ELEMENT_TYPE_EQUALS_EXPRESSION;

final class DeploymentViewExpressionParser extends AbstractExpressionParser {
final class DeploymentViewExpressionParser extends ExpressionParser {

@Override
protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.structurizr.dsl;

import com.structurizr.model.Element;
import com.structurizr.model.ModelItem;
import com.structurizr.model.Relationship;
import com.structurizr.model.StaticStructureElementInstance;
import com.structurizr.model.*;
import com.structurizr.util.StringUtils;

import java.util.HashSet;
Expand All @@ -13,7 +10,7 @@

import static com.structurizr.dsl.StructurizrDslExpressions.*;

abstract class AbstractExpressionParser {
class ExpressionParser {

private static final String WILDCARD = "*";

Expand All @@ -24,6 +21,8 @@ static boolean isExpression(String token) {
token.startsWith(ELEMENT_TYPE_EQUALS_EXPRESSION.toLowerCase()) ||
token.startsWith(ELEMENT_TAG_EQUALS_EXPRESSION.toLowerCase()) ||
token.startsWith(ELEMENT_TAG_NOT_EQUALS_EXPRESSION.toLowerCase()) ||
token.startsWith(ELEMENT_TECHNOLOGY_EQUALS_EXPRESSION.toLowerCase()) ||
token.startsWith(ELEMENT_TECHNOLOGY_NOT_EQUALS_EXPRESSION.toLowerCase()) ||
token.matches(ELEMENT_PROPERTY_EQUALS_EXPRESSION) ||
token.startsWith(ELEMENT_PARENT_EQUALS_EXPRESSION.toLowerCase()) ||
token.startsWith(RELATIONSHIP) || token.endsWith(RELATIONSHIP) || token.contains(RELATIONSHIP) ||
Expand Down Expand Up @@ -52,10 +51,10 @@ final Set<ModelItem> parseExpression(String expr, DslContext context) {
Set<ModelItem> modelItems1 = evaluateExpression(expressions[0], context);
Set<ModelItem> modelItems2 = evaluateExpression(expressions[1], context);

Set<ModelItem> elements = new HashSet<>(modelItems1);
elements.addAll(modelItems2);
Set<ModelItem> modelItems = new HashSet<>(modelItems1);
modelItems.addAll(modelItems2);

return elements;
return modelItems;
} else {
return evaluateExpression(expr, context);
}
Expand Down Expand Up @@ -171,6 +170,20 @@ private Set<ModelItem> evaluateExpression(String expr, DslContext context) {
modelItems.add(element);
}
});
} else if (expr.toLowerCase().startsWith(ELEMENT_TECHNOLOGY_EQUALS_EXPRESSION.toLowerCase())) {
String technology = expr.substring(ELEMENT_TECHNOLOGY_EQUALS_EXPRESSION.length());
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Container).map(e -> (Container)e).filter(c -> technology.equals(c.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Component).map(e -> (Component)e).filter(c -> technology.equals(c.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof DeploymentNode).map(e -> (DeploymentNode)e).filter(dn -> technology.equals(dn.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof InfrastructureNode).map(e -> (InfrastructureNode)e).filter(in -> technology.equals(in.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof ContainerInstance).map(e -> (ContainerInstance)e).filter(c -> technology.equals(c.getContainer().getTechnology())).collect(Collectors.toSet()));
} else if (expr.toLowerCase().startsWith(ELEMENT_TECHNOLOGY_NOT_EQUALS_EXPRESSION)) {
String technology = expr.substring(ELEMENT_TECHNOLOGY_NOT_EQUALS_EXPRESSION.length());
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Container).map(e -> (Container)e).filter(c -> !technology.equals(c.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Component).map(e -> (Component)e).filter(c -> !technology.equals(c.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof DeploymentNode).map(e -> (DeploymentNode)e).filter(dn -> !technology.equals(dn.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof InfrastructureNode).map(e -> (InfrastructureNode)e).filter(in -> !technology.equals(in.getTechnology())).collect(Collectors.toSet()));
modelItems.addAll(context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof ContainerInstance).map(e -> (ContainerInstance)e).filter(c -> !technology.equals(c.getContainer().getTechnology())).collect(Collectors.toSet()));
} else if (expr.matches(ELEMENT_PROPERTY_EQUALS_EXPRESSION)) {
String propertyName = expr.substring(expr.indexOf("[")+1, expr.indexOf("]"));
String propertyValue = expr.substring(expr.indexOf("==")+2);
Expand Down Expand Up @@ -266,7 +279,42 @@ private Set<ModelItem> evaluateExpression(String expr, DslContext context) {
return modelItems;
}

protected abstract Set<Element> evaluateElementTypeExpression(String expr, DslContext context);
protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) {
Set<Element> elements = new LinkedHashSet<>();

String type = expr.substring(ELEMENT_TYPE_EQUALS_EXPRESSION.length());
switch (type.toLowerCase()) {
case "custom":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof CustomElement).forEach(elements::add);
break;
case "person":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Person).forEach(elements::add);
break;
case "softwaresystem":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof SoftwareSystem).forEach(elements::add);
break;
case "container":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Container).forEach(elements::add);
break;
case "component":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Component).forEach(elements::add);
break;
case "deploymentnode":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof DeploymentNode).forEach(elements::add);
break;
case "infrastructurenode":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof InfrastructureNode).forEach(elements::add);
break;
case "softwaresysteminstance":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof SoftwareSystemInstance).forEach(elements::add);
break;
case "containerinstance":
context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof ContainerInstance).forEach(elements::add);
break;
}

return elements;
}

private boolean hasAllTags(ModelItem modelItem, String[] tags) {
boolean result = true;
Expand Down Expand Up @@ -318,7 +366,9 @@ private boolean hasProperty(ModelItem modelItem, String name, String value) {
return result;
}

protected abstract Set<Element> findAfferentCouplings(Element element);
protected Set<Element> findAfferentCouplings(Element element) {
return new LinkedHashSet<>(findAfferentCouplings(element, Element.class));
}

protected <T extends Element> Set<Element> findAfferentCouplings(Element element, Class<T> typeOfElement) {
Set<Element> elements = new LinkedHashSet<>();
Expand All @@ -331,7 +381,9 @@ protected <T extends Element> Set<Element> findAfferentCouplings(Element element
return elements;
}

protected abstract Set<Element> findEfferentCouplings(Element element);
protected Set<Element> findEfferentCouplings(Element element) {
return new LinkedHashSet<>(findEfferentCouplings(element, Element.class));
}

protected <T extends Element> Set<Element> findEfferentCouplings(Element element, Class<T> typeOfElement) {
Set<Element> elements = new LinkedHashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ abstract class ModelViewContentParser extends AbstractParser {
protected static final String ELEMENT_WILDCARD = "element==*";

protected boolean isExpression(String token) {
return AbstractExpressionParser.isExpression(token.toLowerCase());
return ExpressionParser.isExpression(token.toLowerCase());
}

protected void removeRelationshipFromView(Relationship relationship, ModelView view) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import static com.structurizr.dsl.StructurizrDslExpressions.ELEMENT_TYPE_EQUALS_EXPRESSION;

final class StaticViewExpressionParser extends AbstractExpressionParser {
final class StaticViewExpressionParser extends ExpressionParser {

@Override
protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
class StructurizrDslExpressions {

static final String ELEMENT_TYPE_EQUALS_EXPRESSION = "element.type==";
static final String ELEMENT_TECHNOLOGY_EQUALS_EXPRESSION = "element.technology==";
static final String ELEMENT_TECHNOLOGY_NOT_EQUALS_EXPRESSION = "element.technology!=";
static final String ELEMENT_TAG_EQUALS_EXPRESSION = "element.tag==";
static final String ELEMENT_TAG_NOT_EQUALS_EXPRESSION = "element.tag!=";
static final String ELEMENT_PROPERTY_EQUALS_EXPRESSION = "element\\.properties\\[.*]==.*";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

import static org.junit.jupiter.api.Assertions.*;

class AbstractExpressionParserTests extends AbstractTests {
class ExpressionParserTests extends AbstractTests {

private final StaticViewExpressionParser parser = new StaticViewExpressionParser();
private final ExpressionParser parser = new ExpressionParser();

@Test
void test_parseExpression_ThrowsAnException_WhenTheRelationshipSourceIsSpecifiedUsingLongSyntaxButDoesNotExist() {
Expand Down Expand Up @@ -396,6 +396,32 @@ void test_parseExpression_ReturnsElementsAndElementInstances_WhenUsingAnElementT
assertTrue(elements.contains(ssi)); // this is not tagged "Software System", but the element it's based upon is
}

@Test
void test_parseExpression_ReturnsElementsAndElementInstances_WhenUsingAnElementTechnologyEqualsExpression() {
model.addPerson("User");
SoftwareSystem ss = model.addSoftwareSystem("Software System");
Container c = ss.addContainer("Container");
c.setTechnology("Java");
DeploymentNode dn = model.addDeploymentNode("DN");
dn.setTechnology("EC2");
InfrastructureNode in = dn.addInfrastructureNode("Infrastructure Node");
in.setTechnology("ELB");
ContainerInstance ci = dn.add(c);

Set<ModelItem> elements = parser.parseExpression("element.technology==Java", context());
assertEquals(2, elements.size());
assertTrue(elements.contains(c)); // this has a technology property of "Java"
assertTrue(elements.contains(ci)); // this has no technology property, but the element it's based upon is

elements = parser.parseExpression("element.technology==EC2", context());
assertEquals(1, elements.size());
assertTrue(elements.contains(dn));

elements = parser.parseExpression("element.technology==ELB", context());
assertEquals(1, elements.size());
assertTrue(elements.contains(in));
}

@Test
void test_parseExpression_ReturnsElementsAndElementInstances_WhenUsingAnElementPropertyEqualsExpression() {
SoftwareSystem a = model.addSoftwareSystem("A");
Expand Down

0 comments on commit 3d7e92b

Please sign in to comment.