Merge pull request #396 from directhex/master
[mono.git] / mcs / class / System / System.IO / SearchPattern.cs
1 //
2 // System.IO.SearchPattern2.cs: Filename glob support.
3 //
4 // Author:
5 //   Dan Lewis (dihlewis@yahoo.co.uk)
6 //
7 // (C) 2002
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 // Copied from corlib/System.IO/SearchPatter.cs
32 using System;
33
34 namespace System.IO {
35
36         // FIXME: there's a complication with this algorithm under windows.
37         // the pattern '*.*' matches all files (i think . matches the extension),
38         // whereas under UNIX it should only match files containing the '.' character.
39
40         class SearchPattern2 {
41                 public SearchPattern2 (string pattern) : this (pattern, false) { }
42
43                 public SearchPattern2 (string pattern, bool ignore)
44                 {
45                         this.ignore = ignore;
46                         this.pattern = pattern;
47                         Compile (pattern);
48                 }
49
50                 // OSX has a retarded case-insensitive yet case-aware filesystem
51                 // so we need a overload in here for the Kqueue watcher
52                 public bool IsMatch (string text, bool ignorecase)
53                 {
54                         if (!hasWildcard) {
55                                 bool match = String.Compare (pattern, text, ignorecase) == 0;
56                                 if (match)
57                                         return true;
58                                 
59                                 // This is a special case for FSW. It needs to match e.g. subdir/file.txt
60                                 // when the pattern is "file.txt"
61                                 int idx = text.LastIndexOf ('/');
62                                 if (idx == -1)
63                                         return false;
64                                 idx++;
65                                 if (idx == text.Length)
66                                         return false;
67                                 
68                                 return (String.Compare (pattern, text.Substring (idx), ignorecase) == 0);
69                         }
70                         
71                         return Match (ops, text, 0);
72                 }
73
74                 public bool IsMatch (string text)
75                 {
76                         return IsMatch (text, ignore);
77                 }
78
79                 public bool HasWildcard {
80                         get { return hasWildcard; }
81                 }
82                 // private
83
84                 Op ops;         // the compiled pattern
85                 bool ignore;    // ignore case
86                 bool hasWildcard;
87                 string pattern;
88
89                 private void Compile (string pattern)
90                 {
91                         if (pattern == null || pattern.IndexOfAny (InvalidChars) >= 0)
92                                 throw new ArgumentException ("Invalid search pattern: '" + pattern + "'");
93
94                         if (pattern == "*") {   // common case
95                                 ops = new Op (OpCode.True);
96                                 hasWildcard = true;
97                                 return;
98                         }
99
100                         ops = null;
101
102                         int ptr = 0;
103                         Op last_op = null;
104                         while (ptr < pattern.Length) {
105                                 Op op;
106                         
107                                 switch (pattern [ptr]) {
108                                 case '?':
109                                         op = new Op (OpCode.AnyChar);
110                                         ++ ptr;
111                                         hasWildcard = true;
112                                         break;
113
114                                 case '*':
115                                         op = new Op (OpCode.AnyString);
116                                         ++ ptr;
117                                         hasWildcard = true;
118                                         break;
119                                         
120                                 default:
121                                         op = new Op (OpCode.ExactString);
122                                         int end = pattern.IndexOfAny (WildcardChars, ptr);
123                                         if (end < 0)
124                                                 end = pattern.Length;
125
126                                         op.Argument = pattern.Substring (ptr, end - ptr);
127                                         if (ignore)
128                                                 op.Argument = op.Argument.ToLower ();
129
130                                         ptr = end;
131                                         break;
132                                 }
133
134                                 if (last_op == null)
135                                         ops = op;
136                                 else
137                                         last_op.Next = op;
138
139                                 last_op = op;
140                         }
141
142                         if (last_op == null)
143                                 ops = new Op (OpCode.End);
144                         else
145                                 last_op.Next = new Op (OpCode.End);
146                 }
147
148                 private bool Match (Op op, string text, int ptr)
149                 {
150                         while (op != null) {
151                                 switch (op.Code) {
152                                 case OpCode.True:
153                                         return true;
154
155                                 case OpCode.End:
156                                         if (ptr == text.Length)
157                                                 return true;
158
159                                         return false;
160                                 
161                                 case OpCode.ExactString:
162                                         int length = op.Argument.Length;
163                                         if (ptr + length > text.Length)
164                                                 return false;
165
166                                         string str = text.Substring (ptr, length);
167                                         if (ignore)
168                                                 str = str.ToLower ();
169
170                                         if (str != op.Argument)
171                                                 return false;
172
173                                         ptr += length;
174                                         break;
175
176                                 case OpCode.AnyChar:
177                                         if (++ ptr > text.Length)
178                                                 return false;
179                                         break;
180
181                                 case OpCode.AnyString:
182                                         while (ptr <= text.Length) {
183                                                 if (Match (op.Next, text, ptr))
184                                                         return true;
185
186                                                 ++ ptr;
187                                         }
188
189                                         return false;
190                                 }
191
192                                 op = op.Next;
193                         }
194
195                         return true;
196                 }
197
198                 // private static
199
200                 internal static readonly char [] WildcardChars = { '*', '?' };
201                 internal static readonly char [] InvalidChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
202
203                 private class Op {
204                         public Op (OpCode code)
205                         {
206                                 this.Code = code;
207                                 this.Argument = null;
208                                 this.Next = null;
209                         }
210                 
211                         public OpCode Code;
212                         public string Argument;
213                         public Op Next;
214                 }
215
216                 private enum OpCode {
217                         ExactString,            // literal
218                         AnyChar,                // ?
219                         AnyString,              // *
220                         End,                    // end of pattern
221                         True                    // always succeeds
222                 };
223         }
224 }