Wed Feb 24 15:47:16 CET 2010 Paolo Molaro <lupus@ximian.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                 List<BuildItem> 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 List<BuildItem> ();
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.Add (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 (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
129                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
130                                 return (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : 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 (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : metadata;
144                         } else if (unevaluatedMetadata.Contains (metadataName))
145                                 return (string) unevaluatedMetadata [metadataName];
146                         else
147                                 return String.Empty;
148                 }
149                 
150                 public bool HasMetadata (string metadataName)
151                 {
152                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
153                                 return true;
154                         else
155                                 return evaluatedMetadata.Contains (metadataName);
156                 }
157
158                 public void RemoveMetadata (string metadataName)
159                 {
160                         if (metadataName == null)
161                                 throw new ArgumentNullException ("metadataName");
162                         
163                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
164                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
165                                         metadataName));
166
167                         if (FromXml) {
168                                 if (unevaluatedMetadata.Contains (metadataName)) {
169                                         XmlNode node = itemElement [metadataName];
170                                         itemElement.RemoveChild (node);
171                                 }
172                         } else if (HasParentItem) {
173                                 if (parent_item.child_items.Count > 1)
174                                         SplitParentItem ();
175                                 parent_item.RemoveMetadata (metadataName);
176                         } 
177                         
178                         DeleteMetadata (metadataName);
179                 }
180
181                 public void SetMetadata (string metadataName,
182                                          string metadataValue)
183                 {
184                         SetMetadata (metadataName, metadataValue, false);
185                 }
186                 
187                 public void SetMetadata (string metadataName,
188                                          string metadataValue,
189                                          bool treatMetadataValueAsLiteral)
190                 {
191                         if (metadataName == null)
192                                 throw new ArgumentNullException ("metadataName");
193                         
194                         if (metadataValue == null)
195                                 throw new ArgumentNullException ("metadataValue");
196                         
197                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
198                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
199                                         metadataName));
200
201                         if (treatMetadataValueAsLiteral && !HasParentItem)
202                                 metadataValue = Utilities.Escape (metadataValue);
203
204                         if (FromXml) {
205                                 XmlElement element = itemElement [metadataName];
206                                 if (element == null) {
207                                         element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
208                                         element.InnerText = metadataValue;
209                                         itemElement.AppendChild (element);
210                                 } else
211                                         element.InnerText = metadataValue;
212                         } else if (HasParentItem) {
213                                 if (parent_item.child_items.Count > 1)
214                                         SplitParentItem ();
215                                 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
216                         }
217                         if (FromXml || HasParentItem) {
218                                 parent_item_group.ParentProject.MarkProjectAsDirty ();
219                                 parent_item_group.ParentProject.NeedToReevaluate ();
220                         }
221                         
222                         DeleteMetadata (metadataName);
223                         AddMetadata (metadataName, metadataValue);
224                 }
225
226                 void AddMetadata (string name, string value)
227                 {
228                         if (parent_item_group != null) {
229                                 Expression e = new Expression ();
230                                 e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
231                                 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
232                                                 typeof (string), ExpressionOptions.ExpandItemRefs);
233                         } else
234                                 evaluatedMetadata [name] = Utilities.Unescape (value);
235                                 
236                         unevaluatedMetadata [name] = value;
237                 }
238
239                 void DeleteMetadata (string name)
240                 {
241                         if (evaluatedMetadata.Contains (name))
242                                 evaluatedMetadata.Remove (name);
243                         
244                         if (unevaluatedMetadata.Contains (name))
245                                 unevaluatedMetadata.Remove (name);
246                 }
247
248                 internal void Evaluate (Project project, bool evaluatedTo)
249                 {
250                         // FIXME: maybe make Expression.ConvertTo (null, ...) work as Utilities.Unescape ()?
251                         if (project == null) {
252                                 this.finalItemSpec = Utilities.Unescape (Include);
253                                 return;
254                         }
255                         
256                         foreach (XmlNode xn in itemElement.ChildNodes) {
257                                 XmlElement xe = xn as XmlElement;
258                                 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
259                                         AddMetadata (xe.Name, xe.InnerText);
260                         }
261
262                         DirectoryScanner directoryScanner;
263                         Expression includeExpr, excludeExpr;
264                         ITaskItem[] includes, excludes;
265
266                         includeExpr = new Expression ();
267                         includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
268                         excludeExpr = new Expression ();
269                         excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
270                         
271                         includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
272                                                                 ExpressionOptions.ExpandItemRefs);
273                         excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
274                                                                 ExpressionOptions.ExpandItemRefs);
275
276                         this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
277                                                         ExpressionOptions.ExpandItemRefs);
278
279                         directoryScanner = new DirectoryScanner ();
280                         
281                         directoryScanner.Includes = includes;
282                         directoryScanner.Excludes = excludes;
283
284                         if (project.FullFileName != String.Empty)
285                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
286                         else
287                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
288                         
289                         directoryScanner.Scan ();
290                         
291                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
292                                 AddEvaluatedItem (project, evaluatedTo, matchedItem);
293                 }
294                 
295                 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
296                 {
297                         BuildItemGroup big;                     
298                         BuildItem bi = new BuildItem (this);
299                         bi.finalItemSpec = taskitem.ItemSpec;
300
301                         foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
302                                 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
303                                 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
304                         }
305
306                         project.EvaluatedItemsIgnoringCondition.AddItem (bi);
307
308                         if (evaluatedTo) {
309                                 project.EvaluatedItems.AddItem (bi);
310         
311                                 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
312                                         big = new BuildItemGroup (null, project, null, true);
313                                         project.EvaluatedItemsByName.Add (bi.Name, big);
314                                 } else {
315                                         big = project.EvaluatedItemsByName [bi.Name];
316                                 }
317
318                                 big.AddItem (bi);
319                         }
320
321                         if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
322                                 big = new BuildItemGroup (null, project, null, true);
323                                 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
324                         } else {
325                                 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
326                         }
327
328                         big.AddItem (bi);
329                 }
330                 
331                 // during item's eval phase, any item refs in this item, have either
332                 // already been expanded or are non-existant, so expand can be _false_
333                 //
334                 // during prop's eval phase, this isn't reached, as it parses expressions
335                 // with allowItems=false, so no ItemReferences are created at all
336                 //
337                 // at other times, item refs have already been expanded, so expand: false
338                 internal string ConvertToString (Expression transform, ExpressionOptions options)
339                 {
340                         return GetItemSpecFromTransform (transform, options);
341                 }
342                 
343                 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
344                 {
345                         TaskItem taskItem;
346                         taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
347                         return taskItem;
348                 }
349
350                 internal void Detach ()
351                 {
352                         if (FromXml)
353                                 itemElement.ParentNode.RemoveChild (itemElement);
354                         else if (HasParentItem) {
355                                 if (parent_item.child_items.Count > 1)
356                                         SplitParentItem ();
357                                 parent_item.Detach ();
358                         }
359                 }
360
361                 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
362                 {
363                         StringBuilder sb;
364                 
365                         if (transform == null) {
366                                 if (options == ExpressionOptions.ExpandItemRefs) {
367                                         // With usual code paths, this will never execute,
368                                         // but letting this be here, incase BI.ConvertTo*
369                                         // is called directly
370                                         Expression expr = new Expression ();
371                                         expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
372
373                                         return (string) expr.ConvertTo (parent_item_group.ParentProject,
374                                                         typeof (string), ExpressionOptions.ExpandItemRefs);
375                                 } else {
376                                         return finalItemSpec;
377                                 }
378                         } else {
379                                 // Transform, _DONT_ expand itemrefs
380                                 sb = new StringBuilder ();
381                                 foreach (object o in transform.Collection) {
382                                         if (o is string) {
383                                                 sb.Append ((string)o);
384                                         } else if (o is PropertyReference) {
385                                                 sb.Append (((PropertyReference)o).ConvertToString (
386                                                                         parent_item_group.ParentProject,
387                                                                         ExpressionOptions.DoNotExpandItemRefs));
388                                         } else if (o is ItemReference) {
389                                                 sb.Append (((ItemReference)o).ConvertToString (
390                                                                         parent_item_group.ParentProject,
391                                                                         ExpressionOptions.DoNotExpandItemRefs));
392                                         } else if (o is MetadataReference) {
393                                                 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
394                                         }
395                                 }
396                                 return sb.ToString ();
397                         }
398                 }
399
400                 void SplitParentItem ()
401                 {
402                         BuildItem parent = parent_item;
403                         List <BuildItem> list = new List <BuildItem> ();
404                         XmlElement insertAfter = parent.itemElement;
405                         foreach (BuildItem bi in parent.child_items) {
406                                 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
407                                 insertAfter = added.itemElement;
408                                 list.Add (added);
409                         }
410                         parent.parent_item_group.ReplaceWith (parent, list);
411                         parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
412                 }
413
414                 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
415                 {
416                         BuildItem newParent;
417
418                         XmlDocument doc = parent.itemElement.OwnerDocument;
419                         XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
420                         newElement.SetAttribute ("Include", child.FinalItemSpec);
421                         if (parent.itemElement.HasAttribute ("Condition"))
422                                 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
423                         foreach (XmlNode xn in parent.itemElement)
424                                 newElement.AppendChild (xn.Clone ());
425                         parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
426
427                         newParent = new BuildItem (newElement, parent.parent_item_group);
428                         newParent.child_items.Add (child);
429                         child.parent_item = newParent;
430
431                         return newParent;
432                 }
433
434                 public string Condition {
435                         get {
436                                 if (FromXml)
437                                         return itemElement.GetAttribute ("Condition");
438                                 else
439                                         return String.Empty;
440                         }
441                         set {
442                                 if (FromXml)
443                                         itemElement.SetAttribute ("Condition", value);
444                                 else if (!HasParentItem)
445                                         throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
446                         }
447                 }
448
449                 public string Exclude {
450                         get {
451                                 if (FromXml)
452                                         return itemElement.GetAttribute ("Exclude");
453                                 else
454                                         return String.Empty;
455                         }
456                         set {
457                                 if (FromXml)
458                                         itemElement.SetAttribute ("Exclude", value);
459                                 else
460                                         throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
461                         }
462                 }
463
464                 public string FinalItemSpec {
465                         get { return finalItemSpec; }
466                 }
467
468                 public string Include {
469                         get {
470                                 if (FromXml)
471                                         return itemElement.GetAttribute ("Include");
472                                 else if (HasParentItem)
473                                         return parent_item.Include;
474                                 else
475                                         return itemInclude;
476                         }
477                         set {
478                                 if (FromXml)
479                                         itemElement.SetAttribute ("Include", value);
480                                 else if (HasParentItem) {
481                                         if (parent_item.child_items.Count > 1)
482                                                 SplitParentItem ();
483                                         parent_item.Include = value;
484                                 } else
485                                         itemInclude = value;
486                         }
487                 }
488
489                 public bool IsImported {
490                         get { return isImported; }
491                 }
492
493                 public string Name {
494                         get {
495                                 if (FromXml)
496                                         return itemElement.Name;
497                                 else if (HasParentItem)
498                                         return parent_item.Name;
499                                 else
500                                         return name;
501                         }
502                         set {
503                                 if (FromXml) {
504                                         XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
505                                         newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
506                                         newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
507                                         foreach (XmlNode xn in itemElement)
508                                                 newElement.AppendChild (xn.Clone ());
509                                         itemElement.ParentNode.ReplaceChild (newElement, itemElement);
510                                         itemElement = newElement;
511                                 } else if (HasParentItem) {
512                                         if (parent_item.child_items.Count > 1)
513                                                 SplitParentItem ();
514                                         parent_item.Name = value;
515                                 } else
516                                         name = value;
517                         }
518                 }
519                 
520                 internal bool FromXml {
521                         get { return itemElement != null; }
522                 }
523                 
524                 internal bool HasParentItem {
525                         get { return parent_item != null; }
526                 }
527
528                 internal BuildItem ParentItem {
529                         get { return parent_item; }
530                 }
531
532                 internal BuildItemGroup ParentItemGroup {
533                         get { return parent_item_group; }
534                         set { parent_item_group = value; }
535                 }
536         }
537 }
538
539 #endif