In class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine:
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
index 0d6c76d8a0dae0051424cdb55adeabff1b595051..45792185cb879213d6d91e197977b0e4ac36be8b 100644 (file)
@@ -29,6 +29,7 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.IO;
 using System.Text;
@@ -40,22 +41,27 @@ using Mono.XBuild.Utilities;
 namespace Microsoft.Build.BuildEngine {
        public class BuildItem {
 
+               List<BuildItem> child_items;
                XmlElement      itemElement;
                string          finalItemSpec;
                bool            isImported;
                string          itemInclude;
                string          name;
-               BuildItemGroup  parentItemGroup;
+               BuildItemGroup  parent_item_group;
+               BuildItem       parent_item;
                //string                recursiveDir;
                IDictionary     evaluatedMetadata;
                IDictionary     unevaluatedMetadata;
 
-               private BuildItem ()
+               BuildItem ()
                {
                }
                
                public BuildItem (string itemName, ITaskItem taskItem)
                {
+                       if (taskItem == null)
+                               throw new ArgumentNullException ("taskItem");
+
                        this.name = itemName;
                        this.finalItemSpec = taskItem.ItemSpec;
                        this.itemInclude = Utilities.Escape (taskItem.ItemSpec);
@@ -70,34 +76,45 @@ namespace Microsoft.Build.BuildEngine {
                        if (itemInclude == String.Empty)
                                throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
 
-                       this.name = itemName;
-                       this.finalItemSpec = itemInclude;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       name = itemName;
+                       finalItemSpec = itemInclude;
+                       this.itemInclude = itemInclude;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
                }
-               
+
                internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
                {
-                       this.isImported = false;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.parentItemGroup = parentItemGroup;
-                       BindToXml (itemElement);
+                       child_items = new List<BuildItem> ();
+                       isImported = parentItemGroup.IsImported;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       this.parent_item_group = parentItemGroup;
+                       
+                       this.itemElement = itemElement;
+                       
+                       if (Include == String.Empty)
+                               throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
                }
                
-               private BuildItem (BuildItem parent)
+               BuildItem (BuildItem parent)
                {
-                       this.isImported = parent.isImported;
-                       this.name = parent.name;
-                       this.parentItemGroup = parent.parentItemGroup;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
+                       isImported = parent.isImported;
+                       name = parent.Name;
+                       parent_item = parent;
+                       parent_item.child_items.Add (this);
+                       parent_item_group = parent.parent_item_group;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
                }
                
                public void CopyCustomMetadataTo (BuildItem destinationItem)
                {
+                       if (destinationItem == null)
+                               throw new ArgumentNullException ("destinationItem");
+
                        foreach (DictionaryEntry de in unevaluatedMetadata)
-                               destinationItem.SetMetadata ((string) de.Key, (string) de.Value);
+                               destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
                }
                
                [MonoTODO]
@@ -108,6 +125,11 @@ namespace Microsoft.Build.BuildEngine {
 
                public string GetEvaluatedMetadata (string metadataName)
                {
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
+                               string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
+                               return (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : metadata;
+                       }
+
                        if (evaluatedMetadata.Contains (metadataName))
                                return (string) evaluatedMetadata [metadataName];
                        else
@@ -116,9 +138,10 @@ namespace Microsoft.Build.BuildEngine {
 
                public string GetMetadata (string metadataName)
                {
-                       if (ReservedNameUtils.IsReservedMetadataName (metadataName))
-                               return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
-                       else if (unevaluatedMetadata.Contains (metadataName))
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
+                               string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
+                               return (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : metadata;
+                       } else if (unevaluatedMetadata.Contains (metadataName))
                                return (string) unevaluatedMetadata [metadataName];
                        else
                                return String.Empty;
@@ -126,7 +149,10 @@ namespace Microsoft.Build.BuildEngine {
                
                public bool HasMetadata (string metadataName)
                {
-                       return evaluatedMetadata.Contains (metadataName);
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName))
+                               return true;
+                       else
+                               return evaluatedMetadata.Contains (metadataName);
                }
 
                public void RemoveMetadata (string metadataName)
@@ -135,13 +161,21 @@ namespace Microsoft.Build.BuildEngine {
                                throw new ArgumentNullException ("metadataName");
                        
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName))
-                               throw new ArgumentException ("Can't remove reserved metadata.");
-                       
-                       if (evaluatedMetadata.Contains (metadataName))
-                               evaluatedMetadata.Remove (metadataName);
+                               throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
+                                       metadataName));
+
+                       if (FromXml) {
+                               if (unevaluatedMetadata.Contains (metadataName)) {
+                                       XmlNode node = itemElement [metadataName];
+                                       itemElement.RemoveChild (node);
+                               }
+                       } else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.RemoveMetadata (metadataName);
+                       } 
                        
-                       if (unevaluatedMetadata.Contains (metadataName))
-                               unevaluatedMetadata.Remove (metadataName);
+                       DeleteMetadata (metadataName);
                }
 
                public void SetMetadata (string metadataName,
@@ -161,46 +195,77 @@ namespace Microsoft.Build.BuildEngine {
                                throw new ArgumentNullException ("metadataValue");
                        
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName))
-                               throw new ArgumentException ("Can't modify reserved metadata.");
-                       
-                       RemoveMetadata (metadataName);
+                               throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
+                                       metadataName));
+
+                       if (treatMetadataValueAsLiteral && !HasParentItem)
+                               metadataValue = Utilities.Escape (metadataValue);
+
+                       if (FromXml) {
+                               XmlElement element = itemElement [metadataName];
+                               if (element == null) {
+                                       element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
+                                       element.InnerText = metadataValue;
+                                       itemElement.AppendChild (element);
+                               } else
+                                       element.InnerText = metadataValue;
+                       } else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
+                       }
+                       if (FromXml || HasParentItem) {
+                               parent_item_group.ParentProject.MarkProjectAsDirty ();
+                               parent_item_group.ParentProject.NeedToReevaluate ();
+                       }
                        
-                       evaluatedMetadata.Add (metadataName, metadataValue);
-                               
-                       if (treatMetadataValueAsLiteral) {      
-                               unevaluatedMetadata.Add (metadataName, Utilities.Escape (metadataValue));
+                       DeleteMetadata (metadataName);
+                       AddMetadata (metadataName, metadataValue);
+               }
+
+               void AddMetadata (string name, string value)
+               {
+                       if (parent_item_group != null) {
+                               Expression e = new Expression ();
+                               e.Parse (value, true);
+                               evaluatedMetadata.Add (name, (string) e.ConvertTo (parent_item_group.ParentProject, typeof (string)));
                        } else
-                               unevaluatedMetadata.Add (metadataName, metadataValue);
+                               evaluatedMetadata.Add (name, Utilities.Unescape (value));
+                               
+                               unevaluatedMetadata.Add (name, value);
                }
-               
-               private void BindToXml (XmlElement xmlElement)
+
+               void DeleteMetadata (string name)
                {
-                       if (xmlElement == null)
-                               throw new ArgumentNullException ("xmlElement");
-                       
-                       this.itemElement = xmlElement;
-                       this.name = xmlElement.Name;
-                       
-                       if (Include == String.Empty)
-                               throw new InvalidProjectFileException ("Item must have Include attribute.");
+                       if (evaluatedMetadata.Contains (name))
+                               evaluatedMetadata.Remove (name);
                        
-                       foreach (XmlElement xe in xmlElement.ChildNodes)
-                               this.SetMetadata (xe.Name, xe.InnerText);
+                       if (unevaluatedMetadata.Contains (name))
+                               unevaluatedMetadata.Remove (name);
                }
 
-               internal void Evaluate (bool evaluatedTo)
+               internal void Evaluate (Project project, bool evaluatedTo)
                {
+                       // FIXME: maybe make Expression.ConvertTo (null, ...) work as Utilities.Unescape ()?
+                       if (project == null) {
+                               this.finalItemSpec = Utilities.Unescape (Include);
+                               return;
+                       }
+                       
+                       foreach (XmlElement xe in itemElement.ChildNodes)
+                               AddMetadata (xe.Name, xe.InnerText);
+
                        DirectoryScanner directoryScanner;
-                       OldExpression includeExpr, excludeExpr;
+                       Expression includeExpr, excludeExpr;
                        string includes, excludes;
 
-                       includeExpr = new OldExpression (parentItemGroup.Project);
-                       includeExpr.ParseSource (Include);
-                       excludeExpr = new OldExpression (parentItemGroup.Project);
-                       excludeExpr.ParseSource (Exclude);
+                       includeExpr = new Expression ();
+                       includeExpr.Parse (Include, true);
+                       excludeExpr = new Expression ();
+                       excludeExpr.Parse (Exclude, true);
                        
-                       includes = (string) includeExpr.ConvertTo (typeof (string));
-                       excludes = (string) excludeExpr.ConvertTo (typeof (string));
+                       includes = (string) includeExpr.ConvertTo (project, typeof (string));
+                       excludes = (string) excludeExpr.ConvertTo (project, typeof (string));
 
                        this.finalItemSpec = includes;
                        
@@ -208,22 +273,20 @@ namespace Microsoft.Build.BuildEngine {
                        
                        directoryScanner.Includes = includes;
                        directoryScanner.Excludes = excludes;
-                       if (parentItemGroup.Project.FullFileName != String.Empty) {
-                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (parentItemGroup.Project.FullFileName));
-                       } else {
+
+                       if (project.FullFileName != String.Empty)
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
+                       else
                                directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
-                       }
                        
                        directoryScanner.Scan ();
                        
-                       foreach (string matchedFile in directoryScanner.MatchedFilenames) {
-                               AddEvaluatedItem (evaluatedTo, matchedFile);
-                       }
+                       foreach (string matchedFile in directoryScanner.MatchedFilenames)
+                               AddEvaluatedItem (project, evaluatedTo, matchedFile);
                }
                
-               private void AddEvaluatedItem (bool evaluatedTo, string itemSpec)
+               void AddEvaluatedItem (Project project, bool evaluatedTo, string itemSpec)
                {
-                       Project project = this.parentItemGroup.Project;
                        BuildItemGroup big;                     
                        BuildItem bi = new BuildItem (this);
                        bi.finalItemSpec = itemSpec;
@@ -231,39 +294,50 @@ namespace Microsoft.Build.BuildEngine {
                        if (evaluatedTo) {
                                project.EvaluatedItems.AddItem (bi);
        
-                               if (!project.EvaluatedItemsByName.ContainsKey (bi.name)) {
-                                       big = new BuildItemGroup (null, project);
-                                       project.EvaluatedItemsByName.Add (bi.name, big);
+                               if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
+                                       big = new BuildItemGroup (null, project, null, true);
+                                       project.EvaluatedItemsByName.Add (bi.Name, big);
                                } else {
-                                       big = project.EvaluatedItemsByName [bi.name];
+                                       big = project.EvaluatedItemsByName [bi.Name];
                                }
 
                                big.AddItem (bi);
                        }
 
-                       if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.name)) {
-                               big = new BuildItemGroup (null, project);
-                               project.EvaluatedItemsByNameIgnoringCondition.Add (bi.name, big);
+                       if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
+                               big = new BuildItemGroup (null, project, null, true);
+                               project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
                        } else {
-                               big = project.EvaluatedItemsByNameIgnoringCondition [bi.name];
+                               big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
                        }
 
                        big.AddItem (bi);
                }
                
-               internal string ConvertToString (OldExpression transform)
+               internal string ConvertToString (Expression transform)
                {
                        return GetItemSpecFromTransform (transform);
                }
                
-               internal ITaskItem ConvertToITaskItem (OldExpression transform)
+               internal ITaskItem ConvertToITaskItem (Expression transform)
                {
                        TaskItem taskItem;
                        taskItem = new TaskItem (GetItemSpecFromTransform (transform), evaluatedMetadata);
                        return taskItem;
                }
 
-               private string GetItemSpecFromTransform (OldExpression transform)
+               internal void Detach ()
+               {
+                       if (FromXml)
+                               itemElement.ParentNode.RemoveChild (itemElement);
+                       else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.Detach ();
+                       }
+               }
+
+               string GetItemSpecFromTransform (Expression transform)
                {
                        StringBuilder sb;
                
@@ -275,9 +349,9 @@ namespace Microsoft.Build.BuildEngine {
                                        if (o is string) {
                                                sb.Append ((string)o);
                                        } else if (o is PropertyReference) {
-                                               sb.Append (((PropertyReference)o).ConvertToString ());
+                                               sb.Append (((PropertyReference)o).ConvertToString (parent_item_group.ParentProject));
                                        } else if (o is ItemReference) {
-                                               sb.Append (((ItemReference)o).ConvertToString ());
+                                               sb.Append (((ItemReference)o).ConvertToString (parent_item_group.ParentProject));
                                        } else if (o is MetadataReference) {
                                                sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
                                        }
@@ -286,6 +360,40 @@ namespace Microsoft.Build.BuildEngine {
                        }
                }
 
+               void SplitParentItem ()
+               {
+                       BuildItem parent = parent_item;
+                       List <BuildItem> list = new List <BuildItem> ();
+                       XmlElement insertAfter = parent.itemElement;
+                       foreach (BuildItem bi in parent.child_items) {
+                               BuildItem added = InsertElementAfter (parent, bi, insertAfter);
+                               insertAfter = added.itemElement;
+                               list.Add (added);
+                       }
+                       parent.parent_item_group.ReplaceWith (parent, list);
+                       parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
+               }
+
+               static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
+               {
+                       BuildItem newParent;
+
+                       XmlDocument doc = parent.itemElement.OwnerDocument;
+                       XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
+                       newElement.SetAttribute ("Include", child.FinalItemSpec);
+                       if (parent.itemElement.HasAttribute ("Condition"))
+                               newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
+                       foreach (XmlElement xe in parent.itemElement)
+                               newElement.AppendChild (xe.Clone ());
+                       parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
+
+                       newParent = new BuildItem (newElement, parent.parent_item_group);
+                       newParent.child_items.Add (child);
+                       child.parent_item = newParent;
+
+                       return newParent;
+               }
+
                public string Condition {
                        get {
                                if (FromXml)
@@ -296,6 +404,8 @@ namespace Microsoft.Build.BuildEngine {
                        set {
                                if (FromXml)
                                        itemElement.SetAttribute ("Condition", value);
+                               else if (!HasParentItem)
+                                       throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
                        }
                }
 
@@ -309,6 +419,8 @@ namespace Microsoft.Build.BuildEngine {
                        set {
                                if (FromXml)
                                        itemElement.SetAttribute ("Exclude", value);
+                               else
+                                       throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
                        }
                }
 
@@ -320,14 +432,20 @@ namespace Microsoft.Build.BuildEngine {
                        get {
                                if (FromXml)
                                        return itemElement.GetAttribute ("Include");
-                               else if (itemInclude != null)
-                                       return itemInclude;
+                               else if (HasParentItem)
+                                       return parent_item.Include;
                                else
-                                       return finalItemSpec;
+                                       return itemInclude;
                        }
                        set {
                                if (FromXml)
                                        itemElement.SetAttribute ("Include", value);
+                               else if (HasParentItem) {
+                                       if (parent_item.child_items.Count > 1)
+                                               SplitParentItem ();
+                                       parent_item.Include = value;
+                               } else
+                                       itemInclude = value;
                        }
                }
 
@@ -336,14 +454,47 @@ namespace Microsoft.Build.BuildEngine {
                }
 
                public string Name {
-                       get { return name; }
-                       set { name = value; }
+                       get {
+                               if (FromXml)
+                                       return itemElement.Name;
+                               else if (HasParentItem)
+                                       return parent_item.Name;
+                               else
+                                       return name;
+                       }
+                       set {
+                               if (FromXml) {
+                                       XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
+                                       newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
+                                       newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
+                                       foreach (XmlElement xe in itemElement)
+                                               newElement.AppendChild (xe.Clone ());
+                                       itemElement.ParentNode.ReplaceChild (newElement, itemElement);
+                                       itemElement = newElement;
+                               } else if (HasParentItem) {
+                                       if (parent_item.child_items.Count > 1)
+                                               SplitParentItem ();
+                                       parent_item.Name = value;
+                               } else
+                                       name = value;
+                       }
                }
                
                internal bool FromXml {
-                       get {
-                               return itemElement != null;
-                       }
+                       get { return itemElement != null; }
+               }
+               
+               internal bool HasParentItem {
+                       get { return parent_item != null; }
+               }
+
+               internal BuildItem ParentItem {
+                       get { return parent_item; }
+               }
+
+               internal BuildItemGroup ParentItemGroup {
+                       get { return parent_item_group; }
+                       set { parent_item_group = value; }
                }
        }
 }