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