From 9004e6baf44200a51c59ea786886bb49c98e0250 Mon Sep 17 00:00:00 2001 From: barsh404error Date: Mon, 29 Dec 2025 13:47:52 +0300 Subject: [PATCH 1/8] Add utilities to detect and replace broken links. --- .../tjbot/features/utils/LinkDetection.java | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 3b6dc18112..62883044e9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -1,5 +1,11 @@ package org.togetherjava.tjbot.features.utils; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.CompletableFuture; + import com.linkedin.urls.Url; import com.linkedin.urls.detection.UrlDetector; import com.linkedin.urls.detection.UrlDetectorOptions; @@ -57,6 +63,54 @@ public static List extractLinks(String content, Set filter) public static boolean containsLink(String content) { return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); } + public static CompletableFuture isLinkBroken(String url) { + HttpClient client = HttpClient.newHttpClient(); + + HttpRequest request = HttpRequest.newBuilder(URI.create(url)) + .method("HEAD", HttpRequest.BodyPublishers.noBody()) + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.discarding()) + .thenApply(response -> response.statusCode() >= 400) + .exceptionally(ignored -> true); + } + public static CompletableFuture replaceDeadLinks( + String text, + String replacement + ) { + Set filters = Set.of( + LinkFilter.SUPPRESSED, + LinkFilter.NON_HTTP_SCHEME + ); + + List links = extractLinks(text, filters); + + if (links.isEmpty()) { + return CompletableFuture.completedFuture(text); + } + + StringBuilder result = new StringBuilder(text); + + List> checks = links.stream() + .map(link -> + isLinkBroken(link).thenAccept(isDead -> { + if (isDead) { + int index = result.indexOf(link); + if (index != -1) { + result.replace( + index, + index + link.length(), + replacement + ); + } + } + }) + ) + .toList(); + + return CompletableFuture.allOf(checks.toArray(new CompletableFuture[0])) + .thenApply(v -> result.toString()); + } private static Optional toLink(Url url, Set filter) { String raw = url.getOriginalUrl(); @@ -76,7 +130,6 @@ private static Optional toLink(Url url, Set filter) { // Remove trailing punctuation link = link.substring(0, link.length() - 1); } - return Optional.of(link); } From 715383387ccc7fb4d0a57a0efcb2220ce802c7d6 Mon Sep 17 00:00:00 2001 From: barsh404error Date: Wed, 31 Dec 2025 11:37:15 +0300 Subject: [PATCH 2/8] style: run spotlessApply --- .../tjbot/features/utils/LinkDetection.java | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 62883044e9..d36f257b31 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -1,18 +1,17 @@ package org.togetherjava.tjbot.features.utils; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.concurrent.CompletableFuture; - import com.linkedin.urls.Url; import com.linkedin.urls.detection.UrlDetector; import com.linkedin.urls.detection.UrlDetectorOptions; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; /** * Utility class to detect links. @@ -63,25 +62,21 @@ public static List extractLinks(String content, Set filter) public static boolean containsLink(String content) { return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); } + public static CompletableFuture isLinkBroken(String url) { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create(url)) - .method("HEAD", HttpRequest.BodyPublishers.noBody()) - .build(); + .method("HEAD", HttpRequest.BodyPublishers.noBody()) + .build(); return client.sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .thenApply(response -> response.statusCode() >= 400) - .exceptionally(ignored -> true); + .thenApply(response -> response.statusCode() >= 400) + .exceptionally(ignored -> true); } - public static CompletableFuture replaceDeadLinks( - String text, - String replacement - ) { - Set filters = Set.of( - LinkFilter.SUPPRESSED, - LinkFilter.NON_HTTP_SCHEME - ); + + public static CompletableFuture replaceDeadLinks(String text, String replacement) { + Set filters = Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME); List links = extractLinks(text, filters); @@ -91,25 +86,18 @@ public static CompletableFuture replaceDeadLinks( StringBuilder result = new StringBuilder(text); - List> checks = links.stream() - .map(link -> - isLinkBroken(link).thenAccept(isDead -> { - if (isDead) { - int index = result.indexOf(link); - if (index != -1) { - result.replace( - index, - index + link.length(), - replacement - ); - } - } - }) - ) - .toList(); + List> checks = + links.stream().map(link -> isLinkBroken(link).thenAccept(isDead -> { + if (isDead) { + int index = result.indexOf(link); + if (index != -1) { + result.replace(index, index + link.length(), replacement); + } + } + })).toList(); return CompletableFuture.allOf(checks.toArray(new CompletableFuture[0])) - .thenApply(v -> result.toString()); + .thenApply(v -> result.toString()); } private static Optional toLink(Url url, Set filter) { From d20507439d5075b804a9ba3a2ec3753fd1afa2ec Mon Sep 17 00:00:00 2001 From: barsh404error Date: Fri, 2 Jan 2026 13:18:49 +0300 Subject: [PATCH 3/8] Add utilities to detect and replace broken links V2 --- .../tjbot/features/utils/LinkDetection.java | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index d36f257b31..5ac1c0f5a7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -9,6 +9,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -17,6 +18,10 @@ * Utility class to detect links. */ public class LinkDetection { + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + + private static final Set DEFAULT_FILTERS = + Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME); /** * Possible ways to filter a link. @@ -63,41 +68,57 @@ public static boolean containsLink(String content) { return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); } + @SuppressWarnings("java:S2095") public static CompletableFuture isLinkBroken(String url) { - HttpClient client = HttpClient.newHttpClient(); - - HttpRequest request = HttpRequest.newBuilder(URI.create(url)) + HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) .build(); - return client.sendAsync(request, HttpResponse.BodyHandlers.discarding()) - .thenApply(response -> response.statusCode() >= 400) - .exceptionally(ignored -> true); + return HTTP_CLIENT.sendAsync(headRequest, HttpResponse.BodyHandlers.discarding()) + .thenApply(response -> { + int status = response.statusCode(); + if (status >= 200 && status < 400) { + return false; + } + return status >= 400 ? true : null; + }) + .exceptionally(ignored -> null) + .thenCompose(result -> { + if (result != null) { + return CompletableFuture.completedFuture(result); + } + HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build(); + + return HTTP_CLIENT.sendAsync(getRequest, HttpResponse.BodyHandlers.discarding()) + .thenApply(resp -> resp.statusCode() >= 400) + .exceptionally(ignored -> true); + }); } public static CompletableFuture replaceDeadLinks(String text, String replacement) { - Set filters = Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME); - - List links = extractLinks(text, filters); + List links = extractLinks(text, DEFAULT_FILTERS); if (links.isEmpty()) { return CompletableFuture.completedFuture(text); } - StringBuilder result = new StringBuilder(text); - - List> checks = - links.stream().map(link -> isLinkBroken(link).thenAccept(isDead -> { - if (isDead) { - int index = result.indexOf(link); - if (index != -1) { - result.replace(index, index + link.length(), replacement); - } - } - })).toList(); + List> deadLinkFutures = links.stream() + .distinct() + .map(link -> isLinkBroken(link).thenApply(isBroken -> isBroken ? link : null)) + .toList(); - return CompletableFuture.allOf(checks.toArray(new CompletableFuture[0])) - .thenApply(v -> result.toString()); + return CompletableFuture.allOf(deadLinkFutures.toArray(new CompletableFuture[0])) + .thenApply(ignored -> deadLinkFutures.stream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .toList()) + .thenApply(deadLinks -> { + String result = text; + for (String deadLink : deadLinks) { + result = result.replace(deadLink, replacement); + } + return result; + }); } private static Optional toLink(Url url, Set filter) { From c3a64e39a4bfb2e1891896905d63e0b18659d855 Mon Sep 17 00:00:00 2001 From: barsh404error Date: Fri, 2 Jan 2026 22:04:21 +0300 Subject: [PATCH 4/8] Add utilities to detect and replace broken links V2 --- .../tjbot/features/utils/LinkDetection.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 5ac1c0f5a7..237ff174b7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -68,7 +68,6 @@ public static boolean containsLink(String content) { return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); } - @SuppressWarnings("java:S2095") public static CompletableFuture isLinkBroken(String url) { HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) @@ -77,21 +76,17 @@ public static CompletableFuture isLinkBroken(String url) { return HTTP_CLIENT.sendAsync(headRequest, HttpResponse.BodyHandlers.discarding()) .thenApply(response -> { int status = response.statusCode(); - if (status >= 200 && status < 400) { - return false; - } - return status >= 400 ? true : null; + return status < 200 || status >= 400; }) - .exceptionally(ignored -> null) + .exceptionally(ignored -> true) .thenCompose(result -> { - if (result != null) { - return CompletableFuture.completedFuture(result); + if (!result) { + return CompletableFuture.completedFuture(false); } HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build(); - return HTTP_CLIENT.sendAsync(getRequest, HttpResponse.BodyHandlers.discarding()) .thenApply(resp -> resp.statusCode() >= 400) - .exceptionally(ignored -> true); + .exceptionally(ignored -> true); // still never null }); } From 48c23abb17254bfdd3d113e9cf635a2e83fbc98e Mon Sep 17 00:00:00 2001 From: barsh404error Date: Sat, 3 Jan 2026 13:48:10 +0300 Subject: [PATCH 5/8] Add utilities to detect and replace broken links V2 --- .../togetherjava/tjbot/features/utils/LinkDetection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 237ff174b7..e3035a667b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -80,7 +80,7 @@ public static CompletableFuture isLinkBroken(String url) { }) .exceptionally(ignored -> true) .thenCompose(result -> { - if (!result) { + if (!Boolean.TRUE.equals(result)) { return CompletableFuture.completedFuture(false); } HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build(); @@ -99,7 +99,9 @@ public static CompletableFuture replaceDeadLinks(String text, String rep List> deadLinkFutures = links.stream() .distinct() - .map(link -> isLinkBroken(link).thenApply(isBroken -> isBroken ? link : null)) + .map(link -> isLinkBroken(link) + .thenApply(isBroken -> Boolean.TRUE.equals(isBroken) ? link : null)) + .toList(); return CompletableFuture.allOf(deadLinkFutures.toArray(new CompletableFuture[0])) From d556a334606e5dce493d3251a8e034551b20cedf Mon Sep 17 00:00:00 2001 From: barsh404error Date: Tue, 6 Jan 2026 19:25:57 +0300 Subject: [PATCH 6/8] Add utilities to detect and replace broken links V2 --- .../tjbot/features/utils/LinkDetection.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index e3035a667b..8b48c01631 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -68,6 +68,28 @@ public static boolean containsLink(String content) { return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); } + /** + * Checks whether the given URL is considered broken. + * + *

+ * A link is considered broken if: + *

    + *
  • The URL is invalid or malformed
  • + *
  • An HTTP request fails
  • + *
  • The HTTP response status code is outside the 200–399 range
  • + *
+ * + *

+ * Notes: + *

    + *
  • Status code {@code 200} is considered valid, even if the response body is empty
  • + *
  • The response body content is not inspected
  • + *
+ * + * @param url the URL to check + * @return a future completing with {@code true} if the link is broken + */ + public static CompletableFuture isLinkBroken(String url) { HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) @@ -90,6 +112,34 @@ public static CompletableFuture isLinkBroken(String url) { }); } + /** + * Replaces all broken links in the given text with the provided replacement string. + * + *

+ * Example: + * + *

{@code
+     * replaceDeadLinks("""
+     *         Test
+     *         http://deadlink/1
+     *         http://workinglink/1
+     *         """, "broken")
+     * }
+ * + *

+ * Results in: + * + *

{@code
+     * Test
+     * broken
+     * http://workinglink/1
+     * }
+ * + * @param text the input text containing URLs + * @param replacement the string to replace broken links with + * @return a future containing the modified text + */ + public static CompletableFuture replaceDeadLinks(String text, String replacement) { List links = extractLinks(text, DEFAULT_FILTERS); From 66b8d6e33a51e4c4c20b0b8a744d2b4ea86e308b Mon Sep 17 00:00:00 2001 From: barsh404error Date: Fri, 9 Jan 2026 14:37:55 +0300 Subject: [PATCH 7/8] Add utilities to detect and replace broken links V2 --- .../tjbot/features/utils/LinkDetection.java | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 8b48c01631..3fdefb571c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -9,7 +9,6 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -65,7 +64,7 @@ public static List extractLinks(String content, Set filter) * @return true if the content contains at least one link */ public static boolean containsLink(String content) { - return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); + return !new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty(); } /** @@ -74,39 +73,46 @@ public static boolean containsLink(String content) { *

* A link is considered broken if: *

    - *
  • The URL is invalid or malformed
  • *
  • An HTTP request fails
  • *
  • The HTTP response status code is outside the 200–399 range
  • *
* *

+ * The method first performs an HTTP {@code HEAD} request and falls back to an HTTP {@code GET} + * request if the {@code HEAD} request indicates a failure. + *

+ * + *

* Notes: *

    *
  • Status code {@code 200} is considered valid, even if the response body is empty
  • *
  • The response body content is not inspected
  • *
* - * @param url the URL to check + * @param url the URL to check (must be a valid {@link URI}) * @return a future completing with {@code true} if the link is broken + * @throws IllegalArgumentException if the given URL is not a valid URI */ public static CompletableFuture isLinkBroken(String url) { - HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url)) + HttpRequest headCheckRequest = HttpRequest.newBuilder(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) .build(); - return HTTP_CLIENT.sendAsync(headRequest, HttpResponse.BodyHandlers.discarding()) + return HTTP_CLIENT.sendAsync(headCheckRequest, HttpResponse.BodyHandlers.discarding()) .thenApply(response -> { int status = response.statusCode(); return status < 200 || status >= 400; }) .exceptionally(ignored -> true) .thenCompose(result -> { - if (!Boolean.TRUE.equals(result)) { + if (!result) { return CompletableFuture.completedFuture(false); } - HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build(); - return HTTP_CLIENT.sendAsync(getRequest, HttpResponse.BodyHandlers.discarding()) + HttpRequest getFallbackRequest = + HttpRequest.newBuilder(URI.create(url)).GET().build(); + return HTTP_CLIENT + .sendAsync(getFallbackRequest, HttpResponse.BodyHandlers.discarding()) .thenApply(resp -> resp.statusCode() >= 400) .exceptionally(ignored -> true); // still never null }); @@ -116,6 +122,10 @@ public static CompletableFuture isLinkBroken(String url) { * Replaces all broken links in the given text with the provided replacement string. * *

+ * The link checks are performed asynchronously. + *

+ * + *

* Example: * *

{@code
@@ -135,11 +145,10 @@ public static CompletableFuture isLinkBroken(String url) {
      * http://workinglink/1
      * }
* - * @param text the input text containing URLs - * @param replacement the string to replace broken links with + * @param text the input text containing URLs (must not be {@code null}) + * @param replacement the string to replace broken links with (must not be {@code null}) * @return a future containing the modified text */ - public static CompletableFuture replaceDeadLinks(String text, String replacement) { List links = extractLinks(text, DEFAULT_FILTERS); @@ -147,17 +156,17 @@ public static CompletableFuture replaceDeadLinks(String text, String rep return CompletableFuture.completedFuture(text); } - List> deadLinkFutures = links.stream() + List>> deadLinkFutures = links.stream() .distinct() .map(link -> isLinkBroken(link) - .thenApply(isBroken -> Boolean.TRUE.equals(isBroken) ? link : null)) - + .thenApply(isBroken -> isBroken ? Optional.of(link) : Optional.empty())) .toList(); - return CompletableFuture.allOf(deadLinkFutures.toArray(new CompletableFuture[0])) + + return CompletableFuture.allOf(deadLinkFutures.toArray(CompletableFuture[]::new)) .thenApply(ignored -> deadLinkFutures.stream() .map(CompletableFuture::join) - .filter(Objects::nonNull) + .flatMap(Optional::stream) .toList()) .thenApply(deadLinks -> { String result = text; From 24998cfac33925a9dd6c6d190d910864f74ec568 Mon Sep 17 00:00:00 2001 From: barsh404error Date: Tue, 13 Jan 2026 19:25:34 +0300 Subject: [PATCH 8/8] Add utilities to detect and replace broken links V2 --- .../tjbot/features/utils/LinkDetection.java | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java index 3fdefb571c..343cea7d5e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java @@ -17,9 +17,13 @@ * Utility class to detect links. */ public class LinkDetection { - private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + private static final HttpClient HTTP_CLIENT = + HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); - private static final Set DEFAULT_FILTERS = + /** + * Default filters used when extracting links: skip suppressed URLs and non-http schemes. + */ + private static final Set DEFAULT_EXTRACT_FILTERS = Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME); /** @@ -78,23 +82,22 @@ public static boolean containsLink(String content) { * * *

- * The method first performs an HTTP {@code HEAD} request and falls back to an HTTP {@code GET} - * request if the {@code HEAD} request indicates a failure. - *

- * - *

* Notes: *

    *
  • Status code {@code 200} is considered valid, even if the response body is empty
  • *
  • The response body content is not inspected
  • *
* - * @param url the URL to check (must be a valid {@link URI}) + * @param url the URL to check * @return a future completing with {@code true} if the link is broken * @throws IllegalArgumentException if the given URL is not a valid URI */ + public static CompletableFuture isLinkBroken(String url) { + // Quick check with HEAD "cheaper". If HEAD indicates failure some servers don't implement + // HEAD properly, + // fall back to GET to confirm the resource status. HttpRequest headCheckRequest = HttpRequest.newBuilder(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) .build(); @@ -104,7 +107,7 @@ public static CompletableFuture isLinkBroken(String url) { int status = response.statusCode(); return status < 200 || status >= 400; }) - .exceptionally(ignored -> true) + .exceptionally(_ -> true) .thenCompose(result -> { if (!result) { return CompletableFuture.completedFuture(false); @@ -127,7 +130,7 @@ public static CompletableFuture isLinkBroken(String url) { * *

* Example: - * + * *

{@code
      * replaceDeadLinks("""
      *         Test
@@ -138,19 +141,19 @@ public static CompletableFuture isLinkBroken(String url) {
      *
      * 

* Results in: - * + * *

{@code
      * Test
      * broken
      * http://workinglink/1
      * }
* - * @param text the input text containing URLs (must not be {@code null}) - * @param replacement the string to replace broken links with (must not be {@code null}) + * @param text the input text containing URLs + * @param replacement the string to replace broken links with * @return a future containing the modified text */ public static CompletableFuture replaceDeadLinks(String text, String replacement) { - List links = extractLinks(text, DEFAULT_FILTERS); + List links = extractLinks(text, DEFAULT_EXTRACT_FILTERS); if (links.isEmpty()) { return CompletableFuture.completedFuture(text); @@ -164,16 +167,20 @@ public static CompletableFuture replaceDeadLinks(String text, String rep return CompletableFuture.allOf(deadLinkFutures.toArray(CompletableFuture[]::new)) - .thenApply(ignored -> deadLinkFutures.stream() + .thenApply(_ -> deadLinkFutures.stream() .map(CompletableFuture::join) .flatMap(Optional::stream) .toList()) .thenApply(deadLinks -> { - String result = text; + StringBuilder sb = new StringBuilder(text); for (String deadLink : deadLinks) { - result = result.replace(deadLink, replacement); + int idx = sb.indexOf(deadLink); + while (idx != -1) { + sb.replace(idx, idx + deadLink.length(), replacement); + idx = sb.indexOf(deadLink, idx + replacement.length()); + } } - return result; + return sb.toString(); }); }