Skip to content
This repository was archived by the owner on Jan 11, 2024. It is now read-only.

Commit 4e47698

Browse files
Adds PlantUML/Mermaid/Kroki importers, refactoring to support new library responsibilities.
1 parent 62bfc45 commit 4e47698

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+588
-29
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repositories {
99
}
1010

1111
dependencies {
12-
api 'com.structurizr:structurizr-client:1.19.0'
12+
api 'com.structurizr:structurizr-client:1.20.0'
1313

1414
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0'
1515
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.structurizr.importer.diagrams;
2+
3+
import com.structurizr.view.View;
4+
import com.structurizr.view.ViewSet;
5+
6+
public abstract class AbstractDiagramImporter {
7+
8+
protected static final String CONTENT_TYPE_IMAGE_PNG = "image/png";
9+
10+
protected String getViewOrViewSetProperty(View view, String name) {
11+
ViewSet views = view.getViewSet();
12+
13+
return
14+
view.getProperties().getOrDefault(name,
15+
views.getConfiguration().getProperties().get(name)
16+
);
17+
}
18+
19+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.structurizr.importer.diagrams.kroki;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.StandardCharsets;
5+
import java.util.Base64;
6+
import java.util.zip.Deflater;
7+
8+
/**
9+
* See https://docs.kroki.io/kroki/setup/encode-diagram/#java
10+
*/
11+
class KrokiEncoder {
12+
13+
public String encode(String decoded) throws IOException {
14+
byte[] bytes = Base64.getUrlEncoder().encode(compress(decoded.getBytes()));
15+
return new String(bytes, StandardCharsets.UTF_8);
16+
}
17+
18+
private byte[] compress(byte[] source) throws IOException {
19+
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
20+
deflater.setInput(source);
21+
deflater.finish();
22+
23+
byte[] buffer = new byte[2048];
24+
int compressedLength = deflater.deflate(buffer);
25+
byte[] result = new byte[compressedLength];
26+
System.arraycopy(buffer, 0, result, 0, compressedLength);
27+
return result;
28+
}
29+
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.structurizr.importer.diagrams.kroki;
2+
3+
import com.structurizr.importer.diagrams.AbstractDiagramImporter;
4+
import com.structurizr.util.StringUtils;
5+
import com.structurizr.view.ImageView;
6+
7+
import java.io.File;
8+
import java.nio.charset.StandardCharsets;
9+
import java.nio.file.Files;
10+
11+
public class KrokiImporter extends AbstractDiagramImporter {
12+
13+
private static final String KROKI_URL_PROPERTY = "kroki.url";
14+
15+
public void importDiagram(ImageView view, String format, File file) throws Exception {
16+
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
17+
view.setTitle(file.getName());
18+
19+
importDiagram(view, format, content);
20+
}
21+
22+
public void importDiagram(ImageView view, String format, String content) throws Exception {
23+
String krokiServer = getViewOrViewSetProperty(view, KROKI_URL_PROPERTY);
24+
if (StringUtils.isNullOrEmpty(krokiServer)) {
25+
throw new IllegalArgumentException("Please define a view/viewset property named " + KROKI_URL_PROPERTY + " to specify your Kroki server");
26+
}
27+
28+
String encodedDiagram = new KrokiEncoder().encode(content);
29+
String url = String.format("%s/%s/png/%s", krokiServer, format, encodedDiagram);
30+
31+
view.setContent(url);
32+
view.setContentType(CONTENT_TYPE_IMAGE_PNG);
33+
}
34+
35+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.structurizr.importer.diagrams.mermaid;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.util.Base64;
5+
6+
/**
7+
* Encodes a Mermaid diagram definition to base64 format, for use with image URLs, etc.
8+
*/
9+
public class MermaidEncoder {
10+
11+
private static final String TEMPLATE = "{ \"code\":\"%s\", \"mermaid\":{\"theme\":\"default\"}}";
12+
13+
public String encode(String mermaidDefinition) {
14+
String s = String.format(TEMPLATE, mermaidDefinition.replaceAll("\n", "\\\\n").replaceAll("\"", "\\\\\""));
15+
return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
16+
}
17+
18+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.structurizr.importer.diagrams.mermaid;
2+
3+
import com.structurizr.importer.diagrams.AbstractDiagramImporter;
4+
import com.structurizr.util.StringUtils;
5+
import com.structurizr.view.ImageView;
6+
7+
import java.io.File;
8+
import java.nio.charset.StandardCharsets;
9+
import java.nio.file.Files;
10+
11+
public class MermaidImporter extends AbstractDiagramImporter {
12+
13+
private static final String MERMAID_URL_PROPERTY = "mermaid.url";
14+
15+
public void importDiagram(ImageView view, File file) throws Exception {
16+
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
17+
view.setTitle(file.getName());
18+
19+
importDiagram(view, content);
20+
}
21+
22+
public void importDiagram(ImageView view, String content) {
23+
String mermaidServer = getViewOrViewSetProperty(view, MERMAID_URL_PROPERTY);
24+
if (StringUtils.isNullOrEmpty(mermaidServer)) {
25+
throw new IllegalArgumentException("Please define a view/viewset property named " + MERMAID_URL_PROPERTY + " to specify your Mermaid server");
26+
}
27+
28+
String encodedMermaid = new MermaidEncoder().encode(content);
29+
String url = String.format("%s/img/%s", mermaidServer, encodedMermaid);
30+
view.setContent(url);
31+
view.setContentType(CONTENT_TYPE_IMAGE_PNG);
32+
}
33+
34+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.structurizr.importer.diagrams.plantuml;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.nio.charset.StandardCharsets;
5+
import java.util.zip.Deflater;
6+
import java.util.zip.DeflaterOutputStream;
7+
8+
/**
9+
* A Java implementation of http://plantuml.com/code-javascript-synchronous
10+
* that uses Java's built-in Deflate algorithm.
11+
*/
12+
class PlantUMLEncoder {
13+
14+
String encode(String plantUMLDefinition) throws Exception {
15+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
16+
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION, true);
17+
18+
DeflaterOutputStream dos = new DeflaterOutputStream(baos, deflater, true);
19+
dos.write(plantUMLDefinition.getBytes(StandardCharsets.UTF_8));
20+
dos.finish();
21+
22+
return encode(baos.toByteArray());
23+
}
24+
25+
private String encode(byte[] bytes) {
26+
StringBuilder buf = new StringBuilder();
27+
for (int i = 0; i < bytes.length; i += 3) {
28+
int b1 = (bytes[i]) & 0xFF;
29+
int b2 = (i + 1 < bytes.length ? bytes[i + 1] : (byte)0) & 0xFF;
30+
int b3 = (i + 2 < bytes.length ? bytes[i + 2] : (byte)0) & 0xFF;
31+
32+
append3bytes(buf, b1, b2, b3);
33+
}
34+
35+
return buf.toString();
36+
}
37+
38+
private char encode6bit(byte b) {
39+
if (b < 10) {
40+
return (char) ('0' + b);
41+
}
42+
b -= 10;
43+
if (b < 26) {
44+
return (char) ('A' + b);
45+
}
46+
b -= 26;
47+
if (b < 26) {
48+
return (char) ('a' + b);
49+
}
50+
b -= 26;
51+
if (b == 0) {
52+
return '-';
53+
}
54+
if (b == 1) {
55+
return '_';
56+
}
57+
58+
return '?';
59+
}
60+
61+
private void append3bytes(StringBuilder buf, int b1, int b2, int b3) {
62+
int c1 = b1 >> 2;
63+
int c2 = (b1 & 0x3) << 4 | b2 >> 4;
64+
int c3 = (b2 & 0xF) << 2 | b3 >> 6;
65+
int c4 = b3 & 0x3F;
66+
67+
buf.append(encode6bit((byte)(c1 & 0x3F)));
68+
buf.append(encode6bit((byte)(c2 & 0x3F)));
69+
buf.append(encode6bit((byte)(c3 & 0x3F)));
70+
buf.append(encode6bit((byte)(c4 & 0x3F)));
71+
}
72+
73+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.structurizr.importer.diagrams.plantuml;
2+
3+
import com.structurizr.importer.diagrams.AbstractDiagramImporter;
4+
import com.structurizr.util.StringUtils;
5+
import com.structurizr.view.ImageView;
6+
7+
import java.io.File;
8+
import java.nio.charset.StandardCharsets;
9+
import java.nio.file.Files;
10+
11+
public class PlantUMLImporter extends AbstractDiagramImporter {
12+
13+
private static final String PLANTUML_URL_PROPERTY = "plantuml.url";
14+
private static final String TITLE_STRING = "title ";
15+
private static final String NEWLINE = "\n";
16+
17+
public void importDiagram(ImageView view, File file) throws Exception {
18+
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
19+
view.setTitle(file.getName());
20+
21+
importDiagram(view, content);
22+
}
23+
24+
public void importDiagram(ImageView view, String content) throws Exception {
25+
String plantUMLServer = getViewOrViewSetProperty(view, PLANTUML_URL_PROPERTY);
26+
if (StringUtils.isNullOrEmpty(plantUMLServer)) {
27+
throw new IllegalArgumentException("Please define a view/viewset property named " + PLANTUML_URL_PROPERTY + " to specify your PlantUML server");
28+
}
29+
30+
String encodedPlantUML = new PlantUMLEncoder().encode(content);
31+
String url = String.format("%s/png/%s", plantUMLServer, encodedPlantUML);
32+
view.setContent(url);
33+
view.setContentType(CONTENT_TYPE_IMAGE_PNG);
34+
35+
String[] lines = content.split(NEWLINE);
36+
for (String line : lines) {
37+
if (line.startsWith(TITLE_STRING)) {
38+
String title = line.substring(TITLE_STRING.length());
39+
view.setTitle(title);
40+
}
41+
}
42+
}
43+
44+
}

src/main/java/com/structurizr/documentation/importer/AdrToolsDecisionImporter.java renamed to src/main/java/com/structurizr/importer/documentation/AdrToolsDecisionImporter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Decision;
44
import com.structurizr.documentation.Documentable;
@@ -97,7 +97,7 @@ public void importDocumentation(Documentable documentable, File path) {
9797
}
9898

9999
try {
100-
Map<String,Decision> decisionsById = new LinkedHashMap<>();
100+
Map<String, Decision> decisionsById = new LinkedHashMap<>();
101101

102102
File[] markdownFiles = path.listFiles((dir, name) -> name.endsWith(".md"));
103103
if (markdownFiles != null) {

src/main/java/com/structurizr/documentation/importer/DefaultDocumentationImporter.java renamed to src/main/java/com/structurizr/importer/documentation/DefaultDocumentationImporter.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Documentable;
44
import com.structurizr.documentation.Format;
55
import com.structurizr.documentation.Section;
6-
import com.structurizr.documentation.util.FormatFinder;
76

87
import java.io.File;
98
import java.nio.charset.StandardCharsets;
109
import java.nio.file.Files;
11-
import java.util.ArrayList;
1210
import java.util.Arrays;
13-
import java.util.List;
1411
import java.util.regex.Matcher;
1512
import java.util.regex.Pattern;
1613

src/main/java/com/structurizr/documentation/importer/DefaultImageImporter.java renamed to src/main/java/com/structurizr/importer/documentation/DefaultImageImporter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Documentable;
44
import com.structurizr.documentation.Image;

src/main/java/com/structurizr/documentation/importer/DocumentationImportException.java renamed to src/main/java/com/structurizr/importer/documentation/DocumentationImportException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
public class DocumentationImportException extends RuntimeException {
44

src/main/java/com/structurizr/documentation/importer/DocumentationImporter.java renamed to src/main/java/com/structurizr/importer/documentation/DocumentationImporter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Documentable;
44

src/main/java/com/structurizr/documentation/util/FormatFinder.java renamed to src/main/java/com/structurizr/importer/documentation/FormatFinder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.util;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Format;
44

@@ -9,9 +9,9 @@
99

1010
public class FormatFinder {
1111

12-
private static Set<String> MARKDOWN_EXTENSIONS = new HashSet<>(Arrays.asList(".md", ".markdown", ".text"));
12+
private static final Set<String> MARKDOWN_EXTENSIONS = new HashSet<>(Arrays.asList(".md", ".markdown", ".text"));
1313

14-
private static Set<String> ASCIIDOC_EXTENSIONS = new HashSet<>(Arrays.asList(".asciidoc", ".adoc", ".asc"));
14+
private static final Set<String> ASCIIDOC_EXTENSIONS = new HashSet<>(Arrays.asList(".asciidoc", ".adoc", ".asc"));
1515

1616
public static boolean isMarkdownOrAsciiDoc(File file) {
1717
String extension = file.getName().substring(file.getName().lastIndexOf("."));

src/main/java/com/structurizr/documentation/importer/RecursiveDefaultDocumentationImporter.java renamed to src/main/java/com/structurizr/importer/documentation/RecursiveDefaultDocumentationImporter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.structurizr.documentation.importer;
1+
package com.structurizr.importer.documentation;
22

33
import com.structurizr.documentation.Documentable;
44
import com.structurizr.documentation.Section;

src/test/diagrams/kroki/diagram.dot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
digraph G {Hello->World}

src/test/diagrams/mermaid/class.mmd

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
classDiagram
2+
Animal <|-- Duck
3+
Animal <|-- Fish
4+
Animal <|-- Zebra
5+
Animal : +int age
6+
Animal : +String gender
7+
Animal: +isMammal()
8+
Animal: +mate()
9+
class Duck{
10+
+String beakColor
11+
+swim()
12+
+quack()
13+
}
14+
class Fish{
15+
-int sizeInFeet
16+
-canEat()
17+
}
18+
class Zebra{
19+
+bool is_wild
20+
+run()
21+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
flowchart TD
2+
Start --> Stop
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@startuml
2+
title Sequence diagram example
3+
Bob -> Alice : hello
4+
@enduml
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@startuml
2+
Bob -> Alice : hello
3+
@enduml

0 commit comments

Comments
 (0)