2003-11-14 Ben Maurer <bmaurer@users.sourceforge.net>
[mono.git] / mcs / class / corlib / System.IO / Path.cs
1 //------------------------------------------------------------------------------
2 // 
3 // System.IO.Path.cs 
4 //
5 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
6 // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
7 // Copyright (C) 2003 Ben Maurer
8 // 
9 // Author:         Jim Richardson, develop@wtfo-guru.com
10 //                 Dan Lewis (dihlewis@yahoo.co.uk)
11 //                 Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 //                 Ben Maurer (bmaurer@users.sourceforge.net)
13 // Created:        Saturday, August 11, 2001 
14 //
15 //------------------------------------------------------------------------------
16
17 using System;
18 using System.Runtime.CompilerServices;
19
20 namespace System.IO
21 {
22         public sealed class Path
23         {
24                 public static readonly char AltDirectorySeparatorChar;
25                 public static readonly char DirectorySeparatorChar;
26                 public static readonly char[] InvalidPathChars;
27                 public static readonly char PathSeparator;
28                 internal static readonly string DirectorySeparatorStr;
29                 public static readonly char VolumeSeparatorChar;
30
31                 private static readonly char[] PathSeparatorChars;
32                 private static bool dirEqualsVolume;
33
34                 private Path () {}
35
36                 // class methods
37                 public static string ChangeExtension (string path, string extension)
38                 {
39                         if (path == null)
40                         {
41                                 return null;
42                         }
43
44                         if (path.IndexOfAny (InvalidPathChars) != -1)
45                                 throw new ArgumentException ("Illegal characters in path", "path");
46
47                         int iExt = findExtension (path);
48
49                         if (extension != null && path.Length != 0) {
50                                 if (extension [0] != '.')
51                                         extension = "." + extension;
52                         } else
53                                 extension = String.Empty;
54                         
55                         if (iExt < 0) {
56                                 return path + extension;
57                         } else if (iExt > 0) {
58                                 string temp = path.Substring (0, iExt);
59                                 return temp + extension;
60                         }
61
62                         return extension;
63                 }
64
65                 public static string Combine (string path1, string path2)
66                 {
67                         if (path1 == null)
68                                 throw new ArgumentNullException ("path1");
69
70                         if (path2 == null)
71                                 throw new ArgumentNullException ("path2");
72
73                         if (path1 == String.Empty)
74                                 return path2;
75
76                         if (path2 == String.Empty)
77                                 return path1;
78
79                         if (path1.IndexOfAny (InvalidPathChars) != -1)
80                                 throw new ArgumentException ("Illegal characters in path", "path1");
81
82                         if (path2.IndexOfAny (InvalidPathChars) != -1)
83                                 throw new ArgumentException ("Illegal characters in path", "path2");
84
85                         //TODO???: UNC names
86                         // LAMESPEC: MS says that if path1 is not empty and path2 is a full path
87                         // it should throw ArgumentException
88                         if (IsPathRooted (path2))
89                                 return path2;
90                         
91                         if (Array.IndexOf (PathSeparatorChars, path1 [path1.Length - 1]) == -1)
92                                 return path1 + DirectorySeparatorChar + path2;
93
94                         return path1 + path2;
95                 }
96
97                 public static string GetDirectoryName (string path)
98                 {
99                         // LAMESPEC: For empty string MS docs say both
100                         // return null AND throw exception.  Seems .NET throws.
101                         if (path == String.Empty)
102                                 throw new ArgumentException();
103
104                         if (path == null || GetPathRoot (path) == path)
105                                 return null;
106
107                         CheckArgument.WhitespaceOnly (path);
108                         CheckArgument.PathChars (path);
109
110                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
111                         if (nLast == 0)
112                                 nLast++;
113
114                         if (nLast > 0)
115                                 return path.Substring (0, nLast);
116
117                         return String.Empty;
118                 }
119
120                 public static string GetExtension (string path)
121                 {
122                         if (path == null)
123                                 return null;
124
125                         if (path.IndexOfAny (InvalidPathChars) != -1)
126                                 throw new ArgumentException ("Illegal characters in path", "path");
127
128                         int iExt = findExtension (path);
129
130                         if (iExt > -1)
131                         {       // okay it has an extension
132                                 return path.Substring (iExt);
133                         }
134                         return string.Empty;
135                 }
136
137                 public static string GetFileName (string path)
138                 {
139                         if (path == null || path == String.Empty)
140                                 return path;
141
142                         if (path.IndexOfAny (InvalidPathChars) != -1)
143                                 throw new ArgumentException ("Illegal characters in path", "path");
144
145                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
146                         if (nLast >= 0)
147                                 return path.Substring (nLast + 1);
148
149                         return path;
150                 }
151
152                 public static string GetFileNameWithoutExtension (string path)
153                 {
154                         return ChangeExtension (GetFileName (path), null);
155                 }
156
157                 public static string GetFullPath (string path)
158                 {
159                         if (path == null)
160                                 throw (new ArgumentNullException (
161                                         "path",
162                                         "You must specify a path when calling System.IO.Path.GetFullPath"));
163                         
164                         if (path.Trim () == String.Empty)
165                                 throw new ArgumentException ("The path is not of a legal form", "path");
166
167                         if (!IsPathRooted (path))
168                                 path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
169                         
170                         return CanonicalizePath (path);
171                 }
172
173                 static bool IsDsc (char c) {
174                         return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
175                 }
176                 
177                 public static string GetPathRoot (string path)
178                 {
179                         if (path == null) return null;
180                         if (!IsPathRooted (path)) return String.Empty;
181                         
182                         if (DirectorySeparatorChar == '/') {
183                                 // UNIX
184                                 return IsDsc (path [0]) ? DirectorySeparatorChar.ToString () : String.Empty;
185                         } else {
186                                 // Windows
187                                 int len = 2;
188                                 
189                                 if (path.Length <= 2) return String.Empty;
190                                         
191                                 if (IsDsc (path [0]) && IsDsc (path[1])) {
192                                         // UNC: \\server or \\server\share
193                                         // Get server
194                                         while (len < path.Length && !IsDsc (path [len])) len++;
195                                         // Get share
196                                         while (len < path.Length && !IsDsc (path [len])) len++;                         
197                                 } else if (path[1] == VolumeSeparatorChar) {
198                                         // C:\folder
199                                         if (path.Length >= 3 && (IsDsc (path [2]))) len++;
200                                 }
201                                 
202                                 return path.Substring (0, len);
203                         }
204                 }
205
206                 public static string GetTempFileName ()
207                 {
208                         FileStream f = null;
209                         string path;
210                         Random rnd;
211                         int num = 0;
212
213                         rnd = new Random ();
214                         do {
215                                 num = rnd.Next ();
216                                 num++;
217                                 path = GetTempPath() + DirectorySeparatorChar + "tmp" + num.ToString("x");
218
219                                 try {
220                                         f = new FileStream (path, FileMode.CreateNew);
221                                 } catch {
222                                 }
223                         } while (f == null);
224                         
225                         f.Close();
226                         return path;
227                 }
228
229                 /// <summary>
230                 /// Returns the path of the current systems temp directory
231                 /// </summary>
232                 public static string GetTempPath ()
233                 {
234                         return get_temp_path ();
235                 }
236
237                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
238                 private static extern string get_temp_path ();
239
240                 public static bool HasExtension (string path)
241                 {  
242                         CheckArgument.Null (path);
243                         CheckArgument.Empty (path);
244                         CheckArgument.WhitespaceOnly (path);
245                         
246                         return findExtension (path) > -1;
247                 }
248
249                 public static bool IsPathRooted (string path)
250                 {
251                         if (path == null || path.Length == 0)
252                                 return false;
253
254                         if (path.IndexOfAny (InvalidPathChars) != -1)
255                                 throw new ArgumentException ("Illegal characters in path", "path");
256
257                         char c = path [0];
258                         return (c == DirectorySeparatorChar     ||
259                                 c == AltDirectorySeparatorChar  ||
260                                 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
261                 }
262
263                 // private class methods
264
265                 private static int findExtension (string path)
266                 {
267                         // method should return the index of the path extension
268                         // start or -1 if no valid extension
269                         if (path != null){
270                                 int iLastDot = path.LastIndexOf (".");
271                                 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
272
273                                 if (iLastDot > iLastSep)
274                                         return iLastDot;
275                         }
276                         return -1;
277                 }
278
279                 static Path () {
280                         VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
281                         DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
282                         AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
283
284                         PathSeparator = MonoIO.PathSeparator;
285                         InvalidPathChars = MonoIO.InvalidPathChars;
286
287                         // internal fields
288
289                         DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
290                         PathSeparatorChars = new char [] {
291                                 DirectorySeparatorChar,
292                                 AltDirectorySeparatorChar,
293                                 VolumeSeparatorChar
294                         };
295
296                         dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
297                 }
298                 
299                 
300                 static string CanonicalizePath (string path) {
301                         
302                         // STEP 1: Check for empty string
303                         if (path == null) return path;
304                         path = path.Trim ();
305                         if (path == String.Empty) return path;
306                         
307                         // STEP 2: Check to see if this is only a root
308                         string root = GetPathRoot (path);
309                         if (root == path) return path;
310                                 
311                         // STEP 3: split the directories, this gets rid of consecutative "/"'s
312                         string [] dirs = path.Split (DirectorySeparatorChar, AltDirectorySeparatorChar);
313                         // STEP 4: Get rid of directories containing . and ..
314                         int target = 0;
315                         
316                         for (int i = 0; i < dirs.Length; i++) {
317                                 if (dirs [i] == "." || (i != 0 && dirs [i] == String.Empty)) continue;
318                                 else if (dirs [i] == "..") {
319                                         if (target != 0) target--;
320                                 }
321                                 else
322                                         dirs [target++] = dirs [i];
323                         }
324                         
325                         // STEP 5: Combine everything.
326                         if (target == 0)
327                                 return root;
328                         else
329                                 return String.Join (DirectorySeparatorStr, dirs, 0, target);
330                 }
331         }
332 }