4 using System.Collections.Generic;
9 namespace SiliconStudio.Core.IO
25 private readonly
string fullPath;
26 private readonly
int hashCode;
39 public const char DirectorySeparatorChar =
'/';
44 public const char DirectorySeparatorCharAlt =
'\\';
49 public const string DirectorySeparatorString =
"/";
54 public const string DirectorySeparatorStringAlt =
"\\";
61 internal UPath(
string filePath,
bool isDirectory)
63 if (!isDirectory && filePath != null && (filePath.EndsWith(
"/") || filePath.EndsWith(
@"\")))
65 throw new ArgumentException(
"A file path cannot end with with directory char '\\' or '/' ");
68 fullPath = Decode(filePath, isDirectory, out driveSpan, out DirectorySpan, out NameSpan, out ExtensionSpan);
69 hashCode = ComputeStringHashCodeCaseInsensitive(fullPath);
80 internal UPath(
string drive,
string directory,
string name,
string extension)
82 var builder =
new StringBuilder();
83 if (!
string.IsNullOrWhiteSpace(drive))
85 builder.Append(drive);
87 if (!
string.IsNullOrWhiteSpace(directory))
89 builder.Append(directory);
90 builder.Append(DirectorySeparatorChar);
92 if (!
string.IsNullOrWhiteSpace(name))
96 if (!
string.IsNullOrWhiteSpace(extension))
98 builder.Append(extension);
101 fullPath = Decode(builder.ToString(), string.IsNullOrWhiteSpace(name) &&
string.IsNullOrWhiteSpace(extension), out this.driveSpan, out this.DirectorySpan, out this.NameSpan, out this.ExtensionSpan);
102 hashCode = ComputeStringHashCodeCaseInsensitive(fullPath);
107 this.fullPath = fullPath;
108 this.hashCode = ComputeStringHashCodeCaseInsensitive(fullPath);
109 this.driveSpan = driveSpan;
110 this.DirectorySpan = directorySpan;
117 public string FullPath
144 return driveSpan.IsValid ? fullPath.Substring(driveSpan) : null;
151 public bool HasDirectory
163 public bool IsDirectoryOnly
167 return FullPath == string.Empty || (HasDirectory && !IsFile);
175 public bool IsRelative
189 return DirectorySpan.IsValid ? fullPath.Substring(DirectorySpan) : null;
201 if (DirectorySpan.IsValid)
203 return new UDirectory(fullPath.Substring(0, DirectorySpan.Next), driveSpan, DirectorySpan);
205 if (driveSpan.IsValid)
210 else if (DirectorySpan.IsValid)
212 if (DirectorySpan.Length > 1)
214 var index = fullPath.IndexOfReverse(DirectorySeparatorChar);
217 index = index == 0 ? index + 1 : index;
218 return new UDirectory(fullPath.Substring(0, index), driveSpan,
new StringSpan(DirectorySpan.Start, index - DirectorySpan.Start));
221 if (driveSpan.IsValid)
227 return UDirectory.Empty;
238 var subPath = fullPath.Substring(0, DirectorySpan.Start + DirectorySpan.Length);
239 return new UDirectory(subPath, driveSpan, DirectorySpan);
244 var subPath = fullPath.Substring(0, driveSpan.Length);
245 return new UDirectory(subPath, driveSpan, DirectorySpan);
268 public bool IsAbsolute
272 return HasDrive || (HasDirectory && fullPath[DirectorySpan.Start] == DirectorySeparatorChar);
284 return IsAbsolute ? UPathType.Absolute : UPathType.Relative;
290 if (ReferenceEquals(null, other))
return false;
291 if (ReferenceEquals(
this, other))
return true;
292 return string.Equals(FullPath, other.FullPath, StringComparison.OrdinalIgnoreCase);
297 if (ReferenceEquals(null, obj))
return false;
298 if (ReferenceEquals(
this, obj))
return true;
299 return obj is
UPath && obj.GetType() == GetType() && Equals((
UPath)obj);
307 private static int ComputeStringHashCodeCaseInsensitive(
string text)
309 return text == null ? 0 : text.Aggregate(0, (current, t) => (current*397) ^ char.ToLowerInvariant(t));
314 var uPath = obj as
UPath;
317 if (FullPath != null && uPath.FullPath != null)
319 return String.Compare(FullPath, uPath.FullPath, StringComparison.OrdinalIgnoreCase);
336 return FullPath != null ? FullPath.Replace(
'/',
'\\') : null;
347 return Equals(left, right);
358 return !Equals(left, right);
369 if (leftPath == null)
throw new ArgumentNullException(
"leftPath");
370 if (rightPath == null)
throw new ArgumentNullException(
"rightPath");
373 if (rightPath.IsAbsolute)
378 if (!leftPath.IsDirectoryOnly)
380 throw new ArgumentException(
"Expecting a directory",
"leftPath");
382 var path = string.Format(
"{0}{1}{2}", leftPath.FullPath, string.IsNullOrEmpty(leftPath.FullPath) ?
string.Empty : DirectorySeparatorString, rightPath.FullPath);
393 if (anchorDirectory == null)
throw new ArgumentNullException(
"anchorDirectory");
404 throw new ArgumentException(
"Expecting an absolute directory",
"anchorDirectory");
407 if (anchorDirectory.
HasDrive != HasDrive)
409 throw new InvalidOperationException(
"Path should have no drive information/or both drive information simultaneously");
413 if (
this is
UDirectory && anchorDirectory ==
this)
415 return UDirectory.This;
419 var anchorPath = anchorDirectory.FullPath;
422 var absoluteFile = Combine(anchorDirectory,
this);
423 var absolutePath = absoluteFile.GetFullDirectory().FullPath;
425 var relativePath =
new StringBuilder();
427 int index = anchorPath.Length;
428 bool foundCommonRoot =
false;
429 for (; index >= 0; index--)
432 if (!((index == anchorPath.Length || anchorPath[index] == DirectorySeparatorChar)))
436 if (index == absolutePath.Length || (index < absolutePath.Length && absolutePath[index] == DirectorySeparatorChar))
438 if (
string.Compare(anchorPath, 0, absolutePath, 0, index,
true) == 0)
440 foundCommonRoot =
true;
445 relativePath.Append(
"..").Append(DirectorySeparatorChar);
448 if (!foundCommonRoot)
453 if (index < absolutePath.Length && absolutePath[index] == DirectorySeparatorChar)
458 relativePath.Append(absolutePath.Substring(index));
459 if (absoluteFile is
UFile)
462 if (relativePath.Length > 0)
463 relativePath.Append(DirectorySeparatorChar);
466 relativePath.Append(((UFile)absoluteFile).GetFileNameWithExtension());
468 var newPath = relativePath.ToString();
469 return IsDirectoryOnly ? (
UPath)
new UDirectory(newPath) :
new UFile(newPath);
477 public static implicit
operator string(
UPath url)
479 return url == null ? null : url.FullPath;
489 return (path != null && (path.Contains(DirectorySeparatorChar) || path.Contains(DirectorySeparatorCharAlt)));
500 Normalize(path, out error);
501 return error == null;
514 var result = Normalize(pathToNormalize, out error);
517 throw new ArgumentException(error,
"pathToNormalize");
519 return result.ToString();
529 public static StringBuilder
Normalize(
string pathToNormalize, out
string error)
534 return Normalize(pathToNormalize, out drive, out directoryOrFileName, out fileName, out error);
553 string path = pathToNormalize;
558 int countDirectories = pathToNormalize.Count(pathItem => pathItem == DirectorySeparatorChar ||
559 pathItem == DirectorySeparatorCharAlt ||
560 pathItem == System.IO.Path.VolumeSeparatorChar);
563 if (countDirectories > 1024)
565 error =
"Path contains too many directory '/' separator or ':'";
571 var paths = stackalloc
StringSpan[countDirectories + 1];
572 var builder =
new StringBuilder(pathToNormalize.Length);
575 foreach (var pathItem
in pathToNormalize)
578 if (pathItem == DirectorySeparatorChar || pathItem == DirectorySeparatorCharAlt)
581 if (builder.Length == 0 || builder[builder.Length - 1] != DirectorySeparatorChar)
584 if (builder.Length == 0)
590 if (TrimParentAndSelfPath(builder, ref currentPath, paths,
false))
596 if (IsInvalidBacktrackOnDrive(builder, currentPath, paths))
598 error =
"Cannot go to parent directory '..' with a root drive";
603 builder.Append(DirectorySeparatorChar);
609 paths[currentPath].Start = builder.Length;
612 else if (pathItem == System.IO.Path.VolumeSeparatorChar)
615 if (IsDriveSpan(paths[0]))
617 error =
"Path contains more than one drive ':' separator";
623 error =
"Path cannot contain a drive ':' separator after a backslash";
627 if (builder.Length == 0)
629 error =
"Path cannot start with a drive ':' separator";
634 builder.Append(pathItem);
637 paths[0].Length = -paths[0].Length;
640 paths[1].Start = builder.Length;
643 else if (!InvalidFileNameChars.Contains(pathItem))
646 builder.Append(pathItem);
647 paths[currentPath].Length++;
652 error =
"Invalid character [{0}] found in path [{1}]".ToFormat(pathItem, pathToNormalize);
658 RemoveTrailing(builder, DirectorySeparatorChar);
661 if (TrimParentAndSelfPath(builder, ref currentPath, paths,
true))
664 RemoveTrailing(builder, DirectorySeparatorChar);
669 if (IsInvalidBacktrackOnDrive(builder, currentPath, paths))
671 error =
"Cannot go to parent directory '..' with a root drive";
677 if (currentPath > 0 && !paths[currentPath].
IsValid)
683 int startDirectory = 0;
684 if (IsDriveSpan(paths[0]))
688 drive.Length = -drive.Length + 1;
693 if (startDirectory <= currentPath)
695 directoryOrFileName.Start = paths[startDirectory].Start == 1 ? 0 : paths[startDirectory].Start;
696 if (currentPath == startDirectory)
698 directoryOrFileName.Length = paths[startDirectory].Length == 0 && paths[startDirectory].Start == 1
700 : paths[startDirectory].Length;
704 directoryOrFileName.Length = paths[currentPath - 1].Start + paths[currentPath - 1].Length - directoryOrFileName.Start;
706 if (paths[currentPath].IsValid)
709 if (IsParentPath(builder, paths[currentPath]))
711 directoryOrFileName.Length += paths[currentPath].Length + 1;
715 fileName.Start = paths[currentPath].Start;
716 fileName.Length = builder.Length - fileName.Start;
725 private static void RemoveTrailing(StringBuilder builder,
char charToRemove)
727 if (builder.Length > 1 && builder[builder.Length - 1] == charToRemove)
729 builder.Length = builder.Length - 1;
733 private static bool IsParentPath(StringBuilder builder, StringSpan path)
735 return path.Length == 2 &&
736 builder[path.Start] ==
'.' &&
737 builder[path.Start + 1] ==
'.';
740 private static bool IsRelativeCurrentPath(StringBuilder builder, StringSpan path)
742 return path.Length == 1 && builder[path.Start] ==
'.';
745 private unsafe
static bool IsInvalidBacktrackOnDrive(StringBuilder builder,
int currentPath, StringSpan* paths)
748 return currentPath > 0 && IsParentPath(builder, paths[currentPath]) && IsInvalidRelativeBacktrackOnDrive(currentPath, paths);
751 private static bool IsDriveSpan(StringSpan stringSpan)
753 return stringSpan.Length < 0;
756 private unsafe
static bool IsInvalidRelativeBacktrackOnDrive(
int currentPath, StringSpan* paths)
759 return IsDriveSpan(paths[0]) && (currentPath == 1 || (currentPath == 2 && paths[1].Length == 0));
770 private unsafe
static bool TrimParentAndSelfPath(StringBuilder builder, ref
int currentPath, StringSpan* paths,
bool isLastTrim)
772 var path = paths[currentPath];
773 if (currentPath > 0 && IsParentPath(builder, path))
776 if (IsInvalidRelativeBacktrackOnDrive(currentPath, paths))
782 var previousPath = paths[currentPath - 1];
783 if (IsParentPath(builder, previousPath))
789 paths[currentPath] =
new StringSpan();
791 paths[currentPath].Length = 0;
792 builder.Length = paths[currentPath].Start;
796 var isRelativeCurrentPath = IsRelativeCurrentPath(builder, path);
797 if (!(isLastTrim && currentPath == 0 && isRelativeCurrentPath) && isRelativeCurrentPath)
800 paths[currentPath].Length = 0;
801 builder.Length = paths[currentPath].Start;
807 private static string Decode(
string pathToNormalize,
bool isPathDirectory, out StringSpan drive, out StringSpan directory, out StringSpan fileName, out StringSpan fileExtension)
809 drive =
new StringSpan();
810 directory =
new StringSpan();
811 fileName =
new StringSpan();
812 fileExtension =
new StringSpan();
814 if (
string.IsNullOrWhiteSpace(pathToNormalize))
822 var path = Normalize(pathToNormalize, out drive, out directory, out fileName, out error);
825 throw new ArgumentException(error);
831 if (fileName.IsValid)
833 directory.Length = directory.Length + 1 + fileName.Length;
834 fileName =
new StringSpan();
840 if (directory.IsValid && !fileName.IsValid)
842 fileName = directory;
843 directory =
new StringSpan();
846 if (fileName.IsValid)
848 var extensionIndex = path.LastIndexOf(
'.', fileName.Start);
849 if (extensionIndex > 0)
851 fileName.Length = extensionIndex - fileName.Start;
852 fileExtension.Start = extensionIndex;
853 fileExtension.Length = path.Length - extensionIndex;
858 return path.ToString();
_Use_decl_annotations_ bool IsValid(DXGI_FORMAT fmt)
static unsafe StringBuilder Normalize(string pathToNormalize, out StringSpan drive, out StringSpan directoryOrFileName, out StringSpan fileName, out string error)
Normalize a path by replacing '\' by '/' and transforming relative '..' or current path '...
static StringBuilder Normalize(string pathToNormalize, out string error)
Normalize a path by replacing '\' by '/' and transforming relative '..' or current path '...
static bool HasDirectoryChars(string path)
Determines whether the specified path contains some directory characeters '\' or '/' ...
string ToWindowsPath()
Converts this path to a Windows path (/ replaced by )
UPath MakeRelative(UDirectory anchorDirectory)
Makes this instance relative to the specified anchor directory.
static bool IsValid(string path)
Determines whether the specified path is a valid UPath
Defines a normalized directory path. See UPath for details. This class cannot be inherited.
UPathType
Describes if a UPath is relative or absolute.
bool HasDrive
Gets a value indicating whether this instance has a Drive != null.
bool IsAbsolute
Determines whether this instance is absolute.
override bool Equals(object obj)
UDirectory GetFullDirectory()
Gets the full directory with GetDrive() + GetDirectory() or empty directory.
Base class that describes a uniform path and provides method to manipulate them. Concrete class are U...
UDirectory GetParent()
Gets the parent directory of this instance. For a file, this is the directory directly containing the...
string GetDirectory()
Gets the directory. Can be null.
UPath(string fullPath, StringSpan driveSpan, StringSpan directorySpan)
bool IsDirectoryOnly
Gets a value indicating whether this instance is a directory only path.
static string Normalize(string pathToNormalize)
Normalize a path by replacing '\' by '/' and transforming relative '..' or current path '...
override string ToString()
int CompareTo(object obj)
override int GetHashCode()
Defines a normalized file path. See UPath for details. This class cannot be inherited.
A region of character in a string.
string GetDrive()
Gets the drive (contains the ':' if any), can be null.