Lucene.Net.Support.IO.FileSupport: Added GetCanonicalPath() method + tests
Project: http://git-wip-us.apache.org/repos/asf/lucenenet/repo Commit: http://git-wip-us.apache.org/repos/asf/lucenenet/commit/8b7b9cb6 Tree: http://git-wip-us.apache.org/repos/asf/lucenenet/tree/8b7b9cb6 Diff: http://git-wip-us.apache.org/repos/asf/lucenenet/diff/8b7b9cb6 Branch: refs/heads/master Commit: 8b7b9cb6d02292e6d31330c0153ae1b975ecbae0 Parents: 5065f10 Author: Shad Storhaug <[email protected]> Authored: Mon Sep 25 00:25:44 2017 +0700 Committer: Shad Storhaug <[email protected]> Committed: Mon Sep 25 00:25:44 2017 +0700 ---------------------------------------------------------------------- .../Support/IO/TestFileSupport.cs | 216 +++++++++++++++++++ src/Lucene.Net/Support/IO/FileSupport.cs | 124 +++++++++++ 2 files changed, 340 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucenenet/blob/8b7b9cb6/src/Lucene.Net.Tests/Support/IO/TestFileSupport.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Support/IO/TestFileSupport.cs b/src/Lucene.Net.Tests/Support/IO/TestFileSupport.cs new file mode 100644 index 0000000..b5ce877 --- /dev/null +++ b/src/Lucene.Net.Tests/Support/IO/TestFileSupport.cs @@ -0,0 +1,216 @@ +using Lucene.Net.Attributes; +using Lucene.Net.Util; +using NUnit.Framework; +using System; +using System.Globalization; +using System.IO; + +namespace Lucene.Net.Support.IO +{ + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + public class TestFileSupport : LuceneTestCase + { + private static String platformId = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.Replace('.', '-'); + + /** Location to store tests in */ + private DirectoryInfo tempDirectory; + + public override void SetUp() + { + base.SetUp(); + tempDirectory = CreateTempDir(this.GetType().Name); + } + + public override void TearDown() + { + if (tempDirectory != null) + { + Directory.Delete(tempDirectory.FullName, true); + tempDirectory = null; + } + base.TearDown(); + } + + [Test, LuceneNetSpecific] + public void TestCreateRandomFile() + { + var dir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "testrandomfile")); + + var file1 = FileSupport.CreateTempFile("foo", "bar", dir); + var file2 = FileSupport.CreateTempFile("foo", "bar", dir); + + Assert.AreNotEqual(file1.FullName, file2.FullName); + } + + [Test, LuceneNetSpecific] + public void TestGetCanonicalPath() + { + // Should work for Unix/Windows. + String dots = ".."; + String @base = tempDirectory.GetCanonicalPath(); + @base = addTrailingSlash(@base); + FileInfo f = new FileInfo(Path.Combine(@base, "temp.tst")); + + assertEquals("Test 1: Incorrect Path Returned.", @base + "temp.tst", f + .GetCanonicalPath()); + f = new FileInfo(@base + "Temp" + Path.DirectorySeparatorChar + dots + Path.DirectorySeparatorChar + "temp.tst"); + assertEquals("Test 2: Incorrect Path Returned.", @base + "temp.tst", f + .GetCanonicalPath()); + + + // Finding a non-existent directory for tests 3 and 4 + // This is necessary because getCanonicalPath is case sensitive and + // could cause a failure in the test if the directory exists but with + // different case letters (e.g "Temp" and "temp") + int dirNumber = 1; + bool dirExists = true; + DirectoryInfo dir1 = new DirectoryInfo(Path.Combine(@base, dirNumber.ToString(CultureInfo.InvariantCulture))); + while (dirExists) + { + if (dir1.Exists) + { + dirNumber++; + dir1 = new DirectoryInfo(Path.Combine(@base, dirNumber.ToString(CultureInfo.InvariantCulture))); + } + else + { + dirExists = false; + } + } + f = new FileInfo(@base + dirNumber + Path.DirectorySeparatorChar + dots + Path.DirectorySeparatorChar + dirNumber + + Path.DirectorySeparatorChar + "temp.tst"); + assertEquals("Test 3: Incorrect Path Returned.", @base + dirNumber + + Path.DirectorySeparatorChar + "temp.tst", f.GetCanonicalPath()); + f = new FileInfo(@base + dirNumber + Path.DirectorySeparatorChar + "Temp" + Path.DirectorySeparatorChar + dots + Path.DirectorySeparatorChar + + "Test" + Path.DirectorySeparatorChar + "temp.tst"); + assertEquals("Test 4: Incorrect Path Returned.", @base + dirNumber + + Path.DirectorySeparatorChar + "Test" + Path.DirectorySeparatorChar + "temp.tst", f.GetCanonicalPath()); + + f = new FileInfo(@base + "1234.567"); + assertEquals("Test 5: Incorrect Path Returned.", @base + "1234.567", f + .GetCanonicalPath()); + + // Test for long file names on Windows + bool onWindows = (Path.DirectorySeparatorChar == '\\'); + if (onWindows) + { + DirectoryInfo testdir = new DirectoryInfo(Path.Combine(@base, "long-" + platformId)); + testdir.Create(); + FileInfo f1 = new FileInfo(Path.Combine(testdir.FullName, "longfilename" + platformId + ".tst")); + using (FileStream fos = new FileStream(f1.FullName, FileMode.CreateNew, FileAccess.Write)) + { } + FileInfo f2 = null, f3 = null; + DirectoryInfo dir2 = null; + try + { + String dirName1 = f1.GetCanonicalPath(); + FileInfo f4 = new FileInfo(Path.Combine(testdir.FullName, "longfi~1.tst")); + /* + * If the "short file name" doesn't exist, then assume that the + * 8.3 file name compatibility is disabled. + */ + if (f4.Exists) + { + String dirName2 = f4.GetCanonicalPath(); + assertEquals("Test 6: Incorrect Path Returned.", dirName1, + dirName2); + dir2 = new DirectoryInfo(Path.Combine(testdir.FullName, "longdirectory" + platformId)); + if (!dir2.Exists) + { + try + { + dir2.Create(); + } + catch + { + } + finally + { + if (!Directory.Exists(dir2.FullName)) + fail("Could not create dir: " + dir2); + } + + } + f2 = new FileInfo(testdir.FullName + Path.DirectorySeparatorChar + "longdirectory" + + platformId + Path.DirectorySeparatorChar + "Test" + Path.DirectorySeparatorChar + dots + + Path.DirectorySeparatorChar + "longfilename.tst"); + using (FileStream fos2 = new FileStream(f2.FullName, FileMode.CreateNew, FileAccess.Write)) + { } + dirName1 = f2.GetCanonicalPath(); + f3 = new FileInfo(testdir.FullName + Path.DirectorySeparatorChar + "longdi~1" + + Path.DirectorySeparatorChar + "Test" + Path.DirectorySeparatorChar + dots + Path.DirectorySeparatorChar + + "longfi~1.tst"); + dirName2 = f3.GetCanonicalPath(); + assertEquals("Test 7: Incorrect Path Returned.", dirName1, + dirName2); + } + } + finally + { + f1.Delete(); + if (f2 != null) + { + f2.Delete(); + } + if (dir2 != null) + { + dir2.Delete(); + } + testdir.Delete(); + } + } + } + + private static String addTrailingSlash(String path) + { + if (Path.DirectorySeparatorChar == path[path.Length - 1]) + { + return path; + } + return path + Path.DirectorySeparatorChar; + } + + [Test, LuceneNetSpecific] + public void TestGetCanonicalPathDriveLetterNormalization() + { + bool onWindows = (Path.DirectorySeparatorChar == '\\'); + if (onWindows) + { + var path = @"f:\testing\on\Windows"; + var expected = @"F:\testing\on\Windows"; + + var dir = new DirectoryInfo(path); + + assertEquals(expected, dir.GetCanonicalPath()); + } + } + + [Test, LuceneNetSpecific] + public void TestGetCanonicalPathDriveLetter() + { + bool onWindows = (Path.DirectorySeparatorChar == '\\'); + if (onWindows) + { + var path = new FileInfo(@"c:\").GetCanonicalPath(); + if (path.Length > 3) + fail("Drive letter incorrectly represented"); + } + } + } +} http://git-wip-us.apache.org/repos/asf/lucenenet/blob/8b7b9cb6/src/Lucene.Net/Support/IO/FileSupport.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net/Support/IO/FileSupport.cs b/src/Lucene.Net/Support/IO/FileSupport.cs index 99ac644..db64b87 100644 --- a/src/Lucene.Net/Support/IO/FileSupport.cs +++ b/src/Lucene.Net/Support/IO/FileSupport.cs @@ -1,5 +1,6 @@ using Lucene.Net.Util; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -177,5 +178,128 @@ namespace Lucene.Net.Support.IO return Path.Combine(directory.FullName, string.Concat(prefix, randomFileName)); } + + private static readonly IDictionary<string, string> fileCanonPathCache = new Dictionary<string, string>(); + + /// <summary> + /// Returns the absolute path of this <see cref="FileSystemInfo"/> with all references resolved and + /// any drive letters normalized to upper case on Windows. An + /// <em>absolute</em> path is one that begins at the root of the file + /// system. The canonical path is one in which all references have been + /// resolved. For the cases of '..' and '.', where the file system supports + /// parent and working directory respectively, these are removed and replaced + /// with a direct directory reference. + /// </summary> + /// <param name="path">This <see cref="FileSystemInfo"/> instance.</param> + /// <returns>The canonical path of this file.</returns> + // LUCENENET NOTE: Implementation ported mostly from Apache Harmony + public static string GetCanonicalPath(this FileSystemInfo path) + { + string absPath = path.FullName; // LUCENENET NOTE: This internally calls GetFullPath(), which resolves relative paths + byte[] result = Encoding.UTF8.GetBytes(absPath); + + string canonPath; + if (fileCanonPathCache.TryGetValue(absPath, out canonPath) && canonPath != null) + { + return canonPath; + } + + // LUCENENET TODO: On Unix, this resolves symbolic links. Not sure + // if it is safe to assume Path.GetFullPath() does that for us. + //if (Path.DirectorySeparatorChar == '/') + //{ + // //// resolve the full path first + // //result = resolveLink(result, result.Length, false); + // //// resolve the parent directories + // //result = resolve(result); + //} + int numSeparators = 1; + for (int i = 0; i < result.Length; i++) + { + if (result[i] == Path.DirectorySeparatorChar) + { + numSeparators++; + } + } + int[] sepLocations = new int[numSeparators]; + int rootLoc = 0; + if (Path.DirectorySeparatorChar == '\\') + { + if (result[0] == '\\') + { + rootLoc = (result.Length > 1 && result[1] == '\\') ? 1 : 0; + } + else + { + rootLoc = 2; // skip drive i.e. c: + } + } + byte[] newResult = new byte[result.Length + 1]; + int newLength = 0, lastSlash = 0, foundDots = 0; + sepLocations[lastSlash] = rootLoc; + for (int i = 0; i <= result.Length; i++) + { + if (i < rootLoc) + { + // Normalize case of Windows drive letter to upper + newResult[newLength++] = (byte)char.ToUpperInvariant((char)result[i]); + } + else + { + if (i == result.Length || result[i] == Path.DirectorySeparatorChar) + { + if (i == result.Length && foundDots == 0) + { + break; + } + if (foundDots == 1) + { + /* Don't write anything, just reset and continue */ + foundDots = 0; + continue; + } + if (foundDots > 1) + { + /* Go back N levels */ + lastSlash = lastSlash > (foundDots - 1) ? lastSlash + - (foundDots - 1) : 0; + newLength = sepLocations[lastSlash] + 1; + foundDots = 0; + continue; + } + sepLocations[++lastSlash] = newLength; + newResult[newLength++] = (byte)Path.DirectorySeparatorChar; + continue; + } + if (result[i] == '.') + { + foundDots++; + continue; + } + /* Found some dots within text, write them out */ + if (foundDots > 0) + { + for (int j = 0; j < foundDots; j++) + { + newResult[newLength++] = (byte)'.'; + } + } + newResult[newLength++] = result[i]; + foundDots = 0; + } + } + // remove trailing slash + if (newLength > (rootLoc + 1) + && newResult[newLength - 1] == Path.DirectorySeparatorChar) + { + newLength--; + } + newResult[newLength] = 0; + //newResult = getCanonImpl(newResult); + newLength = newResult.Length; + canonPath = Encoding.UTF8.GetString(newResult, 0, newLength).TrimEnd('\0'); // LUCENENET: Eliminate null terminator char + fileCanonPathCache[absPath] = canonPath; + return canonPath; + } } } \ No newline at end of file
