1 using System;
2 using System.Xml;
3 using System.Text.RegularExpressions;
4 using Microsoft.Build.Construction;
5 using Microsoft.Build.Framework.XamlTypes;
6 
7 namespace Microsoft.Build.Shared
8 {
9     /// <summary>
10     /// This class contains utility methods for Xaml types.
11     /// </summary>
12     static internal class XamlUtilities
13     {
14         /// <summary>
15         /// Gets an identifier for a property based on its name, its containing object's name and/or type. This is intended to
16         /// help the user zero in on the offending line/element in a xaml file, when we don't have access to a parser to
17         /// report line numbers.
18         /// </summary>
19         /// <param name="propertyName"> The name of the property. </param>
20         /// <param name="containingObjectName"> The name of the containing object. </param>
21         /// <param name="containingObject"> The object which contains this property. </param>
22         /// <returns> Returns "(containingObject's type name)containingObjectName.PropertyName". </returns>
GetPropertyId(string propertyName, string containingObjectName, object containingObject)23         internal static string GetPropertyId(string propertyName, string containingObjectName, object containingObject)
24         {
25             ErrorUtilities.VerifyThrowArgumentLength(propertyName, "propertyName");
26             ErrorUtilities.VerifyThrowArgumentLength(containingObjectName, "containingObjectName");
27             ErrorUtilities.VerifyThrowArgumentNull(containingObject, "containingObject");
28 
29             StringBuilder propertyId = new StringBuilder();
30 
31             propertyId.Append("(");
32             propertyId.Append(containingObject.GetType().Name);
33             propertyId.Append(")");
34 
35             if (!string.IsNullOrEmpty(containingObjectName))
36             {
37                 propertyId.Append(containingObjectName);
38             }
39 
40             propertyId.Append(".");
41 
42             propertyId.Append(propertyName);
43 
44             return propertyId.ToString();
45         }
46 
47         /// <summary>
48         /// Returns an identifier for a property based on its name and its containing object's type. Use this
49         /// overload when the containing object's name is not known (which can be the case when the property
50         /// being tested is the Name property itself).
51         /// </summary>
52         /// <param name="propertyName"> The name of the property. </param>
53         /// <param name="containingObject"> The object which contains this property. </param>
54         /// <returns> Returns "(containingObject's type name)unknown.PropertyName". </returns>
GetPropertyId(string propertyName, object containingObject)55         internal static string GetPropertyId(string propertyName, object containingObject)
56         {
57             string propertyId = GetPropertyId(propertyName, "unknown", containingObject);
58             return propertyId;
59         }
60 
61         /// <summary>
62         /// Throws an <see cref="ArgumentNullException"/> if the given property is null.
63         /// </summary>
64         /// <param name="property">The property to test.</param>
65         /// <param name="propertyId">An identifier of the property to check.</param>
VerifyThrowPropertyNotSet(object property, string propertyId)66         internal static void VerifyThrowPropertyNotSet(object property, string propertyId)
67         {
68             ErrorUtilities.VerifyThrowArgumentLength(propertyId, "propertyId");
69 
70             if (property == null)
71             {
72                 ErrorUtilities.VerifyThrowArgumentNull(null, propertyId, Strings.PropertyValueMustBeSet);
73             }
74         }
75 
76         /// <summary>
77         /// Throws an <see cref="ArgumentNullException"/> if the given property is null.
78         /// </summary>
79         /// <param name="property">The property to test.</param>
80         /// <param name="propertyId">An identifier of the property to check.</param>
81         /// <param name="unformattedMessage"> The text message to display. </param>
VerifyThrowPropertyNotSet(object property, string propertyId, string unformattedMessage)82         internal static void VerifyThrowPropertyNotSet(object property, string propertyId, string unformattedMessage)
83         {
84             ErrorUtilities.VerifyThrowArgumentLength(propertyId, "propertyId");
85 
86             if (property == null)
87             {
88                 ErrorUtilities.VerifyThrowArgumentNull(null, propertyId, unformattedMessage);
89             }
90         }
91 
92         /// <summary>
93         /// Throws an <see cref="ArgumentException"/> if the given property's value is the empty string.
94         /// </summary>
95         /// <param name="property">The parameter to test.</param>
96         /// <param name="propertyId">An identifier of the property to check.</param>
VerifyThrowPropertyEmptyString(string property, string propertyId)97         internal static void VerifyThrowPropertyEmptyString(string property, string propertyId)
98         {
99             ErrorUtilities.VerifyThrowArgumentNull(property, "property");
100             ErrorUtilities.VerifyThrowArgumentLength(propertyId, "propertyId");
101 
102             if (property.Length == 0)
103             {
104                 ErrorUtilities.ThrowArgument(Strings.PropertyCannotBeSetToTheEmptyString, propertyId);
105             }
106         }
107 
108         /// <summary>
109         /// Throws an <see cref="ArgumentNullException"/> if the given property is null and
110         /// <see cref="ArgumentException"/> if the given property's value is the empty string.
111         /// </summary>
112         /// <param name="property">The parameter to test.</param>
113         /// <param name="propertyId">An identifier of the property to check.</param>
VerifyThrowPropertyNotSetOrEmptyString(string property, string propertyId)114         internal static void VerifyThrowPropertyNotSetOrEmptyString(string property, string propertyId)
115         {
116             VerifyThrowPropertyNotSet(property, propertyId);
117             VerifyThrowPropertyEmptyString(property, propertyId);
118         }
119 
120         /// <summary>
121         /// Throws an <see cref="ArgumentException"/> if the given list property has zero elements.
122         /// </summary>
123         /// <param name="listProperty"> The list parameter to test. </param>
124         /// <param name="propertyId"> An identifier of the property to check. </param>
VerifyThrowListPropertyEmpty(IList listProperty, string propertyId)125         internal static void VerifyThrowListPropertyEmpty(IList listProperty, string propertyId)
126         {
127             ErrorUtilities.VerifyThrowArgumentNull(listProperty, "listProperty");
128             ErrorUtilities.VerifyThrowArgumentLength(propertyId, "propertyId");
129 
130             if (listProperty.Count == 0)
131             {
132                 ErrorUtilities.ThrowArgument(Strings.ListPropertyShouldHaveAtLeastOneElement, propertyId);
133             }
134         }
135 
136         #region Extension Methods
137 
138         /// <summary>
139         /// Validates the properties of this object. This method should be called
140         /// after initialization is complete.
141         /// </summary>
Validate(this Argument type)142         internal void Validate(this Argument type)
143         {
144             string propertyId = GetPropertyId("Property", type);
145             VerifyThrowPropertyNotSetOrEmptyString(type.Property, propertyId);
146         }
147 
148         /// <summary>
149         /// Validates the properties of this object. This method should be called
150         /// after initialization is complete.
151         /// </summary>
Validate(this BaseProperty type)152         internal void Validate(this BaseProperty type)
153         {
154             string namePropertyId = GetPropertyId("Name", type);
155             VerifyThrowPropertyNotSetOrEmptyString(type.Name, namePropertyId);
156 
157             string categoryPropertyId = GetPropertyId("Category", type.Name, type);
158             VerifyThrowPropertyEmptyString(typeCategory, categoryPropertyId);
159 
160             // Validate children.
161             if (null != type.DataSource)
162             {
163                 type.DataSource.Validate();
164             }
165 
166             foreach (Argument argument in type.Arguments)
167             {
168                 argument.Validate();
169             }
170 
171             foreach (ValueEditor editor in type.ValueEditors)
172             {
173                 editor.Validate();
174             }
175 
176             // Validate any known derivations.
177             BoolProperty boolProp = type as BoolProperty;
178             if (null != boolProp)
179             {
180                 return;
181             }
182 
183             DynamicEnumProperty dynamicEnumProp = type as DynamicEnumProperty;
184             if (dynamicEnumProp != null)
185             {
186                 dynamicEnumProp.Validate();
187                 return;
188             }
189 
190             EnumProperty enumProp = type as EnumProperty;
191             if (enumProp != null)
192             {
193                 enumProp.Validate();
194                 return;
195             }
196 
197             IntProperty intProp = type as IntProperty;
198             if (intProp != null)
199             {
200                 intProp.Validate();
201                 return;
202             }
203 
204             StringListProperty stringListProp = type as StringListProperty;
205             if (stringListProp != null)
206             {
207                 return;
208             }
209 
210             StringProperty stringProp = type as StringProperty;
211             if (stringProp != null)
212             {
213                 return;
214             }
215 
216             // Unknown derivation, but that's ok.
217         }
218 
219 
220         /// <summary>
221         /// Validates the properties of this object. This method should be called
222         /// after initialization is complete.
223         /// </summary>
Validate(this Category type)224         internal void Validate(this Category type)
225         {
226             string namePropertyId = GetPropertyId("Name", type);
227             VerifyThrowPropertyNotSetOrEmptyString(type.Name, namePropertyId);
228         }
229 
230 
231         /// <summary>
232         /// Validates the properties of this object. This method should be called
233         /// after initialization is complete.
234         /// </summary>
Validate(this DataSource type)235         internal void Validate(this DataSource type)
236         {
237             string persistencePropertyId = GetPropertyId("Persistence", type);
238             VerifyThrowPropertyNotSetOrEmptyString(type.Persistence, persistencePropertyId);
239         }
240 
241         /// <summary>
242         /// Validate the content type data integrity afte deserializing from XAML file
243         /// </summary>
Validate(this ContentType type)244         internal void Validate(this ContentType type)
245         {
246             // content type must at least declare name, and msbuild ItemType to be workable at minimum level
247             string namePropertyId = GetPropertyId("Name", type);
248             VerifyThrowPropertyNotSetOrEmptyString(type.Name, namePropertyId);
249 
250             string itemTypePropertyId = GetPropertyId("ItemType", type);
251             VerifyThrowPropertyNotSetOrEmptyString(type.ItemType, itemTypePropertyId);
252         }
253 
254         /// <summary>
255         /// Validates the properties of this object. This method should be called
256         /// after initialization is complete.
257         /// </summary>
Validate(this DynamicEnumProperty type)258         internal void Validate(this DynamicEnumProperty type)
259         {
260             (type as BaseProperty).Validate();
261             ErrorUtilities.VerifyThrowArgumentLength(type.EnumProvider, "EnumProvider");
262         }
263 
264 
265         /// <summary>
266         /// Validates the properties of this object. This method should be called
267         /// after initialization is complete.
268         /// </summary>
Validate(this EnumProperty type)269         private void Validate(this EnumProperty type)
270         {
271             (type as BaseProperty).Validate();
272 
273             // Validate that the "Default" field is not set on this property.
274             string defaultPropertyId = GetPropertyId("Default", type.Name, type);
275             if (null != Default)
276             {
277                 ErrorUtilities.ThrowArgument(Strings.CannotSetDefaultPropertyOnEnumProperty, typeof(EnumProperty).Name, typeof(EnumValue).Name);
278             }
279 
280             // Make sure that at least one value was defined in AdmissibleValues.
281             string admissibleValuesId = GetPropertyId("AdmissibleValues", type.Name, type);
282             VerifyThrowListPropertyEmpty(type.AdmissibleValues, admissibleValuesId);
283 
284             // Validate that only one of the EnumValues under AdmissibleValues is marked IsDefault.
285             string admissibleValuesPropertyId = GetPropertyId("AdmissibleValues", type.Name, type);
286 
287             bool seen = false;
288             foreach (EnumValue enumValue in type.AdmissibleValues)
289             {
290                 if (enumValue.IsDefault)
291                 {
292                     if (!seen)
293                     {
294                         seen = true;
295                     }
296                     else
297                     {
298                         ErrorUtilities.ThrowArgument(Strings.OnlyOneEnumValueCanBeSetAsDefault, typeof(EnumValue).Name, admissibleValuesPropertyId);
299                     }
300                 }
301             }
302         }
303 
304 
305         /// <summary>
306         /// Validates the properties of this object. This method should be called
307         /// after initialization is complete.
308         /// </summary>
Validate(this IntProperty type)309         internal void Validate(this IntProperty type)
310         {
311             (type as BaseProperty).Validate();
312 
313             if (null != type.MaxValue && null != type.MinValue)
314             {
315                 if (type.MinValue > type.MaxValue)
316                 {
317                     string minValuePropertyId = GetPropertyId("MinValue", type.Name, type);
318                     ErrorUtilities.ThrowArgument(Strings.MinValueShouldNotBeGreaterThanMaxValue, minValuePropertyId);
319                 }
320             }
321         }
322 
323         /// <summary>
324         /// Validate the content type data integrity afte deserializing from XAML file
325         /// </summary>
Validate(this ItemType type)326         internal void Validate(this ItemType type)
327         {
328             // content type must at least declare name, and msbuild ItemType to be workable at minimum level
329             string namePropertyId = GetPropertyId("Name", type);
330             VerifyThrowPropertyNotSetOrEmptyString(type.Name, namePropertyId);
331 
332             if (type.DisplayName == null)
333             {
334                 type.DisplayName = type.Name;
335             }
336         }
337 
338         /// <summary>
339         /// Validates the properties of this object. This method should be called
340         /// after initialization is complete.
341         /// </summary>
Validate(this Rule type)342         internal void Validate(this Rule type)
343         {
344             // Validate "Name" property.
345             string namePropertyId = GetPropertyId("Name", type);
346             VerifyThrowPropertyNotSetOrEmptyString(type.Name, namePropertyId);
347 
348             // Make sure that at least one Property was defined in this Rule.
349             string propertiesId = XamlErrorUtilities.GetPropertyId("Properties", Name, this);
350             VerifyThrowListPropertyEmpty(type.Properties, propertiesId);
351 
352             // Validate the child objects
353             foreach (BaseProperty property in type.Properties)
354             {
355                 property.Validate();
356             }
357 
358             foreach (Category category in type.Categories)
359             {
360                 category.Validate();
361             }
362 
363             // If the DataSource property is not defined on this Rule, check that a DataSource is
364             // specified locally on every property.
365             if (null == type.DataSource)
366             {
367                 foreach (BaseProperty property in type.Properties)
368                 {
369                     string dataSourcePropertyId = GetPropertyId("DataSource", property.Name, property);
370                     VerifyThrowPropertyNotSet(property.DataSource, dataSourcePropertyId, Strings.DataSourceMustBeDefinedOnPropertyOrOnRule);
371                 }
372             }
373             else
374             {
375                 type.DataSource.Validate();
376             }
377 
378             // Create a HashSet for O(1) lookup.
379             HashSet<string> propertyNames = new HashSet<string>();
380             foreach (BaseProperty property in type.Properties)
381             {
382                 if (!propertyNames.Contains(property.Name))
383                 {
384                     propertyNames.Add(property.Name);
385                 }
386             }
387 
388             // Validate that every argument refers to a valid property.
389             foreach (BaseProperty property in type.Properties)
390             {
391                 if (property.Arguments == null || property.Arguments.Count == 0)
392                 {
393                     continue;
394                 }
395 
396                 foreach (Argument argument in property.Arguments)
397                 {
398                     if (!propertyNames.Contains(argument.Property))
399                     {
400                         ErrorUtilities.ThrowArgument(Strings.PropertyReferredToByArgumentDoesNotExist, argument.Property);
401                     }
402                 }
403             }
404         }
405 
406         /// <summary>
407         /// Validates the properties of this object. This method should be called
408         /// after initialization is complete.
409         /// </summary>
Validate(this RuleBag type)410         internal void Validate(this RuleBag type)
411         {
412             // Make sure that at least one Rule was defined in this RuleBag.
413             string rulesId = GetPropertyId("Rules", type);
414             VerifyThrowListPropertyEmpty(type.Rules, rulesId);
415 
416             foreach (Rule rule in Rules)
417             {
418                 rule.Validate();
419             }
420         }
421 
422         /// <summary>
423         /// Validates the properties of this object. This method should be called
424         /// after initialization is complete.
425         /// </summary>
Validate(this ValueEditor type)426         internal void Validate(this ValueEditor type)
427         {
428             string propertyId = GetPropertyId("EditorType", type);
429             VerifyThrowPropertyNotSetOrEmptyString(type.EditorType, propertyId);
430         }
431 
432         #endregion
433 
434     }
435 }
436