Merge pull request #1542 from ninjarobot/UriTemplateMatchException
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / DirectoryScanner.cs
1 //
2 // DirectoryScanner.cs: Class used by BuildItem.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31
32 using Microsoft.Build.Framework;
33 using Microsoft.Build.Utilities;
34
35 namespace Microsoft.Build.BuildEngine {
36         internal class DirectoryScanner {
37                 
38                 DirectoryInfo   baseDirectory;
39                 ITaskItem[]     includes, excludes;
40                 ITaskItem[]     matchedItems;
41                 string projectFile;
42
43                 static bool _runningOnWindows;
44                 
45                 static DirectoryScanner ()
46                 {
47                         PlatformID pid = Environment.OSVersion.Platform;
48                         _runningOnWindows =((int) pid != 128 && (int) pid != 4 && (int) pid != 6);
49                 }
50
51                 public DirectoryScanner ()
52                 {
53                 }
54                 
55                 public void Scan ()
56                 {
57                         Dictionary <string, bool> excludedItems;
58                         List <ITaskItem> includedItems;
59                         
60                         if (includes == null)
61                                 throw new ArgumentNullException ("Includes");
62                         if (baseDirectory == null)
63                                 throw new ArgumentNullException ("BaseDirectory");
64                         
65                         excludedItems = new Dictionary <string, bool> ();
66                         includedItems = new List <ITaskItem> ();
67                         
68                         if (excludes != null)
69                                 foreach (ITaskItem excl in excludes)
70                                         ProcessExclude (excl.ItemSpec, excludedItems);
71
72                         foreach (ITaskItem include_item in includes)
73                                 ProcessInclude (include_item, excludedItems, includedItems);
74
75                         matchedItems = includedItems.ToArray ();
76                 }
77                 
78                 private void ProcessInclude (ITaskItem include_item, Dictionary <string, bool> excludedItems,
79                                 List <ITaskItem> includedItems)
80                 {
81                         string[] separatedPath;
82                         FileInfo[] fileInfo;
83
84                         string name = include_item.ItemSpec;
85                         if (!HasWildcard (name)) {
86                                 if (!excludedItems.ContainsKey (Path.GetFullPath (name))) {
87                                         includedItems.Add (include_item);
88                                         if (projectFile != null)
89                                                 include_item.SetMetadata ("DefiningProjectFullPath", projectFile);
90                                 }
91                         } else {
92                                 if (name.Split (Path.DirectorySeparatorChar).Length > name.Split (Path.AltDirectorySeparatorChar).Length) {
93                                         separatedPath = name.Split (new char [] {Path.DirectorySeparatorChar},
94                                                         StringSplitOptions.RemoveEmptyEntries);
95                                 } else {
96                                         separatedPath = name.Split (new char [] {Path.AltDirectorySeparatorChar},
97                                                         StringSplitOptions.RemoveEmptyEntries);
98                                 }
99                                 if (separatedPath.Length == 1 && separatedPath [0] == String.Empty)
100                                         return;
101
102                                 int offset = 0;
103                                 string full_path;
104                                 if (Path.IsPathRooted (name)) {
105                                         // The path may start with a root indicator, but at the same time can
106                                         // contain relative paths inbetween
107                                         full_path = Path.GetFullPath (name);
108                                         baseDirectory = new DirectoryInfo (Path.GetPathRoot (name));
109                                         if (IsRunningOnWindows)
110                                                 // skip the "drive:"
111                                                 offset = 1;
112                                 } else {
113                                         full_path = Path.GetFullPath (Path.Combine (Environment.CurrentDirectory, name));
114                                 }
115
116                                 fileInfo = ParseIncludeExclude (separatedPath, offset, baseDirectory);
117
118                                 int wildcard_offset = full_path.IndexOf ("**");
119                                 foreach (FileInfo fi in fileInfo) {
120                                         string itemName = fi.FullName;
121                                         if (!Path.IsPathRooted (name) && itemName.Length > baseDirectory.FullName.Length && itemName.StartsWith (baseDirectory.FullName))
122                                                 itemName = itemName.Substring (baseDirectory.FullName.Length + 1);
123
124                                         if (!excludedItems.ContainsKey (itemName) &&  !excludedItems.ContainsKey (Path.GetFullPath (itemName))) {
125                                                 TaskItem item = new TaskItem (include_item);
126                                                 item.ItemSpec = itemName;
127
128                                                 if (wildcard_offset >= 0) {
129                                                         string rec_dir = Path.GetDirectoryName (fi.FullName.Substring (wildcard_offset));
130                                                         if (rec_dir.Length > 0)
131                                                                 rec_dir += Path.DirectorySeparatorChar;
132                                                         item.SetMetadata ("RecursiveDir", rec_dir);
133                                                 }
134                                                 if (projectFile != null)
135                                                         item.SetMetadata ("DefiningProjectFullPath", projectFile);
136                                                 includedItems.Add (item);
137                                         }
138                                 }
139                         }
140                 }
141                 
142                 private void ProcessExclude (string name, Dictionary <string, bool> excludedItems)
143                 {
144                         string[] separatedPath;
145                         FileInfo[] fileInfo;
146                         
147                         if (name.IndexOf ('?') == -1 && name.IndexOf ('*') == -1) {
148                                 if (!excludedItems.ContainsKey (Path.GetFullPath (name)))
149                                         excludedItems.Add (Path.GetFullPath (name), true);
150                         } else {
151                                 if (name.Split (Path.DirectorySeparatorChar).Length > name.Split (Path.AltDirectorySeparatorChar).Length) {
152                                         separatedPath = name.Split (new char [] {Path.DirectorySeparatorChar},
153                                                                         StringSplitOptions.RemoveEmptyEntries);
154                                 } else {
155                                         separatedPath = name.Split (new char [] {Path.AltDirectorySeparatorChar},
156                                                                         StringSplitOptions.RemoveEmptyEntries);
157                                 }
158                                 if (separatedPath.Length == 1 && separatedPath [0] == String.Empty)
159                                         return;
160
161                                 int offset = 0;
162                                 if (Path.IsPathRooted (name)) {
163                                         baseDirectory = new DirectoryInfo (Path.GetPathRoot (name));
164                                         if (IsRunningOnWindows)
165                                                 // skip the "drive:"
166                                                 offset = 1;
167                                 }
168
169                                 fileInfo = ParseIncludeExclude (separatedPath, offset, baseDirectory);
170                                 foreach (FileInfo fi in fileInfo)
171                                         if (!excludedItems.ContainsKey (fi.FullName))
172                                                 excludedItems.Add (fi.FullName, true);
173                         }
174                 }
175                 
176                 private FileInfo[] ParseIncludeExclude (string[] input, int ptr, DirectoryInfo directory)
177                 {
178                         return ParseIncludeExclude (input, ptr, directory, false);
179                 }
180
181                 private FileInfo[] ParseIncludeExclude (string[] input, int ptr, DirectoryInfo directory, bool recursive)
182                 {
183                         DirectoryInfo[] di;
184                         List <FileInfo> fileInfos = new List<FileInfo> ();
185
186                         if (input.Length > 1 && ptr == 0 && input [0] == String.Empty)
187                                 ptr++;
188
189                         string cur = input.Length > ptr ? input[ptr] : input[input.Length-1];
190                         bool dot = cur == ".";
191                         recursive = recursive || cur == "**";
192                         bool parent = cur == "..";
193
194                         if (input.Length <= ptr + 1) {
195                                 if (parent)
196                                         directory = directory.Parent;
197                                 if ((input.Length == ptr + 1 && !recursive) || input.Length <= ptr)
198                                         return directory.GetFiles (cur);
199                         }
200
201                         if (dot) {
202                                 di = new DirectoryInfo [1];
203                                 di [0] = directory;
204                         } else if (parent) {
205                                 di = new DirectoryInfo [1];
206                                 di [0] = directory.Parent;
207                         } else if (recursive)
208                         {
209                                 // Read this directory and all subdirectories recursive
210                                 Stack<DirectoryInfo> currentDirectories = new Stack<DirectoryInfo>();
211                                 currentDirectories.Push(directory);
212                                 List<DirectoryInfo> allDirectories = new List<DirectoryInfo>();
213
214                                 while (currentDirectories.Count > 0)
215                                 {
216                                         DirectoryInfo current = currentDirectories.Pop();
217                                         allDirectories.Insert (0, current);
218                                         foreach (DirectoryInfo dir in current.GetDirectories())
219                                         {
220                                                 currentDirectories.Push(dir);
221                                         }
222                                 }
223
224                                 // No further directories shall be read
225                                 di = allDirectories.ToArray();
226                         } else
227                                 di = directory.GetDirectories (cur);
228
229                         foreach (DirectoryInfo info in di)
230                                 fileInfos.AddRange (ParseIncludeExclude (input, ptr + 1, info, recursive));
231
232                         return fileInfos.ToArray ();
233                 }
234
235                 public static bool HasWildcard (string expression)
236                 {
237                         return expression.IndexOf ('?') >= 0 || expression.IndexOf ('*') >= 0;
238                 }
239                 
240                 public DirectoryInfo BaseDirectory {
241                         get { return baseDirectory; }
242                         set { baseDirectory = value; }
243                 }
244                 
245                 public string ProjectFile {
246                         get { return projectFile; }
247                         set { projectFile = value; }
248                 }
249
250                 public ITaskItem[] Includes {
251                         get { return includes; }
252                         set { includes = value; }
253                 }
254                 
255                 public ITaskItem[] Excludes {
256                         get { return excludes; }
257                         set { excludes = value; }
258                 }
259                 
260                 public ITaskItem[] MatchedItems {
261                         get { return matchedItems; }
262                 }
263                 
264                 static bool IsRunningOnWindows {
265                         get { return _runningOnWindows; }
266                 }
267         }
268 }