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