Skip to content

Commit

Permalink
Merge 2e77acf into d318a22
Browse files Browse the repository at this point in the history
  • Loading branch information
sjoerdtalsma committed Dec 30, 2022
2 parents d318a22 + 2e77acf commit 951e533
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 12 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -325,6 +325,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.17.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
Expand Down
Expand Up @@ -31,7 +31,26 @@
public interface Configuration {

/**
* Tha name of the doclet to delegate main documentation to
* The base URL of the <a href="https://www.plantuml.com/plantuml">PlantUML server</a> to generate diagrams with.
* <p>
* Please note that it is not recommended to use the public, central PlantUML server at
* <a href="https://www.plantuml.com/plantuml">https://www.plantuml.com/plantuml</a>.
* 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.
* <p>
* Using docker to run a local PlantUML server can be a simple as:
* <pre>{@code
* docker run -d -p 8080:8080 plantuml/plantuml-server:latest
* }</pre>
* After that, you can run the UMLDoclet with {@code plantumlServerUrl = "http://localhost:8080/"}
*
* @return The base URL of the PlantUML online server to use.
*/
Optional<String> 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
Expand Down
Expand Up @@ -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.
* <p>
Expand Down Expand Up @@ -139,6 +141,11 @@ public Set<Doclet.Option> mergeOptionsWith(Set<? extends Doclet.Option> standard
return options.mergeWith(standardOptions);
}

@Override
public Optional<String> plantumlServerUrl() {
return Optional.ofNullable(plantumlServerUrl);
}

@Override
public Optional<String> delegateDocletName() {
return Optional.ofNullable(delegateDoclet).filter(name -> !"false".equalsIgnoreCase(name));
Expand Down
Expand Up @@ -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));
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/nl/talsmasoftware/umldoclet/uml/Diagram.java
Expand Up @@ -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;
Expand All @@ -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 <null>");
this.plantumlGenerator = PlantumlGenerator.getPlantumlGenerator(config);
this.formats = config.images().formats().stream()
.map(this::toFileFormat).filter(Objects::nonNull)
.toArray(FileFormat[]::new);
Expand Down Expand Up @@ -90,7 +92,7 @@ public Configuration getConfiguration() {
* Determine the physical file location for the plantuml output.
*
* <p>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.
*/
Expand Down Expand Up @@ -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);
}
}

Expand Down
@@ -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));
}
}
@@ -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;

}
@@ -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 <null>."));
} catch (IOException ioe) {
throw new IllegalStateException("Error encoding diagram: " + ioe.getMessage(), ioe);
}
}

}
Expand Up @@ -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=<url>
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 classname>
doclet.usage.create-puml-files.description=Create PlantUML '.puml' files
Expand Down
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
Expand Up @@ -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();
Expand Down
Expand Up @@ -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();
}

}

0 comments on commit 951e533

Please sign in to comment.