Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>2018-02-28 05:53:54 +0300
committerAnirudh Agnihotry <anirudhagnihotry098@gmail.com>2018-03-01 01:00:31 +0300
commit117da3f807d0213002c32e422b68cc059b55b1fa (patch)
treed719579aa1504ef54c9d21bf57e073ea729617bd
parent1039ab115369640766c900c35c02f97900864010 (diff)
Add Path.Join() methods. (dotnet/coreclr#16561)
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs2
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs24
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/Path.cs186
-rw-r--r--src/System.Private.CoreLib/shared/System/IO/PathInternal.cs8
4 files changed, 136 insertions, 84 deletions
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
index 12949308c..fd24cc810 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Unix.cs
@@ -61,7 +61,7 @@ namespace System.IO
if (IsPathFullyQualified(path))
return GetFullPath(path);
- return GetFullPath(CombineNoChecks(basePath, path));
+ return GetFullPath(CombineInternal(basePath, path));
}
private static string RemoveLongPathPrefix(string path)
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
index c92211f73..b921db9e6 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs
@@ -80,26 +80,30 @@ namespace System.IO
// Path is current drive rooted i.e. starts with \:
// "\Foo" and "C:\Bar" => "C:\Foo"
// "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
- combinedPath = CombineNoChecks(GetPathRoot(basePath), path.AsSpan().Slice(1));
+ combinedPath = Join(GetPathRoot(basePath.AsSpan()), path);
}
else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
{
// Drive relative paths
Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));
- if (StringSpanHelpers.Equals(GetVolumeName(path.AsSpan()), GetVolumeName(basePath.AsSpan())))
+ if (StringSpanHelpers.Equals(GetVolumeName(path), GetVolumeName(basePath)))
{
// Matching root
// "C:Foo" and "C:\Bar" => "C:\Bar\Foo"
// "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
- combinedPath = CombineNoChecks(basePath, path.AsSpan().Slice(2));
+ combinedPath = Join(basePath, path.AsSpan().Slice(2));
}
else
{
// No matching root, root to specified drive
// "D:Foo" and "C:\Bar" => "D:Foo"
// "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
- combinedPath = PathInternal.IsDevice(basePath) ? CombineNoChecksInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)) : path.Insert(2, "\\");
+ combinedPath = !PathInternal.IsDevice(basePath)
+ ? path.Insert(2, @"\")
+ : length == 2
+ ? JoinInternal(basePath.AsSpan().Slice(0, 4), path, @"\")
+ : JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2));
}
}
else
@@ -107,7 +111,7 @@ namespace System.IO
// "Simple" relative path
// "Foo" and "C:\Bar" => "C:\Bar\Foo"
// "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
- combinedPath = CombineNoChecks(basePath, path);
+ combinedPath = JoinInternal(basePath, path);
}
// Device paths are normalized by definition, so passing something of this format
@@ -216,19 +220,11 @@ namespace System.IO
}
/// <summary>
- /// Returns true if the path ends in a directory separator.
- /// </summary>
- internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
- {
- return path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
- }
-
- /// <summary>
/// Trims the ending directory separator if present.
/// </summary>
/// <param name="path"></param>
internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
- EndsInDirectorySeparator(path) ?
+ PathInternal.EndsInDirectorySeparator(path) ?
path.Slice(0, path.Length - 1) :
path;
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.cs b/src/System.Private.CoreLib/shared/System/IO/Path.cs
index 586ddf3a6..41ae1cd0b 100644
--- a/src/System.Private.CoreLib/shared/System/IO/Path.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/Path.cs
@@ -262,7 +262,6 @@ namespace System.IO
return !PathInternal.IsPartiallyQualified(path);
}
-
/// <summary>
/// Tests if a path's file name includes a file extension. A trailing period
/// is not considered an extension.
@@ -296,7 +295,7 @@ namespace System.IO
if (path1 == null || path2 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : nameof(path2));
- return CombineNoChecks(path1, path2);
+ return CombineInternal(path1, path2);
}
public static string Combine(string path1, string path2, string path3)
@@ -304,7 +303,7 @@ namespace System.IO
if (path1 == null || path2 == null || path3 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3));
- return CombineNoChecks(path1, path2, path3);
+ return CombineInternal(path1, path2, path3);
}
public static string Combine(string path1, string path2, string path3, string path4)
@@ -312,7 +311,7 @@ namespace System.IO
if (path1 == null || path2 == null || path3 == null || path4 == null)
throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4));
- return CombineNoChecks(path1, path2, path3, path4);
+ return CombineInternal(path1, path2, path3, path4);
}
public static string Combine(params string[] paths)
@@ -383,11 +382,102 @@ namespace System.IO
return StringBuilderCache.GetStringAndRelease(finalPath);
}
- /// <summary>
- /// Combines two paths. Does no validation of paths, only concatenates the paths
- /// and places a directory separator between them if needed.
- /// </summary>
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
+ // Unlike Combine(), Join() methods do not consider rooting. They simply combine paths, ensuring that there
+ // is a directory separator between them.
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
+ {
+ if (path1.Length == 0)
+ return new string(path2);
+ if (path2.Length == 0)
+ return new string(path1);
+
+ return JoinInternal(path1, path2);
+ }
+
+ public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3)
+ {
+ if (path1.Length == 0)
+ return Join(path2, path3);
+
+ if (path2.Length == 0)
+ return Join(path1, path3);
+
+ if (path3.Length == 0)
+ return Join(path1, path2);
+
+ return JoinInternal(path1, path2, path3);
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0)
+ return true;
+
+ if (path1.Length == 0 || path2.Length == 0)
+ {
+ ref ReadOnlySpan<char> pathToUse = ref path1.Length == 0 ? ref path2 : ref path1;
+ if (destination.Length < pathToUse.Length)
+ {
+ return false;
+ }
+
+ pathToUse.CopyTo(destination);
+ charsWritten = pathToUse.Length;
+ return true;
+ }
+
+ bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2));
+ int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0);
+ if (destination.Length < charsNeeded)
+ return false;
+
+ path1.CopyTo(destination);
+ if (needsSeparator)
+ destination[path1.Length] = DirectorySeparatorChar;
+
+ path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0)));
+
+ charsWritten = charsNeeded;
+ return true;
+ }
+
+ public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten)
+ {
+ charsWritten = 0;
+ if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0)
+ return true;
+
+ if (path1.Length == 0)
+ return TryJoin(path2, path3, destination, out charsWritten);
+ if (path2.Length == 0)
+ return TryJoin(path1, path3, destination, out charsWritten);
+ if (path3.Length == 0)
+ return TryJoin(path1, path2, destination, out charsWritten);
+
+ int neededSeparators = PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1;
+ bool needsSecondSeparator = !(PathInternal.EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3));
+ if (needsSecondSeparator)
+ neededSeparators++;
+
+ int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators;
+ if (destination.Length < charsNeeded)
+ return false;
+
+ bool result = TryJoin(path1, path2, destination, out charsWritten);
+ Debug.Assert(result, "should never fail joining first two paths");
+
+ if (needsSecondSeparator)
+ destination[charsWritten++] = DirectorySeparatorChar;
+
+ path3.CopyTo(destination.Slice(charsWritten));
+ charsWritten += path3.Length;
+
+ return true;
+ }
+
+ private static string CombineInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
if (first.Length == 0)
return second.Length == 0
@@ -400,10 +490,10 @@ namespace System.IO
if (IsPathRooted(second))
return new string(second);
- return CombineNoChecksInternal(first, second);
+ return JoinInternal(first, second);
}
- private static string CombineNoChecks(string first, string second)
+ private static string CombineInternal(string first, string second)
{
if (string.IsNullOrEmpty(first))
return second;
@@ -414,86 +504,48 @@ namespace System.IO
if (IsPathRooted(second.AsSpan()))
return second;
- return CombineNoChecksInternal(first, second);
- }
-
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
- {
- if (first.Length == 0)
- return CombineNoChecks(second, third);
- if (second.Length == 0)
- return CombineNoChecks(first, third);
- if (third.Length == 0)
- return CombineNoChecks(first, second);
-
- if (IsPathRooted(third))
- return new string(third);
- if (IsPathRooted(second))
- return CombineNoChecks(second, third);
-
- return CombineNoChecksInternal(first, second, third);
+ return JoinInternal(first, second);
}
- private static string CombineNoChecks(string first, string second, string third)
+ private static string CombineInternal(string first, string second, string third)
{
if (string.IsNullOrEmpty(first))
- return CombineNoChecks(second, third);
+ return CombineInternal(second, third);
if (string.IsNullOrEmpty(second))
- return CombineNoChecks(first, third);
+ return CombineInternal(first, third);
if (string.IsNullOrEmpty(third))
- return CombineNoChecks(first, second);
+ return CombineInternal(first, second);
if (IsPathRooted(third.AsSpan()))
return third;
if (IsPathRooted(second.AsSpan()))
- return CombineNoChecks(second, third);
-
- return CombineNoChecksInternal(first, second, third);
- }
-
- private static string CombineNoChecks(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
- {
- if (first.Length == 0)
- return CombineNoChecks(second, third, fourth);
- if (second.Length == 0)
- return CombineNoChecks(first, third, fourth);
- if (third.Length == 0)
- return CombineNoChecks(first, second, fourth);
- if (fourth.Length == 0)
- return CombineNoChecks(first, second, third);
-
- if (IsPathRooted(fourth))
- return new string(fourth);
- if (IsPathRooted(third))
- return CombineNoChecks(third, fourth);
- if (IsPathRooted(second))
- return CombineNoChecks(second, third, fourth);
+ return CombineInternal(second, third);
- return CombineNoChecksInternal(first, second, third, fourth);
+ return JoinInternal(first, second, third);
}
- private static string CombineNoChecks(string first, string second, string third, string fourth)
+ private static string CombineInternal(string first, string second, string third, string fourth)
{
if (string.IsNullOrEmpty(first))
- return CombineNoChecks(second, third, fourth);
+ return CombineInternal(second, third, fourth);
if (string.IsNullOrEmpty(second))
- return CombineNoChecks(first, third, fourth);
+ return CombineInternal(first, third, fourth);
if (string.IsNullOrEmpty(third))
- return CombineNoChecks(first, second, fourth);
+ return CombineInternal(first, second, fourth);
if (string.IsNullOrEmpty(fourth))
- return CombineNoChecks(first, second, third);
+ return CombineInternal(first, second, third);
if (IsPathRooted(fourth.AsSpan()))
return fourth;
if (IsPathRooted(third.AsSpan()))
- return CombineNoChecks(third, fourth);
+ return CombineInternal(third, fourth);
if (IsPathRooted(second.AsSpan()))
- return CombineNoChecks(second, third, fourth);
+ return CombineInternal(second, third, fourth);
- return CombineNoChecksInternal(first, second, third, fourth);
+ return JoinInternal(first, second, third, fourth);
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
{
Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
@@ -515,7 +567,7 @@ namespace System.IO
}
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
{
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
@@ -543,7 +595,7 @@ namespace System.IO
}
}
- private unsafe static string CombineNoChecksInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
+ private unsafe static string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
{
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
index 3eac1e74e..f2f350ddd 100644
--- a/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/PathInternal.cs
@@ -10,8 +10,12 @@ namespace System.IO
/// <summary>
/// Returns true if the path ends in a directory separator.
/// </summary>
- internal static bool EndsInDirectorySeparator(string path) =>
- !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]);
+ internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
+
+ /// <summary>
+ /// Returns true if the path starts in a directory separator.
+ /// </summary>
+ internal static bool StartsWithDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[0]);
/// <summary>
/// Get the common path length from the start of the string.