Skip to content
14 changes: 6 additions & 8 deletions .github/workflows/gradle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,16 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build
- uses: actions/upload-artifact@v3
uses: gradle/actions/wrapper-validation@v3
- name: Build with gradle
run: ./gradlew build
- uses: actions/upload-artifact@v4
with:
name: javadoc-viewer-jar
path: build/libs
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Publish snapshot
uses: gradle/gradle-build-action@v2
with:
arguments: publish -P release=true
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASS: ${{ secrets.MAVEN_PASS }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: javadoc-viewer-release-jar
path: build/libs
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/publish-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Publish snapshot
uses: gradle/gradle-build-action@v2
with:
arguments: publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASS: ${{ secrets.MAVEN_PASS }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: javadoc-viewer-snapshot-jar
path: build/libs
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
java = "17"
java = "21"
slf4j = "2.0.7"
javafx = "20"
javafx = "21.0.6"
javafxPlugin = "0.1.0"

[libraries]
Expand Down
179 changes: 83 additions & 96 deletions javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
Expand All @@ -31,73 +29,72 @@
/**
* A Javadoc specified by a {@link URI} and containing {@link JavadocElement JavadocElements}.
* Elements are populated by looking at the {@link #INDEX_ALL_PAGE} page of the Javadoc.
*
* @param uri the URI of this Javadoc
* @param elements an unmodifiable view of the elements of this Javadoc
*/
public class Javadoc {
public record Javadoc(URI uri, List<JavadocElement> elements) {

private static final Logger logger = LoggerFactory.getLogger(Javadoc.class);
private static final String INDEX_PAGE = "index.html";
private static final String INDEX_ALL_PAGE = "index-all.html";
private static final Pattern ENTRY_PATTERN = Pattern.compile("<dt>(.*?)</dt>");
private static final Pattern URI_PATTERN = Pattern.compile("href=\"(.+?)\"");
private static final Pattern NAME_PATTERN = Pattern.compile("<a .*?>(?:<span .*?>)?(.*?)(?:</span>)?</a>");
private static final Pattern CATEGORY_PATTERN = Pattern.compile("</a> - (.+?) ");
private static final String INDEX_PAGE = "index.html";
private static final String INDEX_ALL_PAGE = "index-all.html";
private final URI uri;
private final List<JavadocElement> elements;
private static final int REQUEST_TIMEOUT_SECONDS = 10;

private Javadoc(URI uri, List<JavadocElement> elements) {
/**
* Create a Javadoc from a URI and Javadoc elements. Take a look at {@link #create(URI)}
* to create a Javadoc only from a URI.
*
* @param uri the URI pointing to this Javadoc
* @param elements the elements of this Javadoc
*/
public Javadoc(URI uri, List<JavadocElement> elements) {
this.uri = uri;
this.elements = Collections.unmodifiableList(elements);
}

/**
* Asynchronously attempt to create a Javadoc from the specified URI.
* <p>
* Note that exception handling is left to the caller (the returned CompletableFuture may
* complete exceptionally if the elements of the Javadocs cannot be retrieved for example).
*
* @param uri the URI of the Javadoc
* @return a CompletableFuture with the created Javadoc, or an empty Optional if the creation failed
* @param uri the URI of the Javadoc
* @return a CompletableFuture (that may complete exceptionally) with the created Javadoc
*/
public static CompletableFuture<Optional<Javadoc>> create(URI uri) {
return getIndexAllPage(uri).thenApply(indexAllPage -> indexAllPage.map(page -> new Javadoc(
public static CompletableFuture<Javadoc> create(URI uri) {
return getIndexAllPage(uri).thenApply(indexAllPage -> new Javadoc(
uri,
parseJavadocIndexPage(
uri.toString().substring(0, uri.toString().lastIndexOf('/') + 1),
page
indexAllPage
)
)));
));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Javadoc javadoc = (Javadoc) o;
return Objects.equals(uri, javadoc.uri);
}

@Override
public int hashCode() {
return Objects.hashCode(uri);
}

@Override
public String toString() {
return "Javadoc{" +
"uri=" + uri +
", elements=" + elements +
'}';
}

/**
* @return the URI of this Javadoc
*/
public URI getUri() {
return uri;
}
private static CompletableFuture<String> getIndexAllPage(URI javadocIndexURI) {
String link = javadocIndexURI.toString().replace(INDEX_PAGE, INDEX_ALL_PAGE);
URI indexAllURI;
try {
indexAllURI = new URI(link);
} catch (URISyntaxException e) {
return CompletableFuture.failedFuture(e);
}

/**
* @return an unmodifiable view of the elements of this Javadoc
*/
public List<JavadocElement> getElements() {
return elements;
if (Utils.doesUrilinkToWebsite(indexAllURI)) {
return getIndexAllPageContentFromHttp(indexAllURI);
} else {
return CompletableFuture.supplyAsync(() -> {
if (indexAllURI.getScheme().contains("jar")) {
return getIndexAllPageContentFromJar(indexAllURI);
} else {
return getIndexAllPageContentFromNonJar(indexAllURI);
}
});
}
}

private static List<JavadocElement> parseJavadocIndexPage(String javadocURI, String indexHTMLPage) {
Expand All @@ -119,16 +116,18 @@ private static List<JavadocElement> parseJavadocIndexPage(String javadocURI, Str
name = nameMatcher.group(1) + "." + name;
}
String link = javadocURI + uriMatcher.group(1);
String category = categoryMatcher.group(1);

name = correctNameIfConstructor(name, category);

try {
URI uri = new URI(link);
elements.add(new JavadocElement(
uri,
new URI(link),
name,
categoryMatcher.group(1)
category
));
} catch (URISyntaxException e) {
logger.debug(String.format("Cannot create URI %s of Javadoc element", link), e);
logger.debug("Cannot create URI {} of Javadoc element", link, e);
}
}
}
Expand All @@ -137,85 +136,73 @@ private static List<JavadocElement> parseJavadocIndexPage(String javadocURI, Str
return elements;
}

private static CompletableFuture<Optional<String>> getIndexAllPage(URI javadocIndexURI) {
String link = javadocIndexURI.toString().replace(INDEX_PAGE, INDEX_ALL_PAGE);
URI indexAllURI;
try {
indexAllURI = new URI(link);
} catch (URISyntaxException e) {
logger.debug(String.format("Cannot create URI %s of index page", link), e);
return CompletableFuture.completedFuture(Optional.empty());
}
private static CompletableFuture<String> getIndexAllPageContentFromHttp(URI uri) {
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();

if (indexAllURI.getScheme().contains("http")) {
return getIndexAllPageFromHttp(indexAllURI);
} else {
return CompletableFuture.supplyAsync(() -> {
if (indexAllURI.getScheme().contains("jar")) {
return getIndexAllPageFromJar(indexAllURI);
} else {
return getIndexAllPageFromDirectory(indexAllURI);
}
});
}
}
logger.debug("Sending GET request to {} to read the index-all page content...", uri);

private static CompletableFuture<Optional<String>> getIndexAllPageFromHttp(URI uri) {
return HttpClient.newHttpClient().sendAsync(
return httpClient.sendAsync(
HttpRequest.newBuilder()
.uri(uri)
.timeout(Duration.of(10, ChronoUnit.SECONDS))
.timeout(Duration.of(REQUEST_TIMEOUT_SECONDS, ChronoUnit.SECONDS))
.GET()
.build(),
HttpResponse.BodyHandlers.ofString()
).handle((response, error) -> {
if (response == null || error != null) {
if (error != null) {
logger.debug("Error when retrieving Javadoc index page", error);
}
return Optional.empty();
} else {
return Optional.ofNullable(response.body());
}
});
).thenApply(response -> {
logger.debug("Got response {} from {}", response, uri);
return response.body();
}).whenComplete((b, e) -> httpClient.close());
}

private static Optional<String> getIndexAllPageFromJar(URI uri) {
private static String getIndexAllPageContentFromJar(URI uri) {
String jarURI = uri.toString().substring(
uri.toString().indexOf('/'),
uri.toString().lastIndexOf('!')
);
logger.debug("Opening {} jar file to read the index-all page content...", jarURI);

try (ZipFile zipFile = new ZipFile(jarURI)) {
ZipEntry entry = zipFile.getEntry(INDEX_ALL_PAGE);

if (entry == null) {
logger.debug(String.format("%s not found in %s", INDEX_ALL_PAGE, jarURI));
return Optional.empty();
throw new IllegalArgumentException(String.format("The provided jar file %s doesn't contain any %s entry", jarURI, INDEX_ALL_PAGE));
} else {
try (
InputStream inputStream = zipFile.getInputStream(zipFile.getEntry(INDEX_ALL_PAGE));
InputStream inputStream = zipFile.getInputStream(entry);
Scanner scanner = new Scanner(inputStream)
) {
StringBuilder lines = new StringBuilder();
while (scanner.hasNextLine()) {
lines.append(scanner.nextLine());
}
return Optional.of(lines.toString());
return lines.toString();
}
}
} catch (IOException e) {
logger.debug(String.format("Error while reading %s", jarURI), e);
return Optional.empty();
throw new RuntimeException(e);
}
}

private static Optional<String> getIndexAllPageFromDirectory(URI uri) {
private static String getIndexAllPageContentFromNonJar(URI uri) {
logger.debug("Reading {} file to get the index-all page content...", uri);

try (Stream<String> lines = Files.lines(Paths.get(uri))) {
return Optional.of(lines.collect(Collectors.joining("\n")));
} catch (Exception e) {
logger.debug(String.format("Error while reading %s", uri), e);
return Optional.empty();
return lines.collect(Collectors.joining("\n"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static String correctNameIfConstructor(String name, String category) {
// Constructor are usually written in the following way: "Class.Class(Parameter)"
// This function transforms them into "Class(Parameter)"
if (category.equals("Constructor")) {
int pointIndex = name.indexOf(".");
return name.substring(pointIndex + 1);
} else {
return name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
/**
* An element (function, class, enum...) of a Javadoc.
*
* @param uri the URI of the Javadoc owning this element
* @param name the name of the element (e.g. the function name)
* @param category the category of the element (e.g. "function")
* @param uri the URI of the Javadoc owning this element
* @param name the name of the element (e.g. the function name)
* @param category the category of the element (e.g. "function" or "class")
*/
public record JavadocElement(URI uri, String name, String category) {}
Loading
Loading