1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.niofs;
20
21 import static org.mycore.datamodel.niofs.MCRAbstractFileSystem.SEPARATOR;
22 import static org.mycore.datamodel.niofs.MCRAbstractFileSystem.SEPARATOR_STRING;
23
24 import java.io.File;
25 import java.io.IOError;
26 import java.io.IOException;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.nio.file.FileStore;
30 import java.nio.file.InvalidPathException;
31 import java.nio.file.LinkOption;
32 import java.nio.file.Path;
33 import java.nio.file.ProviderMismatchException;
34 import java.nio.file.WatchEvent;
35 import java.nio.file.WatchEvent.Kind;
36 import java.nio.file.WatchEvent.Modifier;
37 import java.nio.file.WatchKey;
38 import java.nio.file.WatchService;
39 import java.text.MessageFormat;
40 import java.text.Normalizer;
41 import java.util.ArrayList;
42 import java.util.Iterator;
43 import java.util.Locale;
44 import java.util.NoSuchElementException;
45 import java.util.Objects;
46
47 import org.mycore.common.MCRException;
48
49 import com.google.common.primitives.Ints;
50
51
52
53
54
55
56
57 public abstract class MCRPath implements Path {
58
59 String root, path, stringValue;
60
61 private int[] offsets;
62
63
64
65
66 MCRPath(final String root, final String path) {
67 this.root = root;
68 this.path = normalizeAndCheck(Objects.requireNonNull(path, "path may not be null"));
69 if (root == null || root.isEmpty()) {
70 this.root = "";
71 stringValue = this.path;
72 } else {
73 if (!path.isEmpty() && path.charAt(0) != SEPARATOR) {
74 final String msg = new MessageFormat("If root is given, path has to start with ''{0}'': {1}",
75 Locale.ROOT).format(new Object[] { SEPARATOR_STRING, path });
76 throw new IllegalArgumentException(msg);
77 }
78 stringValue = this.root + ":" + (this.path.isEmpty() ? SEPARATOR_STRING : this.path);
79 }
80 initNameComponents();
81 }
82
83 public static MCRPath toMCRPath(final Path other) {
84 if (other == null) {
85 throw new NullPointerException();
86 }
87 if (!(other instanceof MCRPath)) {
88 throw new ProviderMismatchException("other is not an instance of MCRPath: " + other.getClass());
89 }
90 return (MCRPath) other;
91 }
92
93 public static MCRPath getPath(String owner, String path) {
94 Path resolved = MCRPaths.getPath(owner, path);
95 return toMCRPath(resolved);
96 }
97
98
99
100
101
102
103
104 public static MCRPath getRootPath(String owner) {
105 return getPath(owner, "/");
106 }
107
108
109
110
111
112
113
114 static String normalizeAndCheck(final String uncleanPath) {
115 String unicodeNormalizedUncleanPath = Normalizer.normalize(uncleanPath, Normalizer.Form.NFC);
116
117 char prevChar = 0;
118 final boolean afterSeparator = false;
119 for (int i = 0; i < unicodeNormalizedUncleanPath.length(); i++) {
120 final char c = unicodeNormalizedUncleanPath.charAt(i);
121 checkCharacter(unicodeNormalizedUncleanPath, c, afterSeparator);
122 if (c == SEPARATOR && prevChar == SEPARATOR) {
123 return normalize(unicodeNormalizedUncleanPath, unicodeNormalizedUncleanPath.length(), i - 1);
124 }
125 prevChar = c;
126 }
127 if (prevChar == SEPARATOR) {
128
129 return normalize(unicodeNormalizedUncleanPath, unicodeNormalizedUncleanPath.length(),
130 unicodeNormalizedUncleanPath.length() - 1);
131 }
132 return unicodeNormalizedUncleanPath;
133 }
134
135 private static void checkCharacter(final String input, final char c, final boolean afterSeparator) {
136 if (c == '\u0000') {
137 throw new InvalidPathException(input, "Nul character is not allowed.");
138 }
139 if (afterSeparator && c == ':') {
140 throw new InvalidPathException(input, "':' is only allowed after owner id.");
141 }
142
143 }
144
145 private static String normalize(final String input, final int length, final int offset) {
146 if (length == 0) {
147 return input;
148 }
149 int newLength = length;
150 while (newLength > 0 && input.charAt(newLength - 1) == SEPARATOR) {
151 newLength--;
152 }
153 if (newLength == 0) {
154 return SEPARATOR_STRING;
155 }
156 final StringBuilder sb = new StringBuilder(input.length());
157 boolean afterSeparator = false;
158 if (offset > 0) {
159 final String prefix = input.substring(0, offset);
160 afterSeparator = prefix.contains(SEPARATOR_STRING);
161 sb.append(prefix);
162 }
163 char prevChar = 0;
164 for (int i = offset; i < newLength; i++) {
165 final char c = input.charAt(i);
166 checkCharacter(input, c, afterSeparator);
167 if (c == SEPARATOR && prevChar == SEPARATOR) {
168 continue;
169 }
170 sb.append(c);
171 if (!afterSeparator && c == SEPARATOR) {
172 afterSeparator = true;
173 }
174 prevChar = c;
175 }
176 return sb.toString();
177 }
178
179
180
181
182 @Override
183 public int compareTo(final Path other) {
184 final MCRPath that = (MCRPath) Objects.requireNonNull(other);
185 return toString().compareTo(that.toString());
186 }
187
188
189
190
191 @Override
192 public boolean endsWith(final Path other) {
193 if (!(Objects.requireNonNull(other, "other Path may not be null.") instanceof MCRPath)) {
194 return false;
195 }
196 final MCRPath that = (MCRPath) other;
197 if (this == that) {
198 return true;
199 }
200 final int thatOffsetCount = that.offsets.length;
201 final int thisOffsetCount = offsets.length;
202 final int thatPathLength = that.path.length();
203 final int thisPathLength = path.length();
204
205 if (thatPathLength > thisPathLength) {
206 return false;
207 }
208
209 final boolean thatIsAbsolute = that.isAbsolute();
210 final boolean thisIsAbsolute = isAbsolute();
211 if (thatIsAbsolute) {
212 if (!thisIsAbsolute) {
213 return false;
214 }
215
216 if (!root.equals(that.root)) {
217 return false;
218 }
219
220 return Objects.deepEquals(offsets, that.offsets) && path.equals(that.path)
221 && that.getFileSystem().equals(getFileSystem());
222 }
223
224
225
226 if (thatOffsetCount > thisOffsetCount) {
227 return false;
228 }
229 if (thisOffsetCount == thatOffsetCount && thisPathLength != thatPathLength) {
230 return false;
231 }
232 int thisPos = thisOffsetCount - thatOffsetCount;
233 for (int i = thisPos; i < thisOffsetCount; i++) {
234 if (that.offsets[i - thisPos] != offsets[i]) {
235 return false;
236 }
237 }
238
239 thisPos = offsets[thisOffsetCount - thatOffsetCount];
240 int thatPos = that.offsets[0];
241 if (thatPathLength - thatPos != thisPathLength - thisPos) {
242 return false;
243 }
244 while (thisPos < thisPathLength) {
245 if (path.charAt(thisPos++) != that.path.charAt(thatPos++)) {
246 return false;
247 }
248 }
249
250 return that.getFileSystem().equals(getFileSystem());
251 }
252
253
254
255
256 @Override
257 public boolean endsWith(final String other) {
258 return endsWith(getFileSystem().getPath(other));
259 }
260
261 @Override
262 public boolean equals(final Object obj) {
263 if (obj == null) {
264 return false;
265 }
266 if (!(obj instanceof MCRPath)) {
267 return false;
268 }
269 final MCRPath that = (MCRPath) obj;
270 if (!getFileSystem().equals(that.getFileSystem())) {
271 return false;
272 }
273 return stringValue.equals(that.stringValue);
274 }
275
276
277
278
279 @Override
280 public Path getFileName() {
281 final int nameCount = getNameCount();
282 if (nameCount == 0) {
283 return null;
284 }
285 final int lastOffset = offsets[nameCount - 1];
286 final String fileName = path.substring(lastOffset);
287 return MCRAbstractFileSystem.getPath(null, fileName, getFileSystem());
288 }
289
290 @Override
291 public abstract MCRAbstractFileSystem getFileSystem();
292
293
294
295
296 @Override
297 public Path getName(final int index) {
298 final int nameCount = getNameCount();
299 if (index < 0 || index >= nameCount) {
300 throw new IllegalArgumentException();
301 }
302 final String pathElement = getPathElement(index);
303 return MCRAbstractFileSystem.getPath(null, pathElement, getFileSystem());
304 }
305
306
307
308
309 @Override
310 public int getNameCount() {
311 return offsets.length;
312 }
313
314 public String getOwner() {
315 return root;
316 }
317
318 public String getOwnerRelativePath() {
319 return (path.equals("")) ? "/" : path;
320 }
321
322
323
324
325
326 public MCRPath subpathComplete() {
327 return isAbsolute() ? subpath(0, offsets.length) : this;
328 }
329
330
331
332
333 @Override
334 public MCRPath getParent() {
335 final int nameCount = getNameCount();
336 if (nameCount == 0) {
337 return null;
338 }
339 final int lastOffset = offsets[nameCount - 1] - 1;
340 if (lastOffset <= 0) {
341 if (root.isEmpty()) {
342 if (path.startsWith("/")) {
343
344 return MCRAbstractFileSystem.getPath(root, "/", getFileSystem());
345 }
346
347 return null;
348 }
349 return getRoot();
350 }
351 return MCRAbstractFileSystem.getPath(root, path.substring(0, lastOffset), getFileSystem());
352 }
353
354
355
356
357 @Override
358 public MCRPath getRoot() {
359 if (!isAbsolute()) {
360 return null;
361 }
362 if (getNameCount() == 0) {
363 return this;
364 }
365 return getFileSystem().getRootDirectory(root);
366 }
367
368 @Override
369 public int hashCode() {
370 return stringValue.hashCode();
371 }
372
373
374
375
376 @Override
377 public boolean isAbsolute() {
378 return root == null || !root.isEmpty();
379 }
380
381
382
383
384 @Override
385 public Iterator<Path> iterator() {
386 return new Iterator<>() {
387 int i = 0;
388
389 @Override
390 public boolean hasNext() {
391 return i < getNameCount();
392 }
393
394 @Override
395 public Path next() {
396 if (hasNext()) {
397 final Path result = getName(i);
398 i++;
399 return result;
400 }
401 throw new NoSuchElementException();
402 }
403
404 @Override
405 public void remove() {
406 throw new UnsupportedOperationException();
407 }
408 };
409 }
410
411
412
413
414 @Override
415 public Path normalize() {
416 final int count = getNameCount();
417 int remaining = count;
418 final boolean[] ignoreSubPath = new boolean[count];
419
420 for (int i = 0; i < count; i++) {
421 if (ignoreSubPath[i]) {
422 continue;
423 }
424 final int subPathIndex = offsets[i];
425 int subPathLength;
426 if (i == offsets.length - 1) {
427 subPathLength = path.length() - subPathIndex;
428 } else {
429 subPathLength = offsets[i + 1] - subPathIndex - 1;
430 }
431 if (path.charAt(subPathIndex) == '.') {
432 if (subPathLength == 1) {
433 ignoreSubPath[i] = true;
434 remaining--;
435 } else if (subPathLength == 2 && path.charAt(subPathIndex + 1) == '.') {
436 ignoreSubPath[i] = true;
437 remaining--;
438
439
440 for (int r = i - 1; r > 0; r--) {
441 if (!ignoreSubPath[r]) {
442 ignoreSubPath[r] = true;
443 remaining--;
444 break;
445 }
446 }
447 }
448 }
449
450 }
451
452 if (count == remaining) {
453 return this;
454 }
455 if (remaining == 0) {
456 return isAbsolute() ? getRoot() : getFileSystem().emptyPath();
457 }
458 final StringBuilder sb = new StringBuilder(path.length());
459 if (isAbsolute()) {
460 sb.append(SEPARATOR);
461 }
462 for (int i = 0; i < count; i++) {
463 if (ignoreSubPath[i]) {
464 continue;
465 }
466 sb.append(getPathElement(i));
467 if (--remaining > 0) {
468 sb.append('/');
469 }
470 }
471 return MCRAbstractFileSystem.getPath(root, sb.toString(), getFileSystem());
472 }
473
474
475
476
477 @Override
478 public WatchKey register(final WatchService watcher, final Kind<?>... events) throws IOException {
479 return register(watcher, events, new WatchEvent.Modifier[0]);
480 }
481
482
483
484
485 @Override
486 public WatchKey register(final WatchService watcher, final Kind<?>[] events, final Modifier... modifiers)
487 throws IOException {
488 throw new UnsupportedOperationException();
489 }
490
491
492
493
494 @Override
495 public MCRPath relativize(final Path other) {
496 if (equals(Objects.requireNonNull(other, "Cannot relativize against 'null'."))) {
497 return getFileSystem().emptyPath();
498 }
499 if (isAbsolute() != other.isAbsolute()) {
500 throw new IllegalArgumentException("'other' must be absolute if and only if this is absolute, too.");
501 }
502 final MCRPath that = toMCRPath(other);
503 if (!isAbsolute() && isEmpty()) {
504 return that;
505 }
506 URI thisURI;
507 URI thatURI;
508 try {
509 thisURI = new URI(null, null, path, null);
510 thatURI = new URI(null, null, that.path, null);
511 } catch (URISyntaxException e) {
512 throw new MCRException(e);
513 }
514 final URI relativizedURI = thisURI.relativize(thatURI);
515 if (thatURI.equals(relativizedURI)) {
516 return that;
517 }
518 return MCRAbstractFileSystem.getPath(null, relativizedURI.getPath(), getFileSystem());
519 }
520
521 private static boolean isEmpty(Path test) {
522 return test instanceof MCRPath && ((MCRPath) test).isEmpty()
523 || (test.getNameCount() == 1 && test.getName(0).toString().isEmpty());
524 }
525
526
527
528
529 @Override
530 public Path resolve(final Path other) {
531 if (other.isAbsolute()) {
532 return other;
533 }
534 if (isEmpty(other)) {
535 return this;
536 }
537 String otherStr = toMCRPathString(other);
538 final int baseLength = path.length();
539 final int childLength = other.toString().length();
540 if (isEmpty() || otherStr.charAt(0) == SEPARATOR) {
541 return root == null ? other : MCRAbstractFileSystem.getPath(root, otherStr, getFileSystem());
542 }
543 final StringBuilder result = new StringBuilder(baseLength + 1 + childLength);
544 if (baseLength == 1 && path.charAt(0) == SEPARATOR) {
545 result.append(SEPARATOR);
546 result.append(otherStr);
547 } else {
548 result.append(path);
549 result.append(SEPARATOR);
550 result.append(otherStr);
551 }
552 return MCRAbstractFileSystem.getPath(root, result.toString(), getFileSystem());
553 }
554
555 private String toMCRPathString(final Path other) {
556 String otherStr = other.toString();
557 String otherSeperator = other.getFileSystem().getSeparator();
558 return otherSeperator.equals(SEPARATOR_STRING) ? otherStr : otherStr.replace(otherSeperator, SEPARATOR_STRING);
559 }
560
561
562
563
564 @Override
565 public Path resolve(final String other) {
566 return resolve(getFileSystem().getPath(other));
567 }
568
569
570
571
572 @Override
573 public Path resolveSibling(final Path other) {
574 Objects.requireNonNull(other);
575 final Path parent = getParent();
576 return parent == null ? other : parent.resolve(other);
577 }
578
579
580
581
582 @Override
583 public Path resolveSibling(final String other) {
584 return resolveSibling(getFileSystem().getPath(other));
585 }
586
587
588
589
590 @Override
591 public boolean startsWith(final Path other) {
592 if (!(Objects.requireNonNull(other, "other Path may not be null.") instanceof MCRPath)) {
593 return false;
594 }
595 final MCRPath that = (MCRPath) other;
596 if (this == that) {
597 return true;
598 }
599 final int thatOffsetCount = that.offsets.length;
600 final int thisOffsetCount = offsets.length;
601
602
603 if (thatOffsetCount > thisOffsetCount || that.path.length() > path.length()) {
604 return false;
605 }
606 if (thatOffsetCount == thisOffsetCount && that.path.length() != path.length()) {
607 return false;
608 }
609 if (!Objects.deepEquals(root, that.root)) {
610 return false;
611 }
612 if (!Objects.deepEquals(getFileSystem(), that.getFileSystem())) {
613 return false;
614 }
615 for (int i = 0; i < thatOffsetCount; i++) {
616 if (that.offsets[i] != offsets[i]) {
617 return false;
618 }
619 }
620 if (!path.startsWith(that.path)) {
621 return false;
622 }
623 final int thatPathLength = that.path.length();
624
625 return thatPathLength <= path.length() || path.charAt(thatPathLength) == SEPARATOR;
626 }
627
628
629
630
631 @Override
632 public boolean startsWith(final String other) {
633 return startsWith(getFileSystem().getPath(other));
634 }
635
636
637
638
639 @Override
640 public MCRPath subpath(final int beginIndex, final int endIndex) {
641 if (beginIndex < 0) {
642 throw new IllegalArgumentException("beginIndex may not be negative: " + beginIndex);
643 }
644 if (beginIndex >= offsets.length) {
645 throw new IllegalArgumentException("beginIndex may not be greater or qual to the number of path elements("
646 + offsets.length + "): " + beginIndex);
647 }
648 if (endIndex > offsets.length) {
649 throw new IllegalArgumentException("endIndex may not be greater that the number of path elements("
650 + offsets.length + "): " + endIndex);
651 }
652 if (beginIndex >= endIndex) {
653 throw new IllegalArgumentException("endIndex must be greater than beginIndex(" + beginIndex + "): "
654 + endIndex);
655 }
656 final int begin = offsets[beginIndex];
657 final int end = endIndex == offsets.length ? path.length() : offsets[endIndex] - 1;
658 return MCRAbstractFileSystem.getPath(null, path.substring(begin, end), getFileSystem());
659 }
660
661
662
663
664 @Override
665 public Path toAbsolutePath() {
666 if (isAbsolute()) {
667 return this;
668 }
669 throw new IOError(new IOException("There is no default directory to resolve " + this + " against to."));
670 }
671
672
673
674
675 @Override
676 public File toFile() {
677 throw new UnsupportedOperationException();
678 }
679
680
681
682
683 @Override
684 public Path toRealPath(final LinkOption... options) throws IOException {
685 if (isAbsolute()) {
686 final MCRPath normalized = (MCRPath) normalize();
687 getFileSystem().provider().checkAccess(normalized);
688 return normalized;
689 }
690 throw new IOException("Cannot get real path from relative path.");
691 }
692
693 @SuppressWarnings("resource")
694 public Path toPhysicalPath() throws IOException {
695 if (isAbsolute()) {
696 for (FileStore fs : getFileSystem().getFileStores()) {
697 if (fs instanceof MCRAbstractFileStore) {
698 Path physicalPath = ((MCRAbstractFileStore) fs).getPhysicalPath(this);
699 if (physicalPath != null) {
700 return physicalPath;
701 }
702 }
703 }
704 return null;
705 }
706 throw new IOException("Cannot get real path from relative path.");
707 }
708
709 @Override
710 public String toString() {
711 return stringValue;
712 }
713
714
715
716
717 @Override
718 public URI toUri() {
719 try {
720 if (isAbsolute()) {
721 return MCRPaths.getURI(getFileSystem().provider().getScheme(), root, path);
722 }
723 return new URI(null, null, path, null);
724 } catch (URISyntaxException e) {
725 throw new RuntimeException(e);
726 }
727 }
728
729 private String getPathElement(final int index) {
730 final int begin = offsets[index];
731 final int end = index == offsets.length - 1 ? path.length() : offsets[index + 1] - 1;
732 return path.substring(begin, end);
733 }
734
735 private void initNameComponents() {
736 final ArrayList<Integer> list = new ArrayList<>();
737 if (isEmpty()) {
738 if (!isAbsolute()) {
739
740
741 list.add(0);
742 }
743 } else {
744 int start = 0;
745 while (start < path.length()) {
746 if (path.charAt(start) != SEPARATOR) {
747 break;
748 }
749 start++;
750 }
751 int off = start;
752 while (off < path.length()) {
753 if (path.charAt(off) != SEPARATOR) {
754 off++;
755 } else {
756 list.add(start);
757 start = ++off;
758 }
759 }
760 if (start != off) {
761 list.add(start);
762 }
763 }
764 offsets = Ints.toArray(list);
765 }
766
767 private boolean isEmpty() {
768 return path.isEmpty();
769 }
770
771 }