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