Skip to content

Commit

Permalink
Fix ExternalLink class to support 'element-list' module definitions (#…
Browse files Browse the repository at this point in the history
…145)

Fix ExternalLink class to support 'element-list' module definitions
  • Loading branch information
sjoerdtalsma authored Feb 19, 2019
1 parent 75f80a5 commit ace1c97
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ script:
- .travis/scripts/release.sh

after_success:
- if [ -f target/jacoco.exec ]; then ./mvnw jacoco:report coveralls:report; fi
- if [ -f target/jacoco.exec ]; then ./mvnw -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report; fi

deploy:
provider: releases
Expand Down
13 changes: 8 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,14 @@
<artifactId>umldoclet</artifactId>
<version>${project.version}</version>
</docletArtifact>
<additionalOptions>
<additionalOption>-verbose</additionalOption>
<!--<additionalOption>-quiet</additionalOption>-->
<!--<aditionalOption>-umlImageDirectory images</aditionalOption>-->
</additionalOptions>
<jdkToolchain>
<version>9</version>
</jdkToolchain>
<links>
<link>https://docs.oracle.com/javase/9/docs/api</link>
<!--<link>https://docs.oracle.com/javase/10/docs/api</link>-->
<!--<link>https://docs.oracle.com/en/java/javase/11/docs/api</link>-->
</links>
</configuration>
</execution>
</executions>
Expand Down
120 changes: 96 additions & 24 deletions src/main/java/nl/talsmasoftware/umldoclet/javadoc/ExternalLink.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Talsma ICT
* Copyright 2016-2019 Talsma ICT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,13 +21,21 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonMap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static nl.talsmasoftware.umldoclet.util.FileUtils.openReaderTo;
Expand All @@ -44,49 +52,113 @@
* @author Sjoerd Talsma
*/
final class ExternalLink {

private final Configuration config;
private final URI docUri, packageListUri;
private Set<String> packages;
private final URI docUri, baseUri;
private Map<String, Set<String>> modules;
private final Map<String, URI> packageUriCache = new HashMap<>();

ExternalLink(Configuration config, String apidoc, String packageList) {
this.config = requireNonNull(config, "Configuration is <null>.");
this.docUri = createUri(requireNonNull(apidoc, "External apidoc URI is <null>."));
requireNonNull(packageList, "Location URI for \"package-list\" is <null>.");
this.packageListUri = addPathComponent(createUri(packageList), "package-list");
this.baseUri = createUri(packageList);
}

private Map<String, Set<String>> modules() {
if (modules == null) {
synchronized (this) {
Map<String, Set<String>> moduleMap = tryReadModules();
this.modules = moduleMap.isEmpty() ? singletonMap("", tryReadPackages()) : moduleMap;
}
}
return modules;
}

Optional<URI> resolveType(String packagename, String typeName) {
if (packages().contains(packagename)) {
String document = packagename.replace('.', '/') + "/" + typeName + ".html";
return Optional.of(addHttpParam(makeAbsolute(addPathComponent(docUri, document)), "is-external", "true"));
return modules().entrySet().stream()
.filter(entry -> entry.getValue().contains(packagename))
.findFirst()
.map(entry -> cached(packagename, () -> findPackageUri(entry.getKey(), packagename)))
.map(uri -> addPathComponent(uri, typeName + ".html"))
.map(uri -> addHttpParam(uri, "is-external", "true"));
}

private URI findPackageUri(String modulename, String packagename) {
String packagePath = packagename.replace('.', '/');
if (!modulename.isEmpty()) {
URI withModule = addPathComponent(addPathComponent(makeAbsolute(docUri), modulename), packagePath);
if (testLivePackageLocation(withModule)) return withModule;
}
return Optional.empty();
URI packageUri = addPathComponent(makeAbsolute(docUri), packagePath);
if (testLivePackageLocation(packageUri)) return packageUri;
return null; // No package-summary.html found in either location.
}

private Set<String> packages() {
if (packages == null) try {
synchronized (this) {
Set<String> pkglist = new HashSet<>();
try (BufferedReader reader = new BufferedReader(
openReaderTo(config.destinationDirectory(), packageListUri, "UTF-8"))) {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
line = line.trim();
if (!line.isEmpty()) pkglist.add(line);
}
private Map<String, Set<String>> tryReadModules() {
final URI elementListUri = addPathComponent(baseUri, "element-list");
final Map<String, Set<String>> modules = new LinkedHashMap<>();
try (BufferedReader reader = new BufferedReader(
openReaderTo(config.destinationDirectory(), elementListUri, "UTF-8"))) {
String module = ""; // default to unnamed module
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
line = line.trim();
if (line.startsWith("module:")) {
module = line.substring("module:".length()).trim();
} else if (!line.isEmpty()) {
if (!modules.containsKey(module)) modules.put(module, new LinkedHashSet<>());
modules.get(module).add(line);
}
}
} catch (IOException | RuntimeException ex) {
config.logger().debug(Message.DEBUG_CANNOT_READ_ELEMENT_LIST, elementListUri, ex);
}
return modules.isEmpty() ? emptyMap() : unmodifiableMap(modules);
}

private Set<String> tryReadPackages() {
final URI packageListUri = addPathComponent(baseUri, "package-list");
final Set<String> packages = new LinkedHashSet<>();
try {
try (BufferedReader reader = new BufferedReader(
openReaderTo(config.destinationDirectory(), packageListUri, "UTF-8"))) {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
line = line.trim();
if (!line.isEmpty()) packages.add(line);
}
packages = unmodifiableSet(pkglist);
}
} catch (IOException | RuntimeException ex) {
config.logger().warn(Message.WARNING_CANNOT_READ_PACKAGE_LIST, packageListUri, ex);
packages = emptySet();
}
return packages;
return packages.isEmpty() ? emptySet() : unmodifiableSet(packages);
}

/**
* Test for existence of {@code package-summary.html} in the specified location.
*
* @param packageUri The package URI to test.
* @return Whether or not a {@code package-summary.html} could be found at the given URI.
*/
private boolean testLivePackageLocation(URI packageUri) {
try {
try (InputStream in = addPathComponent(packageUri, "package-summary.html").toURL().openStream()) {
return in.read() >= 0;
}
} catch (IOException | RuntimeException notFound) {
config.logger().debug(Message.DEBUG_LIVE_PACKAGE_URL_NOT_FOUND, packageUri, notFound);
return false;
}
}

private URI cached(String packagename, Supplier<URI> uri) {
synchronized (packageUriCache) {
if (!packageUriCache.containsKey(packagename)) packageUriCache.put(packagename, uri.get());
}
return packageUriCache.get(packagename);
}

private URI makeAbsolute(URI uri) {
if (uri != null && !uri.isAbsolute()) {
uri = new File(config.destinationDirectory(), uri.toASCIIString()).toURI();
uri = new File(config.destinationDirectory(), uri.toASCIIString()).toURI().normalize();
}
return uri;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Talsma ICT
* Copyright 2016-2019 Talsma ICT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,8 @@ public enum Message {
DEBUG_SKIPPING_FILE,
DEBUG_RENAMED_FILE_FROM,
DEBUG_COPIED_FILE_FROM,
DEBUG_CANNOT_READ_ELEMENT_LIST,
DEBUG_LIVE_PACKAGE_URL_NOT_FOUND,
INFO_GENERATING_FILE,
INFO_ADD_DIAGRAM_TO_FILE,
WARNING_UNRECOGNIZED_IMAGE_FORMAT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ debug.configured.image.formats=Configured image formats to generate: {0}.
debug.skipping.file=Skipping {0}...
debug.renamed.file.from=Replacing {0} by {1}.
debug.copied.file.from=Moved file {0} from {1}.
debug.cannot.read.element.list=Cannot read element list: \"{0}\".
debug.live.package.url.not.found=Live package documentation not found: {0}.
info.generating.file=Generating {0}...
info.add.diagram.to.file=Add UML to {0}...
warning.unrecognized.image.format=Unrecognized image format encountered: \"{0}\".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ debug.configured.image.formats=Afbeeldingsformaten geconfigureerd: {0}.
debug.skipping.file=Overslaan {0}...
debug.renamed.file.from=Vervangen {0} door {1}.
debug.copied.file.from=Bestand {0} is gekopieerd van {1}.
debug.cannot.read.element.list=Kan javadoc element list niet lezen: "{0}".
debug.live.package.url.not.found=Package documentatie niet gevonden: {0}.
info.generating.file=Genereren {0}...
info.add.diagram.to.file=Toevoegen UML aan {0}...
warning.unrecognized.image.format=Afbeeldingsformaat wordt niet herkend: "{0}".
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Talsma ICT
* Copyright 2016-2019 Talsma ICT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -48,6 +48,7 @@ public static class TestClass implements Serializable {
public void testRelativeExternalLink() {
File externalDir = createDirectory(new File(testoutput, "externalApidocs"));
Testing.write(new File(externalDir, "package-list"), Serializable.class.getPackageName());
Testing.write(new File(externalDir, "java/io/package-summary.html"), "<html></html>");
File outputdir = createDirectory(new File(testoutput, "link-relative"));

ToolProvider.findFirst("javadoc").get().run(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Talsma ICT
* Copyright 2016-2019 Talsma ICT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,30 +18,36 @@
import nl.talsmasoftware.umldoclet.configuration.Configuration;
import nl.talsmasoftware.umldoclet.logging.Message;
import nl.talsmasoftware.umldoclet.logging.TestLogger;
import nl.talsmasoftware.umldoclet.util.Testing;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public class ExternalLinkTest {
private Configuration config;
private TestLogger logger;
private File tempdir;

@Before
public void setup() {
public void setup() throws IOException {
logger = new TestLogger();
config = mock(Configuration.class);
tempdir = File.createTempFile("umldoclet-externallink", ".test");
assertThat("Delete tempfile", tempdir.delete(), is(true));
assertThat("Create tempdir", tempdir.mkdirs(), is(true));
when(config.logger()).thenReturn(logger);
}

Expand All @@ -51,6 +57,11 @@ public void verifyMocks() {
verifyNoMoreInteractions(config);
}

@After
public void deleteTempdir() {
Testing.deleteRecursive(tempdir);
}

@Test(expected = NullPointerException.class)
public void testExternalLinkWithoutConfig() {
new ExternalLink(null, "apidoc", "packageList");
Expand All @@ -75,11 +86,34 @@ public void testNonExistingUrls() {
assertThat(resolved, is(Optional.empty()));

assertThat(logger.countMessages(Message.WARNING_CANNOT_READ_PACKAGE_LIST::equals), is(1));
verify(config, times(1)).destinationDirectory();
verify(config, atLeast(1)).destinationDirectory();
}

@Test(expected = IllegalArgumentException.class)
public void testIllegalUrls() {
new ExternalLink(config, "https://www.google.com?\nq=query", "");
}

@Test
public void testLiveExternalLink_packageList_badUrl() {
when(config.destinationDirectory()).thenReturn("");
Testing.write(new File(tempdir, "package-list"), "java.lang\n");
ExternalLink externalLink = new ExternalLink(config, "https://www.google.com/apidocs", tempdir.getPath());

Optional<URI> resolved = externalLink.resolveType("java.lang", "Object");
assertThat(resolved, is(Optional.empty()));
verify(config, atLeast(1)).destinationDirectory();
}

@Test
public void testLiveExternalLink_elementList_badUrl() {
when(config.destinationDirectory()).thenReturn("");
Testing.write(new File(tempdir, "element-list"), "module:java.base\njava.lang\n");
ExternalLink externalLink = new ExternalLink(config, "https://www.google.com/apidocs", tempdir.getPath());

Optional<URI> resolved = externalLink.resolveType("java.lang", "Object");
assertThat(resolved, is(Optional.empty()));
verify(config, atLeast(1)).destinationDirectory();
}

}
11 changes: 10 additions & 1 deletion src/test/java/nl/talsmasoftware/umldoclet/util/Testing.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 Talsma ICT
* Copyright 2016-2019 Talsma ICT
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -125,4 +125,13 @@ public static File createDirectory(File dir) {
return dir;
}

/**
* Creates a new, empty file (if it doesn't already exist).
*
* @param file The file to create.
*/
public static void touch(File file) {
write(file, "");
}

}

0 comments on commit ace1c97

Please sign in to comment.