From ccebf8181c5e0091822d387b37cf2d54fe8d556e Mon Sep 17 00:00:00 2001 From: Leo Leplat <60394504+Rylern@users.noreply.github.com> Date: Thu, 13 Mar 2025 15:40:37 +0000 Subject: [PATCH 01/14] Better error handling --- gradle/libs.versions.toml | 2 +- .../qupath/ui/javadocviewer/core/Javadoc.java | 163 ++++++++---------- .../ui/javadocviewer/core/JavadocElement.java | 6 +- .../ui/javadocviewer/core/JavadocsFinder.java | 70 +++++--- .../qupath/ui/javadocviewer/core/Utils.java | 26 +++ .../components/AutoCompletionTextField.java | 2 +- .../gui/viewer/JavadocEntry.java | 4 +- .../gui/viewer/JavadocViewer.java | 18 +- .../gui/viewer/JavadocViewerCommand.java | 8 +- 9 files changed, 159 insertions(+), 140 deletions(-) create mode 100644 javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Utils.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 03f2f51..2a87048 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -java = "17" +java = "21" slf4j = "2.0.7" javafx = "20" javafxPlugin = "0.1.0" diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java index d9d9bfd..82488b1 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java @@ -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; @@ -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 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("
(.*?)
"); private static final Pattern URI_PATTERN = Pattern.compile("href=\"(.+?)\""); private static final Pattern NAME_PATTERN = Pattern.compile("(?:)?(.*?)(?:)?"); private static final Pattern CATEGORY_PATTERN = Pattern.compile(" - (.+?) "); - 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 elements; + private static final int REQUEST_TIMEOUT_SECONDS = 10; - private Javadoc(URI uri, List 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 elements) { this.uri = uri; this.elements = Collections.unmodifiableList(elements); } /** * Asynchronously attempt to create a Javadoc from the specified URI. + *

+ * 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> create(URI uri) { - return getIndexAllPage(uri).thenApply(indexAllPage -> indexAllPage.map(page -> new Javadoc( + public static CompletableFuture 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 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 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 parseJavadocIndexPage(String javadocURI, String indexHTMLPage) { @@ -121,14 +118,13 @@ private static List parseJavadocIndexPage(String javadocURI, Str String link = javadocURI + uriMatcher.group(1); try { - URI uri = new URI(link); elements.add(new JavadocElement( - uri, + new URI(link), name, categoryMatcher.group(1) )); } 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); } } } @@ -137,85 +133,62 @@ private static List parseJavadocIndexPage(String javadocURI, Str return elements; } - private static CompletableFuture> 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 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> 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 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 getIndexAllPageFromDirectory(URI uri) { + private static String getIndexAllPageContentFromNonJar(URI uri) { + logger.debug("Reading {} file to get the index-all page content...", uri); + try (Stream 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); } } } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocElement.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocElement.java index 460e706..ecd636a 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocElement.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocElement.java @@ -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) {} diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocsFinder.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocsFinder.java index abec1fd..0686d6c 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocsFinder.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/JavadocsFinder.java @@ -12,8 +12,10 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.stream.Stream; import java.util.zip.ZipFile; @@ -34,62 +36,71 @@ private JavadocsFinder() { /** * Asynchronously search for Javadocs in the specified URIs. * - * @param urisToSearch URIs to search for Javadocs. It can be a directory, an HTTP link, - * a link to a jar file... + * @param urisToSearch URIs to search for Javadocs. It can be a directory, an HTTP link, + * a link to a jar file... * @return a CompletableFuture with the list of Javadocs found */ public static CompletableFuture> findJavadocs(URI... urisToSearch) { return CompletableFuture.supplyAsync(() -> Arrays.stream(urisToSearch) - .map(JavadocsFinder::findJavadocUris) + .map(JavadocsFinder::findJavadocUrisFromUri) .flatMap(List::stream) - .map(Javadoc::create) - .map(CompletableFuture::join) - .flatMap(Optional::stream) + .map(uri -> { + try { + return Javadoc.create(uri).get(); + } catch (InterruptedException | ExecutionException e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + logger.debug("Error when creating javadoc of {}. Skipping it", uri, e); + + return null; + } + }) + .filter(Objects::nonNull) .distinct() .toList() ); } - private static List findJavadocUris(URI uri) { - if (uri.getScheme() != null && List.of("http", "https").contains(uri.getScheme())) { + private static List findJavadocUrisFromUri(URI uri) { + if (Utils.doesUrilinkToWebsite(uri)) { + logger.debug("URI {} retrieved", uri); return List.of(uri); } else { try { - return findJavadocUris(Paths.get(uri)); + return findJavadocUrisFromPath(Paths.get(uri)); } catch (Exception e) { - logger.debug(String.format("Could not convert URI %s to path", uri), e); + logger.debug("Could not convert URI {} to path", uri, e); return List.of(); } } } - private static List findJavadocUris(Path path) { - if (path == null) { - return List.of(); + private static List findJavadocUrisFromPath(Path path) { + if (Files.isDirectory(path)) { + return findJavadocUrisFromDirectory(path); } else { - logger.debug(String.format("Searching for javadocs in %s (depth=%d)", path, SEARCH_DEPTH)); - - if (Files.isDirectory(path)) { - return findJavadocUrisFromDirectory(path); - } else { - return findJavadocUrisFromFile(path).map(List::of).orElse(List.of()); - } + return findJavadocUrisFromFile(path).map(List::of).orElse(List.of()); } } private static List findJavadocUrisFromDirectory(Path directory) { + logger.debug("Searching for javadocs in {} directory with depth {}", directory, SEARCH_DEPTH); + try (Stream walk = Files.walk(directory, JavadocsFinder.SEARCH_DEPTH)) { return walk .map(JavadocsFinder::findJavadocUrisFromFile) .flatMap(Optional::stream) .toList(); } catch (IOException e) { - logger.debug("Exception while requesting javadoc URIs", e); + logger.debug("Exception while searching for javadoc URIs", e); return List.of(); } } private static Optional findJavadocUrisFromFile(Path path) { + logger.debug("Determining if {} contains Javadoc", path); + File file = path.toFile(); if ( @@ -98,10 +109,11 @@ private static Optional findJavadocUrisFromFile(Path path) { ) { try (Stream lines = Files.lines(path)) { if (lines.anyMatch(l -> l.contains("javadoc"))) { + logger.debug("{} points to a Javadoc index page", path); return Optional.of(path.toUri()); } } catch (IOException e) { - logger.debug(String.format("Error while reading %s", path), e); + logger.debug("Error while reading {}", path, e); } } @@ -112,13 +124,21 @@ private static Optional findJavadocUrisFromFile(Path path) { ) { try (ZipFile zipFile = new ZipFile(file)) { if (zipFile.getEntry(JAVADOC_INDEX_FILE) != null) { - return Optional.of(new URI(String.format("jar:%s!/%s", file.toURI(), JAVADOC_INDEX_FILE))); + String uri = String.format("jar:%s!/%s", file.toURI(), JAVADOC_INDEX_FILE); + + try { + logger.debug("{} is an archive containing Javadoc", uri); + return Optional.of(new URI(uri)); + } catch (URISyntaxException e) { + logger.warn("Error while creating URI {}", uri, e); + } } - } catch (IOException | URISyntaxException e) { - logger.warn(String.format("Error while reading %s", path), e); + } catch (IOException e) { + logger.warn("Error while reading {}", path, e); } } + logger.debug("{} doesn't contain Javadoc", path); return Optional.empty(); } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Utils.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Utils.java new file mode 100644 index 0000000..94c2814 --- /dev/null +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Utils.java @@ -0,0 +1,26 @@ +package qupath.ui.javadocviewer.core; + +import java.net.URI; +import java.util.List; + +/** + * A collection of utility functions. + */ +class Utils { + + private static final List WEBSITE_SCHEMES = List.of("http", "https"); + + private Utils() { + throw new AssertionError("This class is not instantiable."); + } + + /** + * Indicate whether the provided URI links to a website. + * + * @param uri the URI to check + * @return whether the provided URI links to a website + */ + public static boolean doesUrilinkToWebsite(URI uri) { + return uri.getScheme() != null && WEBSITE_SCHEMES.contains(uri.getScheme()); + } +} diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index aff94f2..6e36bec 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -19,7 +19,7 @@ * A {@link TextField} that provides suggestions on a context menu. * Suggestions are grouped by category and must implement {@link AutoCompleteTextFieldEntry}. * - * @param the type of suggestions + * @param the type of suggestions */ public class AutoCompletionTextField extends TextField { diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index 2e5f07d..1cd473d 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -14,8 +14,8 @@ class JavadocEntry implements AutoCompleteTextFieldEntry { /** * Create a Javadoc entry from a Javadoc element. * - * @param javadocElement the javadoc element to represent - * @param onSelected a function to call when this element is selected + * @param javadocElement the javadoc element to represent + * @param onSelected a function to call when this element is selected */ public JavadocEntry(JavadocElement javadocElement, Runnable onSelected) { this.javadocElement = javadocElement; diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java index 0721e23..8a57031 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java @@ -50,10 +50,10 @@ public class JavadocViewer extends BorderPane { /** * Create the javadoc viewer. * - * @param stylesheet a property containing a link to a stylesheet which should - * be applied to this viewer. Can be null - * @param urisToSearch URIs to search for Javadocs. See {@link JavadocsFinder#findJavadocs(URI...)} - * @throws IOException when the window creation fails + * @param stylesheet a property containing a link to a stylesheet which should + * be applied to this viewer. Can be null + * @param urisToSearch URIs to search for Javadocs. See {@link JavadocsFinder#findJavadocs(URI...)} + * @throws IOException if the window creation fails */ public JavadocViewer(ReadOnlyStringProperty stylesheet, URI... urisToSearch) throws IOException { initUI(stylesheet, urisToSearch); @@ -62,7 +62,8 @@ public JavadocViewer(ReadOnlyStringProperty stylesheet, URI... urisToSearch) thr /** * Set the search text field to an input query. - * @param input The search query string. + * + * @param input the search query string. */ public void setSearchInput(String input) { autoCompletionTextField.setText(input); @@ -117,9 +118,8 @@ protected void updateItem(URI item, boolean empty) { webView.getEngine().loadContent(resources.getString("JavadocViewer.findingJavadocs")); JavadocsFinder.findJavadocs(urisToSearch).thenAccept(javadocs -> Platform.runLater(() -> { - this.uris.getItems().setAll(javadocs.stream() - .map(Javadoc::getUri) + .map(Javadoc::uri) .sorted(Comparator.comparing(JavadocViewer::getName)) .toList() ); @@ -130,12 +130,12 @@ protected void updateItem(URI item, boolean empty) { this.uris.getSelectionModel().select(this.uris.getItems().stream() .filter(u -> getName(u).toLowerCase().contains("qupath")) .findFirst() - .orElse(this.uris.getItems().get(0)) + .orElse(this.uris.getItems().getFirst()) ); } autoCompletionTextField.getSuggestions().addAll(javadocs.stream() - .map(Javadoc::getElements) + .map(Javadoc::elements) .flatMap(List::stream) .map(javadocElement -> new JavadocEntry( javadocElement, diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java index 6b81104..35d0143 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java @@ -24,10 +24,10 @@ public class JavadocViewerCommand implements Runnable { /** * Create the command. This will not create the viewer until either the command is run or {@link #getJavadocViewer()} is called. * - * @param owner the stage that should own the viewer window. Can be null - * @param stylesheet a property containing a link to a stylesheet which should - * be applied to the viewer. Can be null - * @param urisToSearch URIs to search for Javadocs. See {@link JavadocViewer#JavadocViewer(ReadOnlyStringProperty, URI...)} + * @param owner the stage that should own the viewer window. Can be null + * @param stylesheet a property containing a link to a stylesheet which should + * be applied to the viewer. Can be null + * @param urisToSearch URIs to search for Javadocs. See {@link JavadocViewer#JavadocViewer(ReadOnlyStringProperty, URI...)} */ public JavadocViewerCommand(Stage owner, ReadOnlyStringProperty stylesheet, URI... urisToSearch) { this.owner = owner; From 70349d1293f7f1b79321c7bf0feb936ff32a9a1c Mon Sep 17 00:00:00 2001 From: Leo Leplat <60394504+Rylern@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:15:23 +0000 Subject: [PATCH 02/14] Sort suggestions and remove package and module from them --- .../AutoCompleteTextFieldEntry.java | 2 +- .../components/AutoCompletionTextField.java | 8 ++++- .../gui/viewer/JavadocEntry.java | 30 +++++++++++++++++++ .../gui/viewer/JavadocViewer.java | 3 +- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompleteTextFieldEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompleteTextFieldEntry.java index df99b57..c1b1f6d 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompleteTextFieldEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompleteTextFieldEntry.java @@ -3,7 +3,7 @@ /** * An entry to a {@link AutoCompletionTextField}. */ -public interface AutoCompleteTextFieldEntry { +public interface AutoCompleteTextFieldEntry extends Comparable { /** * @return the text that should be displayed by this entry diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index 6e36bec..51baa5e 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -17,7 +17,12 @@ /** * A {@link TextField} that provides suggestions on a context menu. + *

* Suggestions are grouped by category and must implement {@link AutoCompleteTextFieldEntry}. + *

+ * Suggestions are sorted according to their order. + *

+ * No more than {@link #MAX_ENTRIES} suggestions are displayed at a time. * * @param the type of suggestions */ @@ -69,6 +74,7 @@ private void setUpListeners() { populatePopup( suggestions.stream() .filter(entry -> entry.getSearchableText().toLowerCase().contains(loweredCaseEnteredText)) + .sorted() .limit(MAX_ENTRIES) .toList(), enteredText @@ -109,7 +115,7 @@ private void populatePopup(List entries, String filter) { // Add first item, show popup, and then add other items // This is used to avoid the popup to ignore the anchor position // See https://stackoverflow.com/a/58542568 - entriesPopup.getItems().add(items.get(0)); + entriesPopup.getItems().add(items.getFirst()); entriesPopup.show(this, Side.BOTTOM, 0, 0); entriesPopup.getItems().addAll(items.stream().skip(1).toList()); } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index 1cd473d..ab64c23 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -3,6 +3,8 @@ import qupath.ui.javadocviewer.gui.components.AutoCompleteTextFieldEntry; import qupath.ui.javadocviewer.core.JavadocElement; +import java.util.Map; + /** * An {@link AutoCompleteTextFieldEntry} that represents a {@link JavadocElement}. */ @@ -47,4 +49,32 @@ public String getCategory() { public void onSelected() { onSelected.run(); } + + @Override + public int compareTo(AutoCompleteTextFieldEntry otherEntry) { + Map order = Map.of( + "Class", 1, + "Interface", 2, + "Enum", 3, + "Constructor", 4, + "Static", 5, + "Method", 6, + "Variable", 7, + "Exception", 8, + "Annotation", 9, + "Element", 10 // display categories in that order + ); + + int categoryComparison = order.getOrDefault(getCategory(), 0) - order.getOrDefault(otherEntry.getCategory(), 0); + if (categoryComparison != 0) { + return categoryComparison; + } + + return getName().compareTo(otherEntry.getName()); + } + + @Override + public String toString() { + return String.format("Javadoc entry of %s", javadocElement); + } } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java index 8a57031..20d9564 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java @@ -35,6 +35,7 @@ public class JavadocViewer extends BorderPane { private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ui.javadocviewer.strings"); private static final Pattern REDIRECTION_PATTERN = Pattern.compile("window\\.location\\.replace\\(['\"](.*?)['\"]\\)"); + private static final List CATEGORIES_TO_SKIP = List.of("package", "module"); private final WebView webView = new WebView(); @FXML private Button back; @@ -141,7 +142,7 @@ protected void updateItem(URI item, boolean empty) { javadocElement, () -> webView.getEngine().load(javadocElement.uri().toString()) )) - .sorted(Comparator.comparing(JavadocEntry::getName)) + .filter(javadocEntry -> !CATEGORIES_TO_SKIP.contains(javadocEntry.getCategory())) .toList()); })); } From b942d92570bcef70fced1ae689e1ba5de3512b33 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 09:31:35 +0000 Subject: [PATCH 03/14] Safer use of varargs --- .../qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java | 7 ++++--- .../ui/javadocviewer/gui/viewer/JavadocViewerCommand.java | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java index 20d9564..a1d7881 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -57,7 +58,7 @@ public class JavadocViewer extends BorderPane { * @throws IOException if the window creation fails */ public JavadocViewer(ReadOnlyStringProperty stylesheet, URI... urisToSearch) throws IOException { - initUI(stylesheet, urisToSearch); + initUI(stylesheet, Arrays.stream(urisToSearch).toList()); setUpListeners(); } @@ -80,7 +81,7 @@ private void onForwardClicked(ActionEvent ignoredEvent) { offset(1); } - private void initUI(ReadOnlyStringProperty stylesheet, URI[] urisToSearch) throws IOException { + private void initUI(ReadOnlyStringProperty stylesheet, List urisToSearch) throws IOException { FXMLLoader loader = new FXMLLoader(JavadocViewer.class.getResource("javadoc_viewer.fxml"), resources); loader.setRoot(this); loader.setController(this); @@ -118,7 +119,7 @@ protected void updateItem(URI item, boolean empty) { } webView.getEngine().loadContent(resources.getString("JavadocViewer.findingJavadocs")); - JavadocsFinder.findJavadocs(urisToSearch).thenAccept(javadocs -> Platform.runLater(() -> { + JavadocsFinder.findJavadocs(urisToSearch.toArray(new URI[0])).thenAccept(javadocs -> Platform.runLater(() -> { this.uris.getItems().setAll(javadocs.stream() .map(Javadoc::uri) .sorted(Comparator.comparing(JavadocViewer::getName)) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java index 35d0143..a73468a 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewerCommand.java @@ -6,6 +6,8 @@ import java.io.IOException; import java.net.URI; +import java.util.Arrays; +import java.util.List; import java.util.ResourceBundle; /** @@ -17,7 +19,7 @@ public class JavadocViewerCommand implements Runnable { private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ui.javadocviewer.strings"); private final Stage owner; private final ReadOnlyStringProperty stylesheet; - private final URI[] urisToSearch; + private final List urisToSearch; private Stage stage; private JavadocViewer javadocViewer; @@ -32,7 +34,7 @@ public class JavadocViewerCommand implements Runnable { public JavadocViewerCommand(Stage owner, ReadOnlyStringProperty stylesheet, URI... urisToSearch) { this.owner = owner; this.stylesheet = stylesheet; - this.urisToSearch = urisToSearch; + this.urisToSearch = Arrays.stream(urisToSearch).toList(); } /** @@ -44,7 +46,7 @@ public JavadocViewerCommand(Stage owner, ReadOnlyStringProperty stylesheet, URI. public JavadocViewer getJavadocViewer() { if (javadocViewer == null) { try { - javadocViewer = new JavadocViewer(stylesheet, urisToSearch); + javadocViewer = new JavadocViewer(stylesheet, urisToSearch.toArray(new URI[0])); } catch (IOException e) { throw new RuntimeException(e); } From 8d4010b2c9eb81e6b5121b65050455d090e7bdaa Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 10:28:57 +0000 Subject: [PATCH 04/14] Increase JavaFX version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a87048..b7aceb7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] java = "21" slf4j = "2.0.7" -javafx = "20" +javafx = "21.0.6" javafxPlugin = "0.1.0" [libraries] From 99dae173c30765da4587e80a2c9c2353a092944b Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 11:10:57 +0000 Subject: [PATCH 05/14] Improve filtering --- .../gui/viewer/JavadocEntry.java | 55 ++++++++++++------- .../gui/viewer/JavadocViewer.java | 2 +- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index ab64c23..cf5dde7 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -10,6 +10,14 @@ */ class JavadocEntry implements AutoCompleteTextFieldEntry { + private static final Map CATEGORY_ORDER = Map.of( + "Class", 1, + "Interface", 2, + "Enum", 3, + "Constructor", 4, + "Static", 5, + "Method", 6 + ); private final JavadocElement javadocElement; private final Runnable onSelected; @@ -31,13 +39,33 @@ public String getName() { @Override public String getSearchableText() { - int parenthesisIndex = javadocElement.name().indexOf("("); + return switch (javadocElement.category()) { + // expect "some.package.Class". Retain "Class" + case "Class", "Interface" -> javadocElement.name().substring(javadocElement.name().indexOf(".") + 1); + // expects "some.package.Class.Enum" or "Class.Enum.variable". Retain "Class.Enum" or "Enum.variable" + case "Enum" -> { + int lastPointIndex = javadocElement.name().lastIndexOf("."); + if (lastPointIndex > -1) { + int secondLastPointIndex = javadocElement.name().lastIndexOf(".", lastPointIndex-1); + if (secondLastPointIndex > -1) { + yield javadocElement.name().substring(secondLastPointIndex+1); + } + } + yield javadocElement.name(); + } + // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter) for functions. Retain "Class" or "function" + case "Constructor", "Static", "Method" -> { + int pointIndex = javadocElement.name().indexOf("."); + int parenthesisIndex = javadocElement.name().indexOf("("); - if (parenthesisIndex > -1) { - return javadocElement.name().substring(0, parenthesisIndex); - } else { - return javadocElement.name(); - } + if (parenthesisIndex > -1) { + yield javadocElement.name().substring(pointIndex+1, parenthesisIndex); + } else { + yield javadocElement.name().substring(pointIndex+1); + } + } + default -> javadocElement.name(); + }; } @Override @@ -52,20 +80,7 @@ public void onSelected() { @Override public int compareTo(AutoCompleteTextFieldEntry otherEntry) { - Map order = Map.of( - "Class", 1, - "Interface", 2, - "Enum", 3, - "Constructor", 4, - "Static", 5, - "Method", 6, - "Variable", 7, - "Exception", 8, - "Annotation", 9, - "Element", 10 // display categories in that order - ); - - int categoryComparison = order.getOrDefault(getCategory(), 0) - order.getOrDefault(otherEntry.getCategory(), 0); + int categoryComparison = CATEGORY_ORDER.getOrDefault(getCategory(), 0) - CATEGORY_ORDER.getOrDefault(otherEntry.getCategory(), 0); if (categoryComparison != 0) { return categoryComparison; } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java index a1d7881..6b63bdf 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocViewer.java @@ -36,7 +36,7 @@ public class JavadocViewer extends BorderPane { private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ui.javadocviewer.strings"); private static final Pattern REDIRECTION_PATTERN = Pattern.compile("window\\.location\\.replace\\(['\"](.*?)['\"]\\)"); - private static final List CATEGORIES_TO_SKIP = List.of("package", "module"); + private static final List CATEGORIES_TO_SKIP = List.of("package", "module", "Variable", "Exception", "Annotation", "Element"); private final WebView webView = new WebView(); @FXML private Button back; From 330febb786e9ff089d384af1737ef1fd7fe99532 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 11:33:33 +0000 Subject: [PATCH 06/14] Adapt highlighting to new filter --- .../components/AutoCompletionTextField.java | 10 ++-- .../gui/viewer/JavadocEntry.java | 50 ++++++++++--------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index 51baa5e..3968f48 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -97,7 +97,7 @@ private void populatePopup(List entries, String filter) { entries.stream() .filter(entry -> entry.getCategory().equals(category)) .map(entry -> { - MenuItem menuItem = new CustomMenuItem(createEntryItemText(entry.getName(), filter), true); + MenuItem menuItem = new CustomMenuItem(createEntryItemText(entry, filter), true); menuItem.setOnAction(actionEvent -> { setText(entry.getName()); @@ -127,8 +127,12 @@ private static Node createCategoryItemText(String category) { return text; } - private static Node createEntryItemText(String text, String filter) { - int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase()); + private Node createEntryItemText(T entry, String filter) { + String searchableText = entry.getSearchableText(); + String text = entry.getName(); + + int searchableTextIndex = text.indexOf(searchableText); + int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase(), searchableTextIndex); Text textBefore = new Text(text.substring(0, filterIndex)); Text textFiltered = new Text(text.substring(filterIndex, filterIndex + filter.length())); diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index cf5dde7..6032fe7 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -20,6 +20,7 @@ class JavadocEntry implements AutoCompleteTextFieldEntry { ); private final JavadocElement javadocElement; private final Runnable onSelected; + private String searchableText; /** * Create a Javadoc entry from a Javadoc element. @@ -39,33 +40,36 @@ public String getName() { @Override public String getSearchableText() { - return switch (javadocElement.category()) { - // expect "some.package.Class". Retain "Class" - case "Class", "Interface" -> javadocElement.name().substring(javadocElement.name().indexOf(".") + 1); - // expects "some.package.Class.Enum" or "Class.Enum.variable". Retain "Class.Enum" or "Enum.variable" - case "Enum" -> { - int lastPointIndex = javadocElement.name().lastIndexOf("."); - if (lastPointIndex > -1) { - int secondLastPointIndex = javadocElement.name().lastIndexOf(".", lastPointIndex-1); - if (secondLastPointIndex > -1) { - yield javadocElement.name().substring(secondLastPointIndex+1); + if (searchableText == null) { + searchableText = switch (javadocElement.category()) { + // expect "some.package.Class". Retain "Class" + case "Class", "Interface" -> javadocElement.name().substring(javadocElement.name().indexOf(".") + 1); + // expects "some.package.Class.Enum" or "Class.Enum.variable". Retain "Class.Enum" or "Enum.variable" + case "Enum" -> { + int lastPointIndex = javadocElement.name().lastIndexOf("."); + if (lastPointIndex > -1) { + int secondLastPointIndex = javadocElement.name().lastIndexOf(".", lastPointIndex-1); + if (secondLastPointIndex > -1) { + yield javadocElement.name().substring(secondLastPointIndex+1); + } } + yield javadocElement.name(); } - yield javadocElement.name(); - } - // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter) for functions. Retain "Class" or "function" - case "Constructor", "Static", "Method" -> { - int pointIndex = javadocElement.name().indexOf("."); - int parenthesisIndex = javadocElement.name().indexOf("("); + // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter) for functions. Retain "Class" or "function" + case "Constructor", "Static", "Method" -> { + int pointIndex = javadocElement.name().indexOf("."); + int parenthesisIndex = javadocElement.name().indexOf("("); - if (parenthesisIndex > -1) { - yield javadocElement.name().substring(pointIndex+1, parenthesisIndex); - } else { - yield javadocElement.name().substring(pointIndex+1); + if (parenthesisIndex > -1) { + yield javadocElement.name().substring(pointIndex+1, parenthesisIndex); + } else { + yield javadocElement.name().substring(pointIndex+1); + } } - } - default -> javadocElement.name(); - }; + default -> javadocElement.name(); + }; + } + return searchableText; } @Override From 06b14184111d740fabf119b474c33e92b2be8c37 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 11:37:29 +0000 Subject: [PATCH 07/14] Class filter fix --- .../java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index 6032fe7..38d05ef 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -43,7 +43,7 @@ public String getSearchableText() { if (searchableText == null) { searchableText = switch (javadocElement.category()) { // expect "some.package.Class". Retain "Class" - case "Class", "Interface" -> javadocElement.name().substring(javadocElement.name().indexOf(".") + 1); + case "Class", "Interface" -> javadocElement.name().substring(javadocElement.name().lastIndexOf(".") + 1); // expects "some.package.Class.Enum" or "Class.Enum.variable". Retain "Class.Enum" or "Enum.variable" case "Enum" -> { int lastPointIndex = javadocElement.name().lastIndexOf("."); From 25f4d7284763e0732bf2dd363b8851023720965a Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 11:47:28 +0000 Subject: [PATCH 08/14] Typo --- .../java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index 38d05ef..674d613 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -55,7 +55,7 @@ public String getSearchableText() { } yield javadocElement.name(); } - // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter) for functions. Retain "Class" or "function" + // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter)" for functions. Retain "Class" or "function" case "Constructor", "Static", "Method" -> { int pointIndex = javadocElement.name().indexOf("."); int parenthesisIndex = javadocElement.name().indexOf("("); From 9aae2b1a659fcc3c15a510993d94c14a558d49a3 Mon Sep 17 00:00:00 2001 From: lleplat Date: Fri, 14 Mar 2025 11:50:13 +0000 Subject: [PATCH 09/14] Update GitHub actions --- .github/workflows/gradle.yaml | 8 ++++---- .github/workflows/publish-release.yml | 8 ++++---- .github/workflows/publish-snapshot.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml index 49ae1f4..4c10505 100644 --- a/.github/workflows/gradle.yaml +++ b/.github/workflows/gradle.yaml @@ -24,18 +24,18 @@ 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: Build with Gradle uses: gradle/gradle-build-action@v2 with: arguments: build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: javadoc-viewer-jar path: build/libs diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 9e8eaf3..6dd6881 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -11,13 +11,13 @@ 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: @@ -25,7 +25,7 @@ jobs: 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 diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index e0773db..07e242c 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -10,13 +10,13 @@ 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: @@ -24,7 +24,7 @@ jobs: 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 From 2edfc3109d1836bd3439534ce95ee6e44fc815d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Leplat?= <60394504+Rylern@users.noreply.github.com> Date: Mon, 17 Mar 2025 09:28:57 +0000 Subject: [PATCH 10/14] Update .github/workflows/gradle.yaml Co-authored-by: Alan O'Callaghan --- .github/workflows/gradle.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml index 4c10505..1fddf0a 100644 --- a/.github/workflows/gradle.yaml +++ b/.github/workflows/gradle.yaml @@ -31,10 +31,8 @@ jobs: distribution: 'temurin' - name: Validate Gradle wrapper uses: gradle/actions/wrapper-validation@v3 - - name: Build with Gradle - uses: gradle/gradle-build-action@v2 - with: - arguments: build + - name: Build with gradle + run: ./gradlew build - uses: actions/upload-artifact@v4 with: name: javadoc-viewer-jar From 519518b32832a58b2a77b97533d9976e7e060212 Mon Sep 17 00:00:00 2001 From: lleplat Date: Mon, 17 Mar 2025 09:33:24 +0000 Subject: [PATCH 11/14] Selecting item shows search in text field --- .../javadocviewer/gui/components/AutoCompletionTextField.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index 3968f48..81d5072 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -100,8 +100,6 @@ private void populatePopup(List entries, String filter) { MenuItem menuItem = new CustomMenuItem(createEntryItemText(entry, filter), true); menuItem.setOnAction(actionEvent -> { - setText(entry.getName()); - positionCaret(entry.getName().length()); entriesPopup.hide(); entry.onSelected(); }); From 4cee6a7488283d5be9f8ec9eb83e34741799c045 Mon Sep 17 00:00:00 2001 From: lleplat Date: Mon, 17 Mar 2025 09:35:03 +0000 Subject: [PATCH 12/14] Increase max entry --- .../javadocviewer/gui/components/AutoCompletionTextField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index 81d5072..8777aea 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -28,7 +28,7 @@ */ public class AutoCompletionTextField extends TextField { - private static final int MAX_ENTRIES = 50; + private static final int MAX_ENTRIES = 100; private static final int MAX_POPUP_HEIGHT = 300; private final ContextMenu entriesPopup = new ContextMenu(); private final List suggestions = new ArrayList<>(); From f5a935c73c9a27948a41ed3a5bea471d40071132 Mon Sep 17 00:00:00 2001 From: lleplat Date: Mon, 17 Mar 2025 09:37:41 +0000 Subject: [PATCH 13/14] Custom comparator in auto completion text field --- .../gui/components/AutoCompletionTextField.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java index 8777aea..3182954 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/components/AutoCompletionTextField.java @@ -12,6 +12,7 @@ import javafx.scene.text.TextFlow; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.stream.Stream; @@ -71,10 +72,15 @@ private void setUpListeners() { } else { String loweredCaseEnteredText = enteredText.toLowerCase(); + Comparator comparator = + Comparator.comparing((AutoCompleteTextFieldEntry e) -> e.getSearchableText().toLowerCase().equals(loweredCaseEnteredText) ? -1 : 1) + .thenComparing(e -> e.getSearchableText().toLowerCase().startsWith(loweredCaseEnteredText) ? -1 : 1) + .thenComparing(AutoCompleteTextFieldEntry::compareTo); + populatePopup( suggestions.stream() .filter(entry -> entry.getSearchableText().toLowerCase().contains(loweredCaseEnteredText)) - .sorted() + .sorted(comparator) .limit(MAX_ENTRIES) .toList(), enteredText From bc786061f8c24bf9c2746d5953c3798ca896a357 Mon Sep 17 00:00:00 2001 From: lleplat Date: Mon, 17 Mar 2025 09:48:22 +0000 Subject: [PATCH 14/14] Correct constructor names --- .../qupath/ui/javadocviewer/core/Javadoc.java | 16 +++++++++++++++- .../javadocviewer/gui/viewer/JavadocEntry.java | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java index 82488b1..b6ee07c 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/core/Javadoc.java @@ -116,12 +116,15 @@ private static List 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 { elements.add(new JavadocElement( new URI(link), name, - categoryMatcher.group(1) + category )); } catch (URISyntaxException e) { logger.debug("Cannot create URI {} of Javadoc element", link, e); @@ -191,4 +194,15 @@ private static String getIndexAllPageContentFromNonJar(URI uri) { 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; + } + } } diff --git a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java index 674d613..1528b9f 100644 --- a/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java +++ b/javadocviewer/src/main/java/qupath/ui/javadocviewer/gui/viewer/JavadocEntry.java @@ -55,8 +55,8 @@ public String getSearchableText() { } yield javadocElement.name(); } - // expect "Class.Class(Parameter)" for constructors or "Class.function(Parameter)" for functions. Retain "Class" or "function" - case "Constructor", "Static", "Method" -> { + // expect "Class.function(Parameter)". Retain "function" + case "Static", "Method" -> { int pointIndex = javadocElement.name().indexOf("."); int parenthesisIndex = javadocElement.name().indexOf("("); @@ -66,6 +66,16 @@ public String getSearchableText() { yield javadocElement.name().substring(pointIndex+1); } } + // expect "Class(Parameter)". Retain "Class" + case "Constructor" -> { + int parenthesisIndex = javadocElement.name().indexOf("("); + + if (parenthesisIndex > -1) { + yield javadocElement.name().substring(0, parenthesisIndex); + } else { + yield javadocElement.name(); + } + } default -> javadocElement.name(); }; }