1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.document;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.FileSystem;
24 import java.nio.file.FileSystems;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.Optional;
33 import java.util.stream.Collectors;
34
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.commons.lang3.builder.CompareToBuilder;
37 import org.apache.commons.lang3.builder.EqualsBuilder;
38 import org.apache.commons.lang3.builder.HashCodeBuilder;
39 import org.apache.commons.lang3.tuple.ImmutablePair;
40 import org.apache.commons.lang3.tuple.Pair;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public class DocumentName implements Comparable<DocumentName> {
60
61 private final String name;
62
63 private final DocumentName baseName;
64
65 private final FSInfo fsInfo;
66
67 private final String root;
68
69
70
71
72
73
74 public static Builder builder() {
75 return new Builder(FSInfo.getDefault());
76 }
77
78
79
80
81
82
83 public static Builder builder(final FSInfo fsInfo) {
84 return new Builder(fsInfo);
85 }
86
87
88
89
90
91
92 public static Builder builder(final FileSystem fileSystem) {
93 return new Builder(fileSystem);
94 }
95
96
97
98
99
100
101
102 public static Builder builder(final File file) {
103 return new Builder(file);
104 }
105
106
107
108
109
110
111 public static Builder builder(final DocumentName documentName) {
112 return new Builder(documentName);
113 }
114
115
116
117
118
119 DocumentName(final Builder builder) {
120 this.name = builder.name;
121 this.fsInfo = builder.fsInfo;
122 this.root = builder.root;
123 this.baseName = builder.sameNameFlag ? this : builder.baseName;
124 }
125
126
127
128
129
130 public File asFile() {
131 return new File(getName());
132 }
133
134
135
136
137
138 public Path asPath() {
139 return Paths.get(name);
140 }
141
142
143
144
145
146
147
148
149 public DocumentName resolve(final String child) {
150 if (StringUtils.isBlank(child)) {
151 return this;
152 }
153 String separator = getDirectorySeparator();
154 String pattern = separator.equals("/") ? child.replace('\\', '/') :
155 child.replace('/', '\\');
156
157 if (!pattern.startsWith(separator)) {
158 pattern = name + separator + pattern;
159 }
160
161 return new Builder(this).setName(fsInfo.normalize(pattern)).build();
162 }
163
164
165
166
167
168 public String getName() {
169 return root + fsInfo.dirSeparator() + name;
170 }
171
172
173
174
175
176 public String getBaseName() {
177 return baseName.getName();
178 }
179
180
181
182
183
184 public String getRoot() {
185 return root;
186 }
187
188
189
190
191
192 public DocumentName getBaseDocumentName() {
193 return baseName;
194 }
195
196
197
198
199
200 public String getDirectorySeparator() {
201 return fsInfo.dirSeparator();
202 }
203
204
205
206
207
208
209
210
211 boolean startsWithRootOrSeparator(final String candidate, final String root, final String separator) {
212 if (StringUtils.isBlank(candidate)) {
213 return false;
214 }
215 boolean result = !StringUtils.isBlank(root) && candidate.startsWith(root);
216 if (!result) {
217 result = !StringUtils.isBlank(separator) && candidate.startsWith(separator);
218 }
219 return result;
220 }
221
222
223
224
225
226
227 public String localized() {
228 String result = getName();
229 String baseNameStr = baseName.getName();
230 if (result.startsWith(baseNameStr)) {
231 result = result.substring(baseNameStr.length());
232 }
233 if (!startsWithRootOrSeparator(result, getRoot(), fsInfo.dirSeparator())) {
234 result = fsInfo.dirSeparator() + result;
235 }
236 return result;
237 }
238
239
240
241
242
243
244
245 public String localized(final String dirSeparator) {
246 String[] tokens = fsInfo.tokenize(localized());
247 if (tokens.length == 0) {
248 return dirSeparator;
249 }
250 if (tokens.length == 1) {
251 return dirSeparator + tokens[0];
252 }
253
254 String modifiedRoot = dirSeparator.equals("/") ? root.replace('\\', '/') :
255 root.replace('/', '\\');
256 String result = String.join(dirSeparator, tokens);
257 return startsWithRootOrSeparator(result, modifiedRoot, dirSeparator) ? result : dirSeparator + result;
258 }
259
260
261
262
263
264 public String getShortName() {
265 int pos = name.lastIndexOf(fsInfo.dirSeparator());
266 return pos == -1 ? name : name.substring(pos + 1);
267 }
268
269
270
271
272
273 public boolean isCaseSensitive() {
274 return fsInfo.isCaseSensitive();
275 }
276
277
278
279
280
281 @Override
282 public String toString() {
283 return localized();
284 }
285
286 @Override
287 public int compareTo(final DocumentName other) {
288 return CompareToBuilder.reflectionCompare(this, other);
289 }
290
291 @Override
292 public boolean equals(final Object other) {
293 return EqualsBuilder.reflectionEquals(this, other);
294 }
295
296 @Override
297 public int hashCode() {
298 return HashCodeBuilder.reflectionHashCode(this);
299 }
300
301
302
303
304 public static class FSInfo implements Comparable<FSInfo> {
305
306 private final String name;
307
308 private final String separator;
309
310 private final boolean isCaseSensitive;
311
312 private final List<String> roots;
313
314 public static FSInfo getDefault() {
315 FSInfo result = (FSInfo) System.getProperties().get("FSInfo");
316 return result == null ?
317 new FSInfo("default", FileSystems.getDefault())
318 : result;
319 }
320
321
322
323
324 public FSInfo(final FileSystem fileSystem) {
325 this("anon", fileSystem);
326 }
327
328
329
330
331
332 public FSInfo(final String name, final FileSystem fileSystem) {
333 this.name = name;
334 this.separator = fileSystem.getSeparator();
335 this.isCaseSensitive = isCaseSensitive(fileSystem);
336 roots = new ArrayList<>();
337 fileSystem.getRootDirectories().forEach(r -> roots.add(r.toString()));
338 }
339
340
341
342
343
344
345 private static boolean isCaseSensitive(final FileSystem fileSystem) {
346 boolean isCaseSensitive = false;
347 Path nameSet = null;
348 Path filea = null;
349 Path fileA = null;
350 try {
351 try {
352 Path root = fileSystem.getPath("");
353 nameSet = Files.createTempDirectory(root, "NameSet");
354 filea = nameSet.resolve("a");
355 fileA = nameSet.resolve("A");
356 Files.createFile(filea);
357 Files.createFile(fileA);
358 isCaseSensitive = true;
359 } catch (IOException e) {
360
361 } finally {
362 if (filea != null) {
363 Files.deleteIfExists(filea);
364 }
365 if (fileA != null) {
366 Files.deleteIfExists(fileA);
367 }
368 if (nameSet != null) {
369 Files.deleteIfExists(nameSet);
370 }
371 }
372 } catch (IOException e) {
373
374 }
375 return isCaseSensitive;
376 }
377
378
379
380
381
382 @Override
383 public String toString() {
384 return name;
385 }
386
387
388
389
390
391
392
393 FSInfo(final String name, final String separator, final boolean isCaseSensitive, final List<String> roots) {
394 this.name = name;
395 this.separator = separator;
396 this.isCaseSensitive = isCaseSensitive;
397 this.roots = new ArrayList<>(roots);
398 }
399
400
401
402
403
404 public String dirSeparator() {
405 return separator;
406 }
407
408
409
410
411
412 public boolean isCaseSensitive() {
413 return isCaseSensitive;
414 }
415
416
417
418
419
420
421 public Optional<String> rootFor(final String name) {
422 for (String sysRoot : roots) {
423 if (name.startsWith(sysRoot)) {
424 return Optional.of(sysRoot);
425 }
426 }
427 return Optional.empty();
428 }
429
430
431
432
433
434
435 public String[] tokenize(final String source) {
436 return source.split("\\Q" + dirSeparator() + "\\E");
437 }
438
439
440
441
442
443
444 public String normalize(final String pattern) {
445 if (StringUtils.isBlank(pattern)) {
446 return "";
447 }
448 List<String> parts = new ArrayList<>(Arrays.asList(tokenize(pattern)));
449 for (int i = 0; i < parts.size(); i++) {
450 String part = parts.get(i);
451 if (part.equals("..")) {
452 if (i == 0) {
453 throw new IllegalStateException("Unable to create path before root");
454 }
455 parts.set(i - 1, null);
456 parts.set(i, null);
457 } else if (part.equals(".")) {
458 parts.set(i, null);
459 }
460 }
461 return parts.stream().filter(Objects::nonNull).collect(Collectors.joining(dirSeparator()));
462 }
463
464 @Override
465 public int compareTo(final FSInfo other) {
466 return CompareToBuilder.reflectionCompare(this, other);
467 }
468
469 @Override
470 public boolean equals(final Object other) {
471 return EqualsBuilder.reflectionEquals(this, other);
472 }
473
474 @Override
475 public int hashCode() {
476 return HashCodeBuilder.reflectionHashCode(this);
477 }
478 }
479
480
481
482
483 public static final class Builder {
484
485 private String name;
486
487 private DocumentName baseName;
488
489 private final FSInfo fsInfo;
490
491 private String root;
492
493 private boolean sameNameFlag;
494
495
496
497
498 private Builder(final FSInfo fsInfo) {
499 this.fsInfo = fsInfo;
500 root = "";
501 }
502
503
504
505
506 private Builder(final FileSystem fileSystem) {
507 this(new FSInfo(fileSystem));
508 }
509
510
511
512
513
514 private Builder(final File file) {
515 this(FSInfo.getDefault());
516 setName(file);
517 }
518
519
520
521
522
523
524 Builder(final FSInfo fsInfo, final File file) {
525 this(fsInfo);
526 setName(file);
527 }
528
529
530
531
532
533 Builder(final DocumentName documentName) {
534 this.root = documentName.root;
535 this.name = documentName.name;
536 this.baseName = documentName.baseName;
537 this.fsInfo = documentName.fsInfo;
538 }
539
540
541
542
543
544 public String directorySeparator() {
545 return fsInfo.dirSeparator();
546 }
547
548
549
550
551 private void verify() {
552 Objects.requireNonNull(name, "Name must not be null");
553 if (name.startsWith(fsInfo.dirSeparator())) {
554 name = name.substring(fsInfo.dirSeparator().length());
555 }
556 if (!sameNameFlag) {
557 Objects.requireNonNull(baseName, "Basename must not be null");
558 }
559 }
560
561
562
563
564
565
566 public Builder setRoot(final String root) {
567 this.root = StringUtils.defaultIfBlank(root, "");
568 return this;
569 }
570
571
572
573
574
575
576
577
578
579
580
581 public Builder setName(final String name) {
582 Pair<String, String> pair = splitRoot(StringUtils.defaultIfBlank(name, ""));
583 if (this.root.isEmpty()) {
584 this.root = pair.getLeft();
585 }
586 this.name = fsInfo.normalize(pair.getRight());
587 if (this.baseName != null && !baseName.name.isEmpty()) {
588 if (!this.name.startsWith(baseName.name)) {
589 this.name = this.name.isEmpty() ? baseName.name :
590 baseName.name + fsInfo.dirSeparator() + this.name;
591 }
592 }
593 return this;
594 }
595
596
597
598
599
600
601
602
603
604 Pair<String, String> splitRoot(final String name) {
605 String workingName = name;
606 Optional<String> maybeRoot = fsInfo.rootFor(name);
607 String root = maybeRoot.orElse("");
608 if (!root.isEmpty()) {
609 if (workingName.startsWith(root)) {
610 workingName = workingName.substring(root.length());
611 if (!workingName.startsWith(fsInfo.dirSeparator())) {
612 if (root.endsWith(fsInfo.dirSeparator())) {
613 root = root.substring(0, root.length() - fsInfo.dirSeparator().length());
614 }
615 }
616 }
617 }
618 return ImmutablePair.of(root, workingName);
619 }
620
621
622
623
624
625 private void setEmptyRoot(final String root) {
626 if (this.root.isEmpty()) {
627 this.root = root;
628 }
629 }
630
631
632
633
634
635
636 public Builder setName(final File file) {
637 Pair<String, String> pair = splitRoot(file.getAbsolutePath());
638 setEmptyRoot(pair.getLeft());
639 this.name = fsInfo.normalize(pair.getRight());
640 if (file.isDirectory()) {
641 sameNameFlag = true;
642 } else {
643 File p = file.getParentFile();
644 if (p != null) {
645 setBaseName(p);
646 } else {
647 Builder baseBuilder = new Builder(this.fsInfo).setName(this.directorySeparator());
648 baseBuilder.sameNameFlag = true;
649 setBaseName(baseBuilder.build());
650 }
651 }
652 return this;
653 }
654
655
656
657
658
659
660
661
662
663
664 public Builder setBaseName(final String baseName) {
665 DocumentName.Builder builder = DocumentName.builder(fsInfo).setName(baseName);
666 builder.sameNameFlag = true;
667 setBaseName(builder);
668 return this;
669 }
670
671
672
673
674
675
676
677 public Builder setBaseName(final DocumentName baseName) {
678 this.baseName = baseName;
679 if (!baseName.getRoot().isEmpty()) {
680 this.root = baseName.getRoot();
681 }
682 return this;
683 }
684
685
686
687
688
689 private void setBaseName(final DocumentName.Builder builder) {
690 this.baseName = builder.build();
691 this.sameNameFlag = false;
692 }
693
694
695
696
697
698
699
700 public Builder setBaseName(final File file) {
701 DocumentName.Builder builder = DocumentName.builder(fsInfo).setName(file);
702 builder.sameNameFlag = true;
703 setBaseName(builder);
704 return this;
705 }
706
707
708
709
710
711 public DocumentName build() {
712 verify();
713 return new DocumentName(this);
714 }
715 }
716 }