[xbuild] Don't reevaluate project when setting metadata in a dynamic ..
[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.Linq;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
36 using Microsoft.Build.Framework;
37 using Microsoft.Build.Utilities;
38 using Mono.XBuild.Utilities;
39
40 namespace Microsoft.Build.BuildEngine {
41         public class BuildItem {
42
43                 List<BuildItem> child_items;
44                 XmlElement      itemElement;
45                 string          finalItemSpec;
46                 bool            isImported;
47                 string          itemInclude;
48                 string          name;
49                 BuildItemGroup  parent_item_group;
50                 BuildItem       parent_item;
51                 //string                recursiveDir;
52                 IDictionary     evaluatedMetadata;
53                 IDictionary     unevaluatedMetadata;
54                 bool            isDynamic;
55                 bool            keepDuplicates = true;
56                 string          removeMetadata, keepMetadata;
57
58                 BuildItem ()
59                 {
60                 }
61                 
62                 public BuildItem (string itemName, ITaskItem taskItem)
63                 {
64                         if (taskItem == null)
65                                 throw new ArgumentNullException ("taskItem");
66
67                         this.name = itemName;
68                         this.finalItemSpec = taskItem.ItemSpec;
69                         this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
70                         this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
71                         this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
72                 }
73
74                 public BuildItem (string itemName, string itemInclude)
75                 {
76                         if (itemInclude == null)
77                                 throw new ArgumentNullException ("itemInclude");
78                         if (itemInclude == String.Empty)
79                                 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
80
81                         name = itemName;
82                         finalItemSpec = itemInclude;
83                         this.itemInclude = itemInclude;
84                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
85                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
86                 }
87
88                 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
89                 {
90                         child_items = new List<BuildItem> ();
91                         isImported = parentItemGroup.IsImported;
92                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
93                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
94                         this.parent_item_group = parentItemGroup;
95                         
96                         this.itemElement = itemElement;
97                         isDynamic = parentItemGroup.IsDynamic;
98
99                         if (IsDynamic) {
100                                 if (!string.IsNullOrEmpty (Remove)) {
101                                         if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
102                                                 throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
103                                         if (itemElement.HasChildNodes)
104                                                 throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
105                                 }
106                                 if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
107                                         throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
108                         } else {
109                                 if (string.IsNullOrEmpty (Include))
110                                         throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
111                                 if (!string.IsNullOrEmpty (Remove))
112                                         throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
113                         }
114
115                         foreach (XmlAttribute attr in itemElement.Attributes) {
116                                 if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
117                                         continue;
118                                 if (!IsDynamic)
119                                         throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
120
121                                 switch (attr.Name) {
122                                 case "Remove":
123                                         Remove = attr.Value;
124                                         break;
125                                 case "KeepDuplicates":
126                                         KeepDuplicates = bool.Parse (attr.Value);
127                                         break;
128                                 case "RemoveMetadata":
129                                         removeMetadata = attr.Value;
130                                         break;
131                                 case "KeepMetadata":
132                                         keepMetadata = attr.Value;
133                                         break;
134                                 default:
135                                         throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
136                                 }
137                         }
138                 }
139
140                 BuildItem (BuildItem parent)
141                 {
142                         isImported = parent.isImported;
143                         name = parent.Name;
144                         parent_item = parent;
145                         parent_item.child_items.Add (this);
146                         parent_item_group = parent.parent_item_group;
147                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
148                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
149                 }
150                 
151                 public void CopyCustomMetadataTo (BuildItem destinationItem)
152                 {
153                         if (destinationItem == null)
154                                 throw new ArgumentNullException ("destinationItem");
155
156                         foreach (DictionaryEntry de in unevaluatedMetadata)
157                                 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
158                 }
159                 
160                 [MonoTODO]
161                 public BuildItem Clone ()
162                 {
163                         return (BuildItem) this.MemberwiseClone ();
164                 }
165
166                 public string GetEvaluatedMetadata (string metadataName)
167                 {
168                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
169                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
170                                 return MSBuildUtils.Unescape (metadata);
171                         }
172
173                         if (evaluatedMetadata.Contains (metadataName))
174                                 return (string) evaluatedMetadata [metadataName];
175                         else
176                                 return String.Empty;
177                 }
178
179                 public string GetMetadata (string metadataName)
180                 {
181                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
182                                 return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
183                         } else if (unevaluatedMetadata.Contains (metadataName))
184                                 return (string) unevaluatedMetadata [metadataName];
185                         else
186                                 return String.Empty;
187                 }
188                 
189                 public bool HasMetadata (string metadataName)
190                 {
191                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
192                                 return true;
193                         else
194                                 return evaluatedMetadata.Contains (metadataName);
195                 }
196
197                 public void RemoveMetadata (string metadataName)
198                 {
199                         if (metadataName == null)
200                                 throw new ArgumentNullException ("metadataName");
201                         
202                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
203                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
204                                         metadataName));
205
206                         if (FromXml) {
207                                 if (unevaluatedMetadata.Contains (metadataName)) {
208                                         XmlNode node = itemElement [metadataName];
209                                         itemElement.RemoveChild (node);
210                                 }
211                         } else if (HasParentItem) {
212                                 if (parent_item.child_items.Count > 1)
213                                         SplitParentItem ();
214                                 parent_item.RemoveMetadata (metadataName);
215                         } 
216                         
217                         DeleteMetadata (metadataName);
218                 }
219
220                 public void SetMetadata (string metadataName,
221                                          string metadataValue)
222                 {
223                         SetMetadata (metadataName, metadataValue, false);
224                 }
225                 
226                 public void SetMetadata (string metadataName,
227                                          string metadataValue,
228                                          bool treatMetadataValueAsLiteral)
229                 {
230                         SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, false);
231                 }
232
233                 void SetMetadata (string metadataName,
234                                          string metadataValue,
235                                          bool treatMetadataValueAsLiteral,
236                                          bool fromDynamicItem)
237                 {
238                         if (metadataName == null)
239                                 throw new ArgumentNullException ("metadataName");
240                         
241                         if (metadataValue == null)
242                                 throw new ArgumentNullException ("metadataValue");
243                         
244                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
245                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
246                                         metadataName));
247
248                         if (treatMetadataValueAsLiteral && !HasParentItem)
249                                 metadataValue = MSBuildUtils.Escape (metadataValue);
250
251                         if (FromXml) {
252                                 XmlElement element = itemElement [metadataName];
253                                 if (element == null) {
254                                         element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
255                                         element.InnerText = metadataValue;
256                                         itemElement.AppendChild (element);
257                                 } else
258                                         element.InnerText = metadataValue;
259                         } else if (HasParentItem) {
260                                 if (parent_item.child_items.Count > 1)
261                                         SplitParentItem ();
262                                 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, fromDynamicItem);
263                         }
264
265                         // We don't want to reevalute the project for dynamic items
266                         if (!fromDynamicItem && !IsDynamic && (FromXml || HasParentItem)) {
267                                 parent_item_group.ParentProject.MarkProjectAsDirty ();
268                                 parent_item_group.ParentProject.NeedToReevaluate ();
269                         }
270                         
271                         DeleteMetadata (metadataName);
272                         AddMetadata (metadataName, metadataValue);
273                 }
274
275                 void AddMetadata (string name, string value)
276                 {
277                         var options = IsDynamic ?
278                                       ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
279
280                         if (parent_item_group != null) {
281                                 Expression e = new Expression ();
282                                 e.Parse (value, options);
283                                 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
284                                                 typeof (string), ExpressionOptions.ExpandItemRefs);
285                         } else
286                                 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
287                                 
288                         unevaluatedMetadata [name] = value;
289                 }
290
291                 void DeleteMetadata (string name)
292                 {
293                         if (evaluatedMetadata.Contains (name))
294                                 evaluatedMetadata.Remove (name);
295                         
296                         if (unevaluatedMetadata.Contains (name))
297                                 unevaluatedMetadata.Remove (name);
298                 }
299
300                 internal void Evaluate (Project project, bool evaluatedTo)
301                 {
302                         // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
303                         if (project == null) {
304                                 this.finalItemSpec = MSBuildUtils.Unescape (Include);
305                                 return;
306                         }
307
308                         foreach (XmlNode xn in itemElement.ChildNodes) {
309                                 XmlElement xe = xn as XmlElement;
310                                 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
311                                         AddMetadata (xe.Name, xe.InnerText);
312                         }
313
314                         if (IsDynamic) {
315                                 if (!evaluatedTo)
316                                         return;
317
318                                 if (!string.IsNullOrEmpty (Remove)) {
319                                         RemoveItems (project);
320                                         return;
321                                 }
322
323                                 if (string.IsNullOrEmpty (Include)) {
324                                         UpdateMetadata (project);
325                                         return;
326                                 }
327                         }
328
329                         DirectoryScanner directoryScanner;
330                         Expression includeExpr, excludeExpr;
331                         ITaskItem[] includes, excludes;
332
333                         var options = IsDynamic ?
334                                 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
335
336                         includeExpr = new Expression ();
337                         includeExpr.Parse (Include, options);
338                         excludeExpr = new Expression ();
339                         excludeExpr.Parse (Exclude, options);
340                         
341                         includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
342                                                                 ExpressionOptions.ExpandItemRefs);
343                         excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
344                                                                 ExpressionOptions.ExpandItemRefs);
345
346                         this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
347                                                         ExpressionOptions.ExpandItemRefs);
348
349                         directoryScanner = new DirectoryScanner ();
350                         
351                         directoryScanner.Includes = includes;
352                         directoryScanner.Excludes = excludes;
353
354                         if (project.FullFileName != String.Empty) {
355                                 directoryScanner.ProjectFile = project.ThisFileFullPath;
356                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
357                         }
358                         else
359                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
360                         
361                         directoryScanner.Scan ();
362                         
363                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
364                                 AddEvaluatedItem (project, evaluatedTo, matchedItem);
365                 }
366
367                 bool CheckCondition (Project project)
368                 {
369                         if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
370                                 return false;
371                         if (parent_item != null && !parent_item.CheckCondition (project))
372                                 return false;
373                         return ConditionParser.ParseAndEvaluate (Condition, project);
374                 }
375
376                 void UpdateMetadata (Project project)
377                 {
378                         BuildItemGroup group;
379                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
380                                 return;
381
382                         foreach (BuildItem item in group) {
383                                 if (!item.CheckCondition (project))
384                                         continue;
385                                 
386                                 foreach (string name in evaluatedMetadata.Keys) {
387                                         item.SetMetadata (name, (string)evaluatedMetadata [name], false, IsDynamic);
388                                 }
389
390                                 AddAndRemoveMetadata (project, item);
391                         }
392                 }
393
394                 void AddAndRemoveMetadata (Project project, BuildItem item)
395                 {
396                         if (!string.IsNullOrEmpty (removeMetadata)) {
397                                 var removeExpr = new Expression ();
398                                 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
399
400                                 var removeSpec = (string[]) removeExpr.ConvertTo (
401                                         project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
402
403                                 foreach (var remove in removeSpec) {
404                                         item.DeleteMetadata (remove);
405                                 }
406                         }
407
408                         if (!string.IsNullOrEmpty (keepMetadata)) {
409                                 var keepExpr = new Expression ();
410                                 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
411
412                                 var keepSpec = (string[]) keepExpr.ConvertTo (
413                                         project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
414
415                                 var metadataNames = new string [item.evaluatedMetadata.Count];
416                                 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
417
418                                 foreach (string name in metadataNames) {
419                                         if (!keepSpec.Contains (name))
420                                                 item.DeleteMetadata (name);
421                                 }
422                         }
423                 }
424
425                 void RemoveItems (Project project)
426                 {
427                         BuildItemGroup group;
428                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
429                                 return;
430
431                         var removeExpr = new Expression ();
432                         removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
433
434                         var removes = (ITaskItem[]) removeExpr.ConvertTo (
435                                 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
436
437                         var directoryScanner = new DirectoryScanner ();
438                         
439                         directoryScanner.Includes = removes;
440
441                         if (project.FullFileName != String.Empty)
442                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
443                         else
444                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
445                         
446                         directoryScanner.Scan ();
447
448                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
449                                 group.RemoveItem (matchedItem);
450                         }
451                 }
452
453                 bool ContainsItem (Project project, ITaskItem taskItem)
454                 {
455                         BuildItemGroup group;
456                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
457                                 return false;
458
459                         var item = group.FindItem (taskItem);
460                         if (item == null)
461                                 return false;
462
463                         foreach (string metadataName in evaluatedMetadata.Keys) {
464                                 string metadataValue = (string)evaluatedMetadata [metadataName];
465                                 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
466                                         return false;
467                         }
468                         
469                         foreach (string metadataName in item.evaluatedMetadata.Keys) {
470                                 string metadataValue = (string)item.evaluatedMetadata [metadataName];
471                                 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
472                                         return false;
473                         }
474
475                         return true;
476                 }
477
478                 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
479                 {
480                         if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
481                                 return;
482
483                         BuildItemGroup big;                     
484                         BuildItem bi = new BuildItem (this);
485                         bi.finalItemSpec = taskitem.ItemSpec;
486
487                         foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
488                                 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
489                                 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
490                         }
491
492                         project.EvaluatedItemsIgnoringCondition.AddItem (bi);
493
494                         if (evaluatedTo) {
495                                 project.EvaluatedItems.AddItem (bi);
496         
497                                 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
498                                         big = new BuildItemGroup (null, project, null, true);
499                                         project.EvaluatedItemsByName.Add (bi.Name, big);
500                                 } else {
501                                         big = project.EvaluatedItemsByName [bi.Name];
502                                 }
503
504                                 big.AddItem (bi);
505                         }
506
507                         if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
508                                 big = new BuildItemGroup (null, project, null, true);
509                                 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
510                         } else {
511                                 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
512                         }
513
514                         big.AddItem (bi);
515
516                         if (IsDynamic)
517                                 AddAndRemoveMetadata (project, bi);
518                 }
519                 
520                 // during item's eval phase, any item refs in this item, have either
521                 // already been expanded or are non-existant, so expand can be _false_
522                 //
523                 // during prop's eval phase, this isn't reached, as it parses expressions
524                 // with allowItems=false, so no ItemReferences are created at all
525                 //
526                 // at other times, item refs have already been expanded, so expand: false
527                 internal string ConvertToString (Expression transform, ExpressionOptions options)
528                 {
529                         return GetItemSpecFromTransform (transform, options);
530                 }
531                 
532                 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
533                 {
534                         TaskItem taskItem;
535                         taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
536                         return taskItem;
537                 }
538
539                 internal void Detach ()
540                 {
541                         if (FromXml)
542                                 itemElement.ParentNode.RemoveChild (itemElement);
543                         else if (HasParentItem) {
544                                 if (parent_item.child_items.Count > 1)
545                                         SplitParentItem ();
546                                 parent_item.Detach ();
547                         }
548                 }
549
550                 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
551                 {
552                         StringBuilder sb;
553                 
554                         if (transform == null) {
555                                 if (options == ExpressionOptions.ExpandItemRefs) {
556                                         // With usual code paths, this will never execute,
557                                         // but letting this be here, incase BI.ConvertTo*
558                                         // is called directly
559                                         Expression expr = new Expression ();
560                                         expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
561
562                                         return (string) expr.ConvertTo (parent_item_group.ParentProject,
563                                                         typeof (string), ExpressionOptions.ExpandItemRefs);
564                                 } else {
565                                         return finalItemSpec;
566                                 }
567                         } else {
568                                 // Transform, _DONT_ expand itemrefs
569                                 sb = new StringBuilder ();
570                                 foreach (object o in transform.Collection) {
571                                         if (o is string) {
572                                                 sb.Append ((string)o);
573                                         } else if (o is PropertyReference) {
574                                                 sb.Append (((PropertyReference)o).ConvertToString (
575                                                                         parent_item_group.ParentProject,
576                                                                         ExpressionOptions.DoNotExpandItemRefs));
577                                         } else if (o is ItemReference) {
578                                                 sb.Append (((ItemReference)o).ConvertToString (
579                                                                         parent_item_group.ParentProject,
580                                                                         ExpressionOptions.DoNotExpandItemRefs));
581                                         } else if (o is MetadataReference) {
582                                                 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
583                                         }
584                                 }
585                                 return sb.ToString ();
586                         }
587                 }
588
589                 void SplitParentItem ()
590                 {
591                         BuildItem parent = parent_item;
592                         List <BuildItem> list = new List <BuildItem> ();
593                         XmlElement insertAfter = parent.itemElement;
594                         foreach (BuildItem bi in parent.child_items) {
595                                 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
596                                 insertAfter = added.itemElement;
597                                 list.Add (added);
598                         }
599                         parent.parent_item_group.ReplaceWith (parent, list);
600                         parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
601                 }
602
603                 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
604                 {
605                         BuildItem newParent;
606
607                         XmlDocument doc = parent.itemElement.OwnerDocument;
608                         XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
609                         newElement.SetAttribute ("Include", child.FinalItemSpec);
610                         if (parent.itemElement.HasAttribute ("Condition"))
611                                 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
612                         foreach (XmlNode xn in parent.itemElement)
613                                 newElement.AppendChild (xn.Clone ());
614                         parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
615
616                         newParent = new BuildItem (newElement, parent.parent_item_group);
617                         newParent.child_items.Add (child);
618                         child.parent_item = newParent;
619
620                         return newParent;
621                 }
622
623                 public string Condition {
624                         get {
625                                 if (FromXml)
626                                         return itemElement.GetAttribute ("Condition");
627                                 else
628                                         return String.Empty;
629                         }
630                         set {
631                                 if (FromXml)
632                                         itemElement.SetAttribute ("Condition", value);
633                                 else if (!HasParentItem)
634                                         throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
635                         }
636                 }
637
638                 public string Exclude {
639                         get {
640                                 if (FromXml)
641                                         return itemElement.GetAttribute ("Exclude");
642                                 else
643                                         return String.Empty;
644                         }
645                         set {
646                                 if (FromXml)
647                                         itemElement.SetAttribute ("Exclude", value);
648                                 else
649                                         throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
650                         }
651                 }
652
653                 public string FinalItemSpec {
654                         get { return finalItemSpec; }
655                 }
656
657                 public string Include {
658                         get {
659                                 if (FromXml)
660                                         return itemElement.GetAttribute ("Include");
661                                 else if (HasParentItem)
662                                         return parent_item.Include;
663                                 else
664                                         return itemInclude;
665                         }
666                         set {
667                                 if (FromXml)
668                                         itemElement.SetAttribute ("Include", value);
669                                 else if (HasParentItem) {
670                                         if (parent_item.child_items.Count > 1)
671                                                 SplitParentItem ();
672                                         parent_item.Include = value;
673                                 } else
674                                         itemInclude = value;
675                         }
676                 }
677
678                 internal bool IsDynamic {
679                         get { return isDynamic; }
680                 }
681
682                 internal string Remove {
683                         get;
684                         private set;
685                 }
686
687                 internal bool KeepDuplicates {
688                         get { return keepDuplicates; }
689                         private set { keepDuplicates = value; }
690                 }
691
692                 public bool IsImported {
693                         get { return isImported; }
694                 }
695
696                 public string Name {
697                         get {
698                                 if (FromXml)
699                                         return itemElement.Name;
700                                 else if (HasParentItem)
701                                         return parent_item.Name;
702                                 else
703                                         return name;
704                         }
705                         set {
706                                 if (FromXml) {
707                                         XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
708                                         newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
709                                         newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
710                                         foreach (XmlNode xn in itemElement)
711                                                 newElement.AppendChild (xn.Clone ());
712                                         itemElement.ParentNode.ReplaceChild (newElement, itemElement);
713                                         itemElement = newElement;
714                                 } else if (HasParentItem) {
715                                         if (parent_item.child_items.Count > 1)
716                                                 SplitParentItem ();
717                                         parent_item.Name = value;
718                                 } else
719                                         name = value;
720                         }
721                 }
722                 
723                 internal bool FromXml {
724                         get { return itemElement != null; }
725                 }
726
727                 internal XmlElement XmlElement {
728                         get { return itemElement; }
729                 }
730                 
731                 internal bool HasParentItem {
732                         get { return parent_item != null; }
733                 }
734
735                 internal BuildItem ParentItem {
736                         get { return parent_item; }
737                 }
738
739                 internal BuildItemGroup ParentItemGroup {
740                         get { return parent_item_group; }
741                         set { parent_item_group = value; }
742                 }
743         }
744 }