1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Xml; 6 using System.Collections; 7 using System.Globalization; 8 9 using Microsoft.Build.BuildEngine.Shared; 10 11 using error = Microsoft.Build.BuildEngine.Shared.ErrorUtilities; 12 13 namespace Microsoft.Build.BuildEngine 14 { 15 /// <summary> 16 /// Class representing the Choose construct. The Choose class holds the list 17 /// of When blocks and the Otherwise block. It also contains other data such 18 /// as the XmlElement, parent project, etc. 19 /// </summary> 20 internal class Choose : IItemPropertyGrouping 21 { 22 #region Member Data 23 private ArrayList whenClauseList = null; 24 private When otherwiseClause = null; 25 private When whenLastTaken = null; 26 27 // If this is a persisted <Choose>, this boolean tells us whether 28 // it came from the main project file, or an imported project file. 29 private bool importedFromAnotherProject; 30 31 // Maximum nesting level of <Choose> elements. No reasonable project needs more 32 // than this. 33 private const int maximumChooseNesting = 50; 34 35 #endregion 36 37 #region Constructors 38 39 /// <summary> 40 /// Empty constructor for the Choose object. This really should only 41 /// be used by unit tests. 42 /// </summary> 43 /// <remarks> 44 /// </remarks> 45 /// <owner>DavidLe</owner> Choose( )46 internal Choose 47 ( 48 ) 49 { 50 whenClauseList = new ArrayList(); 51 } 52 53 /// <summary> 54 /// Constructor for the Choose object. Parses the contents of the Choose 55 /// and sets up list of When blocks 56 /// </summary> 57 /// <remarks> 58 /// </remarks> 59 /// <owner>DavidLe</owner> 60 /// <param name="parentProject"></param> 61 /// <param name="parentGroupingCollection"></param> 62 /// <param name="chooseElement"></param> 63 /// <param name="importedFromAnotherProject"></param> 64 /// <param name="nestingDepth">stack overflow guard</param> Choose( Project parentProject, GroupingCollection parentGroupingCollection, XmlElement chooseElement, bool importedFromAnotherProject, int nestingDepth )65 internal Choose 66 ( 67 Project parentProject, 68 GroupingCollection parentGroupingCollection, 69 XmlElement chooseElement, 70 bool importedFromAnotherProject, 71 int nestingDepth 72 ) 73 { 74 whenClauseList = new ArrayList(); 75 76 error.VerifyThrow(chooseElement != null, "Need valid <Choose> element."); 77 78 // Make sure this really is the <Choose> node. 79 ProjectXmlUtilities.VerifyThrowElementName(chooseElement, XMakeElements.choose); 80 81 // Stack overflow guard. The only way in the MSBuild file format that MSBuild elements can be 82 // legitimately nested without limit is the <Choose> construct. So, enforce a nesting limit 83 // to avoid blowing our stack. 84 nestingDepth++; 85 ProjectErrorUtilities.VerifyThrowInvalidProject(nestingDepth <= maximumChooseNesting, chooseElement, "ChooseOverflow", maximumChooseNesting); 86 87 this.importedFromAnotherProject = importedFromAnotherProject; 88 89 // This <Choose> is coming from an existing XML element, so 90 // walk through all the attributes and child elements, creating the 91 // necessary When objects. 92 93 // No attributes on the <Choose> element, so don't allow any. 94 ProjectXmlUtilities.VerifyThrowProjectNoAttributes(chooseElement); 95 96 bool foundOtherwise = false; 97 // Loop through the child nodes of the <Choose> element. 98 foreach (XmlNode chooseChildNode in chooseElement) 99 { 100 switch (chooseChildNode.NodeType) 101 { 102 // Handle XML comments under the <PropertyGroup> node (just ignore them). 103 case XmlNodeType.Comment: 104 // fall through 105 case XmlNodeType.Whitespace: 106 // ignore whitespace 107 break; 108 109 case XmlNodeType.Element: 110 // The only two types of child nodes that a <Choose> element can contain 111 // is are <When> elements and zero or one <Otherwise> elements. 112 113 ProjectXmlUtilities.VerifyThrowProjectValidNamespace((XmlElement)chooseChildNode); 114 115 if (chooseChildNode.Name == XMakeElements.when) 116 { 117 // don't allow <When> to follow <Otherwise> 118 ProjectErrorUtilities.VerifyThrowInvalidProject(!foundOtherwise, 119 chooseChildNode, "WhenNotAllowedAfterOtherwise"); 120 When newWhen = new When(parentProject, 121 parentGroupingCollection, 122 (XmlElement)chooseChildNode, 123 importedFromAnotherProject, 124 When.Options.ProcessWhen, 125 nestingDepth); 126 this.whenClauseList.Add(newWhen); 127 } 128 else if (chooseChildNode.Name == XMakeElements.otherwise) 129 { 130 ProjectErrorUtilities.VerifyThrowInvalidProject(!foundOtherwise, 131 chooseChildNode, "MultipleOtherwise"); 132 When newWhen = new When(parentProject, 133 parentGroupingCollection, 134 (XmlElement)chooseChildNode, 135 importedFromAnotherProject, 136 When.Options.ProcessOtherwise, 137 nestingDepth); 138 otherwiseClause = newWhen; 139 foundOtherwise = true; 140 } 141 else 142 { 143 ProjectXmlUtilities.ThrowProjectInvalidChildElement(chooseChildNode); 144 } 145 break; 146 147 default: 148 // Unrecognized child element. 149 ProjectXmlUtilities.ThrowProjectInvalidChildElement(chooseChildNode); 150 break; 151 } 152 } 153 ProjectErrorUtilities.VerifyThrowInvalidProject(this.whenClauseList.Count != 0, 154 chooseElement, "ChooseMustContainWhen"); 155 } 156 #endregion 157 158 #region Properties 159 160 /// <summary> 161 /// The list of When nodes inside this Choose 162 /// </summary> 163 internal ArrayList Whens 164 { 165 get 166 { 167 return whenClauseList; 168 } 169 } 170 171 /// <summary> 172 /// The Otherwise node inside this Choose. May be null. 173 /// </summary> 174 internal When Otherwise 175 { 176 get 177 { 178 return otherwiseClause; 179 } 180 } 181 182 /// <summary> 183 /// True if this Choose is located in an imported project. 184 /// </summary> 185 internal bool IsImported 186 { 187 get 188 { 189 return importedFromAnotherProject; 190 } 191 } 192 193 #endregion 194 195 #region Methods 196 197 /// <summary> 198 /// Evaluates the Choose clause by stepping through each when and evaluating. 199 /// </summary> 200 /// <remarks> 201 /// </remarks> 202 /// <owner>DavidLe</owner> 203 /// <param name="parentPropertyBag"></param> 204 /// <param name="ignoreCondition"></param> 205 /// <param name="honorCondition"></param> 206 /// <param name="conditionedPropertiesTable"></param> 207 /// <param name="pass"></param> Evaluate( BuildPropertyGroup parentPropertyBag, bool ignoreCondition, bool honorCondition, Hashtable conditionedPropertiesTable, ProcessingPass pass )208 internal void Evaluate 209 ( 210 BuildPropertyGroup parentPropertyBag, 211 bool ignoreCondition, bool honorCondition, 212 Hashtable conditionedPropertiesTable, 213 ProcessingPass pass 214 ) 215 { 216 if (pass == ProcessingPass.Pass1) 217 { 218 whenLastTaken = null; 219 bool whenTaken = false; 220 foreach (When currentWhen in this.whenClauseList) 221 { 222 if (currentWhen.EvaluateCondition(parentPropertyBag, conditionedPropertiesTable)) 223 { 224 whenTaken = true; 225 currentWhen.Evaluate(parentPropertyBag, ignoreCondition, honorCondition, conditionedPropertiesTable, pass); 226 whenLastTaken = currentWhen; 227 break; 228 } 229 } 230 if (!whenTaken && otherwiseClause != null) 231 { 232 // Process otherwise 233 whenLastTaken = otherwiseClause; 234 otherwiseClause.Evaluate(parentPropertyBag, ignoreCondition, honorCondition, conditionedPropertiesTable, pass); 235 } 236 } 237 else 238 { 239 ErrorUtilities.VerifyThrow(pass == ProcessingPass.Pass2, "ProcessingPass must be Pass1 or Pass2."); 240 if (whenLastTaken != null) 241 { 242 whenLastTaken.Evaluate(parentPropertyBag, ignoreCondition, honorCondition, conditionedPropertiesTable, pass); 243 } 244 } 245 } 246 247 #endregion 248 } 249 } 250