//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities.Validation { using System; using System.Activities; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime; using System.Globalization; static class ValidationHelper { public static void ValidateArguments(Activity activity, OverloadGroupEquivalenceInfo equivalenceInfo, Dictionary> overloadGroups, List requiredArgumentsNotInOverloadGroups, IDictionary inputs, ref IList validationErrors) { if (!requiredArgumentsNotInOverloadGroups.IsNullOrEmpty()) { // 1. Check if there are any Required arguments (outside overload groups) that were not specified. foreach (RuntimeArgument argument in requiredArgumentsNotInOverloadGroups) { if (CheckIfArgumentIsNotBound(argument, inputs)) { ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.RequiredArgumentValueNotSupplied(argument.Name), false, argument.Name, activity)); } } } if (!overloadGroups.IsNullOrEmpty()) { //1. Check to see if any of the overload groups are configured. // An overload group is considered to be completely configured if all it's required arguments // are non-null. If an overload group does not have any required arguments then the group is // considered configured if any of the optional arguments are configured. Dictionary configurationResults = new Dictionary(); string configuredGroupName = string.Empty; int configuredCount = 0; int overloadGroupsWithNoRequiredArgs = 0; foreach (KeyValuePair> entry in overloadGroups) { string groupName = entry.Key; configurationResults.Add(groupName, false); IEnumerable requiredArguments = entry.Value.Where((a) => a.IsRequired); if (requiredArguments.Count() > 0) { if (requiredArguments.All(localArgument => CheckIfArgumentIsBound(localArgument, inputs))) { configurationResults[groupName] = true; configuredGroupName = groupName; configuredCount++; } } else { overloadGroupsWithNoRequiredArgs++; IEnumerable optionalArguments = entry.Value.Where((a) => !a.IsRequired); if (optionalArguments.Any(localArgument => CheckIfArgumentIsBound(localArgument, inputs))) { configurationResults[groupName] = true; configuredGroupName = groupName; configuredCount++; } } } //2. It's an error if none of the groups are configured unless there // is atleast one overload group with no required arguments in it. if (configuredCount == 0) { if (overloadGroupsWithNoRequiredArgs == 0) { ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.NoOverloadGroupsAreConfigured, false, activity)); } } //3. If only one overload group was configured, ensure none of the disjoint/overlapping groups have any // required or optional activity arguments set. else if (configuredCount == 1) { HashSet configuredOverloadSet = new HashSet(overloadGroups[configuredGroupName]); Predicate checkIfArgumentIsBound = new Predicate(localArgument => CheckIfArgumentIsBound(localArgument, inputs)); List disjointGroups = null; if (!equivalenceInfo.DisjointGroupsDictionary.IsNullOrEmpty()) { equivalenceInfo.DisjointGroupsDictionary.TryGetValue(configuredGroupName, out disjointGroups); } List overlappingGroups = null; if (!equivalenceInfo.OverlappingGroupsDictionary.IsNullOrEmpty()) { equivalenceInfo.OverlappingGroupsDictionary.TryGetValue(configuredGroupName, out overlappingGroups); } // Iterate over the groups that may not be completely configured. foreach (string groupName in configurationResults.Keys.Where((k) => configurationResults[k] == false)) { // Check if the partially configured group name is in the disjoint groups list. // If so, find all configured arguments. if (disjointGroups != null && disjointGroups.Contains(groupName)) { foreach (RuntimeArgument configuredArgument in overloadGroups[groupName].FindAll(checkIfArgumentIsBound)) { ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.ExtraOverloadGroupPropertiesConfigured(configuredGroupName, configuredArgument.Name, groupName), false, activity)); } } else if (overlappingGroups != null && overlappingGroups.Contains(groupName)) { // Find all arguments of the Overlapping group that are not in the configuredOverloadSet. HashSet overloadGroupSet = new HashSet(overloadGroups[groupName]); IEnumerable intersectSet = overloadGroupSet.Intersect(configuredOverloadSet); List exceptList = overloadGroupSet.Except(intersectSet).ToList(); foreach (RuntimeArgument configuredArgument in exceptList.FindAll(checkIfArgumentIsBound)) { ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.ExtraOverloadGroupPropertiesConfigured(configuredGroupName, configuredArgument.Name, groupName), false, activity)); } } } } //4. If more than one overload group is configured, generate an error. else { IEnumerable configuredGroups = configurationResults.Keys.Where((k) => configurationResults[k]).OrderBy((k) => k, StringComparer.Ordinal); ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.MultipleOverloadGroupsConfigured(configuredGroups.AsCommaSeparatedValues()), false, activity)); } } } public static bool GatherAndValidateOverloads(Activity activity, out Dictionary> overloadGroups, out List requiredArgumentsNotInOverloadGroups, out OverloadGroupEquivalenceInfo equivalenceInfo, ref IList validationErrors) { overloadGroups = null; requiredArgumentsNotInOverloadGroups = null; IEnumerable runtimeArguments = activity.RuntimeArguments; foreach (RuntimeArgument runtimeArgument in runtimeArguments) { if (!runtimeArgument.OverloadGroupNames.IsNullOrEmpty()) { foreach (string groupName in runtimeArgument.OverloadGroupNames) { if (overloadGroups == null) { overloadGroups = new Dictionary>(); } List arguments = null; if (!overloadGroups.TryGetValue(groupName, out arguments)) { arguments = new List(); overloadGroups.Add(groupName, arguments); } arguments.Add(runtimeArgument); } } else { if (runtimeArgument.IsRequired) { if (requiredArgumentsNotInOverloadGroups == null) { requiredArgumentsNotInOverloadGroups = new List(); } requiredArgumentsNotInOverloadGroups.Add(runtimeArgument); } } } equivalenceInfo = GetOverloadGroupEquivalence(overloadGroups); return ValidateOverloadGroupDefinitions(activity, equivalenceInfo, overloadGroups, ref validationErrors); } // This method checks if any of the overload groups are equivalent and/or are a subset/superset of another // overload group. Returns true if there are not any errors. static bool ValidateOverloadGroupDefinitions(Activity activity, OverloadGroupEquivalenceInfo equivalenceInfo, Dictionary> overloadGroups, ref IList validationErrors) { Fx.Assert(equivalenceInfo != null, "equivalenceInfo should have been setup before calling this method"); bool noErrors = true; if (!equivalenceInfo.EquivalentGroupsDictionary.IsNullOrEmpty()) { Hashtable keysVisited = new Hashtable(equivalenceInfo.EquivalentGroupsDictionary.Count); foreach (KeyValuePair> entry in equivalenceInfo.EquivalentGroupsDictionary) { if (!keysVisited.Contains(entry.Key)) { string[] equivalentGroups = new string[entry.Value.Count + 1]; equivalentGroups[0] = entry.Key; entry.Value.CopyTo(equivalentGroups, 1); IEnumerable sortedList = equivalentGroups.OrderBy((s) => s, StringComparer.Ordinal); ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.OverloadGroupsAreEquivalent(sortedList.AsCommaSeparatedValues()), false, activity)); noErrors = false; for (int i = 0; i < equivalentGroups.Length; i++) { keysVisited.Add(equivalentGroups[i], null); } } } } else if (!equivalenceInfo.SupersetOfGroupsDictionary.IsNullOrEmpty()) { foreach (KeyValuePair> entry in equivalenceInfo.SupersetOfGroupsDictionary) { IList sortedList = entry.Value.OrderBy((s) => s, StringComparer.Ordinal).ToList(); string[] subsetGroups = new string[sortedList.Count]; int index = 0; // Select only subsets that have atleast one required argument in them. // We ignore the subsets that have no required arguments in them. foreach (string subsetGroup in sortedList) { if (overloadGroups[subsetGroup].Any((a) => a.IsRequired)) { subsetGroups[index++] = subsetGroup; } } // If there were any subsets with required arguments generate an error. if (index > 0) { ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.OverloadGroupHasSubsets(entry.Key, subsetGroups.AsCommaSeparatedValues()), false, activity)); noErrors = false; } } } return noErrors; } static OverloadGroupEquivalenceInfo GetOverloadGroupEquivalence(Dictionary> groupDefinitions) { OverloadGroupEquivalenceInfo overloadGroupsInfo = new OverloadGroupEquivalenceInfo(); if (!groupDefinitions.IsNullOrEmpty()) { string[] groupNames = new string[groupDefinitions.Count]; groupDefinitions.Keys.CopyTo(groupNames, 0); for (int i = 0; i < groupNames.Length; i++) { string group1 = groupNames[i]; HashSet group1Args = new HashSet(groupDefinitions[group1]); for (int j = i + 1; j < groupNames.Length; j++) { string group2 = groupNames[j]; HashSet group2Args = new HashSet(groupDefinitions[group2]); if (group1Args.IsProperSupersetOf(group2Args)) { overloadGroupsInfo.SetAsSuperset(group1, group2); } else if (group1Args.IsProperSubsetOf(group2Args)) { overloadGroupsInfo.SetAsSuperset(group2, group1); } else if (group1Args.SetEquals(group2Args)) { overloadGroupsInfo.SetAsEquivalent(group1, group2); } else if (group1Args.Overlaps(group2Args)) { overloadGroupsInfo.SetAsOverlapping(group1, group2); } else // the groups are disjoint. { overloadGroupsInfo.SetAsDisjoint(group1, group2); } } } } return overloadGroupsInfo; } static bool CheckIfArgumentIsNotBound(RuntimeArgument argument, IDictionary inputs) { if (argument.Owner != null && argument.Owner.Parent == null && ArgumentDirectionHelper.IsOut(argument.Direction)) { // Skip the validation for root node's out argument // as it will be added to the output dictionary return false; } if (argument.BoundArgument != null && argument.BoundArgument.Expression != null) { return false; } if (inputs != null) { if (inputs.ContainsKey(argument.Name)) { return false; } } return true; } static bool CheckIfArgumentIsBound(RuntimeArgument argument, IDictionary inputs) { return !(CheckIfArgumentIsNotBound(argument, inputs)); } public class OverloadGroupEquivalenceInfo { Dictionary> equivalentGroupsDictionary; Dictionary> supersetOfGroupsDictionary; Dictionary> overlappingGroupsDictionary; Dictionary> disjointGroupsDictionary; public OverloadGroupEquivalenceInfo() { } public Dictionary> EquivalentGroupsDictionary { get { return this.equivalentGroupsDictionary; } } public Dictionary> SupersetOfGroupsDictionary { get { return this.supersetOfGroupsDictionary; } } public Dictionary> OverlappingGroupsDictionary { get { return this.overlappingGroupsDictionary; } } public Dictionary> DisjointGroupsDictionary { get { return this.disjointGroupsDictionary; } } public void SetAsEquivalent(string group1, string group2) { // Setup EquivalentGroups for group1 this.AddToDictionary(ref this.equivalentGroupsDictionary, group1, group2); // Setup EquivalentGroups for group2 this.AddToDictionary(ref this.equivalentGroupsDictionary, group2, group1); } public void SetAsSuperset(string group1, string group2) { this.AddToDictionary(ref this.supersetOfGroupsDictionary, group1, group2); } public void SetAsOverlapping(string group1, string group2) { // Setup OverlapGroups for group1 this.AddToDictionary(ref this.overlappingGroupsDictionary, group1, group2); // Setup OverlapGroups for group2 this.AddToDictionary(ref this.overlappingGroupsDictionary, group2, group1); } public void SetAsDisjoint(string group1, string group2) { // Setup DisjointGroups for group1 this.AddToDictionary(ref this.disjointGroupsDictionary, group1, group2); // Setup DisjointGroups for group2 this.AddToDictionary(ref this.disjointGroupsDictionary, group2, group1); } void AddToDictionary(ref Dictionary> dictionary, string dictionaryKey, string listEntry) { if (dictionary == null) { dictionary = new Dictionary>(); } List listValues = null; if (!dictionary.TryGetValue(dictionaryKey, out listValues)) { listValues = new List { listEntry }; dictionary.Add(dictionaryKey, listValues); } else { Fx.Assert(!listValues.Contains(listEntry), string.Format(CultureInfo.InvariantCulture, "Duplicate group entry '{0}' getting added.", listEntry)); listValues.Add(listEntry); } } } } }