· 4 years ago · Sep 07, 2021, 12:28 PM
1package com.boden.websites.common.text;
2
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.List;
9import java.util.function.Consumer;
10import java.util.function.Predicate;
11import java.util.stream.Collectors;
12
13import org.apache.commons.collections.CollectionUtils;
14import org.apache.commons.lang3.ArrayUtils;
15import org.apache.commons.lang3.StringUtils;
16
17import com.day.cq.commons.PathInfo;
18import com.google.common.base.Joiner;
19import com.google.common.base.Splitter;
20import com.google.common.collect.ImmutableList;
21import com.google.common.collect.ImmutableListMultimap;
22import com.google.common.collect.LinkedListMultimap;
23import com.google.common.collect.ListMultimap;
24import com.google.common.collect.Lists;
25
26/**
27 * Composes and decomposes url or path in case of Sling script resolution / specific parts of path like
28 * selectors, suffix etc. Update parts using fluent API.
29 */
30public final class Link {
31
32 public static final String PROTOCOL_SEPARATOR = "://";
33
34 public static final String USER_INFO_SEPARATOR = "@";
35
36 public static final String PORT_SEPARATOR = ":";
37
38 public static final String PATH_SEPARATOR = "/";
39
40 public static final String SELECTOR_SEPARATOR = ".";
41
42 public static final String QUERY_STRING_SEPARATOR = "?";
43
44 public static final String QUERY_ARG_SEPARATOR = "&";
45
46 public static final String QUERY_ARG_ASSSIGNMENT = "=";
47
48 public static final String FRAGMENT_SEPARATOR = "#";
49
50 public static final String EXTENSION_SEPARATOR = ".";
51
52 public static final String EXTENSION_HTML = "html";
53
54 public static final String EXTENSION_JSON = "json";
55
56 public static final String EXTENSION_XML = "xml";
57
58 public static final String EXTENSION_ZIP = "zip";
59
60 private String protocol;
61
62 private String user;
63
64 private String password;
65
66 private String host;
67
68 private Integer port;
69
70 private String resourcePath;
71
72 private List<String> selectors;
73
74 private String extension;
75
76 private String suffix;
77
78 private ListMultimap<String, String> query;
79
80 private String fragment;
81
82 private Link() {
83 this.selectors = Lists.newLinkedList();
84 this.query = LinkedListMultimap.create();
85 }
86
87 public static Link empty() {
88 return new Link();
89 }
90
91 public static Link of(String url) {
92 return empty().setUrl(url);
93 }
94
95 public static String create(Consumer<Link> builder) {
96 final Link result = empty();
97 builder.accept(result);
98
99 return result.toString();
100 }
101
102 public String getProtocol() {
103 return this.protocol;
104 }
105
106 public Link setProtocol(String protocol) {
107 this.protocol = protocol;
108
109 return this;
110 }
111
112 public String getUser() {
113 return this.user;
114 }
115
116 public Link setUser(String user) {
117 this.user = user;
118
119 return this;
120 }
121
122 public String getPassword() {
123 return password;
124 }
125
126 public Link setPassword(String password) {
127 this.password = password;
128
129 return this;
130 }
131
132 public String getHost() {
133 return host;
134 }
135
136 public Link setHost(String host) {
137 this.host = host;
138
139 return this;
140 }
141
142 public Integer getPort() {
143 return port;
144 }
145
146 public Link setPort(Integer port) {
147 this.port = port;
148
149 return this;
150 }
151
152 public String getResourcePath() {
153 return resourcePath;
154 }
155
156 public Link setResourcePath(String resourcePath) {
157 this.resourcePath = resourcePath;
158
159 return this;
160 }
161
162 public Link removeResourcePath() {
163 return setResourcePath(null);
164 }
165
166 public List getSelectors() {
167 return ImmutableList.copyOf(selectors);
168 }
169
170 public Link setSelectors(List<String> selectors) {
171 this.selectors = Lists.newLinkedList(selectors);
172
173 return this;
174 }
175
176 public Link setSelectors(String... selectors) {
177 return setSelectors(ArrayUtils.isNotEmpty(selectors) ? Arrays.asList(selectors) : Lists.newLinkedList());
178 }
179
180 public Link addSelector(String selector) {
181 selectors.add(selector);
182
183 return this;
184 }
185
186 public Link addSelectors(String... selectors) {
187 if (ArrayUtils.isNotEmpty(selectors)) {
188 addSelectors(Arrays.asList(selectors));
189 }
190
191 return this;
192 }
193
194 public Link addSelectors(Collection<String> selectors) {
195 this.selectors.addAll(selectors);
196
197 return this;
198 }
199
200 public Link removeSelectors() {
201 selectors.clear();
202
203 return this;
204 }
205
206 public Link removeSelector(String selector) {
207 selectors.remove(selector);
208
209 return this;
210 }
211
212 public Link filterSelectors(Predicate<String> filter) {
213 selectors = selectors.stream().filter(filter).collect(Collectors.toList());
214
215 return this;
216 }
217
218 public String getExtension() {
219 return this.extension;
220 }
221
222 public Link setExtension(String extension) {
223 this.extension = extension;
224
225 return this;
226 }
227
228 public Link removeExtension() {
229 return setExtension(null);
230 }
231
232 public String getSuffix() {
233 return this.suffix;
234 }
235
236 public Link setSuffix(String value) {
237 if (StringUtils.isNotBlank(value)) {
238 suffix = PATH_SEPARATOR + StringUtils.strip(value, PATH_SEPARATOR);
239 } else {
240 suffix = null;
241 }
242
243 return this;
244 }
245
246 public Link removeSuffix() {
247 return setSuffix(null);
248 }
249
250 public ListMultimap<String, String> getQuery() {
251 return ImmutableListMultimap.copyOf(query);
252 }
253
254 public Link setQuery(ListMultimap<String, String> query) {
255 this.query = LinkedListMultimap.create(query);
256
257 return this;
258 }
259
260 public Link removeQuery() {
261 query.clear();
262
263 return this;
264 }
265
266 public String getFragment() {
267 return this.fragment;
268 }
269
270 public Link setFragment(String fragment) {
271 this.fragment = fragment;
272
273 return this;
274 }
275
276 public Link removeFragment() {
277 this.fragment = null;
278
279 return this;
280 }
281
282 public String getServer() {
283 final StringBuilder builder = new StringBuilder();
284
285 if (StringUtils.isNotBlank(getProtocol())) {
286 builder.append(getProtocol());
287 builder.append(PROTOCOL_SEPARATOR);
288 }
289
290 final String userInfoPart = getUserInfo();
291 if (StringUtils.isNotBlank(userInfoPart)) {
292 builder.append(userInfoPart);
293 builder.append(USER_INFO_SEPARATOR);
294 }
295
296 if (StringUtils.isNotBlank(getHost())) {
297 builder.append(getHost());
298 }
299
300 if (getPort() != null) {
301 builder.append(PORT_SEPARATOR);
302 builder.append(getPort());
303 }
304
305 return builder.toString();
306 }
307
308 public Link setServer(String value) {
309 try {
310 final URL info = new URL(value);
311
312 setProtocol(info.getProtocol());
313 setHost(info.getHost());
314 setUserInfo(info.getUserInfo());
315 setPort((info.getPort() != -1) ? info.getPort() : null);
316 } catch (MalformedURLException e) {
317 throw new IllegalArgumentException(String.format("Link server value is invalid: %s", value), e);
318 }
319
320 return this;
321 }
322
323 public String getUserInfo() {
324 String result = null;
325
326 if (StringUtils.isNotBlank(getUser()) && StringUtils.isNotBlank(getPassword())) {
327 result = String.format("%s:%s", getUser(), getPassword());
328 } else if (StringUtils.isNotBlank(getUser())) {
329 result = getUser();
330 }
331
332 return result;
333 }
334
335 public Link setUserInfo(String value) {
336 final List<String> info = Splitter.on(":").splitToList(StringUtils.defaultString(value));
337
338 switch (info.size()) {
339 case 1:
340 user = info.get(0);
341 break;
342 case 2:
343 user = info.get(0);
344 password = info.get(1);
345 break;
346 default:
347 user = null;
348 password = null;
349 break;
350 }
351
352 return this;
353 }
354
355 public String getUrl() {
356 final StringBuilder builder = new StringBuilder();
357
358 final String serverPart = getServer();
359 if (StringUtils.isNotBlank(serverPart)) {
360 builder.append(serverPart);
361 }
362
363 final String pathPart = getPath();
364 if (StringUtils.isNotBlank(pathPart)) {
365 builder.append(pathPart);
366 }
367
368 final String suffixPart = getSuffix();
369 if (StringUtils.isNotBlank(suffixPart)) {
370 builder.append(suffixPart);
371 }
372
373 final String queryStringPart = getQueryString();
374 if (StringUtils.isNotBlank(queryStringPart)) {
375 builder.append(QUERY_STRING_SEPARATOR);
376 builder.append(queryStringPart);
377 }
378
379 final String fragmentPart = getFragment();
380 if (StringUtils.isNotBlank(fragmentPart)) {
381 builder.append(FRAGMENT_SEPARATOR);
382 builder.append(fragmentPart);
383 }
384
385 return builder.toString();
386 }
387
388 public Link setUrl(String value) {
389 try {
390 if (value.contains("://")) {
391 URL urlInfo = new URL(value);
392 setProtocol(urlInfo.getProtocol());
393 setUserInfo(urlInfo.getUserInfo());
394 setHost(urlInfo.getHost());
395 setPort(urlInfo.getPort() != -1 ? urlInfo.getPort() : null);
396 setPath(urlInfo.getPath());
397 setQueryString(urlInfo.getQuery());
398 setFragment(urlInfo.getRef());
399 } else {
400 final URL pathInfo = new URL("http://any.com" + value);
401 setPath(pathInfo.getPath());
402 setQueryString(pathInfo.getQuery());
403 setFragment(pathInfo.getRef());
404 }
405 } catch (MalformedURLException e) {
406 throw new IllegalArgumentException("Invalid url specified: " + value, e);
407 }
408 return this;
409 }
410
411 public String getPath() {
412 final StringBuilder builder = new StringBuilder();
413
414 final String resourcePathPart = getResourcePath();
415 if (StringUtils.isNotBlank(resourcePathPart)) {
416 builder.append(resourcePathPart);
417 }
418
419 final String selectorStringPart = getSelectorString();
420 if (StringUtils.isNotBlank(selectorStringPart)) {
421 builder.append(SELECTOR_SEPARATOR);
422 builder.append(selectorStringPart);
423 }
424
425 final String extensionPart = getExtension();
426 if (StringUtils.isNotBlank(extensionPart)) {
427 builder.append(EXTENSION_SEPARATOR);
428 builder.append(extensionPart);
429 }
430
431 String result = builder.toString();
432 if (StringUtils.isBlank(result)) {
433 result = null;
434 }
435
436 return result;
437 }
438
439 public Link setPath(String value) {
440 if (StringUtils.isNotBlank(value)) {
441 final PathInfo info = new PathInfo(value);
442
443 setResourcePath(info.getResourcePath());
444 setExtension(info.getExtension());
445 setSelectors(info.getSelectors());
446 setSuffix(info.getSuffix());
447 } else {
448 removeResourcePath();
449 removeExtension();
450 removeSelectors();
451 removeSuffix();
452 }
453
454 return this;
455 }
456
457 public List<String> getResourceParts() {
458 List<String> result = Collections.emptyList();
459 if (StringUtils.isNotBlank(resourcePath)) {
460 result = Splitter.on("/").splitToList(StringUtils.removeStart(resourcePath, "/"));
461 }
462
463 return result;
464 }
465
466 public Link setResourceParts(List<String> value) {
467 if (CollectionUtils.isNotEmpty(value)) {
468 this.resourcePath = Joiner.on(PATH_SEPARATOR).join(value);
469 } else {
470 this.resourcePath = null;
471 }
472
473 return this;
474 }
475
476 public Link setResourcePart(int index, String value) {
477 final List<String> parts = getResourceParts();
478 parts.set(index, value);
479 setResourceParts(parts);
480
481 return this;
482 }
483
484 public String getResourcePart(int index) {
485 return getResourceParts().get(resourcePathIndex(index));
486 }
487
488 public String getResourcePartial(int indexFrom, int indexTo) {
489 final List<String> parts = getResourceParts().subList(
490 resourcePathIndex(indexFrom),
491 resourcePathIndex(indexTo)
492 );
493
494 return Joiner.on(PATH_SEPARATOR).join(parts);
495 }
496
497 private int resourcePathIndex(int index) {
498 int maxIndex = getResourceParts().size();
499 int normalizedIndex = index >= 0 ? index : index + maxIndex;
500
501 return Math.min(Math.max(0, normalizedIndex), maxIndex);
502 }
503
504 public String getSelectorString() {
505 String result = null;
506 if (!getSelectors().isEmpty()) {
507 result = Joiner.on(SELECTOR_SEPARATOR).join(getSelectors());
508 }
509
510 return result;
511 }
512
513 public Link setSelectorString(String value) {
514 if (StringUtils.isNotBlank(value)) {
515 this.selectors = Splitter.on(SELECTOR_SEPARATOR).splitToList(value);
516 } else {
517 this.selectors = Lists.newLinkedList();
518 }
519
520 return this;
521 }
522
523 public String getQueryString() {
524 return query.entries().stream()
525 .map(e -> StringUtils.isBlank(e.getValue())
526 ? e.getKey()
527 : e.getKey() + QUERY_ARG_ASSSIGNMENT + e.getValue()
528 ).collect(Collectors.joining(QUERY_ARG_SEPARATOR));
529 }
530
531 public Link setQueryString(String value) {
532 ListMultimap<String, String> args = LinkedListMultimap.create();
533
534 if (StringUtils.isNotBlank(value)) {
535 for (String pair : Splitter.on(QUERY_ARG_SEPARATOR).split(value)) {
536 List<String> parts = Splitter.on(QUERY_ARG_ASSSIGNMENT).splitToList(pair);
537 if (parts.size() == 1) {
538 args.put(parts.get(0), StringUtils.EMPTY);
539 } else if (parts.size() == 2) {
540 args.put(parts.get(0), parts.get(1));
541 }
542 }
543 }
544
545 this.query = args;
546
547 return this;
548 }
549
550 public Link addQueryParam(String key, String value) {
551 query.put(key, value);
552
553 return this;
554 }
555
556 public Link setQueryParam(String key, String value) {
557 query.removeAll(key);
558 if (value != null) {
559 query.put(key, value);
560 }
561
562 return this;
563 }
564
565 public Link setQueryParam(String key) {
566 return setQueryParam(key, StringUtils.EMPTY);
567 }
568
569 public Link removeQueryParam(String key) {
570 return setQueryParam(key, null);
571 }
572
573 public Link clearPath() {
574 return removeExtension()
575 .removeSuffix()
576 .removeSelectors()
577 .removeQuery()
578 .removeFragment();
579 }
580
581 public Link copy() {
582 return of(toString());
583 }
584
585 public String toString() {
586 return this.getUrl();
587 }
588
589}