Deny cyclic FSFolders #3
| @@ -1,21 +1,30 @@ | ||||
| package filesystem | ||||
|  | ||||
| import java.nio.file.FileAlreadyExistsException | ||||
| import java.nio.file.FileSystemException | ||||
| import java.nio.file.Files | ||||
| import java.nio.file.Path | ||||
|  | ||||
| class FSCreator { | ||||
|     /** | ||||
|      * Create entry, leaving existing folders' contents, but overwriting existing files. | ||||
|      * @throws CyclicFolderException Cyclic folders cannot be created. | ||||
|      */ | ||||
|     @Throws(FileSystemException::class) | ||||
|     @Throws(CyclicFolderException::class) | ||||
|     fun create( | ||||
|         entryToCreate: FSEntry, | ||||
|         destination: String, | ||||
|     ) { | ||||
|         val queue = ArrayDeque<Pair<FSEntry, Path>>() | ||||
|         queue.add(entryToCreate to Path.of(destination)) | ||||
|         // No point in running anything if we know the input is invalid. | ||||
|         if (entryToCreate is FSFolder && entryToCreate.isCyclic()) { | ||||
|             throw CyclicFolderException() | ||||
|         } | ||||
|  | ||||
|         val queue = | ||||
|             ArrayDeque( | ||||
|                 listOf( | ||||
|                     entryToCreate to Path.of(destination), | ||||
|                 ), | ||||
|             ) | ||||
|  | ||||
|         while (queue.isNotEmpty()) { | ||||
|             val (entry, dest) = queue.removeFirst() | ||||
| @@ -34,3 +43,5 @@ class FSCreator { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| class CyclicFolderException : Exception("Cyclic FSFolders are not supported") | ||||
| @@ -6,4 +6,22 @@ sealed class FSEntry(val name: String) | ||||
|  | ||||
| class FSFile(name: String, val content: String) : FSEntry(name) | ||||
|  | ||||
| class FSFolder(name: String, val entries: List<FSEntry>) : FSEntry(name) | ||||
| class FSFolder(name: String, val entries: List<FSEntry>) : FSEntry(name) { | ||||
|     /** | ||||
|      * Check whether a folder is cyclic. | ||||
|      */ | ||||
|     fun isCyclic(): Boolean { | ||||
|         val seen = listOf(this).toHashSet<FSEntry>() | ||||
|         val queue = ArrayDeque(entries) | ||||
|         while (queue.isNotEmpty()) { | ||||
|             val entry = queue.removeFirst() | ||||
|             if (!seen.add(entry)) { | ||||
|                 return true | ||||
|             } | ||||
|             if (entry is FSFolder) { | ||||
|                 queue.addAll(entry.entries) | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,6 @@ | ||||
| package filesystem | ||||
|  | ||||
| import org.junit.jupiter.api.* | ||||
| import java.nio.file.FileSystemException | ||||
| import java.nio.file.Files | ||||
| import java.nio.file.Path | ||||
| import java.util.concurrent.TimeUnit | ||||
| @@ -81,7 +80,7 @@ class FSCreatorTest { | ||||
|                                 FSFile("hi", "hi"), | ||||
|                             ), | ||||
|                         ), | ||||
|                         FSFolder("another-folder", listOf()), | ||||
|                         FSFolder("folder", listOf()), | ||||
|                         FSFile("1.txt", "One!"), | ||||
|                         FSFile("2.txt", "Two!"), | ||||
|                     ), | ||||
| @@ -95,7 +94,7 @@ class FSCreatorTest { | ||||
|                     "folder", | ||||
|                     listOf( | ||||
|                         FSFolder( | ||||
|                             "another-folder", | ||||
|                             "folder", | ||||
|                             listOf( | ||||
|                                 FSFolder( | ||||
|                                     "secrets", | ||||
| @@ -113,22 +112,36 @@ class FSCreatorTest { | ||||
|             ) | ||||
|         } | ||||
|         assertEquals("hi", Files.readString(Path.of("_tmp/folder/sub-folder/hi"))) | ||||
|         assertEquals("P4ssW0rd", Files.readString(Path.of("_tmp/folder/another-folder/secrets/secret"))) | ||||
|         assertEquals("P4ssW0rd", Files.readString(Path.of("_tmp/folder/folder/secrets/secret"))) | ||||
|         assertEquals("One is a good number", Files.readString(Path.of("_tmp/folder/1.txt"))) | ||||
|         assertEquals("Two!", Files.readString(Path.of("_tmp/folder/2.txt"))) | ||||
|         assertEquals("Three!", Files.readString(Path.of("_tmp/folder/3.txt"))) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Timeout(500, unit = TimeUnit.MILLISECONDS) // in case implementation starts trying to handle recursion | ||||
|     fun `create throws on recursive folder`() { | ||||
|     @Timeout(500, unit = TimeUnit.MILLISECONDS) // in case implementation starts trying to handle cyclic folders | ||||
|     fun `create throws on cyclic folder`() { | ||||
|         val files = mutableListOf<FSEntry>() | ||||
|         val folder = FSFolder("folder", files) | ||||
|         files.add(folder) | ||||
|         assertThrows<FileSystemException> { | ||||
|         assertThrows<CyclicFolderException> { | ||||
|             creator.create(folder, "_tmp") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Timeout(500, unit = TimeUnit.MILLISECONDS) | ||||
|     fun `create throws on long cyclic folder`() { | ||||
|         val files = mutableListOf<FSEntry>() | ||||
|         val folder1 = FSFolder("folder", files) | ||||
|         val folder2 = FSFolder("folder2", listOf(folder1)) | ||||
|         val folder3 = FSFolder("folder3", listOf(folder2)) | ||||
|         val folder4 = FSFolder("folder4", listOf(folder3)) | ||||
|         files.add(folder4) | ||||
|         assertThrows<CyclicFolderException> { | ||||
|             creator.create(folder4, "_tmp") | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun deleteRecursive(path: Path) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user