diff --git a/src/main/java/com/github/fge/filesystem/path/GenericPath.java b/src/main/java/com/github/fge/filesystem/path/GenericPath.java index ff7fb60..9ecf8dc 100644 --- a/src/main/java/com/github/fge/filesystem/path/GenericPath.java +++ b/src/main/java/com/github/fge/filesystem/path/GenericPath.java @@ -129,6 +129,10 @@ public int getNameCount() @Override public Path getName(final int index) { + if (elements.root != null && elements.root.isEmpty() && elements.names.length == 0) { + return new GenericPath(fs, factory, PathElements.EMPTY); + } + final String name; //noinspection ProhibitedExceptionCaught @@ -191,23 +195,53 @@ public boolean endsWith(final Path other) if (!fs.equals(other.getFileSystem())) return false; + final String separator = getFileSystem().getSeparator(); + final PathElements otherElements = ((GenericPath) other).elements; - //noinspection VariableNotUsedInsideIf - if (otherElements.root != null) - return false; + /* If the path of comparison starts with a separator, then we can + * do a simple String.endsWith() to come to the correct value. + */ + if (other.toString().startsWith(separator)) { + return toString().equals(other.toString()); + } + + final String[] otherNames = otherElements.rootAndNames(); + final String[] rootAndNames = elements.rootAndNames(); + + // We make a copy of the array because we are modifying its values + final String[] names = Arrays.copyOf(rootAndNames, rootAndNames.length); + + if (names.length == 1 && names[0].equals(separator)) { + // Do nothing - just preserve our single separator as root + + /* Remove separator from the start of the first element, so that + * everything will match seamlessly. */ + } else if (names.length > 0 && names[0].startsWith(separator)) { + names[0] = names[0].substring(separator.length()); + } - final String[] names = elements.names; final int length = names.length; - final String[] otherNames = otherElements.names; final int otherLength = otherNames.length; + // If searching for an ending larger than our elements, it's false if (length < otherLength) return false; - for (int i = 0; i < otherLength; i++) - if (!names[length - i].equals(otherNames[otherLength - i])) + // If both element's length is the same, just do an equals test + if (length == otherLength) + return Arrays.equals(names, otherNames); + + // Otherwise, we need to move through the last elements one by one + for (int i = 1; i <= otherLength; i++) { + final int nameIdx = length - i; + final String name = names[nameIdx]; + final int otherNameIdx = otherLength - i; + final String otherName = otherNames[otherNameIdx]; + + if (!name.equals(otherName)) return false; + } return true; } @@ -236,7 +270,7 @@ public Path resolve(final Path other) final GenericPath otherPath = (GenericPath) other; final PathElements newNames - = factory.resolve(elements, otherPath.elements); + = factory.resolve(elements, otherPath.elements); /* * See PathElementsFactory's .resolve() diff --git a/src/main/java/com/github/fge/filesystem/path/PathElements.java b/src/main/java/com/github/fge/filesystem/path/PathElements.java index 4b2f3af..64d225c 100644 --- a/src/main/java/com/github/fge/filesystem/path/PathElements.java +++ b/src/main/java/com/github/fge/filesystem/path/PathElements.java @@ -157,6 +157,33 @@ PathElements lastName() return length == 0 ? null : singleton(names[length - 1]); } + /** + * Return an array of strings starting with the root and then all of the + * names. + * + * @return null if there is no root and no names, otherwise as description + */ + @Nonnull + String[] rootAndNames() + { + if (root != null && names.length == 0) { + return new String[] { root }; + } else if (root == null && names.length > 0) { + return names; + } else if (root != null && names.length > 0) { + final String[] elements = new String[names.length + 1]; + elements[0] = root; + int i = 1; + for (String name : names) { + elements[i++] = name; + } + + return elements; + } else { + return new String[] { root == null ? "" : root }; + } + } + /** * Returns an iterator over a set of elements of type T. * diff --git a/src/main/java/com/github/fge/filesystem/path/PathElementsFactory.java b/src/main/java/com/github/fge/filesystem/path/PathElementsFactory.java index 6d715a4..8cd6af7 100644 --- a/src/main/java/com/github/fge/filesystem/path/PathElementsFactory.java +++ b/src/main/java/com/github/fge/filesystem/path/PathElementsFactory.java @@ -435,7 +435,8 @@ protected final String toString(final PathElements elements) { final StringBuilder sb = new StringBuilder(); - final boolean hasRoot = elements.root != null; + final String root = elements.root; + final boolean hasRoot = root != null; final String[] names = elements.names; final int len = names.length; @@ -445,10 +446,18 @@ protected final String toString(final PathElements elements) if (len == 0) return sb.toString(); - if (hasRoot) + if (hasRoot) { sb.append(rootSeparator); + } + + final String firstName = names[0]; + + if (hasRoot && !firstName.startsWith(separator) && + !root.endsWith(separator)) { + sb.append(separator); + } - sb.append(names[0]); + sb.append(firstName); for (int i = 1; i < len; i++) sb.append(separator).append(names[i]); diff --git a/src/test/java/com/github/fge/filesystem/attributes/FileAttributesFactoryTest.java b/src/test/java/com/github/fge/filesystem/attributes/FileAttributesFactoryTest.java index 3cc0b7a..76aadd6 100644 --- a/src/test/java/com/github/fge/filesystem/attributes/FileAttributesFactoryTest.java +++ b/src/test/java/com/github/fge/filesystem/attributes/FileAttributesFactoryTest.java @@ -33,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.mockito.Mockito.mock; -import static org.testng.Assert.assertTrue; public final class FileAttributesFactoryTest { @@ -67,7 +66,7 @@ public void registeringNewProviderWorksWhenClassIsCorrect() } }; - assertTrue(true); + assertThat(true).isTrue(); } @Test @@ -207,4 +206,4 @@ public void attributeProviderExtendingBasicReportsBasicViewSupported() .as("attribute provider extending basic supports basic") .isTrue(); } -} \ No newline at end of file +} diff --git a/src/test/java/com/github/fge/filesystem/path/GenericPathTest.java b/src/test/java/com/github/fge/filesystem/path/GenericPathTest.java index 7e1519d..40ab0b9 100644 --- a/src/test/java/com/github/fge/filesystem/path/GenericPathTest.java +++ b/src/test/java/com/github/fge/filesystem/path/GenericPathTest.java @@ -19,9 +19,9 @@ package com.github.fge.filesystem.path; import com.github.fge.filesystem.CustomSoftAssertions; -import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import com.github.fge.filesystem.driver.FileSystemDriver; import com.github.fge.filesystem.fs.GenericFileSystem; +import com.github.fge.filesystem.provider.FileSystemFactoryProvider; import com.github.fge.filesystem.provider.FileSystemRepository; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -60,26 +60,28 @@ public void initMocks() repository = mock(FileSystemRepository.class); when(repository.getFactoryProvider()).thenReturn(factoryProvider); driver = mock(FileSystemDriver.class); - factory = mock(PathElementsFactory.class); + factory = factoryProvider.getPathElementsFactory(); fs = new GenericFileSystem(uri, repository, driver, provider); } @Test public void isAbsoluteDelegatesToPathElementsFactory() { + final PathElementsFactory mockFactory = mock(PathElementsFactory.class); + final PathElements elements1 = new PathElements("/", NO_NAMES); final PathElements elements2 = PathElements.EMPTY; - when(factory.isAbsolute(elements1)).thenReturn(false); - when(factory.isAbsolute(elements2)).thenReturn(true); + when(mockFactory.isAbsolute(elements1)).thenReturn(false); + when(mockFactory.isAbsolute(elements2)).thenReturn(true); Path path; - path = new GenericPath(fs, factory, elements1); + path = new GenericPath(fs, mockFactory, elements1); assertThat(path.isAbsolute()).isFalse(); - path = new GenericPath(fs, factory, elements2); + path = new GenericPath(fs, mockFactory, elements2); assertThat(path.isAbsolute()).isTrue(); } @@ -120,6 +122,103 @@ public void getFileNameWithNameElementsDoesNotReturnNull() assertPath(path.getFileName()).isNotNull(); } + /* The typical behavior of a Unix path is to return a path with an + * empty root and no names when element 0 is requested from a path + * with an empty root and no names. + */ + @Test + public void getEmptyFileNameReturnsEmptyFirstName() { + final PathElements elements = new PathElements("", new String[] {}); + final Path path = new GenericPath(fs, factory, elements); + + assertThat(path.getName(0).toString()).isEqualTo(""); + } + + @Test + public void pathToStringWithRootDirectoryAsName() { + final PathElements elements + = new PathElements("", new String[] { "/" }); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.toString()).isEqualTo("/"); + } + + @Test + public void pathToStringWithRootDirectoryAndSingleName() { + final PathElements elements + = new PathElements("/", new String[] { "tmp" }); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.toString()).isEqualTo("/tmp"); + } + + @Test + public void pathToStringFromDirectoryRoot() { + final PathElements elements + = new PathElements("/tmp", PathElements.NO_NAMES); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.toString()).isEqualTo("/tmp"); + } + + @Test + public void pathToStringFromDirectoryRootWithSubdir() { + final PathElements elements + = new PathElements("/tmp", new String[] { "foo.txt" }); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.toString()).isEqualTo("/tmp/foo.txt"); + } + + @Test + public void pathToStringFromDirectoryRootWithNoSubdir() { + final PathElements elements + = new PathElements("/tmp/foo.txt", PathElements.NO_NAMES); + FileSystemFactoryProvider provider = repository.getFactoryProvider(); + final Path path = new GenericPath(fs, provider.getPathElementsFactory(), + elements); + assertThat(path.toString()).isEqualTo("/tmp/foo.txt"); + } + + @Test + public void endsWithMatchesLastName() { + final PathElements elements + = new PathElements("/tmp/", new String[] { "foo.txt" }); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.endsWith("foo.txt")).isTrue(); + assertThat(path.endsWith("/tmp/foo.txt")).isTrue(); + } + + @Test + public void endsWithMatchesLastDirectoryAndName() { + final PathElements elements = new PathElements("/tmp", + new String[] { "bar", "foo.txt" }); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.endsWith("bar/foo.txt")).isTrue(); + assertThat(path.endsWith("/tmp/bar/foo.txt")).isTrue(); + } + + @Test + public void endsWithMatchesRoot() { + final PathElements elements + = new PathElements("/tmp", PathElements.NO_NAMES); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.endsWith("tmp")).isTrue(); + assertThat(path.endsWith("/tmp")).isTrue(); + } + + @Test + public void endsWithRootDoesntMatchEmpty() { + final PathElements elements + = new PathElements("/", PathElements.NO_NAMES); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.endsWith("")).isFalse(); + } + + @Test + public void endsWithEmptyPathsMatch() { + final PathElements elements + = new PathElements("", PathElements.NO_NAMES); + final Path path = new GenericPath(fs, factory, elements); + assertThat(path.endsWith("")).isTrue(); + } + /* * This test this part of the Path's .relativize() method: * @@ -240,4 +339,4 @@ public void toUriPathReturnsCorrectURI(final String s, final String path, assertThat(p.toUri().toString()).as("generated URI is correct") .isEqualTo(expected); } -} \ No newline at end of file +} diff --git a/src/test/java/com/github/fge/filesystem/path/PathElementsFactoryTest.java b/src/test/java/com/github/fge/filesystem/path/PathElementsFactoryTest.java index e347261..a22e623 100644 --- a/src/test/java/com/github/fge/filesystem/path/PathElementsFactoryTest.java +++ b/src/test/java/com/github/fge/filesystem/path/PathElementsFactoryTest.java @@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -import static org.testng.Assert.assertTrue; public final class PathElementsFactoryTest { @@ -229,14 +228,14 @@ public void relativizingWithDifferentRootsThrowsIAE() factory.relativize(elements1, elements2); fail("No exception thrown!"); } catch (IllegalArgumentException ignored) { - assertTrue(true); + assertThat(true).isTrue(); } try { factory.relativize(elements2, elements1); fail("No exception thrown!"); } catch (IllegalArgumentException ignored) { - assertTrue(true); + assertThat(true).isTrue(); } } @@ -370,4 +369,4 @@ private static String[] stringArray(final String first, System.arraycopy(other, 0, ret, 1, other.length); return ret; } -} \ No newline at end of file +}