[xbuild] Replace string.ToLower() comparison with OrdinalIgnoreCase comparison
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
1 //
2 // BuildItem.cs:
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;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.IO;
33 using System.Text;
34 using System.Xml;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37 using Mono.XBuild.Utilities;
38
39 namespace Microsoft.Build.BuildEngine {
40         public class BuildItem {
41
42                 List<BuildItem> child_items;
43                 XmlElement      itemElement;
44                 string          finalItemSpec;
45                 bool            isImported;
46                 string          itemInclude;
47                 string          name;
48                 BuildItemGroup  parent_item_group;
49                 BuildItem       parent_item;
50                 //string                recursiveDir;
51                 IDictionary     evaluatedMetadata;
52                 IDictionary     unevaluatedMetadata;
53
54                 BuildItem ()
55                 {
56                 }
57                 
58                 public BuildItem (string itemName, ITaskItem taskItem)
59                 {
60                         if (taskItem == null)
61                                 throw new ArgumentNullException ("taskItem");
62
63                         this.name = itemName;
64                         this.finalItemSpec = taskItem.ItemSpec;
65                         this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
66                         this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
67                         this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
68                 }
69
70                 public BuildItem (string itemName, string itemInclude)
71                 {
72                         if (itemInclude == null)
73                                 throw new ArgumentNullException ("itemInclude");
74                         if (itemInclude == String.Empty)
75                                 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
76
77                         name = itemName;
78                         finalItemSpec = itemInclude;
79                         this.itemInclude = itemInclude;
80                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
81                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
82                 }
83
84                 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
85                 {
86                         child_items = new List<BuildItem> ();
87                         isImported = parentItemGroup.IsImported;
88                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
89                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
90                         this.parent_item_group = parentItemGroup;
91                         
92                         this.itemElement = itemElement;
93                         
94                         if (Include == String.Empty)
95                                 throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
96                 }
97                 
98                 BuildItem (BuildItem parent)
99                 {
100                         isImported = parent.isImported;
101                         name = parent.Name;
102                         parent_item = parent;
103                         parent_item.child_items.Add (this);
104                         parent_item_group = parent.parent_item_group;
105                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
106                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
107                 }
108                 
109                 public void CopyCustomMetadataTo (BuildItem destinationItem)
110                 {
111                         if (destinationItem == null)
112                                 throw new ArgumentNullException ("destinationItem");
113
114                         foreach (DictionaryEntry de in unevaluatedMetadata)
115                                 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
116                 }
117                 
118                 [MonoTODO]
119                 public BuildItem Clone ()
120                 {
121                         return (BuildItem) this.MemberwiseClone ();
122                 }
123
124                 public string GetEvaluatedMetadata (string metadataName)
125                 {
126                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
127                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
128                                 return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
129                                                 ? MSBuildUtils.Escape (metadata)
130                                                 : metadata;
131                         }
132
133                         if (evaluatedMetadata.Contains (metadataName))
134                                 return (string) evaluatedMetadata [metadataName];
135                         else
136                                 return String.Empty;
137                 }
138
139                 public string GetMetadata (string metadataName)
140                 {
141                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
142                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
143                                 return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
144                                         ? MSBuildUtils.Escape (metadata)
145                                         : metadata;
146                         } else if (unevaluatedMetadata.Contains (metadataName))
147                                 return (string) unevaluatedMetadata [metadataName];
148                         else
149                                 return String.Empty;
150                 }
151                 
152                 public bool HasMetadata (string metadataName)
153                 {
154                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
155                                 return true;
156                         else
157                                 return evaluatedMetadata.Contains (metadataName);
158                 }
159
160                 public void RemoveMetadata (string metadataName)
161                 {
162                         if (metadataName == null)
163                                 throw new ArgumentNullException ("metadataName");
164                         
165                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
166                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
167                                         metadataName));
168
169                         if (FromXml) {
170                                 if (unevaluatedMetadata.Contains (metadataName)) {
171                                         XmlNode node = itemElement [metadataName];
172                                         itemElement.RemoveChild (node);
173                                 }
174                         } else if (HasParentItem) {
175                                 if (parent_item.child_items.Count > 1)
176                                         SplitParentItem ();
177                                 parent_item.RemoveMetadata (metadataName);
178                         } 
179                         
180                         DeleteMetadata (metadataName);
181                 }
182
183                 public void SetMetadata (string metadataName,
184                                          string metadataValue)
185                 {
186                         SetMetadata (metadataName, metadataValue, false);
187                 }
188                 
189                 public void SetMetadata (string metadataName,
190                                          string metadataValue,
191                                          bool treatMetadataValueAsLiteral)
192                 {
193                         if (metadataName == null)
194                                 throw new ArgumentNullException ("metadataName");
195                         
196                         if (metadataValue == null)
197                                 throw new ArgumentNullException ("metadataValue");
198                         
199                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
200                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
201                                         metadataName));
202
203                         if (treatMetadataValueAsLiteral && !HasParentItem)
204                                 metadataValue = MSBuildUtils.Escape (metadataValue);
205
206                         if (FromXml) {
207                                 XmlElement element = itemElement [metadataName];
208                                 if (element == null) {
209                                         element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
210                                         element.InnerText = metadataValue;
211                                         itemElement.AppendChild (element);
212                                 } else
213                                         element.InnerText = metadataValue;
214                         } else if (HasParentItem) {
215                                 if (parent_item.child_items.Count > 1)
216                                         SplitParentItem ();
217                                 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
218                         }
219                         if (FromXml || HasParentItem) {
220                                 parent_item_group.ParentProject.MarkProjectAsDirty ();
221                                 parent_item_group.ParentProject.NeedToReevaluate ();
222                         }
223                         
224                         DeleteMetadata (metadataName);
225                         AddMetadata (metadataName, metadataValue);
226                 }
227
228                 void AddMetadata (string name, string value)
229                 {
230                         if (parent_item_group != null) {
231                                 Expression e = new Expression ();
232                                 e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
233                                 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
234                                                 typeof (string), ExpressionOptions.ExpandItemRefs);
235                         } else
236                                 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
237                                 
238                         unevaluatedMetadata [name] = value;
239                 }
240
241                 void DeleteMetadata (string name)
242                 {
243                         if (evaluatedMetadata.Contains (name))
244                                 evaluatedMetadata.Remove (name);
245                         
246                         if (unevaluatedMetadata.Contains (name))
247                                 unevaluatedMetadata.Remove (name);
248                 }
249
250                 internal void Evaluate (Project project, bool evaluatedTo)
251                 {
252                         // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
253                         if (project == null) {
254                                 this.finalItemSpec = MSBuildUtils.Unescape (Include);
255                                 return;
256                         }
257                         
258                         foreach (XmlNode xn in itemElement.ChildNodes) {
259                                 XmlElement xe = xn as XmlElement;
260                                 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
261                                         AddMetadata (xe.Name, xe.InnerText);
262                         }
263
264                         DirectoryScanner directoryScanner;
265                         Expression includeExpr, excludeExpr;
266                         ITaskItem[] includes, excludes;
267
268                         includeExpr = new Expression ();
269                         includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
270                         excludeExpr = new Expression ();
271                         excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
272                         
273                         includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
274                                                                 ExpressionOptions.ExpandItemRefs);
275                         excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
276                                                                 ExpressionOptions.ExpandItemRefs);
277
278                         this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
279                                                         ExpressionOptions.ExpandItemRefs);
280
281                         directoryScanner = new DirectoryScanner ();
282                         
283                         directoryScanner.Includes = includes;
284                         directoryScanner.Excludes = excludes;
285
286                         if (project.FullFileName != String.Empty)
287                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
288                         else
289                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
290                         
291                         directoryScanner.Scan ();
292                         
293                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
294                                 AddEvaluatedItem (project, evaluatedTo, matchedItem);
295                 }
296                 
297                 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
298                 {
299                         BuildItemGroup big;                     
300                         BuildItem bi = new BuildItem (this);
301                         bi.finalItemSpec = taskitem.ItemSpec;
302
303                         foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
304                                 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
305                                 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
306                         }
307
308                         project.EvaluatedItemsIgnoringCondition.AddItem (bi);
309
310                         if (evaluatedTo) {
311                                 project.EvaluatedItems.AddItem (bi);
312         
313                                 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
314                                         big = new BuildItemGroup (null, project, null, true);
315                                         project.EvaluatedItemsByName.Add (bi.Name, big);
316                                 } else {
317                                         big = project.EvaluatedItemsByName [bi.Name];
318                                 }
319
320                                 big.AddItem (bi);
321                         }
322
323                         if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
324                                 big = new BuildItemGroup (null, project, null, true);
325                                 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
326                         } else {
327                                 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
328                         }
329
330                         big.AddItem (bi);
331                 }
332                 
333                 // during item's eval phase, any item refs in this item, have either
334                 // already been expanded or are non-existant, so expand can be _false_
335                 //
336                 // during prop's eval phase, this isn't reached, as it parses expressions
337                 // with allowItems=false, so no ItemReferences are created at all
338                 //
339                 // at other times, item refs have already been expanded, so expand: false
340                 internal string ConvertToString (Expression transform, ExpressionOptions options)
341                 {
342                         return GetItemSpecFromTransform (transform, options);
343                 }
344                 
345                 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
346                 {
347                         TaskItem taskItem;
348                         taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
349                         return taskItem;
350                 }
351
352                 internal void Detach ()
353                 {
354                         if (FromXml)
355                                 itemElement.ParentNode.RemoveChild (itemElement);
356                         else if (HasParentItem) {
357                                 if (parent_item.child_items.Count > 1)
358                                         SplitParentItem ();
359                                 parent_item.Detach ();
360                         }
361                 }
362
363                 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
364                 {
365                         StringBuilder sb;
366                 
367                         if (transform == null) {
368                                 if (options == ExpressionOptions.ExpandItemRefs) {
369                                         // With usual code paths, this will never execute,
370                                         // but letting this be here, incase BI.ConvertTo*
371                                         // is called directly
372                                         Expression expr = new Expression ();
373                                         expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
374
375                                         return (string) expr.ConvertTo (parent_item_group.ParentProject,
376                                                         typeof (string), ExpressionOptions.ExpandItemRefs);
377                                 } else {
378                                         return finalItemSpec;
379                                 }
380                         } else {
381                                 // Transform, _DONT_ expand itemrefs
382                                 sb = new StringBuilder ();
383                                 foreach (object o in transform.Collection) {
384                                         if (o is string) {
385                                                 sb.Append ((string)o);
386                                         } else if (o is PropertyReference) {
387                                                 sb.Append (((PropertyReference)o).ConvertToString (
388                                                                         parent_item_group.ParentProject,
389                                                                         ExpressionOptions.DoNotExpandItemRefs));
390                                         } else if (o is ItemReference) {
391                                                 sb.Append (((ItemReference)o).ConvertToString (
392                                                                         parent_item_group.ParentProject,
393                                                                         ExpressionOptions.DoNotExpandItemRefs));
394                                         } else if (o is MetadataReference) {
395                                                 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
396                                         }
397                                 }
398                                 return sb.ToString ();
399                         }
400                 }
401
402                 void SplitParentItem ()
403                 {
404                         BuildItem parent = parent_item;
405                         List <BuildItem> list = new List <BuildItem> ();
406                         XmlElement insertAfter = parent.itemElement;
407                         foreach (BuildItem bi in parent.child_items) {
408                                 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
409                                 insertAfter = added.itemElement;
410                                 list.Add (added);
411                         }
412                         parent.parent_item_group.ReplaceWith (parent, list);
413                         parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
414                 }
415
416                 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
417                 {
418                         BuildItem newParent;
419
420                         XmlDocument doc = parent.itemElement.OwnerDocument;
421                         XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
422                         newElement.SetAttribute ("Include", child.FinalItemSpec);
423                         if (parent.itemElement.HasAttribute ("Condition"))
424                                 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
425                         foreach (XmlNode xn in parent.itemElement)
426                                 newElement.AppendChild (xn.Clone ());
427                         parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
428
429                         newParent = new BuildItem (newElement, parent.parent_item_group);
430                         newParent.child_items.Add (child);
431                         child.parent_item = newParent;
432
433                         return newParent;
434                 }
435
436                 public string Condition {
437                         get {
438                                 if (FromXml)
439                                         return itemElement.GetAttribute ("Condition");
440                                 else
441                                         return String.Empty;
442                         }
443                         set {
444                                 if (FromXml)
445                                         itemElement.SetAttribute ("Condition", value);
446                                 else if (!HasParentItem)
447                                         throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
448                         }
449                 }
450
451                 public string Exclude {
452                         get {
453                                 if (FromXml)
454                                         return itemElement.GetAttribute ("Exclude");
455                                 else
456                                         return String.Empty;
457                         }
458                         set {
459                                 if (FromXml)
460                                         itemElement.SetAttribute ("Exclude", value);
461                                 else
462                                         throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
463                         }
464                 }
465
466                 public string FinalItemSpec {
467                         get { return finalItemSpec; }
468                 }
469
470                 public string Include {
471                         get {
472                                 if (FromXml)
473                                         return itemElement.GetAttribute ("Include");
474                                 else if (HasParentItem)
475                                         return parent_item.Include;
476                                 else
477                                         return itemInclude;
478                         }
479                         set {
480                                 if (FromXml)
481                                         itemElement.SetAttribute ("Include", value);
482                                 else if (HasParentItem) {
483                                         if (parent_item.child_items.Count > 1)
484                                                 SplitParentItem ();
485                                         parent_item.Include = value;
486                                 } else
487                                         itemInclude = value;
488                         }
489                 }
490
491                 public bool IsImported {
492                         get { return isImported; }
493                 }
494
495                 public string Name {
496                         get {
497                                 if (FromXml)
498                                         return itemElement.Name;
499                                 else if (HasParentItem)
500                                         return parent_item.Name;
501                                 else
502                                         return name;
503                         }
504                         set {
505                                 if (FromXml) {
506                                         XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
507                                         newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
508                                         newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
509                                         foreach (XmlNode xn in itemElement)
510                                                 newElement.AppendChild (xn.Clone ());
511                                         itemElement.ParentNode.ReplaceChild (newElement, itemElement);
512                                         itemElement = newElement;
513                                 } else if (HasParentItem) {
514                                         if (parent_item.child_items.Count > 1)
515                                                 SplitParentItem ();
516                                         parent_item.Name = value;
517                                 } else
518                                         name = value;
519                         }
520                 }
521                 
522                 internal bool FromXml {
523                         get { return itemElement != null; }
524                 }
525                 
526                 internal bool HasParentItem {
527                         get { return parent_item != null; }
528                 }
529
530                 internal BuildItem ParentItem {
531                         get { return parent_item; }
532                 }
533
534                 internal BuildItemGroup ParentItemGroup {
535                         get { return parent_item_group; }
536                         set { parent_item_group = value; }
537                 }
538         }
539 }