package com.github.marschall.memoryfilesystem; import static java.lang.Math.min; import java.io.IOException; import java.nio.file.LinkOption; import java.nio.file.Path; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; abstract class NonEmptyPath extends ElementPath { private final List<String> nameElements; NonEmptyPath(MemoryFileSystem fileSystem, List<String> nameElements) { super(fileSystem); this.nameElements = nameElements; } @Override List<String> getNameElements() { return this.nameElements; } @Override String getNameElement(int index) { return this.nameElements.get(index); } @Override String getLastNameElement() { return this.nameElements.get(this.nameElements.size() - 1); } int compareNameElements(List<String> otherElements) { int thisSize = this.nameElements.size(); int otherSize = otherElements.size(); Collator collator = this.getMemoryFileSystem().getCollator(); for (int i = 0; i < thisSize; i++) { if (i == otherSize) { // bail out before accessing return 1; } String thisElement = this.nameElements.get(i); String otherElement = otherElements.get(i); int comparison = collator.compare(thisElement, otherElement); if (comparison != 0) { return comparison; } } if (otherSize > thisSize) { return -1; } else { return 0; } } boolean equalElementsAs(List<String> otherElements) { int thisSize = this.nameElements.size(); if (thisSize != otherElements.size()) { return false; } Collator collator = this.getMemoryFileSystem().getCollator(); for (int i = 0; i < thisSize; i++) { String thisElement = this.nameElements.get(i); String otherElement = otherElements.get(i); if (!collator.equals(thisElement, otherElement)) { return false; } } return true; } @Override public Path getFileName() { String lastElement = this.nameElements.get(this.nameElements.size() - 1); return createRelative(this.getMemoryFileSystem(), lastElement); } @Override public Path toRealPath(LinkOption... options) throws IOException { return this.getMemoryFileSystem().toRealPath(this, options); } @Override boolean isRoot() { return false; } @Override public int getNameCount() { return this.nameElements.size(); } @Override public boolean startsWith(String other) { Path path = this.getMemoryFileSystem().getPath(other); return this.startsWith(path); } void checkNameRange(int beginIndex, int endIndex) { int nameCount = this.getNameCount(); if (beginIndex < 0) { throw new IllegalArgumentException("beginIndex must not be negative but was " + beginIndex); } if (beginIndex >= nameCount) { throw new IllegalArgumentException("beginIndex must not be bigger than " + nameCount + " but was " + beginIndex); } if (endIndex <= beginIndex) { throw new IllegalArgumentException("endIndex must not be smaller than or equal to " + beginIndex + " but was " + beginIndex); } if (endIndex > nameCount) { throw new IllegalArgumentException("endIndex must not be bigger than " + nameCount + " but was " + beginIndex); } } @Override public boolean endsWith(String other) { Path path = this.getMemoryFileSystem().getPath(other); return this.endsWith(path); } @Override public Iterator<Path> iterator() { return new ElementIterator(this.getMemoryFileSystem(), this.nameElements.iterator()); } protected int firstDifferenceIndex(List<?> l1, List<?> l2) { int endIndex = min(l1.size(), l2.size()); for (int i = 0; i < endIndex; ++i) { if (!l1.get(i).equals(l2.get(i))) { return i; } } return endIndex; } Path buildRelativePathAgainst(AbstractPath other) { ElementPath otherPath = (ElementPath) other; int firstDifferenceIndex = this.firstDifferenceIndex(this.getNameElements(), otherPath.getNameElements()); List<String> first = Collections.emptyList(); if (firstDifferenceIndex < this.getNameCount()) { first = ParentReferenceList.create(this.getNameCount() - firstDifferenceIndex); } List<String> second = Collections.emptyList(); if (firstDifferenceIndex < other.getNameCount()) { second = otherPath.getNameElements().subList(firstDifferenceIndex, otherPath.getNameCount()); } List<String> relativeElements = CompositeList.create(first, second); return createRelative(this.getMemoryFileSystem(), relativeElements); } abstract List<String> handleDotDotNormalizationNotYetModified(List<String> nameElements, int nameElementsSize, int i); abstract void handleDotDotNormalizationAlreadyModified(List<String> normalized); abstract List<String> handleSingleDotDot(List<String> normalized); @Override public Path normalize() { List<String> nameElements = this.getNameElements(); int nameElementsSize = nameElements.size(); List<String> normalized = nameElements; boolean modified = false; for (int i = 0; i < nameElementsSize; ++i) { String each = nameElements.get(i); if (each.equals(".")) { if (!modified) { if (nameElementsSize == 1) { // path is just "." normalized = Collections.emptyList(); modified = true; break; } if (nameElementsSize == 2) { // path is either "/a/." or "/./a" String element = i == 0 ? nameElements.get(1) : nameElements.get(0); normalized = Collections.singletonList(element); modified = true; break; } // copy everything preceding a normalized = new ArrayList<>(nameElementsSize - 1); if (i > 0) { normalized.addAll(nameElements.subList(0, i)); } modified = true; } // ignore continue; } if (each.equals("..")) { if (modified) { // just remove the last entry if possible if (!normalized.isEmpty()) { this.handleDotDotNormalizationAlreadyModified(normalized); } } else { if (nameElementsSize == 1) { // path is just "/.." normalized = this.handleSingleDotDot(normalized); modified = normalized != nameElements; break; } else { normalized = this.handleDotDotNormalizationNotYetModified(nameElements, nameElementsSize, i); modified = normalized != nameElements; } } continue; } if (modified) { normalized.add(each); } } if (modified) { return this.newInstance(this.getMemoryFileSystem(), normalized); } else { return this; } } abstract Path newInstance(MemoryFileSystem fileSystem, List<String> pathElements); protected boolean endsWithRelativePath(AbstractPath other) { ElementPath otherPath = (ElementPath) other; int otherNameCount = otherPath.getNameCount(); int thisNameCount = this.getNameCount(); if (otherNameCount == 0) { // empty path return false; } if (otherNameCount > thisNameCount) { return false; } // otherNameCount smaller or equal to this.getNameCount() int offset = thisNameCount - otherNameCount; Collator collator = this.getMemoryFileSystem().getCollator(); for (int i = 0; i < otherNameCount; ++i) { String thisElement = this.getNameElement(i + offset); String otherElement = otherPath.getNameElement(i); if (!collator.equals(thisElement, otherElement)) { return false; } } return true; } static final class ElementIterator implements Iterator<Path> { private final MemoryFileSystem fileSystem; private final Iterator<String> nameIterator; ElementIterator(MemoryFileSystem fileSystem, Iterator<String> nameIterator) { this.fileSystem = fileSystem; this.nameIterator = nameIterator; } @Override public boolean hasNext() { return this.nameIterator.hasNext(); } @Override public Path next() { return createRelative(this.fileSystem, this.nameIterator.next()); } @Override public void remove() { throw new UnsupportedOperationException("can't remove from a path iterator"); } } }