+ * Please note that it is not recommended to use the public, central PlantUML server at
+ * https://www.plantuml.com/plantuml.
+ * Although not strictly forbidden by the author of PlantUML, using the central server to generate your
+ * javadoc diagrams is causing additional load on the central server and is a lot slower than running your own
+ * local server.
+ *
+ * Using docker to run a local PlantUML server can be a simple as:
+ *
plantumlServerUrl();
+
+ /**
+ * The name of the doclet to delegate main documentation to
* or {@link Optional#empty} if no delegation is wanted.
*
* @return The name of the doclet to delegate main documentation to
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/javadoc/DocletConfig.java b/src/main/java/nl/talsmasoftware/umldoclet/javadoc/DocletConfig.java
index ca400fc8a..85983f3a3 100644
--- a/src/main/java/nl/talsmasoftware/umldoclet/javadoc/DocletConfig.java
+++ b/src/main/java/nl/talsmasoftware/umldoclet/javadoc/DocletConfig.java
@@ -52,6 +52,8 @@ public class DocletConfig implements Configuration {
private final UMLOptions options;
private volatile LocalizedReporter reporter;
+ String plantumlServerUrl = null;
+
/**
* The name of the delegate doclet to use for the main documentation task.
*
@@ -139,6 +141,11 @@ public Set mergeOptionsWith(Set extends Doclet.Option> standard
return options.mergeWith(standardOptions);
}
+ @Override
+ public Optional plantumlServerUrl() {
+ return Optional.ofNullable(plantumlServerUrl);
+ }
+
@Override
public Optional delegateDocletName() {
return Optional.ofNullable(delegateDoclet).filter(name -> !"false".equalsIgnoreCase(name));
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/javadoc/UMLOptions.java b/src/main/java/nl/talsmasoftware/umldoclet/javadoc/UMLOptions.java
index 4c8ef7525..ed7da611d 100644
--- a/src/main/java/nl/talsmasoftware/umldoclet/javadoc/UMLOptions.java
+++ b/src/main/java/nl/talsmasoftware/umldoclet/javadoc/UMLOptions.java
@@ -80,6 +80,8 @@ private UMLOptions(DocletConfig config, Set extends Doclet.Option> standardOpt
this.options.add(new Option("-d", 1, Kind.OTHER, args -> config.destDirName = args.get(0)));
// Our own options
+ this.options.add(new Option("--plantuml-server-url -plantumlServerUrl", 1, Kind.STANDARD,
+ args -> config.plantumlServerUrl = args.get(0)));
this.options.add(new Option("--delegate-doclet -delegateDoclet", 1, Kind.STANDARD,
args -> config.delegateDoclet = args.get(0)));
this.options.add(new Option("--create-puml-files -createPumlFiles", 0, Kind.STANDARD, args -> config.renderPumlFile = true));
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/uml/Diagram.java b/src/main/java/nl/talsmasoftware/umldoclet/uml/Diagram.java
index 6814a6375..517683e58 100644
--- a/src/main/java/nl/talsmasoftware/umldoclet/uml/Diagram.java
+++ b/src/main/java/nl/talsmasoftware/umldoclet/uml/Diagram.java
@@ -16,19 +16,18 @@
package nl.talsmasoftware.umldoclet.uml;
import net.sourceforge.plantuml.FileFormat;
-import net.sourceforge.plantuml.FileFormatOption;
-import net.sourceforge.plantuml.SourceStringReader;
import nl.talsmasoftware.umldoclet.configuration.Configuration;
import nl.talsmasoftware.umldoclet.configuration.ImageConfig;
import nl.talsmasoftware.umldoclet.logging.Message;
import nl.talsmasoftware.umldoclet.rendering.indent.IndentingPrintWriter;
import nl.talsmasoftware.umldoclet.rendering.writers.StringBufferingWriter;
+import nl.talsmasoftware.umldoclet.uml.plantuml.PlantumlGenerator;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.nio.file.Files;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -40,16 +39,19 @@
import static nl.talsmasoftware.umldoclet.util.FileUtils.withoutExtension;
/**
- * Abstract class corresponding to a single UML diagram.
+ * Abstract UML Diagram class.
*/
public abstract class Diagram extends UMLNode {
+
private final Configuration config;
+ private final PlantumlGenerator plantumlGenerator;
private final FileFormat[] formats;
private File diagramBaseFile;
protected Diagram(Configuration config) {
super(null);
this.config = requireNonNull(config, "Configuration is ");
+ this.plantumlGenerator = PlantumlGenerator.getPlantumlGenerator(config);
this.formats = config.images().formats().stream()
.map(this::toFileFormat).filter(Objects::nonNull)
.toArray(FileFormat[]::new);
@@ -90,7 +92,7 @@ public Configuration getConfiguration() {
* Determine the physical file location for the plantuml output.
*
* This will even be called if {@code -createPumlFiles} is not enabled,
- * to determine the {@linkplain #getDiagramBaseFile()}.
+ * to determine the {@code diagram base file}.
*
* @return The physical file for the plantuml output.
*/
@@ -167,15 +169,15 @@ private String writePlantumlSourceToFile() throws IOException {
private StringBufferingWriter createBufferingPlantumlFileWriter(File pumlFile) throws IOException {
return new StringBufferingWriter(
new OutputStreamWriter(
- new FileOutputStream(pumlFile), config.umlCharset()));
+ Files.newOutputStream(pumlFile.toPath()), config.umlCharset()));
}
private void renderDiagramFile(String plantumlSource, FileFormat format) throws IOException {
final File diagramFile = getDiagramFile(format);
config.logger().info(Message.INFO_GENERATING_FILE, diagramFile);
ensureParentDir(diagramFile);
- try (OutputStream out = new FileOutputStream(diagramFile)) {
- new SourceStringReader(plantumlSource).outputImage(out, new FileFormatOption(format));
+ try (OutputStream out = Files.newOutputStream(diagramFile.toPath())) {
+ plantumlGenerator.generatePlantumlDiagramFromSource(plantumlSource, format, out);
}
}
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/BuiltinPlantumlGenerator.java b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/BuiltinPlantumlGenerator.java
new file mode 100644
index 000000000..3c251e2bb
--- /dev/null
+++ b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/BuiltinPlantumlGenerator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2022 Talsma ICT
+ *
+ * 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 nl.talsmasoftware.umldoclet.uml.plantuml;
+
+import net.sourceforge.plantuml.FileFormat;
+import net.sourceforge.plantuml.FileFormatOption;
+import net.sourceforge.plantuml.SourceStringReader;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public final class BuiltinPlantumlGenerator implements PlantumlGenerator {
+ @Override
+ public void generatePlantumlDiagramFromSource(String plantumlSource, FileFormat format, OutputStream out) throws IOException {
+ new SourceStringReader(plantumlSource).outputImage(out, new FileFormatOption(format));
+ }
+}
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/PlantumlGenerator.java b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/PlantumlGenerator.java
new file mode 100644
index 000000000..ae82784de
--- /dev/null
+++ b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/PlantumlGenerator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2022 Talsma ICT
+ *
+ * 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 nl.talsmasoftware.umldoclet.uml.plantuml;
+
+import net.sourceforge.plantuml.FileFormat;
+import nl.talsmasoftware.umldoclet.configuration.Configuration;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.regex.Pattern;
+
+public interface PlantumlGenerator {
+ Pattern HTTP_URLS = Pattern.compile("^https?://");
+
+ static PlantumlGenerator getPlantumlGenerator(Configuration configuration) {
+ return configuration.plantumlServerUrl()
+ .filter(url -> HTTP_URLS.matcher(url).find())
+ .map(url -> (PlantumlGenerator) new RemotePlantumlGenerator(url))
+ .orElseGet(BuiltinPlantumlGenerator::new);
+ }
+
+ void generatePlantumlDiagramFromSource(String plantumlSource, FileFormat format, OutputStream out) throws IOException;
+
+}
diff --git a/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGenerator.java b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGenerator.java
new file mode 100644
index 000000000..491c1825c
--- /dev/null
+++ b/src/main/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGenerator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2022 Talsma ICT
+ *
+ * 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 nl.talsmasoftware.umldoclet.uml.plantuml;
+
+import net.sourceforge.plantuml.FileFormat;
+import net.sourceforge.plantuml.code.ArobaseStringCompressor;
+import net.sourceforge.plantuml.code.AsciiEncoder;
+import net.sourceforge.plantuml.code.CompressionZlib;
+import net.sourceforge.plantuml.code.Transcoder;
+import net.sourceforge.plantuml.code.TranscoderImpl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public class RemotePlantumlGenerator implements PlantumlGenerator {
+ private static final String DEFAULT_PLANTUML_BASE_URL = "https://www.plantuml.com/plantuml/";
+ private static final Transcoder TRANSCODER =
+ TranscoderImpl.utf8(new AsciiEncoder(), new ArobaseStringCompressor(), new CompressionZlib());
+
+ private final String baseUrl;
+
+ public RemotePlantumlGenerator(final String baseUrl) {
+ String url = Objects.toString(baseUrl, DEFAULT_PLANTUML_BASE_URL);
+ if (!url.endsWith("/")) url += "/";
+ this.baseUrl = url;
+ }
+
+ @Override
+ public void generatePlantumlDiagramFromSource(String plantumlSource, FileFormat format, OutputStream out) {
+ final String encodedDiagram = encodeDiagram(plantumlSource);
+ final String diagramUrl = baseUrl + format.name().toLowerCase() + '/' + encodedDiagram;
+ try (InputStream in = new URL(diagramUrl).openConnection().getInputStream()) {
+ final byte[] buf = new byte[4096];
+ for (int read = in.read(buf); read >= 0; read = in.read(buf)) {
+ out.write(buf, 0, read);
+ }
+ } catch (IOException | RuntimeException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String encodeDiagram(final String diagramSource) {
+ try {
+ // TODO internalize transcoder to be able to remove PlantUML dependency altogether.
+ return TRANSCODER.encode(requireNonNull(diagramSource, "UML diagram source was ."));
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error encoding diagram: " + ioe.getMessage(), ioe);
+ }
+ }
+
+}
diff --git a/src/main/resources/nl/talsmasoftware/umldoclet/UMLDoclet.properties b/src/main/resources/nl/talsmasoftware/umldoclet/UMLDoclet.properties
index 70377b444..5312763b5 100644
--- a/src/main/resources/nl/talsmasoftware/umldoclet/UMLDoclet.properties
+++ b/src/main/resources/nl/talsmasoftware/umldoclet/UMLDoclet.properties
@@ -23,6 +23,8 @@ error.unanticipated.error.generating.diagrams=Unanticipated error generating dia
error.unanticipated.error.postprocessing.html=Unanticipated error post-processing HTML: {0}
# Usage
+doclet.usage.plantuml-server-url.description=Base URL for the PlantUML server\nExamples: http://localhost:8080/, https://www.plantuml.com/plantuml/
+doclet.usage.plantuml-server-url.parameters=
doclet.usage.delegate-doclet.description=The delegate doclet providing the main documentation\nDefaults to 'jdk.javadoc.doclet.StandardDoclet'\nSpecify 'false' to disable delegation
doclet.usage.delegate-doclet.parameters=
doclet.usage.create-puml-files.description=Create PlantUML '.puml' files
diff --git a/src/test/java/nl/talsmasoftware/umldoclet/uml/DependencyDiagramTest.java b/src/test/java/nl/talsmasoftware/umldoclet/uml/DependencyDiagramTest.java
index 7673b0879..70a32d599 100644
--- a/src/test/java/nl/talsmasoftware/umldoclet/uml/DependencyDiagramTest.java
+++ b/src/test/java/nl/talsmasoftware/umldoclet/uml/DependencyDiagramTest.java
@@ -30,8 +30,17 @@
import static java.util.Collections.singleton;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.*;
-import static org.mockito.Mockito.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.hasToString;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
public class DependencyDiagramTest {
private ImageConfig mockImages;
@@ -54,6 +63,7 @@ public void prepareMocksDiagramAndExclusions() {
public void verifyMocks() {
verify(mockConfig, atLeastOnce()).images();
verify(mockImages, atLeastOnce()).formats();
+ verify(mockConfig, atLeast(0)).plantumlServerUrl();
verify(mockConfig, atLeast(0)).excludedPackageDependencies();
verify(mockConfig, atLeast(0)).indentation();
verifyNoMoreInteractions(mockConfig, mockImages);
diff --git a/src/test/java/nl/talsmasoftware/umldoclet/uml/DiagramTest.java b/src/test/java/nl/talsmasoftware/umldoclet/uml/DiagramTest.java
index 8e11b618f..94643b1b9 100644
--- a/src/test/java/nl/talsmasoftware/umldoclet/uml/DiagramTest.java
+++ b/src/test/java/nl/talsmasoftware/umldoclet/uml/DiagramTest.java
@@ -71,6 +71,7 @@ public void setUp() {
@AfterEach
public void tearDown() {
+ verify(config, atLeast(0)).plantumlServerUrl();
verify(config, atLeast(0)).images();
verify(config, atLeast(0)).destinationDirectory();
verify(config, atLeast(0)).logger();
diff --git a/src/test/java/nl/talsmasoftware/umldoclet/uml/NamespaceTest.java b/src/test/java/nl/talsmasoftware/umldoclet/uml/NamespaceTest.java
index b00c11e3b..28035d9d5 100644
--- a/src/test/java/nl/talsmasoftware/umldoclet/uml/NamespaceTest.java
+++ b/src/test/java/nl/talsmasoftware/umldoclet/uml/NamespaceTest.java
@@ -52,10 +52,12 @@ public void verifyMocks() {
public void testEquals() {
PackageDiagram packageUml = new PackageDiagram(config, "a.b.c", randomString());
Namespace namespace = new Namespace(packageUml, "a.b.c", randomString());
+
assertThat(namespace.equals(namespace), is(true));
assertThat(namespace, is(equalTo(new Namespace(null, "a.b.c", randomString()))));
assertThat(namespace, is(equalTo(new Namespace(packageUml, "a.b.c", randomString()))));
assertThat(namespace, is(not(equalTo(new Namespace(packageUml, "A.B.C", randomString())))));
+ verify(config, atLeastOnce()).plantumlServerUrl();
}
}
diff --git a/src/test/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGeneratorTest.java b/src/test/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGeneratorTest.java
new file mode 100644
index 000000000..3529aa3e7
--- /dev/null
+++ b/src/test/java/nl/talsmasoftware/umldoclet/uml/plantuml/RemotePlantumlGeneratorTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2022 Talsma ICT
+ *
+ * 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 nl.talsmasoftware.umldoclet.uml.plantuml;
+
+import net.sourceforge.plantuml.FileFormat;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+@Testcontainers
+class RemotePlantumlGeneratorTest {
+ static final String testUml = "@startuml\r\nBob -> Alice : hello\r\n@enduml";
+
+ @Container
+ GenericContainer plantumlServer = new GenericContainer(DockerImageName.parse("plantuml/plantuml-server"))
+ .withExposedPorts(8080);
+
+ PlantumlGenerator subject;
+
+ @BeforeEach
+ void setUp() {
+ subject = new RemotePlantumlGenerator(String.format("http://%s:%s/",
+ plantumlServer.getHost(), plantumlServer.getMappedPort(8080)));
+// subject = new RemotePlantumlGenerator("https://www.plantuml.com/plantuml/");
+// subject = new RemotePlantumlGenerator("http://localhost:8080/");
+ }
+
+ @Test
+ void simpleDiagramCanBeGenerated() throws IOException {
+ // prepare
+ final File testDiagram = new File("target/test-classes/"
+ + getClass().getPackageName().replace('.', '/')
+ + "/testUml.svg");
+ testDiagram.delete();
+
+ // execute
+ try (OutputStream out = new FileOutputStream(testDiagram)) {
+ subject.generatePlantumlDiagramFromSource(testUml, FileFormat.SVG, out);
+ }
+
+ // verify
+ assertThat(testDiagram.isFile(), is(true));
+ }
+
+}
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback-test.xml
similarity index 96%
rename from src/test/resources/logback.xml
rename to src/test/resources/logback-test.xml
index 5c2c51bd4..5131db2ad 100644
--- a/src/test/resources/logback.xml
+++ b/src/test/resources/logback-test.xml
@@ -1,7 +1,6 @@
-
true