com.structurizr.view.DefaultLayoutMergeStrategy Maven / Gradle / Ivy
The newest version!
package com.structurizr.view;
import com.structurizr.model.Element;
import com.structurizr.model.Relationship;
import com.structurizr.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
/**
* A default implementation of a LayoutMergeStrategy that:
*
* - Sets the paper size (if not set).
* - Copies element x,y positions.
* - Copies relationship vertices.
*
* Elements are matched using the following properties, in order:
* - the element's full canonical name
* - the element's name
* - the element's description
*/
public class DefaultLayoutMergeStrategy implements LayoutMergeStrategy {
private static final Log log = LogFactory.getLog(View.class);
/**
* Attempts to copy the visual layout information (e.g. x,y coordinates) of elements and relationships
* from the specified source view into the specified destination view.
*
* @param viewWithLayoutInformation the source view (e.g. the version stored by the Structurizr service)
* @param viewWithoutLayoutInformation the destination View (e.g. the new version, created locally with code)
*/
public void copyLayoutInformation(@Nonnull ModelView viewWithLayoutInformation, @Nonnull ModelView viewWithoutLayoutInformation) {
setPaperSizeIfNotSpecified(viewWithLayoutInformation, viewWithoutLayoutInformation);
setDimensionsIfNotSpecified(viewWithLayoutInformation, viewWithoutLayoutInformation);
Map elementViewMap = new HashMap<>();
Map elementMap = new HashMap<>();
for (ElementView elementViewWithoutLayoutInformation : viewWithoutLayoutInformation.getElements()) {
ElementView elementViewWithLayoutInformation = findElementView(viewWithLayoutInformation, elementViewWithoutLayoutInformation.getElement());
if (elementViewWithLayoutInformation != null) {
elementViewMap.put(elementViewWithoutLayoutInformation, elementViewWithLayoutInformation);
elementMap.put(elementViewWithoutLayoutInformation.getElement(), elementViewWithLayoutInformation.getElement());
} else {
log.warn("There is no layout information for the element named " + elementViewWithoutLayoutInformation.getElement().getName() + " on view " + viewWithLayoutInformation.getKey());
}
}
for (ElementView elementViewWithoutLayoutInformation : elementViewMap.keySet()) {
ElementView elementViewWithLayoutInformation = elementViewMap.get(elementViewWithoutLayoutInformation);
elementViewWithoutLayoutInformation.copyLayoutInformationFrom(elementViewWithLayoutInformation);
}
for (RelationshipView relationshipViewWithoutLayoutInformation : viewWithoutLayoutInformation.getRelationships()) {
RelationshipView relationshipViewWithLayoutInformation;
if (viewWithoutLayoutInformation instanceof DynamicView) {
relationshipViewWithLayoutInformation = findRelationshipView(viewWithLayoutInformation, relationshipViewWithoutLayoutInformation, elementMap);
} else {
relationshipViewWithLayoutInformation = findRelationshipView(viewWithLayoutInformation, relationshipViewWithoutLayoutInformation.getRelationship(), elementMap);
}
if (relationshipViewWithLayoutInformation != null) {
relationshipViewWithoutLayoutInformation.copyLayoutInformationFrom(relationshipViewWithLayoutInformation);
}
}
}
private void setPaperSizeIfNotSpecified(@Nonnull ModelView remoteView, @Nonnull ModelView localView) {
if (localView.getPaperSize() == null) {
localView.setPaperSize(remoteView.getPaperSize());
}
}
private void setDimensionsIfNotSpecified(@Nonnull ModelView remoteView, @Nonnull ModelView localView) {
if (localView.getDimensions() == null) {
localView.setDimensions(remoteView.getDimensions());
}
}
/**
* Finds an element. Override this to change the behaviour.
*
* @param viewWithLayoutInformation the view to search
* @param elementWithoutLayoutInformation the Element to find
* @return an ElementView
*/
protected ElementView findElementView(ModelView viewWithLayoutInformation, Element elementWithoutLayoutInformation) {
// see if we can find an element with the same canonical name in the source view
ElementView elementView = viewWithLayoutInformation.getElements().stream().filter(ev -> ev.getElement().getCanonicalName().equals(elementWithoutLayoutInformation.getCanonicalName())).findFirst().orElse(null);
if (elementView == null) {
// no element was found, so try finding an element of the same type with the same name (in this situation, the parent element may have been renamed)
elementView = viewWithLayoutInformation.getElements().stream().filter(ev -> ev.getElement().getName().equals(elementWithoutLayoutInformation.getName()) && ev.getElement().getClass().equals(elementWithoutLayoutInformation.getClass())).findFirst().orElse(null);
}
if (elementView == null) {
// no element was found, so try finding an element of the same type with the same description if set (in this situation, the element itself may have been renamed)
if (!StringUtils.isNullOrEmpty(elementWithoutLayoutInformation.getDescription())) {
elementView = viewWithLayoutInformation.getElements().stream().filter(ev -> elementWithoutLayoutInformation.getDescription().equals(ev.getElement().getDescription()) && ev.getElement().getClass().equals(elementWithoutLayoutInformation.getClass())).findFirst().orElse(null);
}
}
if (elementView == null) {
// no element was found, so try finding an element of the same type with the same ID (in this situation, the name and description may have changed)
elementView = viewWithLayoutInformation.getElements().stream().filter(ev -> ev.getElement().getId().equals(elementWithoutLayoutInformation.getId()) && ev.getElement().getClass().equals(elementWithoutLayoutInformation.getClass())).findFirst().orElse(null);
}
return elementView;
}
private RelationshipView findRelationshipView(ModelView viewWithLayoutInformation, Relationship relationshipWithoutLayoutInformation, Map elementMap) {
if (!elementMap.containsKey(relationshipWithoutLayoutInformation.getSource()) || !elementMap.containsKey(relationshipWithoutLayoutInformation.getDestination())) {
return null;
}
Element sourceElementWithLayoutInformation = elementMap.get(relationshipWithoutLayoutInformation.getSource());
Element destinationElementWithLayoutInformation = elementMap.get(relationshipWithoutLayoutInformation.getDestination());
for (RelationshipView rv : viewWithLayoutInformation.getRelationships()) {
if (
rv.getRelationship().getSource().equals(sourceElementWithLayoutInformation) &&
rv.getRelationship().getDestination().equals(destinationElementWithLayoutInformation) &&
rv.getRelationship().getDescription().equals(relationshipWithoutLayoutInformation.getDescription())
) {
return rv;
}
}
// if we got this far, perhaps the relationship description was changed, so try matching on ID instead
for (RelationshipView rv : viewWithLayoutInformation.getRelationships()) {
if (
rv.getRelationship().getSource().equals(sourceElementWithLayoutInformation) &&
rv.getRelationship().getDestination().equals(destinationElementWithLayoutInformation) &&
rv.getRelationship().getId().equals(relationshipWithoutLayoutInformation.getId())
) {
return rv;
}
}
return null;
}
private RelationshipView findRelationshipView(ModelView view, RelationshipView relationshipWithoutLayoutInformation, Map elementMap) {
if (!elementMap.containsKey(relationshipWithoutLayoutInformation.getRelationship().getSource()) || !elementMap.containsKey(relationshipWithoutLayoutInformation.getRelationship().getDestination())) {
return null;
}
Element sourceElementWithLayoutInformation = elementMap.get(relationshipWithoutLayoutInformation.getRelationship().getSource());
Element destinationElementWithLayoutInformation = elementMap.get(relationshipWithoutLayoutInformation.getRelationship().getDestination());
for (RelationshipView rv : view.getRelationships()) {
if (
rv.getRelationship().getSource().equals(sourceElementWithLayoutInformation) &&
rv.getRelationship().getDestination().equals(destinationElementWithLayoutInformation) &&
rv.getDescription().equals(relationshipWithoutLayoutInformation.getDescription()) &&
rv.getOrder().equals(relationshipWithoutLayoutInformation.getOrder())) {
return rv;
}
}
// if we got this far, perhaps the relationship description was changed, so try matching on ID instead
for (RelationshipView rv : view.getRelationships()) {
if (
rv.getRelationship().getSource().equals(sourceElementWithLayoutInformation) &&
rv.getRelationship().getDestination().equals(destinationElementWithLayoutInformation) &&
rv.getRelationship().getId().equals(relationshipWithoutLayoutInformation.getId()) &&
rv.getOrder().equals(relationshipWithoutLayoutInformation.getOrder())) {
return rv;
}
}
return null;
}
}