diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/PropertyCacheFile.java b/src/main/java/com/puppycrawl/tools/checkstyle/PropertyCacheFile.java index 55f51820745..c53bc6d5c9c 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/PropertyCacheFile.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/PropertyCacheFile.java @@ -143,8 +143,21 @@ public void load() throws IOException { */ public void persist() throws IOException { final Path path = Paths.get(fileName); - final Path directory = path.getParent(); + Path directory = path.getParent(); + if (directory != null) { + if (Files.isSymbolicLink(directory)) { + final Path actualDir = directory.toRealPath(); + + if (Files.isDirectory(actualDir)) { + directory = actualDir; + } + else { + throw new IOException( + "Resolved symbolic link " + directory + + " is not a directory."); + } + } Files.createDirectories(directory); } try (OutputStream out = Files.newOutputStream(path)) { diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/PropertyCacheFileTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/PropertyCacheFileTest.java index 8f381a93c1b..eba2e8c09d4 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/PropertyCacheFileTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/PropertyCacheFileTest.java @@ -274,12 +274,97 @@ public void testPathToCacheContainsOnlyFileName() throws IOException { // no exception expected cache.persist(); + assertWithMessage("Cache file does not exist") .that(Files.exists(filePath)) .isTrue(); Files.delete(filePath); } + @Test + public void testPersistWithSymbolicLinkToDirectory() throws IOException { + final Path tempDirectory = Files.createTempDirectory("tempDir"); + final Path symbolicLinkDirectory = Files.createTempDirectory("symbolicLinkDir") + .resolve("symbolicLink"); + Files.createSymbolicLink(symbolicLinkDirectory, tempDirectory); + + final Configuration config = new DefaultConfiguration("myName"); + final String cacheFilePath = symbolicLinkDirectory.resolve("cache.temp").toString(); + final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath); + + cache.persist(); + + final Path expectedFilePath = tempDirectory.resolve("cache.temp"); + assertWithMessage("Cache file should be created in the actual directory") + .that(Files.exists(expectedFilePath)) + .isTrue(); + } + + @Test + public void testSymbolicLinkResolution() throws IOException { + final Path tempDirectory = Files.createTempDirectory("tempDir"); + final Path symbolicLinkDirectory = Files.createTempDirectory("symbolicLinkDir") + .resolve("symbolicLink"); + Files.createSymbolicLink(symbolicLinkDirectory, tempDirectory); + + final Configuration config = new DefaultConfiguration("myName"); + final String cacheFilePath = symbolicLinkDirectory.resolve("cache.temp").toString(); + final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath); + + cache.persist(); + + final Path expectedFilePath = tempDirectory.resolve("cache.temp"); + assertWithMessage( + "Cache file should be created in the actual directory.") + .that(Files.exists(expectedFilePath)) + .isTrue(); + } + + @Test + public void testSymbolicLinkToNonDirectory() throws IOException { + final Path tempFile = Files.createTempFile("tempFile", null); + final Path symbolicLinkDirectory = Files.createTempDirectory("symbolicLinkDir"); + final Path symbolicLink = symbolicLinkDirectory.resolve("symbolicLink"); + Files.createSymbolicLink(symbolicLink, tempFile); + + final Configuration config = new DefaultConfiguration("myName"); + final String cacheFilePath = symbolicLink.resolve("cache.temp").toString(); + final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath); + + final IOException thrown = assertThrows(IOException.class, cache::persist); + + final String expectedMessage = "Resolved symbolic link " + symbolicLink + + " is not a directory."; + + assertWithMessage( + "Expected IOException when symbolicLink is not a directory") + .that(thrown.getMessage()) + .contains(expectedMessage); + } + + @Test + public void testMultipleSymbolicLinkResolution() throws IOException { + final Path actualDirectory = Files.createTempDirectory("actualDir"); + final Path firstSymbolicLink = Files.createTempDirectory("firstLinkDir") + .resolve("firstLink"); + Files.createSymbolicLink(firstSymbolicLink, actualDirectory); + + final Path secondSymbolicLink = Files.createTempDirectory("secondLinkDir") + .resolve("secondLink"); + Files.createSymbolicLink(secondSymbolicLink, firstSymbolicLink); + + final Configuration config = new DefaultConfiguration("myName"); + final String cacheFilePath = secondSymbolicLink.resolve("cache.temp").toString(); + final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath); + + cache.persist(); + + final Path expectedFilePath = actualDirectory.resolve("cache.temp"); + assertWithMessage("Cache file should be created in the final actual directory") + .that(Files.exists(expectedFilePath)) + .isTrue(); + } + @Test public void testChangeInConfig() throws Exception { final DefaultConfiguration config = new DefaultConfiguration("myConfig");