Merge pull request #2003 from esdrubal/seq_test_fix2
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / ExpressionCollection.cs
1 //
2 // ExpressionCollection.cs
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Ankit Jain (jankit@novell.com)
7 // 
8 // (C) 2006 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
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 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Text;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36
37 namespace Microsoft.Build.BuildEngine {
38
39         internal class ExpressionCollection {
40         
41                 IList objects;
42                 static Dictionary<string, bool> boolValues;
43
44                 static ExpressionCollection ()
45                 {
46                         string[] trueValuesArray = new string[] {"true", "on", "yes"};
47                         string[] falseValuesArray = new string[] {"false", "off", "no"};
48
49                         boolValues = new Dictionary<string, bool> (StringComparer.OrdinalIgnoreCase);
50                         foreach (string s in trueValuesArray)
51                                 boolValues.Add (s, true);
52                         foreach (string s in falseValuesArray)
53                                 boolValues.Add (s, false);
54                 }
55         
56                 public ExpressionCollection ()
57                 {
58                         objects = new ArrayList ();
59                 }
60
61                 public int Count {
62                         get { return objects.Count; }
63                 }
64                 
65                 public void Add (IReference reference)
66                 {
67                         objects.Add (reference);
68                 }
69                 
70                 public void Add (string s)
71                 {
72                         objects.Add (s);
73                 }
74                 
75                 public object ConvertTo (Project project, Type type, ExpressionOptions options)
76                 {
77                         if (type.IsArray) {
78                                 if (type == typeof (ITaskItem[]))
79                                         return ConvertToITaskItemArray (project, options);
80                                 else
81                                         return ConvertToArray (project, type, options);
82                         } else {
83                                 if (type == typeof (ITaskItem))
84                                         return ConvertToITaskItem (project, options);
85                                 else
86                                         return ConvertToNonArray (project, type, options);
87                         }
88                 }
89                 
90                 public IEnumerator GetEnumerator ()
91                 {
92                         foreach (object o in objects)
93                                 yield return o;
94                 }
95                 
96                 object ConvertToNonArray (Project project, Type type, ExpressionOptions options)
97                 {
98                         return ConvertToObject (ConvertToString (project, options), type, options);
99                 }
100
101                 object ConvertToArray (Project project, Type type, ExpressionOptions options)
102                 {
103                         ITaskItem[] items = ConvertToITaskItemArray (project, options);
104
105                         Type element_type = type.GetElementType ();
106                         Array arr = Array.CreateInstance (element_type, items.Length);
107                         for (int i = 0; i < arr.Length; i ++)
108                                 arr.SetValue (ConvertToObject (items [i].ItemSpec, element_type, options), i);
109                         return arr;
110                 }
111
112                 object ConvertToObject (string raw, Type type, ExpressionOptions options)
113                 {
114                         if (type == typeof (bool)) {
115                                 bool value;
116                                 if (boolValues.TryGetValue (raw, out value))
117                                         return value;
118                                 else
119                                         return false;
120                         }
121
122                         if (type == typeof (string))
123                                 return raw;
124
125                         if (type.IsPrimitive)
126                                 return Convert.ChangeType (raw, type);
127
128                         if (type == typeof (DateTime))
129                                 return DateTime.Parse (raw);
130
131                         throw new Exception (String.Format ("Unsupported type: {0}", type));
132                 }
133
134                 string ConvertToString (Project project, ExpressionOptions options)
135                 {
136                         StringBuilder sb = new StringBuilder ();
137                         
138                         foreach (object o in objects) {
139                                 string s = o as string;
140                                 if (s != null) {
141                                         sb.Append (s);
142                                         continue;
143                                 }
144
145                                 IReference br = o as IReference;
146                                 if (br != null)
147                                         sb.Append (br.ConvertToString (project, options));
148                                 else
149                                         throw new Exception ("BUG: Invalid type in objects collection.");
150                         }
151                         return sb.ToString ();
152                 }
153
154                 ITaskItem ConvertToITaskItem (Project project, ExpressionOptions options)
155                 {
156                         if (objects == null)
157                                 throw new Exception ("Cannot cast empty expression to ITaskItem.");
158
159                         ITaskItem[] items = ConvertToITaskItemArray (project, options);
160                         if (items.Length > 1)
161                                 //FIXME: msbuild gives better errors
162                                 throw new Exception (String.Format ("Exactly one item required, but got: {0}", items.Length));
163
164                         if (items.Length == 0) return null;
165                         return items [0];
166                 }
167                 
168                 // Concat rules (deduced)
169                 // - ItemRef can concat only with a string ';' or PropertyRef ending in ';'
170                 // - MetadataRef can concat with anything other than ItemRef
171                 // - PropertyRef cannot be right after a ItemRef
172                 //   PropertyRef concats if it doesn't end in ';'
173                 // - string cannot concat with ItemRef unless it is ';'.
174                 //   string concats if it ends in ';'
175                 ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options)
176                 {
177                         List <ITaskItem> finalItems = new List <ITaskItem> ();
178                         
179                         object prev = null;
180                         bool prev_can_concat = false;
181
182                         foreach (object o in objects) {
183                                 bool can_concat = prev_can_concat;
184
185                                 string str = o as string;
186                                 if (str != null) {
187                                         string trimmed_str = str.Trim ();
188                                         if (!IsSemicolon (str) && trimmed_str.Length > 0 && prev != null && prev is ItemReference)
189                                                 // non-empty, non-semicolon string after item ref
190                                                 ThrowCantConcatError (prev, str);
191
192                                         if (trimmed_str.Length == 0 && prev is string && IsSemicolon ((string) prev)) {
193                                                 // empty string after a ';', ignore it
194                                                 continue;
195                                         }
196
197                                         // empty string _after_ a itemref, not an error
198                                         prev_can_concat = !(str.Length > 0 && str [str.Length - 1] == ';') && trimmed_str.Length > 0;
199                                         AddItemsToArray (finalItems,
200                                                         ConvertToITaskItemArrayFromString (str),
201                                                         can_concat);
202                                         prev = o;
203                                         continue;
204                                 }
205
206                                 IReference br = o as IReference;
207                                 if (br == null)
208                                         throw new Exception ("BUG: Invalid type in objects collection.");
209
210                                 if (o is ItemReference) {
211                                         if (prev != null && !(prev is string && (string)prev == ";"))
212                                                 ThrowCantConcatError (prev, br);
213
214                                         prev_can_concat = true;
215                                 } else if (o is MetadataReference) {
216                                         if (prev != null && prev is ItemReference)
217                                                 ThrowCantConcatError (prev, br);
218
219                                         prev_can_concat = true;
220                                 } else if (o is PropertyReference) {
221                                         if (prev != null && prev is ItemReference)
222                                                 ThrowCantConcatError (prev, br);
223
224                                         string value = ((PropertyReference) o).GetValue (project);
225                                         prev_can_concat = !(value.Length > 0 && value [value.Length - 1] == ';');
226                                 }
227
228                                 AddItemsToArray (finalItems, br.ConvertToITaskItemArray (project, options), can_concat);
229
230                                 prev = o;
231                         }
232
233                         // Trim and Remove empty items
234                         List<ITaskItem> toRemove = new List<ITaskItem> ();
235                         for (int i = 0; i < finalItems.Count; i ++) {
236                                 string s = finalItems [i].ItemSpec.Trim ();
237                                 if (s.Length == 0)
238                                         toRemove.Add (finalItems [i]);
239                                 else
240                                         finalItems [i].ItemSpec = s;
241                         }
242                         foreach (ITaskItem ti in toRemove)
243                                 finalItems.Remove (ti);
244                         
245                         return finalItems.ToArray ();
246                 }
247
248                 // concat's first item in @items to last item in @list if @concat is true
249                 // else just adds all @items to @list
250                 void AddItemsToArray (List<ITaskItem> list, ITaskItem[] items, bool concat)
251                 {
252                         if (items == null || items.Length == 0)
253                                 return;
254
255                         int start_index = 1;
256                         if (concat && list.Count > 0)
257                                 list [list.Count - 1].ItemSpec += items [0].ItemSpec;
258                         else
259                                 start_index = 0;
260
261                         for (int i = start_index; i < items.Length; i ++)
262                                 list.Add (items [i]);
263                 }
264                 
265                 ITaskItem [] ConvertToITaskItemArrayFromString (string source)
266                 {
267                         List <ITaskItem> items = new List <ITaskItem> ();
268                         string [] splitSource = source.Split (new char [] {';'},
269                                         StringSplitOptions.RemoveEmptyEntries);
270
271                         foreach (string s in splitSource)
272                                 items.Add (new TaskItem (s));
273
274                         return items.ToArray ();
275                 }
276
277                 bool IsSemicolon (string str)
278                 {
279                         return str != null && str.Length == 1 && str [0] == ';';
280                 }
281
282                 void ThrowCantConcatError (object first, object second)
283                 {
284                         throw new Exception (String.Format (
285                                         "Can't concatenate Item list with other strings where an item list is " +
286                                         "expected ('{0}', '{1}'). Use semi colon to separate items.",
287                                         first.ToString (), second.ToString ()));
288                 }
289
290         }
291 }