Skip to content

Commit

Permalink
Adds Workspace.trim() to trim a workspace of unused elements (i.e. …
Browse files Browse the repository at this point in the history
…those not associated with any views).
  • Loading branch information
simonbrowndotje committed Jan 5, 2024
1 parent b703dda commit d830b15
Show file tree
Hide file tree
Showing 8 changed files with 623 additions and 17 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## unreleased

- Adds `Workspace.trim()` to trim a workspace of unused elements (i.e. those not associated with any views).

## 1.29.0 (28th December 2023)

- Adds `com.structurizr.api.AdminApiClient` as a client for the cloud service/on-premises admin APIs.
Expand Down
194 changes: 186 additions & 8 deletions structurizr-core/src/com/structurizr/Workspace.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import com.structurizr.documentation.Documentable;
import com.structurizr.documentation.Documentation;
import com.structurizr.model.*;
import com.structurizr.view.ViewSet;
import com.structurizr.view.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
* Represents a Structurizr workspace, which is a wrapper for a
Expand Down Expand Up @@ -231,12 +231,190 @@ public int countAndLogWarnings() {
return warnings.size();
}

private String typeof(Element element) {
if (element instanceof SoftwareSystem) {
return "software system";
} else {
return element.getClass().getSimpleName().toLowerCase();
/**
* Trims the workspace by removing all unused elements.
*/
public void trim() {
for (CustomElement element : model.getCustomElements()) {
remove(element);
}

for (Person person : model.getPeople()) {
remove(person);
}

for (SoftwareSystem softwareSystem : model.getSoftwareSystems()) {
remove(softwareSystem);
}

for (DeploymentNode deploymentNode : model.getDeploymentNodes()) {
remove(deploymentNode);
}
}

void remove(CustomElement element) {
if (!isElementAssociatedWithAnyViews(element)) {
try {
Method method = Model.class.getDeclaredMethod("remove", CustomElement.class);
method.setAccessible(true);
method.invoke(model, element);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(Person person) {
if (!isElementAssociatedWithAnyViews(person)) {
try {
Method method = Model.class.getDeclaredMethod("remove", Person.class);
method.setAccessible(true);
method.invoke(model, person);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(SoftwareSystem softwareSystem) {
Set<SoftwareSystemInstance> softwareSystemInstances = model.getElements().stream().filter(e -> e instanceof SoftwareSystemInstance && ((SoftwareSystemInstance)e).getSoftwareSystem() == softwareSystem).map(e -> (SoftwareSystemInstance)e).collect(Collectors.toSet());
for (SoftwareSystemInstance softwareSystemInstance : softwareSystemInstances) {
remove(softwareSystemInstance);
}

for (Container container : softwareSystem.getContainers()) {
remove(container);
}

boolean hasContainers = softwareSystem.hasContainers();
boolean hasSoftwareSystemInstances = model.getElements().stream().anyMatch(e -> e instanceof SoftwareSystemInstance && ((SoftwareSystemInstance)e).getSoftwareSystem() == softwareSystem);
if (!hasContainers && !hasSoftwareSystemInstances && !isElementAssociatedWithAnyViews(softwareSystem)) {
try {
Method method = Model.class.getDeclaredMethod("remove", SoftwareSystem.class);
method.setAccessible(true);
method.invoke(model, softwareSystem);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(Container container) {
for (Component component : container.getComponents()) {
remove(component);
}

if (!isElementAssociatedWithAnyViews(container)) {
Set<ContainerInstance> containerInstances = model.getElements().stream().filter(e -> e instanceof ContainerInstance && ((ContainerInstance)e).getContainer() == container).map(e -> (ContainerInstance)e).collect(Collectors.toSet());
for (ContainerInstance containerInstance : containerInstances) {
remove(containerInstance);
}

boolean hasComponents = container.hasComponents();
boolean hasContainerInstances = model.getElements().stream().anyMatch(e -> e instanceof ContainerInstance && ((ContainerInstance)e).getContainer() == container);
if (!hasComponents && !hasContainerInstances && !isElementAssociatedWithAnyViews(container)) {
try {
Method method = Model.class.getDeclaredMethod("remove", Container.class);
method.setAccessible(true);
method.invoke(model, container);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

void remove(Component component) {
if (!isElementAssociatedWithAnyViews(component)) {
try {
Method method = Model.class.getDeclaredMethod("remove", Component.class);
method.setAccessible(true);
method.invoke(model, component);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(SoftwareSystemInstance softwareSystemInstance) {
if (!isElementAssociatedWithAnyViews(softwareSystemInstance)) {
try {
Method method = Model.class.getDeclaredMethod("remove", SoftwareSystemInstance.class);
method.setAccessible(true);
method.invoke(model, softwareSystemInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(ContainerInstance containerInstance) {
if (!isElementAssociatedWithAnyViews(containerInstance)) {
try {
Method method = Model.class.getDeclaredMethod("remove", ContainerInstance.class);
method.setAccessible(true);
method.invoke(model, containerInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

void remove(DeploymentNode deploymentNode) {
if (deploymentNode.hasChildren()) {
for (DeploymentNode child : deploymentNode.getChildren()) {
remove(child);
}
}

if (!deploymentNode.hasChildren() && !deploymentNode.hasSoftwareSystemInstances() && !deploymentNode.hasContainerInstances()) {
try {
Method method = Model.class.getDeclaredMethod("remove", DeploymentNode.class);
method.setAccessible(true);
method.invoke(model, deploymentNode);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

private boolean isElementAssociatedWithAnyViews(Element element) {
boolean result = false;

// is the element used in any views
for (View view : viewSet.getViews()) {
if (view instanceof ModelView) {
ModelView modelView = (ModelView)view;
result = result | modelView.isElementInView(element);
}
}

// is the element the scope of any views?
for (SystemContextView view : viewSet.getSystemContextViews()) {
result = result | view.getSoftwareSystem() == element;
}

for (ContainerView view : viewSet.getContainerViews()) {
result = result | view.getSoftwareSystem() == element;
}

for (ComponentView view : viewSet.getComponentViews()) {
result = result | view.getContainer() == element;
}

for (DynamicView view : viewSet.getDynamicViews()) {
result = result | view.getElement() == element;
}

for (DeploymentView view : viewSet.getDeploymentViews()) {
result = result | view.getSoftwareSystem() == element;
}

for (ImageView view : viewSet.getImageViews()) {
result = result | view.getElement() == element;
}

return result;
}

}
14 changes: 14 additions & 0 deletions structurizr-core/src/com/structurizr/model/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ void add(Component component) {
}
}

void remove(Component component) {
components.remove(component);
}

/**
* Gets the set of components within this software system.
*
Expand All @@ -123,6 +127,16 @@ void setComponents(Set<Component> components) {
}
}

/**
* Determines whether this container has any components.
*
* @return true if it has components, false otherwise
*/
@JsonIgnore
public boolean hasComponents() {
return !components.isEmpty();
}

/**
* Gets the component with the specified name.
*
Expand Down
37 changes: 37 additions & 0 deletions structurizr-core/src/com/structurizr/model/DeploymentNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public SoftwareSystemInstance add(SoftwareSystem softwareSystem, String... deplo
return softwareSystemInstance;
}

void remove(SoftwareSystemInstance softwareSystemInstance) {
this.softwareSystemInstances.remove(softwareSystemInstance);
}

void remove(ContainerInstance containerInstance) {
this.containerInstances.remove(containerInstance);
}

/**
* Adds a container instance to this deployment node, replicating relationships.
*
Expand Down Expand Up @@ -131,6 +139,10 @@ public DeploymentNode addDeploymentNode(String name, String description, String
return deploymentNode;
}

void remove(DeploymentNode deploymentNode) {
children.remove(deploymentNode);
}

/**
* Gets the DeploymentNode with the specified name.
*
Expand Down Expand Up @@ -318,11 +330,36 @@ void setInfrastructureNodes(Set<InfrastructureNode> infrastructureNodes) {
}
}

/**
* Determines whether this deployment node has any child deployment nodes.
*
* @return true if it has child deployment nodes, false otherwise
*/
@JsonIgnore
public boolean hasChildren() {
return !children.isEmpty();
}

/**
* Determines whether this deployment node has any software system instances.
*
* @return true if it has software system instances, false otherwise
*/
@JsonIgnore
public boolean hasSoftwareSystemInstances() {
return !softwareSystemInstances.isEmpty();
}

/**
* Determines whether this deployment node has any container instances.
*
* @return true if it has container instances, false otherwise
*/
@JsonIgnore
public boolean hasContainerInstances() {
return !containerInstances.isEmpty();
}

/**
* Gets the set of software system instances associated with this deployment node.
*
Expand Down
6 changes: 5 additions & 1 deletion structurizr-core/src/com/structurizr/model/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,14 @@ boolean has(Relationship relationship) {
return relationships.stream().anyMatch(r -> r.getDestination().equals(relationship.getDestination()) && r.getDescription().equals(relationship.getDescription()));
}

void addRelationship(Relationship relationship) {
void add(Relationship relationship) {
relationships.add(relationship);
}

void remove(Relationship relationship) {
relationships.remove(relationship);
}

/**
* Adds a unidirectional "uses" style relationship between this element and the specified custom element.
*
Expand Down
Loading

0 comments on commit d830b15

Please sign in to comment.