2 // ExpressionCollection.cs
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2006 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections;
34 using System.Collections.Generic;
36 using Microsoft.Build.Framework;
37 using Microsoft.Build.Utilities;
39 namespace Microsoft.Build.BuildEngine {
41 internal class ExpressionCollection {
44 static Dictionary<string, bool> boolValues;
46 static ExpressionCollection ()
48 string[] trueValuesArray = new string[] {"true", "on", "yes"};
49 string[] falseValuesArray = new string[] {"false", "off", "no"};
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);
58 public ExpressionCollection ()
60 objects = new ArrayList ();
64 get { return objects.Count; }
67 public void Add (IReference reference)
69 objects.Add (reference);
72 public void Add (string s)
77 public object ConvertTo (Project project, Type type, ExpressionOptions options)
80 if (type == typeof (ITaskItem[]))
81 return ConvertToITaskItemArray (project, options);
83 return ConvertToArray (project, type, options);
85 if (type == typeof (ITaskItem))
86 return ConvertToITaskItem (project, options);
88 return ConvertToNonArray (project, type, options);
92 public IEnumerator GetEnumerator ()
94 foreach (object o in objects)
98 object ConvertToNonArray (Project project, Type type, ExpressionOptions options)
100 return ConvertToObject (ConvertToString (project, options), type, options);
103 object ConvertToArray (Project project, Type type, ExpressionOptions options)
105 ITaskItem[] items = ConvertToITaskItemArray (project, options);
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);
114 object ConvertToObject (string raw, Type type, ExpressionOptions options)
116 if (type == typeof (bool)) {
118 if (boolValues.TryGetValue (raw, out value))
124 if (type == typeof (string))
127 if (type.IsPrimitive)
128 return Convert.ChangeType (raw, type);
130 if (type == typeof (DateTime))
131 return DateTime.Parse (raw);
133 throw new Exception (String.Format ("Unsupported type: {0}", type));
136 string ConvertToString (Project project, ExpressionOptions options)
138 StringBuilder sb = new StringBuilder ();
140 foreach (object o in objects) {
141 string s = o as string;
147 IReference br = o as IReference;
149 sb.Append (br.ConvertToString (project, options));
151 throw new Exception ("BUG: Invalid type in objects collection.");
153 return sb.ToString ();
156 ITaskItem ConvertToITaskItem (Project project, ExpressionOptions options)
159 throw new Exception ("Cannot cast empty expression to ITaskItem.");
161 ITaskItem[] items = ConvertToITaskItemArray (project, options);
162 if (items.Length > 1)
163 //FIXME: msbuild gives better errors
164 throw new Exception (String.Format ("Exactly one item required, but got: {0}", items.Length));
166 if (items.Length == 0) return null;
170 // Concat rules (deduced)
171 // - ItemRef can concat only with a string ';' or PropertyRef ending in ';'
172 // - MetadataRef can concat with anything other than ItemRef
173 // - PropertyRef cannot be right after a ItemRef
174 // PropertyRef concats if it doesn't end in ';'
175 // - string cannot concat with ItemRef unless it is ';'.
176 // string concats if it ends in ';'
177 ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options)
179 List <ITaskItem> finalItems = new List <ITaskItem> ();
182 bool prev_can_concat = false;
184 foreach (object o in objects) {
185 bool can_concat = prev_can_concat;
187 string str = o as string;
189 string trimmed_str = str.Trim ();
190 if (!IsSemicolon (str) && trimmed_str.Length > 0 && prev != null && prev is ItemReference)
191 // non-empty, non-semicolon string after item ref
192 ThrowCantConcatError (prev, str);
194 if (trimmed_str.Length == 0 && prev is string && IsSemicolon ((string) prev)) {
195 // empty string after a ';', ignore it
199 // empty string _after_ a itemref, not an error
200 prev_can_concat = !(str.Length > 0 && str [str.Length - 1] == ';') && trimmed_str.Length > 0;
201 AddItemsToArray (finalItems,
202 ConvertToITaskItemArrayFromString (str),
208 IReference br = o as IReference;
210 throw new Exception ("BUG: Invalid type in objects collection.");
212 if (o is ItemReference) {
213 if (prev != null && !(prev is string && (string)prev == ";"))
214 ThrowCantConcatError (prev, br);
216 prev_can_concat = true;
217 } else if (o is MetadataReference) {
218 if (prev != null && prev is ItemReference)
219 ThrowCantConcatError (prev, br);
221 prev_can_concat = true;
222 } else if (o is PropertyReference) {
223 if (prev != null && prev is ItemReference)
224 ThrowCantConcatError (prev, br);
226 string value = ((PropertyReference) o).GetValue (project);
227 prev_can_concat = !(value.Length > 0 && value [value.Length - 1] == ';');
230 AddItemsToArray (finalItems, br.ConvertToITaskItemArray (project, options), can_concat);
235 // Trim and Remove empty items
236 List<ITaskItem> toRemove = new List<ITaskItem> ();
237 for (int i = 0; i < finalItems.Count; i ++) {
238 string s = finalItems [i].ItemSpec.Trim ();
240 toRemove.Add (finalItems [i]);
242 finalItems [i].ItemSpec = s;
244 foreach (ITaskItem ti in toRemove)
245 finalItems.Remove (ti);
247 return finalItems.ToArray ();
250 // concat's first item in @items to last item in @list if @concat is true
251 // else just adds all @items to @list
252 void AddItemsToArray (List<ITaskItem> list, ITaskItem[] items, bool concat)
254 if (items == null || items.Length == 0)
258 if (concat && list.Count > 0)
259 list [list.Count - 1].ItemSpec += items [0].ItemSpec;
263 for (int i = start_index; i < items.Length; i ++)
264 list.Add (items [i]);
267 ITaskItem [] ConvertToITaskItemArrayFromString (string source)
269 List <ITaskItem> items = new List <ITaskItem> ();
270 string [] splitSource = source.Split (new char [] {';'},
271 StringSplitOptions.RemoveEmptyEntries);
273 foreach (string s in splitSource)
274 items.Add (new TaskItem (s));
276 return items.ToArray ();
279 bool IsSemicolon (string str)
281 return str != null && str.Length == 1 && str [0] == ';';
284 void ThrowCantConcatError (object first, object second)
286 throw new Exception (String.Format (
287 "Can't concatenate Item list with other strings where an item list is " +
288 "expected ('{0}', '{1}'). Use semi colon to separate items.",
289 first.ToString (), second.ToString ()));