e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
410 lines
19 KiB
C#
410 lines
19 KiB
C#
//-----------------------------------------------------------------------------
|
|
// 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<string, List<RuntimeArgument>> overloadGroups, List<RuntimeArgument> requiredArgumentsNotInOverloadGroups, IDictionary<string, object> inputs, ref IList<ValidationError> 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<string, bool> configurationResults = new Dictionary<string, bool>();
|
|
string configuredGroupName = string.Empty;
|
|
int configuredCount = 0;
|
|
int overloadGroupsWithNoRequiredArgs = 0;
|
|
|
|
foreach (KeyValuePair<string, List<RuntimeArgument>> entry in overloadGroups)
|
|
{
|
|
string groupName = entry.Key;
|
|
configurationResults.Add(groupName, false);
|
|
IEnumerable<RuntimeArgument> 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<RuntimeArgument> 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<RuntimeArgument> configuredOverloadSet = new HashSet<RuntimeArgument>(overloadGroups[configuredGroupName]);
|
|
Predicate<RuntimeArgument> checkIfArgumentIsBound = new Predicate<RuntimeArgument>(localArgument => CheckIfArgumentIsBound(localArgument, inputs));
|
|
|
|
List<string> disjointGroups = null;
|
|
if (!equivalenceInfo.DisjointGroupsDictionary.IsNullOrEmpty())
|
|
{
|
|
equivalenceInfo.DisjointGroupsDictionary.TryGetValue(configuredGroupName, out disjointGroups);
|
|
}
|
|
|
|
List<string> 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<RuntimeArgument> overloadGroupSet = new HashSet<RuntimeArgument>(overloadGroups[groupName]);
|
|
IEnumerable<RuntimeArgument> intersectSet = overloadGroupSet.Intersect(configuredOverloadSet);
|
|
List<RuntimeArgument> 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<string> 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<string, List<RuntimeArgument>> overloadGroups, out List<RuntimeArgument> requiredArgumentsNotInOverloadGroups, out OverloadGroupEquivalenceInfo equivalenceInfo, ref IList<ValidationError> validationErrors)
|
|
{
|
|
overloadGroups = null;
|
|
requiredArgumentsNotInOverloadGroups = null;
|
|
IEnumerable<RuntimeArgument> runtimeArguments = activity.RuntimeArguments;
|
|
|
|
foreach (RuntimeArgument runtimeArgument in runtimeArguments)
|
|
{
|
|
if (!runtimeArgument.OverloadGroupNames.IsNullOrEmpty())
|
|
{
|
|
foreach (string groupName in runtimeArgument.OverloadGroupNames)
|
|
{
|
|
if (overloadGroups == null)
|
|
{
|
|
overloadGroups = new Dictionary<string, List<RuntimeArgument>>();
|
|
}
|
|
|
|
List<RuntimeArgument> arguments = null;
|
|
if (!overloadGroups.TryGetValue(groupName, out arguments))
|
|
{
|
|
arguments = new List<RuntimeArgument>();
|
|
overloadGroups.Add(groupName, arguments);
|
|
}
|
|
arguments.Add(runtimeArgument);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (runtimeArgument.IsRequired)
|
|
{
|
|
if (requiredArgumentsNotInOverloadGroups == null)
|
|
{
|
|
requiredArgumentsNotInOverloadGroups = new List<RuntimeArgument>();
|
|
}
|
|
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<string, List<RuntimeArgument>> overloadGroups, ref IList<ValidationError> 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<string, List<string>> 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<string> 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<string, List<string>> entry in equivalenceInfo.SupersetOfGroupsDictionary)
|
|
{
|
|
IList<string> 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<RuntimeArgument>((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<string, List<RuntimeArgument>> 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<RuntimeArgument> group1Args = new HashSet<RuntimeArgument>(groupDefinitions[group1]);
|
|
for (int j = i + 1; j < groupNames.Length; j++)
|
|
{
|
|
string group2 = groupNames[j];
|
|
HashSet<RuntimeArgument> group2Args = new HashSet<RuntimeArgument>(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<string, object> 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<string, object> inputs)
|
|
{
|
|
return !(CheckIfArgumentIsNotBound(argument, inputs));
|
|
}
|
|
|
|
public class OverloadGroupEquivalenceInfo
|
|
{
|
|
Dictionary<string, List<string>> equivalentGroupsDictionary;
|
|
Dictionary<string, List<string>> supersetOfGroupsDictionary;
|
|
Dictionary<string, List<string>> overlappingGroupsDictionary;
|
|
Dictionary<string, List<string>> disjointGroupsDictionary;
|
|
|
|
public OverloadGroupEquivalenceInfo()
|
|
{
|
|
}
|
|
|
|
public Dictionary<string, List<string>> EquivalentGroupsDictionary
|
|
{
|
|
get
|
|
{
|
|
return this.equivalentGroupsDictionary;
|
|
}
|
|
}
|
|
|
|
public Dictionary<string, List<string>> SupersetOfGroupsDictionary
|
|
{
|
|
get
|
|
{
|
|
return this.supersetOfGroupsDictionary;
|
|
}
|
|
}
|
|
|
|
public Dictionary<string, List<string>> OverlappingGroupsDictionary
|
|
{
|
|
get
|
|
{
|
|
return this.overlappingGroupsDictionary;
|
|
}
|
|
}
|
|
|
|
public Dictionary<string, List<string>> 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<string, List<string>> dictionary, string dictionaryKey, string listEntry)
|
|
{
|
|
if (dictionary == null)
|
|
{
|
|
dictionary = new Dictionary<string, List<string>>();
|
|
}
|
|
|
|
List<string> listValues = null;
|
|
if (!dictionary.TryGetValue(dictionaryKey, out listValues))
|
|
{
|
|
listValues = new List<string> { 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|