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