Skip to content

Commit d205074

Browse files
committed
Add utilities to detect and replace broken links V2
1 parent 7153833 commit d205074

1 file changed

Lines changed: 43 additions & 22 deletions

File tree

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

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.net.http.HttpRequest;
1010
import java.net.http.HttpResponse;
1111
import java.util.List;
12+
import java.util.Objects;
1213
import java.util.Optional;
1314
import java.util.Set;
1415
import java.util.concurrent.CompletableFuture;
@@ -17,6 +18,10 @@
1718
* Utility class to detect links.
1819
*/
1920
public class LinkDetection {
21+
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
22+
23+
private static final Set<LinkFilter> DEFAULT_FILTERS =
24+
Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME);
2025

2126
/**
2227
* Possible ways to filter a link.
@@ -63,41 +68,57 @@ public static boolean containsLink(String content) {
6368
return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty());
6469
}
6570

71+
@SuppressWarnings("java:S2095")
6672
public static CompletableFuture<Boolean> isLinkBroken(String url) {
67-
HttpClient client = HttpClient.newHttpClient();
68-
69-
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
73+
HttpRequest headRequest = HttpRequest.newBuilder(URI.create(url))
7074
.method("HEAD", HttpRequest.BodyPublishers.noBody())
7175
.build();
7276

73-
return client.sendAsync(request, HttpResponse.BodyHandlers.discarding())
74-
.thenApply(response -> response.statusCode() >= 400)
75-
.exceptionally(ignored -> true);
77+
return HTTP_CLIENT.sendAsync(headRequest, HttpResponse.BodyHandlers.discarding())
78+
.thenApply(response -> {
79+
int status = response.statusCode();
80+
if (status >= 200 && status < 400) {
81+
return false;
82+
}
83+
return status >= 400 ? true : null;
84+
})
85+
.exceptionally(ignored -> null)
86+
.thenCompose(result -> {
87+
if (result != null) {
88+
return CompletableFuture.completedFuture(result);
89+
}
90+
HttpRequest getRequest = HttpRequest.newBuilder(URI.create(url)).GET().build();
91+
92+
return HTTP_CLIENT.sendAsync(getRequest, HttpResponse.BodyHandlers.discarding())
93+
.thenApply(resp -> resp.statusCode() >= 400)
94+
.exceptionally(ignored -> true);
95+
});
7696
}
7797

7898
public static CompletableFuture<String> replaceDeadLinks(String text, String replacement) {
79-
Set<LinkFilter> filters = Set.of(LinkFilter.SUPPRESSED, LinkFilter.NON_HTTP_SCHEME);
80-
81-
List<String> links = extractLinks(text, filters);
99+
List<String> links = extractLinks(text, DEFAULT_FILTERS);
82100

83101
if (links.isEmpty()) {
84102
return CompletableFuture.completedFuture(text);
85103
}
86104

87-
StringBuilder result = new StringBuilder(text);
88-
89-
List<CompletableFuture<Void>> checks =
90-
links.stream().map(link -> isLinkBroken(link).thenAccept(isDead -> {
91-
if (isDead) {
92-
int index = result.indexOf(link);
93-
if (index != -1) {
94-
result.replace(index, index + link.length(), replacement);
95-
}
96-
}
97-
})).toList();
105+
List<CompletableFuture<String>> deadLinkFutures = links.stream()
106+
.distinct()
107+
.map(link -> isLinkBroken(link).thenApply(isBroken -> isBroken ? link : null))
108+
.toList();
98109

99-
return CompletableFuture.allOf(checks.toArray(new CompletableFuture[0]))
100-
.thenApply(v -> result.toString());
110+
return CompletableFuture.allOf(deadLinkFutures.toArray(new CompletableFuture[0]))
111+
.thenApply(ignored -> deadLinkFutures.stream()
112+
.map(CompletableFuture::join)
113+
.filter(Objects::nonNull)
114+
.toList())
115+
.thenApply(deadLinks -> {
116+
String result = text;
117+
for (String deadLink : deadLinks) {
118+
result = result.replace(deadLink, replacement);
119+
}
120+
return result;
121+
});
101122
}
102123

103124
private static Optional<String> toLink(Url url, Set<LinkFilter> filter) {

0 commit comments

Comments
 (0)