Skip to content

Commit 66b8d6e

Browse files
committed
Add utilities to detect and replace broken links V2
1 parent d556a33 commit 66b8d6e

File tree

1 file changed

+26
-17
lines changed

1 file changed

+26
-17
lines changed

application/src/main/java/org/togetherjava/tjbot/features/utils/LinkDetection.java

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.net.http.HttpRequest;
1010
import java.net.http.HttpResponse;
1111
import java.util.List;
12-
import java.util.Objects;
1312
import java.util.Optional;
1413
import java.util.Set;
1514
import java.util.concurrent.CompletableFuture;
@@ -65,7 +64,7 @@ public static List<String> extractLinks(String content, Set<LinkFilter> filter)
6564
* @return true if the content contains at least one link
6665
*/
6766
public static boolean containsLink(String content) {
68-
return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty());
67+
return !new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty();
6968
}
7069

7170
/**
@@ -74,39 +73,46 @@ public static boolean containsLink(String content) {
7473
* <p>
7574
* A link is considered broken if:
7675
* <ul>
77-
* <li>The URL is invalid or malformed</li>
7876
* <li>An HTTP request fails</li>
7977
* <li>The HTTP response status code is outside the 200–399 range</li>
8078
* </ul>
8179
*
8280
* <p>
81+
* The method first performs an HTTP {@code HEAD} request and falls back to an HTTP {@code GET}
82+
* request if the {@code HEAD} request indicates a failure.
83+
* </p>
84+
*
85+
* <p>
8386
* Notes:
8487
* <ul>
8588
* <li>Status code {@code 200} is considered valid, even if the response body is empty</li>
8689
* <li>The response body content is not inspected</li>
8790
* </ul>
8891
*
89-
* @param url the URL to check
92+
* @param url the URL to check (must be a valid {@link URI})
9093
* @return a future completing with {@code true} if the link is broken
94+
* @throws IllegalArgumentException if the given URL is not a valid URI
9195
*/
9296

9397
public static CompletableFuture<Boolean> isLinkBroken(String url) {
94-
HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url))
98+
HttpRequest headCheckRequest = HttpRequest.newBuilder(URI.create(url))
9599
.method("HEAD", HttpRequest.BodyPublishers.noBody())
96100
.build();
97101

98-
return HTTP_CLIENT.sendAsync(headRequest, HttpResponse.BodyHandlers.discarding())
102+
return HTTP_CLIENT.sendAsync(headCheckRequest, HttpResponse.BodyHandlers.discarding())
99103
.thenApply(response -> {
100104
int status = response.statusCode();
101105
return status < 200 || status >= 400;
102106
})
103107
.exceptionally(ignored -> true)
104108
.thenCompose(result -> {
105-
if (!Boolean.TRUE.equals(result)) {
109+
if (!result) {
106110
return CompletableFuture.completedFuture(false);
107111
}
108-
HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build();
109-
return HTTP_CLIENT.sendAsync(getRequest, HttpResponse.BodyHandlers.discarding())
112+
HttpRequest getFallbackRequest =
113+
HttpRequest.newBuilder(URI.create(url)).GET().build();
114+
return HTTP_CLIENT
115+
.sendAsync(getFallbackRequest, HttpResponse.BodyHandlers.discarding())
110116
.thenApply(resp -> resp.statusCode() >= 400)
111117
.exceptionally(ignored -> true); // still never null
112118
});
@@ -116,6 +122,10 @@ public static CompletableFuture<Boolean> isLinkBroken(String url) {
116122
* Replaces all broken links in the given text with the provided replacement string.
117123
*
118124
* <p>
125+
* The link checks are performed asynchronously.
126+
* </p>
127+
*
128+
* <p>
119129
* Example:
120130
*
121131
* <pre>{@code
@@ -135,29 +145,28 @@ public static CompletableFuture<Boolean> isLinkBroken(String url) {
135145
* http://workinglink/1
136146
* }</pre>
137147
*
138-
* @param text the input text containing URLs
139-
* @param replacement the string to replace broken links with
148+
* @param text the input text containing URLs (must not be {@code null})
149+
* @param replacement the string to replace broken links with (must not be {@code null})
140150
* @return a future containing the modified text
141151
*/
142-
143152
public static CompletableFuture<String> replaceDeadLinks(String text, String replacement) {
144153
List<String> links = extractLinks(text, DEFAULT_FILTERS);
145154

146155
if (links.isEmpty()) {
147156
return CompletableFuture.completedFuture(text);
148157
}
149158

150-
List<CompletableFuture<String>> deadLinkFutures = links.stream()
159+
List<CompletableFuture<Optional<String>>> deadLinkFutures = links.stream()
151160
.distinct()
152161
.map(link -> isLinkBroken(link)
153-
.thenApply(isBroken -> Boolean.TRUE.equals(isBroken) ? link : null))
154-
162+
.thenApply(isBroken -> isBroken ? Optional.of(link) : Optional.<String>empty()))
155163
.toList();
156164

157-
return CompletableFuture.allOf(deadLinkFutures.toArray(new CompletableFuture[0]))
165+
166+
return CompletableFuture.allOf(deadLinkFutures.toArray(CompletableFuture[]::new))
158167
.thenApply(ignored -> deadLinkFutures.stream()
159168
.map(CompletableFuture::join)
160-
.filter(Objects::nonNull)
169+
.flatMap(Optional::stream)
161170
.toList())
162171
.thenApply(deadLinks -> {
163172
String result = text;

0 commit comments

Comments
 (0)