From 435e7dfce545b8a71a5ccb8fe0aff201267647fc Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 14:26:31 +0200 Subject: [PATCH 1/8] Enhanced context map generator with team maps (distinguish between BCs of type generic and/or teams) --- .../guru/nidi/graphviz/engine/Graphviz.java | 2 + .../generator/ContextMapGenerator.java | 250 +++++++++++++----- .../generator/model/BoundedContext.java | 62 +++++ .../generator/model/BoundedContextType.java | 27 ++ .../BoundedContextIsNotATeamException.java | 24 ++ .../TeamCannotImplementTeamException.java | 24 ++ src/main/resources/team-icon.png | Bin 0 -> 1255 bytes .../generator/ContextMapGeneratorTest.java | 204 +++++++++++++- .../generator/model/BoundedContextTest.java | 76 ++++++ 9 files changed, 604 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/contextmapper/contextmap/generator/model/BoundedContextType.java create mode 100644 src/main/java/org/contextmapper/contextmap/generator/model/exception/BoundedContextIsNotATeamException.java create mode 100644 src/main/java/org/contextmapper/contextmap/generator/model/exception/TeamCannotImplementTeamException.java create mode 100644 src/main/resources/team-icon.png diff --git a/src/main/java/guru/nidi/graphviz/engine/Graphviz.java b/src/main/java/guru/nidi/graphviz/engine/Graphviz.java index 8fe2041..02738d0 100644 --- a/src/main/java/guru/nidi/graphviz/engine/Graphviz.java +++ b/src/main/java/guru/nidi/graphviz/engine/Graphviz.java @@ -17,6 +17,8 @@ import guru.nidi.graphviz.model.Graph; import guru.nidi.graphviz.model.MutableGraph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.util.*; diff --git a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java index da4b13b..541b26f 100644 --- a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java +++ b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java @@ -19,17 +19,13 @@ import guru.nidi.graphviz.attribute.Shape; import guru.nidi.graphviz.engine.Format; import guru.nidi.graphviz.engine.Graphviz; +import guru.nidi.graphviz.engine.Renderer; import guru.nidi.graphviz.model.MutableGraph; import guru.nidi.graphviz.model.MutableNode; import org.contextmapper.contextmap.generator.model.*; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.TreeMap; +import java.io.*; +import java.util.*; import java.util.stream.Collectors; import static guru.nidi.graphviz.attribute.Attributes.attr; @@ -45,12 +41,31 @@ public class ContextMapGenerator { private static final String EDGE_SPACING_UNIT = " "; private Map bcNodesMap; + private Set genericNodes; + private Set teamNodes; + private File baseDir; // used for Graphviz images protected int labelSpacingFactor = 1; protected int height = 1000; protected int width = 2000; protected boolean useHeight = false; protected boolean useWidth = true; + protected boolean clusterTeams = true; + + public ContextMapGenerator() { + this.baseDir = new File(System.getProperty("java.io.tmpdir") + File.separator + "GraphvizJava"); + } + + /** + * Sets the base directory for included images (team maps). + * In case you work with SVG or DOT files it is recommended to set the directory into which you generate the images. + * + * @param baseDir the baseDir into which we copy the team map image. + */ + public ContextMapGenerator setBaseDir(File baseDir) { + this.baseDir = baseDir; + return this; + } /** * Defines how much spacing we add to push the edges apart from each other. @@ -94,6 +109,17 @@ public ContextMapGenerator setWidth(int width) { return this; } + /** + * Defines whether teams (also generic contexts) are clustered together; is only relevant for mixed team maps + * containing both types of BCs. If true, the resulting layout clusters BCs of the same types. + * + * @param clusterTeams whether BCs of the same type shall be clustered or not + */ + public ContextMapGenerator clusterTeams(boolean clusterTeams) { + this.clusterTeams = clusterTeams; + return this; + } + /** * Generates the graphical Context Map. * @@ -103,13 +129,7 @@ public ContextMapGenerator setWidth(int width) { * @throws IOException */ public void generateContextMapGraphic(ContextMap contextMap, Format format, String fileName) throws IOException { - MutableGraph graph = createGraph(contextMap); - - // store file - if (useWidth) - Graphviz.fromGraph(graph).width(width).render(format).toFile(new File(fileName)); - else - Graphviz.fromGraph(graph).height(height).render(format).toFile(new File(fileName)); + generateContextMapGraphic(contextMap, format).toFile(new File(fileName)); } /** @@ -121,69 +141,170 @@ public void generateContextMapGraphic(ContextMap contextMap, Format format, Stri * @throws IOException */ public void generateContextMapGraphic(ContextMap contextMap, Format format, OutputStream outputStream) throws IOException { + generateContextMapGraphic(contextMap, format).toOutputStream(outputStream); + } + + private Renderer generateContextMapGraphic(ContextMap contextMap, Format format) throws IOException { + exportImages(); MutableGraph graph = createGraph(contextMap); // store file if (useWidth) - Graphviz.fromGraph(graph).width(width).render(format).toOutputStream(outputStream); + return Graphviz.fromGraph(graph).basedir(baseDir).width(width).render(format); else - Graphviz.fromGraph(graph).height(height).render(format).toOutputStream(outputStream); + return Graphviz.fromGraph(graph).basedir(baseDir).height(height).render(format); } private MutableGraph createGraph(ContextMap contextMap) { this.bcNodesMap = new TreeMap<>(); - MutableGraph graph = mutGraph("ContextMapGraph"); - - // create nodes - contextMap.getBoundedContexts().forEach(bc -> { - MutableNode node = mutNode(bc.getName()); - node.add(Label.lines(bc.getName())); - node.add(Shape.EGG); - node.add(attr("margin", "0.3")); - node.add(attr("orientation", orientationDegree())); - node.add(attr("fontname", "sans-serif")); - node.add(attr("fontsize", "16")); - node.add(attr("style", "bold")); + this.genericNodes = new HashSet<>(); + this.teamNodes = new HashSet<>(); + MutableGraph rootGraph = createGraph("ContextMapGraph"); + + createNodes(contextMap.getBoundedContexts()); + + if (!needsSubGraphs(contextMap)) { + addNodesToGraph(rootGraph, bcNodesMap.values()); + createRelationshipLinks4ExistingNodes(contextMap.getRelationships()); + } else { + MutableGraph genericGraph = createGraph(getSubgraphName("GenericSubgraph")) + .graphAttrs().add("color", "white"); + addNodesToGraph(genericGraph, genericNodes); + MutableGraph teamGraph = createGraph(getSubgraphName("Teams_Subgraph")) + .graphAttrs().add("color", "white"); + addNodesToGraph(teamGraph, teamNodes); + genericGraph.addTo(rootGraph); + teamGraph.addTo(rootGraph); + + createRelationshipLinks4ExistingNodes(contextMap.getRelationships().stream().filter(rel -> rel.getFirstParticipant().getType() == rel.getSecondParticipant().getType()) + .collect(Collectors.toSet())); + createRelationshipLinks(rootGraph, contextMap.getRelationships().stream().filter(rel -> rel.getFirstParticipant().getType() != rel.getSecondParticipant().getType()) + .collect(Collectors.toSet())); + createTeamImplementationLinks(rootGraph, contextMap.getBoundedContexts().stream().filter(bc -> bc.getType() == BoundedContextType.TEAM + && !bc.getRealizedBoundedContexts().isEmpty()).collect(Collectors.toList())); + } + return rootGraph; + } + + private String getSubgraphName(String baseName) { + return clusterTeams ? "cluster_" + baseName : baseName; + } + + private boolean needsSubGraphs(ContextMap contextMap) { + boolean hasTeams = contextMap.getBoundedContexts().stream().anyMatch(bc -> bc.getType() == BoundedContextType.TEAM); + boolean hasGenericContexts = contextMap.getBoundedContexts().stream().anyMatch(bc -> bc.getType() == BoundedContextType.GENERIC); + return hasGenericContexts && hasTeams; + } + + private MutableGraph createGraph(String name) { + MutableGraph rootGraph = mutGraph(name); + rootGraph.setDirected(true); + rootGraph.graphAttrs().add(attr("imagepath", baseDir.getAbsolutePath())); + return rootGraph; + } + + private void addNodesToGraph(MutableGraph graph, Collection nodes) { + for (MutableNode node : nodes) { + graph.add(node); + } + } + + private void createNodes(Set boundedContexts) { + boundedContexts.forEach(bc -> { + MutableNode node = createNode(bc); bcNodesMap.put(bc.getName(), node); + if (bc.getType() == BoundedContextType.TEAM) + teamNodes.add(node); + else + genericNodes.add(node); + }); + } + + private MutableNode createNode(BoundedContext bc) { + MutableNode node = mutNode(bc.getName()); + node.add(createNodeLabel(bc)); + node.add(Shape.EGG); + node.add(attr("margin", "0.3")); + node.add(attr("orientation", orientationDegree())); + node.add(attr("fontname", "sans-serif")); + node.add(attr("fontsize", "16")); + node.add(attr("style", "bold")); + return node; + } + + private void createRelationshipLinks4ExistingNodes(Set relationships) { + relationships.forEach(rel -> { + createRelationshipLink(this.bcNodesMap.get(rel.getFirstParticipant().getName()), + this.bcNodesMap.get(rel.getSecondParticipant().getName()), rel); }); + } - // link nodes - contextMap.getRelationships().forEach(rel -> { - MutableNode node1 = this.bcNodesMap.get(rel.getFirstParticipant().getName()); - MutableNode node2 = this.bcNodesMap.get(rel.getSecondParticipant().getName()); - - if (rel instanceof Partnership) { - node1.addLink(to(node2).with(createLabel("Partnership", rel.getName(), rel.getImplementationTechnology())) - .add(attr("fontname", "sans-serif")) - .add(attr("style", "bold")) - .add(attr("fontsize", "12"))); - } else if (rel instanceof SharedKernel) { - node1.addLink(to(node2).with(createLabel("Shared Kernel", rel.getName(), rel.getImplementationTechnology())) - .add(attr("fontname", "sans-serif")) - .add(attr("style", "bold")) - .add(attr("fontsize", "12"))); - } else { - UpstreamDownstreamRelationship upDownRel = (UpstreamDownstreamRelationship) rel; - node1.addLink(to(node2).with( - createLabel(upDownRel.isCustomerSupplier() ? "Customer/Supplier" : "", rel.getName(), rel.getImplementationTechnology()), - attr("labeldistance", "0"), - attr("fontname", "sans-serif"), - attr("fontsize", "12"), - attr("style", "bold"), - attr("headlabel", getEdgeHTMLLabel("D", downstreamPatternsToStrings(upDownRel.getDownstreamPatterns()))), - attr("taillabel", getEdgeHTMLLabel("U", upstreamPatternsToStrings(upDownRel.getUpstreamPatterns()))) - )); - } + private void createRelationshipLinks(MutableGraph graph, Set relationships) { + relationships.forEach(rel -> { + MutableNode node1 = createNode(rel.getFirstParticipant()); + MutableNode node2 = createNode(rel.getSecondParticipant()); + createRelationshipLink(node1, node2, rel); + graph.add(node1); + graph.add(node2); }); + } - // add nodes to graph - for (MutableNode node : this.bcNodesMap.values()) { - graph.add(node); + private void createRelationshipLink(MutableNode node1, MutableNode node2, Relationship rel) { + if (rel instanceof Partnership) { + node1.addLink(to(node2).with(createRelationshipLabel("Partnership", rel.getName(), rel.getImplementationTechnology())) + .add(attr("dir", "none")) + .add(attr("fontname", "sans-serif")) + .add(attr("style", "bold")) + .add(attr("fontsize", "12"))); + } else if (rel instanceof SharedKernel) { + node1.addLink(to(node2).with(createRelationshipLabel("Shared Kernel", rel.getName(), rel.getImplementationTechnology())) + .add(attr("dir", "none")) + .add(attr("fontname", "sans-serif")) + .add(attr("style", "bold")) + .add(attr("fontsize", "12"))); + } else { + UpstreamDownstreamRelationship upDownRel = (UpstreamDownstreamRelationship) rel; + node1.addLink(to(node2).with( + createRelationshipLabel(upDownRel.isCustomerSupplier() ? "Customer/Supplier" : "", rel.getName(), rel.getImplementationTechnology()), + attr("dir", "none"), + attr("labeldistance", "0"), + attr("fontname", "sans-serif"), + attr("fontsize", "12"), + attr("style", "bold"), + attr("headlabel", getEdgeHTMLLabel("D", downstreamPatternsToStrings(upDownRel.getDownstreamPatterns()))), + attr("taillabel", getEdgeHTMLLabel("U", upstreamPatternsToStrings(upDownRel.getUpstreamPatterns()))) + )); } - return graph; } - private Label createLabel(String relationshipType, String relationshipName, String implementationTechnology) { + private void createTeamImplementationLinks(MutableGraph graph, List teams) { + for (BoundedContext team : teams) { + team.getRealizedBoundedContexts().forEach(system -> { + if (bcNodesMap.containsKey(team.getName()) && bcNodesMap.containsKey(system.getName())) { + MutableNode node1 = createNode(team); + MutableNode node2 = createNode(system); + node1.addLink(to(node2).with( + Label.lines(" «realizes»"), + attr("color", "#686868"), + attr("fontname", "sans-serif"), + attr("fontsize", "12"), + attr("fontcolor", "#686868"), + attr("style", "dashed"))); + graph.add(node1); + graph.add(node2); + } + }); + } + } + + private Label createNodeLabel(BoundedContext boundedContext) { + if (boundedContext.getType() == BoundedContextType.TEAM) + return Label.html("
" + + "Team
" + boundedContext.getName() + "
"); + return Label.lines(boundedContext.getName()); + } + + private Label createRelationshipLabel(String relationshipType, String relationshipName, String implementationTechnology) { boolean relationshipTypeDefined = relationshipType != null && !"".equals(relationshipType); boolean nameDefined = relationshipName != null && !"".equals(relationshipName); boolean implementationTechnologyDefined = implementationTechnology != null && !"".equals(implementationTechnology); @@ -243,4 +364,13 @@ private Label getEdgeHTMLLabel(String upstreamDownstreamLabel, Set patte ""); } + private void exportImages() throws IOException { + InputStream teamIconInputStream = ContextMapGenerator.class.getClassLoader().getResourceAsStream("team-icon.png"); + byte[] buffer = new byte[teamIconInputStream.available()]; + teamIconInputStream.read(buffer); + File targetFile = new File(baseDir, "team-icon.png"); + OutputStream outStream = new FileOutputStream(targetFile); + outStream.write(buffer); + } + } diff --git a/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContext.java b/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContext.java index ecb8d96..ff46e68 100644 --- a/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContext.java +++ b/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContext.java @@ -17,6 +17,12 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.contextmapper.contextmap.generator.model.exception.BoundedContextIsNotATeamException; +import org.contextmapper.contextmap.generator.model.exception.TeamCannotImplementTeamException; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; /** * Represents a Bounded Context for the graphical Context Map to be generated. @@ -26,9 +32,44 @@ public class BoundedContext { private String name; + private BoundedContextType type; + private List realizedBoundedContexts; // for teams only! public BoundedContext(String name) { this.name = name; + this.type = BoundedContextType.GENERIC; + this.realizedBoundedContexts = new LinkedList<>(); + } + + public BoundedContext(String name, BoundedContextType type) { + this(name); + this.type = type; + } + + /** + * Sets the type of the Bounded Context. + * + * @param type the type that shall be set on the represented Bounded Context + * @return returns the Bounded Context + */ + public BoundedContext withType(BoundedContextType type) { + this.type = type; + return this; + } + + /** + * Adds a generic context to the realized systems; only allowed to use if the represented Bounded Context is a team. + * + * @param genericContext the generic Bounded Context that shall be realized by the team. + * @return the team + */ + public BoundedContext realizing(BoundedContext genericContext) { + if (this.type != BoundedContextType.TEAM) + throw new BoundedContextIsNotATeamException(this.name); + if (genericContext.getType() == BoundedContextType.TEAM) + throw new TeamCannotImplementTeamException(genericContext.getName()); + this.realizedBoundedContexts.add(genericContext); + return this; } /** @@ -40,6 +81,27 @@ public String getName() { return name; } + /** + * Gets the type of the Bounded Context. + * + * @return the type of the Bounded Context + */ + public BoundedContextType getType() { + return type; + } + + /** + * Returns the Bounded Contexts that are realized by the represented team. If the represented Bounded Context + * is not a team, this method returns an empty list. + * + * @return the list of realized Bounded Contexts in case the represented context is a team, an empty list otherwise + */ + public List getRealizedBoundedContexts() { + if (this.type != BoundedContextType.TEAM) + return new LinkedList<>(); + return realizedBoundedContexts.stream().filter(bc -> bc.getType() != BoundedContextType.TEAM).collect(Collectors.toList()); + } + @Override public boolean equals(Object object) { if (!(object instanceof BoundedContext)) diff --git a/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContextType.java b/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContextType.java new file mode 100644 index 0000000..6b3d35c --- /dev/null +++ b/src/main/java/org/contextmapper/contextmap/generator/model/BoundedContextType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 The Context Mapper Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.contextmapper.contextmap.generator.model; + +/** + * Used to indicate whether a Bounded Context is a generic context or a team. + * + * @author Stefan Kapferer + */ +public enum BoundedContextType { + + GENERIC, TEAM; + +} diff --git a/src/main/java/org/contextmapper/contextmap/generator/model/exception/BoundedContextIsNotATeamException.java b/src/main/java/org/contextmapper/contextmap/generator/model/exception/BoundedContextIsNotATeamException.java new file mode 100644 index 0000000..5e384d7 --- /dev/null +++ b/src/main/java/org/contextmapper/contextmap/generator/model/exception/BoundedContextIsNotATeamException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Context Mapper Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.contextmapper.contextmap.generator.model.exception; + +public class BoundedContextIsNotATeamException extends RuntimeException { + + public BoundedContextIsNotATeamException(String boundedContextName) { + super("The Bounded Context '" + boundedContextName + "' is not a team and can therefore not implement another Bounded Context"); + } + +} diff --git a/src/main/java/org/contextmapper/contextmap/generator/model/exception/TeamCannotImplementTeamException.java b/src/main/java/org/contextmapper/contextmap/generator/model/exception/TeamCannotImplementTeamException.java new file mode 100644 index 0000000..1c448f8 --- /dev/null +++ b/src/main/java/org/contextmapper/contextmap/generator/model/exception/TeamCannotImplementTeamException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Context Mapper Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.contextmapper.contextmap.generator.model.exception; + +public class TeamCannotImplementTeamException extends RuntimeException { + + public TeamCannotImplementTeamException(String implementedContextName) { + super("The Bounded Context '" + implementedContextName + "' is a team. A team cannot realize another team. Use a Bounded Context of the type GENERIC"); + } + +} diff --git a/src/main/resources/team-icon.png b/src/main/resources/team-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fbba501f51a6a6edfe5a019ffcd78087503ae2d7 GIT binary patch literal 1255 zcmVp$(PuQcSE*W%s-+|pMU!X=;zB9n2knA&0lz3N6;z6#t)RtE z3My2oDAo@m6(}_`hcAi{$H!m^_`_mwBTz1Y$TgNZBkk}p_=bbQ1&$liv*5 z+YY#ODJL@@*l)lM05|wXtxkb!4Hk1up*b!cmav)B(^BR?2Y3Sb9@q^$3(O6P9y5WL zEepBHfLRBOI}%yp7$`b`C0TP<8CV1C)m7_PUTJSI{#9VYk;v02i{Ea*`~WP?8unX( zc`0Modz{D}27D9nlmqs8;0&L|F9Kczz6O2-b^)IOYk`Vke7oa`Ov(q6@~-11)Pav( zNxTmXH83&`98&F-lr0X6SZ_#@8$3uv`brv!uRM(1W5D-X#&0#8#`VDA03t_$WhMeM zfKLMqE=||~cwCvi5BOOD(RElAXylXFqV87dU$cy_ z0mHzp!pw23v~4;Th2v&RgtF;(i5%jKUoC6}*qM<`E4tslz+vEAEo4>7DcJNr*;ojS zWFT^9bR~R&QLQvjJ`lQqLEu{1dM1ONmymP%fO>%QHTh3DIO}_{N}(^%MmNA755NTQ zs3u*V1Bv82{N6zP0Kf?kz+T`2O?n_lBHIg0PHD%gJN|e<#dLXl}nnep0n=LqOR6lF5|cA`@u5s0C0)w zqt~@ilZ+{GUJRU7Ky&UYeGch0+L|(cNNHe`BI*BfoxH!%4)n(SXpQ8!K3mjHEJC73*U6M|HQ*x~ zk$2QXGRbJHRn$}vkth<5r;O`KRNh9%eop>UHT1gTd8Y`Oh&{}(1fAPVy(^RK0!~-^ z@^aNy`}MyO#qA=lS9E>PjQK*ed85D%;4Z@^r#8F#rExg|{9O={L;Bn%r=K0mVcoHW zoQqru{8CW(4fefK;I17j;57O@%B5V0j5vhH5P6s>kYyq>mhmc8mHc)$Xu}pNQ4{V# zb=9*F=~Y?M!W2nFra6ZPRW4Er#MNVoE7V_VdO}Pokw~h*=YiO<4A?@XE%2SaC(lLX zQT!d%!n!;#gZ%fq`*Eh3RDg#(8W@Y2#?mB0YxO{@>dKAl)V|cu3p0zD{9o3P^?8-B RgFpZP002ovPDHLkV1f&dL*W1b literal 0 HcmV?d00001 diff --git a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java index 924988b..b314dd9 100644 --- a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java +++ b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java @@ -33,11 +33,19 @@ public class ContextMapGeneratorTest { - private static final String CONTEXT_MAP_FILE = "./src-gen/contextmap.png"; - private static final String CONTEXT_MAP_FILE_FIXED_WIDTH = "./src-gen/contextmap-width.png"; - private static final String CONTEXT_MAP_FILE_FIXED_HEIGHT = "./src-gen/contextmap-height.png"; - private static final String CONTEXT_MAP_FILE_DOT_FORMAT = "./src-gen/contextmap.dot"; - private static final String CONTEXT_MAP_FILE_SVG_FORMAT = "./src-gen/contextmap.svg"; + private static final String BASE_DIR = "./src-gen"; + private static final String CONTEXT_MAP_FILE = BASE_DIR + "/contextmap.png"; + private static final String CONTEXT_MAP_FILE_FIXED_WIDTH = BASE_DIR + "/contextmap-width.png"; + private static final String CONTEXT_MAP_FILE_FIXED_HEIGHT = BASE_DIR + "/contextmap-height.png"; + private static final String CONTEXT_MAP_FILE_DOT_FORMAT = BASE_DIR + "/contextmap.gv"; + private static final String CONTEXT_MAP_FILE_SVG_FORMAT = BASE_DIR + "/contextmap.svg"; + private static final String TEAM_MAP_FILE = BASE_DIR + "/teammap.png"; + private static final String TEAM_MAP_FILE_SVG_FORMAT = BASE_DIR + "/teammap.svg"; + private static final String TEAM_MAP_FILE_DOT_FORMAT = BASE_DIR + "/teammap.gv"; + private static final String TEAMS_ONLY_FILE = BASE_DIR + "/team-only_map.png"; + private static final String TEAM_MAP_NOT_CLUSTERED_FILE = BASE_DIR + "/teammap-not-clustered.png"; + private static final String TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE = BASE_DIR + "/teammap-with-inter-type-reference.png"; + private static final String TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE = BASE_DIR + "/teammap-with-inter-type-reference-not-clustered.png"; @BeforeAll static void prepare() { @@ -46,6 +54,13 @@ static void prepare() { deleteFileIfExisting(CONTEXT_MAP_FILE_FIXED_HEIGHT); deleteFileIfExisting(CONTEXT_MAP_FILE_DOT_FORMAT); deleteFileIfExisting(CONTEXT_MAP_FILE_SVG_FORMAT); + deleteFileIfExisting(TEAM_MAP_FILE); + deleteFileIfExisting(TEAM_MAP_FILE_SVG_FORMAT); + deleteFileIfExisting(TEAM_MAP_FILE_DOT_FORMAT); + deleteFileIfExisting(TEAMS_ONLY_FILE); + deleteFileIfExisting(TEAM_MAP_NOT_CLUSTERED_FILE); + deleteFileIfExisting(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE); + deleteFileIfExisting(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE); } static void deleteFileIfExisting(String filename) { @@ -71,6 +86,132 @@ public void canGenerateContextMapByOutputstream() throws IOException { assertTrue(new File(CONTEXT_MAP_FILE).exists()); } + @Test + public void canGenerateTeamMap() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + assertFalse(new File(TEAM_MAP_FILE).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_FILE)); + generator.setLabelSpacingFactor(10) + .setWidth(2000) + .generateContextMapGraphic(createTestTeamMap(), Format.PNG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_FILE).exists()); + } + + @Test + public void canGenerateTeamMapNotClustered() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + assertFalse(new File(TEAM_MAP_NOT_CLUSTERED_FILE).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_NOT_CLUSTERED_FILE)); + generator.setLabelSpacingFactor(10) + .clusterTeams(false) + .generateContextMapGraphic(createTestTeamMap(), Format.PNG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_NOT_CLUSTERED_FILE).exists()); + } + + @Test + public void canGenerateTeamMapWithInterTypeReference() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + ContextMap contextMap = createTestTeamMap(); + contextMap.addRelationship(new UpstreamDownstreamRelationship( + contextMap.getBoundedContexts().stream().filter(bc -> bc.getName().equals("Customer Management Context")).findFirst().get(), + contextMap.getBoundedContexts().stream().filter(bc -> bc.getName().equals("Customers Backend Team")).findFirst().get())); + assertFalse(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE)); + generator.setLabelSpacingFactor(10) + .generateContextMapGraphic(contextMap, Format.PNG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE).exists()); + } + + @Test + public void canGenerateTeamMapWithInterTypeReferenceNotClustered() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + ContextMap contextMap = createTestTeamMap(); + contextMap.addRelationship(new UpstreamDownstreamRelationship( + contextMap.getBoundedContexts().stream().filter(bc -> bc.getName().equals("Customer Management Context")).findFirst().get(), + contextMap.getBoundedContexts().stream().filter(bc -> bc.getName().equals("Customers Backend Team")).findFirst().get())); + assertFalse(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE)); + generator.setLabelSpacingFactor(10) + .clusterTeams(false) + .generateContextMapGraphic(contextMap, Format.PNG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE).exists()); + } + + @Test + public void canGenerateTeamMapAsSVG() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator() + .setBaseDir(new File(BASE_DIR)); + + // when + assertFalse(new File(TEAM_MAP_FILE_SVG_FORMAT).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_FILE_SVG_FORMAT)); + generator.setLabelSpacingFactor(10) + .setWidth(2000) + .generateContextMapGraphic(createTestTeamMap(), Format.SVG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_FILE_SVG_FORMAT).exists()); + } + + @Test + public void canGenerateTeamMapAsDOT() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + assertFalse(new File(TEAM_MAP_FILE_DOT_FORMAT).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAM_MAP_FILE_DOT_FORMAT)); + generator.setLabelSpacingFactor(10) + .setWidth(2000) + .generateContextMapGraphic(createTestTeamMap(), Format.DOT, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAM_MAP_FILE_DOT_FORMAT).exists()); + } + + @Test + public void canGenerateMapWithTeamsOnly() throws IOException { + // given + ContextMapGenerator generator = new ContextMapGenerator(); + + // when + assertFalse(new File(TEAMS_ONLY_FILE).exists()); + OutputStream outputStream = new FileOutputStream(new File(TEAMS_ONLY_FILE)); + generator.setLabelSpacingFactor(10) + .generateContextMapGraphic(createTeamsOnlyMap(), Format.PNG, outputStream); + outputStream.close(); + + // then + assertTrue(new File(TEAMS_ONLY_FILE).exists()); + } + @Test public void canGenerateContextMapWithFixedWith() throws IOException { // given @@ -218,4 +359,57 @@ private ContextMap createTestContextMap() { .addRelationship(new Partnership(riskManagement, policyManagement).setName("RelNameTest")); } + private ContextMap createTestTeamMap() { + BoundedContext customerManagement = new BoundedContext("Customer Management Context"); + BoundedContext customerSelfService = new BoundedContext("Customer Self-Service Context"); + BoundedContext policyManagementContext = new BoundedContext("Policy Management Context"); + BoundedContext riskManagementContext = new BoundedContext("Risk Management Context"); + + BoundedContext customersBackendTeam = new BoundedContext("Customers Backend Team", BoundedContextType.TEAM) + .realizing(customerManagement); + BoundedContext customersFrontendTeam = new BoundedContext("Customers Frontend Team", BoundedContextType.TEAM) + .realizing(customerSelfService); + BoundedContext contractsTeam = new BoundedContext("Contracts", BoundedContextType.TEAM) + .realizing(policyManagementContext); + BoundedContext claimsTeam = new BoundedContext("Claims", BoundedContextType.TEAM) + .realizing(riskManagementContext); + + return new ContextMap() + .addBoundedContext(customerManagement) + .addBoundedContext(customerSelfService) + .addBoundedContext(policyManagementContext) + .addBoundedContext(riskManagementContext) + .addBoundedContext(customersBackendTeam) + .addBoundedContext(customersFrontendTeam) + .addBoundedContext(contractsTeam) + .addBoundedContext(claimsTeam) + .addRelationship(new UpstreamDownstreamRelationship(customerManagement, customerSelfService) + .setCustomerSupplier(true)) + .addRelationship(new UpstreamDownstreamRelationship(customerManagement, policyManagementContext) + .setUpstreamPatterns(OPEN_HOST_SERVICE, PUBLISHED_LANGUAGE) + .setDownstreamPatterns(CONFORMIST)) + .addRelationship(new Partnership(policyManagementContext, riskManagementContext)) + .addRelationship(new UpstreamDownstreamRelationship(customersBackendTeam, customersFrontendTeam) + .setCustomerSupplier(true)) + .addRelationship(new UpstreamDownstreamRelationship(customersBackendTeam, contractsTeam)) + .addRelationship(new Partnership(contractsTeam, claimsTeam)); + } + + private ContextMap createTeamsOnlyMap() { + BoundedContext customersBackendTeam = new BoundedContext("Customers Backend Team", BoundedContextType.TEAM); + BoundedContext customersFrontendTeam = new BoundedContext("Customers Frontend Team", BoundedContextType.TEAM); + BoundedContext contractsTeam = new BoundedContext("Contracts", BoundedContextType.TEAM); + BoundedContext claimsTeam = new BoundedContext("Claims", BoundedContextType.TEAM); + + return new ContextMap() + .addBoundedContext(customersBackendTeam) + .addBoundedContext(customersFrontendTeam) + .addBoundedContext(contractsTeam) + .addBoundedContext(claimsTeam) + .addRelationship(new UpstreamDownstreamRelationship(customersBackendTeam, customersFrontendTeam) + .setCustomerSupplier(true)) + .addRelationship(new UpstreamDownstreamRelationship(customersBackendTeam, contractsTeam)) + .addRelationship(new Partnership(contractsTeam, claimsTeam)); + } + } diff --git a/src/test/java/org/contextmapper/contextmap/generator/model/BoundedContextTest.java b/src/test/java/org/contextmapper/contextmap/generator/model/BoundedContextTest.java index 43a7799..18f666d 100644 --- a/src/test/java/org/contextmapper/contextmap/generator/model/BoundedContextTest.java +++ b/src/test/java/org/contextmapper/contextmap/generator/model/BoundedContextTest.java @@ -15,6 +15,8 @@ */ package org.contextmapper.contextmap.generator.model; +import org.contextmapper.contextmap.generator.model.exception.BoundedContextIsNotATeamException; +import org.contextmapper.contextmap.generator.model.exception.TeamCannotImplementTeamException; import org.junit.jupiter.api.Test; import java.util.HashSet; @@ -53,6 +55,80 @@ public void boundedContextsWithSameNameAreEqual() { assertEquals(1, bcSet.size()); } + @Test + public void BoundedContextIsGenericByDefault() { + // given + BoundedContext testContext; + + // when + testContext = new BoundedContext("TestContext"); + + // then + assertEquals(BoundedContextType.GENERIC, testContext.getType()); + } + + @Test + public void canChangeBoundedContextType() { + // given + BoundedContext testContext = new BoundedContext("TestContext"); + + // when + testContext.withType(BoundedContextType.TEAM); + + // then + assertEquals(BoundedContextType.TEAM, testContext.getType()); + } + + @Test + public void genericContextCannotRealizeAnything() { + // given + BoundedContext testContext = new BoundedContext("TestContext"); + + // when, then + assertThrows(BoundedContextIsNotATeamException.class, () -> { + testContext.realizing(new BoundedContext("AnotherContext")); + }); + } + + @Test + public void teamCannotImplementOtherTeam() { + // given + BoundedContext team = new BoundedContext("TestTeam", BoundedContextType.TEAM); + + // when, then + assertThrows(TeamCannotImplementTeamException.class, () -> { + team.realizing(new BoundedContext("AnotherTeam", BoundedContextType.TEAM)); + }); + } + + @Test + public void teamCanRealizeSystem() { + // given + BoundedContext team = new BoundedContext("TestTeam", BoundedContextType.TEAM); + BoundedContext implementedSystem = new BoundedContext("TestSystem"); + + // when + team.realizing(implementedSystem); + + // then + assertEquals(1, team.getRealizedBoundedContexts().size()); + assertEquals("TestSystem", team.getRealizedBoundedContexts().get(0).getName()); + } + + @Test + public void genericSystemDoesNotRealizeAnything() { + // given + BoundedContext testContext1 = new BoundedContext("TestContext1", BoundedContextType.TEAM); + BoundedContext testContext2 = new BoundedContext("TestContext2"); + + // when + testContext1.realizing(testContext2); + testContext1.withType(BoundedContextType.GENERIC); // converting team to generic context + + // then + assertTrue(testContext1.getRealizedBoundedContexts().isEmpty()); + } + @Test public void otherObjectsAreNotEqual() { // given From 22916691e6e09c7f4c5781c0549c3b201115a8d3 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 14:39:28 +0200 Subject: [PATCH 2/8] Import fix --- .../java/guru/nidi/graphviz/engine/Graphviz.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/guru/nidi/graphviz/engine/Graphviz.java b/src/main/java/guru/nidi/graphviz/engine/Graphviz.java index 02738d0..c920567 100644 --- a/src/main/java/guru/nidi/graphviz/engine/Graphviz.java +++ b/src/main/java/guru/nidi/graphviz/engine/Graphviz.java @@ -17,12 +17,17 @@ import guru.nidi.graphviz.model.Graph; import guru.nidi.graphviz.model.MutableGraph; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.*; -import java.util.*; -import java.util.concurrent.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; From 32b763f300e0ad30ebd7efff8b482735c37c08cb Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 14:46:46 +0200 Subject: [PATCH 3/8] Need stracktrace on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3185de6..baea30d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ script: if [[ $TRAVIS_TAG =~ ^v.* ]]; then ./gradlew clean build publish -Prelease.useLastTag=true -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE} else - ./gradlew clean build snapshot $(if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo "publish -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE}"; fi) + ./gradlew clean build snapshot --stacktrace $(if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo "publish -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE}"; fi) fi before_install: From 4c3c0187bfe04bf520f669ae2e806716b521edae Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 14:58:11 +0200 Subject: [PATCH 4/8] Gradle: more test failures output --- .travis.yml | 2 +- build.gradle | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index baea30d..3185de6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ script: if [[ $TRAVIS_TAG =~ ^v.* ]]; then ./gradlew clean build publish -Prelease.useLastTag=true -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE} else - ./gradlew clean build snapshot --stacktrace $(if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo "publish -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE}"; fi) + ./gradlew clean build snapshot $(if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo "publish -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE}"; fi) fi before_install: diff --git a/build.gradle b/build.gradle index 104179a..cfeed2e 100644 --- a/build.gradle +++ b/build.gradle @@ -211,6 +211,14 @@ publish.finalizedBy(publishP2Repo) test { useJUnitPlatform() + + testLogging { + showExceptions true + exceptionFormat "full" + + showCauses true + showStackTraces true + } } jacocoTestReport { From 3d3b5d9a91fbfaa8def04d60e97ccc2c39f8ed44 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 15:07:28 +0200 Subject: [PATCH 5/8] Flush and close outputstream --- .../contextmapper/contextmap/generator/ContextMapGenerator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java index 541b26f..5f4af99 100644 --- a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java +++ b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java @@ -371,6 +371,8 @@ private void exportImages() throws IOException { File targetFile = new File(baseDir, "team-icon.png"); OutputStream outStream = new FileOutputStream(targetFile); outStream.write(buffer); + outStream.flush(); + outStream.close(); } } From f2562fb827e10113cb08453307919edd6104a8d9 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 15:13:35 +0200 Subject: [PATCH 6/8] File writing improvement --- .../generator/ContextMapGenerator.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java index 5f4af99..0f49a9f 100644 --- a/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java +++ b/src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java @@ -365,14 +365,18 @@ private Label getEdgeHTMLLabel(String upstreamDownstreamLabel, Set patte } private void exportImages() throws IOException { - InputStream teamIconInputStream = ContextMapGenerator.class.getClassLoader().getResourceAsStream("team-icon.png"); - byte[] buffer = new byte[teamIconInputStream.available()]; - teamIconInputStream.read(buffer); - File targetFile = new File(baseDir, "team-icon.png"); - OutputStream outStream = new FileOutputStream(targetFile); - outStream.write(buffer); - outStream.flush(); - outStream.close(); + if (!baseDir.exists()) + baseDir.mkdir(); + if (!new File(baseDir, "team-icon.png").exists()) { + InputStream teamIconInputStream = ContextMapGenerator.class.getClassLoader().getResourceAsStream("team-icon.png"); + byte[] buffer = new byte[teamIconInputStream.available()]; + teamIconInputStream.read(buffer); + File targetFile = new File(baseDir, "team-icon.png"); + OutputStream outStream = new FileOutputStream(targetFile); + outStream.write(buffer); + outStream.flush(); + outStream.close(); + } } } From 62e018131dea60a28d0f521ebf0b456385efcf41 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 15:19:51 +0200 Subject: [PATCH 7/8] Remove an underline in file name --- .../contextmap/generator/ContextMapGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java index b314dd9..efaeb44 100644 --- a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java +++ b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java @@ -42,7 +42,7 @@ public class ContextMapGeneratorTest { private static final String TEAM_MAP_FILE = BASE_DIR + "/teammap.png"; private static final String TEAM_MAP_FILE_SVG_FORMAT = BASE_DIR + "/teammap.svg"; private static final String TEAM_MAP_FILE_DOT_FORMAT = BASE_DIR + "/teammap.gv"; - private static final String TEAMS_ONLY_FILE = BASE_DIR + "/team-only_map.png"; + private static final String TEAMS_ONLY_FILE = BASE_DIR + "/team-only-map.png"; private static final String TEAM_MAP_NOT_CLUSTERED_FILE = BASE_DIR + "/teammap-not-clustered.png"; private static final String TEAM_MAP_WITH_INTER_TYPE_REFERENCE_FILE = BASE_DIR + "/teammap-with-inter-type-reference.png"; private static final String TEAM_MAP_WITH_INTER_TYPE_REFERENCE_NOT_CLUSTERED_FILE = BASE_DIR + "/teammap-with-inter-type-reference-not-clustered.png"; From 29dd31ae8a6053d212b9d3e1617012beba7c9e80 Mon Sep 17 00:00:00 2001 From: Stefan Kapferer Date: Fri, 2 Oct 2020 15:24:38 +0200 Subject: [PATCH 8/8] Test: ensure src-gen exists --- .../contextmap/generator/ContextMapGeneratorTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java index efaeb44..6bccff8 100644 --- a/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java +++ b/src/test/java/org/contextmapper/contextmap/generator/ContextMapGeneratorTest.java @@ -49,6 +49,8 @@ public class ContextMapGeneratorTest { @BeforeAll static void prepare() { + if (!new File(BASE_DIR).exists()) + new File(BASE_DIR).mkdir(); deleteFileIfExisting(CONTEXT_MAP_FILE); deleteFileIfExisting(CONTEXT_MAP_FILE_FIXED_WIDTH); deleteFileIfExisting(CONTEXT_MAP_FILE_FIXED_HEIGHT);