|
9 | 9 | import java.net.http.HttpRequest; |
10 | 10 | import java.net.http.HttpResponse; |
11 | 11 | import java.util.List; |
| 12 | +import java.util.Objects; |
12 | 13 | import java.util.Optional; |
13 | 14 | import java.util.Set; |
14 | 15 | import java.util.concurrent.CompletableFuture; |
|
17 | 18 | * Utility class to detect links. |
18 | 19 | */ |
19 | 20 | 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); |
20 | 25 |
|
21 | 26 | /** |
22 | 27 | * Possible ways to filter a link. |
@@ -63,41 +68,57 @@ public static boolean containsLink(String content) { |
63 | 68 | return !(new UrlDetector(content, UrlDetectorOptions.BRACKET_MATCH).detect().isEmpty()); |
64 | 69 | } |
65 | 70 |
|
| 71 | + @SuppressWarnings("java:S2095") |
66 | 72 | 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)) |
70 | 74 | .method("HEAD", HttpRequest.BodyPublishers.noBody()) |
71 | 75 | .build(); |
72 | 76 |
|
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 | + }); |
76 | 96 | } |
77 | 97 |
|
78 | 98 | 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); |
82 | 100 |
|
83 | 101 | if (links.isEmpty()) { |
84 | 102 | return CompletableFuture.completedFuture(text); |
85 | 103 | } |
86 | 104 |
|
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(); |
98 | 109 |
|
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 | + }); |
101 | 122 | } |
102 | 123 |
|
103 | 124 | private static Optional<String> toLink(Url url, Set<LinkFilter> filter) { |
|
0 commit comments