namespace System.Web.ModelBinding { using System; using System.Collections.Generic; using System.Linq; /// /// This is a container for prefix values. It normalizes all the values into dotted-form and then stores /// them in a sorted array. All queries for prefixes are also normalized to dotted-form, and searches /// for ContainsPrefix are done with a binary search. /// internal sealed class PrefixContainer { private readonly string[] _sortedValues; internal PrefixContainer(IEnumerable values) { if (values == null) { throw new ArgumentNullException("values"); } _sortedValues = values.Where(val => val != null).ToArray(); Array.Sort(_sortedValues, StringComparer.OrdinalIgnoreCase); } internal bool ContainsPrefix(string prefix) { if (prefix == null) { throw new ArgumentNullException("prefix"); } if (prefix.Length == 0) { return _sortedValues.Length > 0; // only match empty string when we have some value } return Array.BinarySearch(_sortedValues, prefix, new PrefixComparer(prefix)) > -1; } internal static bool IsPrefixMatch(string prefix, string testString) { if (testString == null) { return false; } if (prefix.Length == 0) { return true; // shortcut - non-null testString matches empty prefix } if (prefix.Length > testString.Length) { return false; // not long enough } if (!testString.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return false; // prefix doesn't match } if (testString.Length == prefix.Length) { return true; // exact match } // invariant: testString.Length > prefix.Length switch (testString[prefix.Length]) { case '.': case '[': return true; // known delimiters default: return false; // not known delimiter } } private sealed class PrefixComparer : IComparer { private string _prefix; public PrefixComparer(string prefix) { _prefix = prefix; } public int Compare(string x, string y) { string testString = Object.ReferenceEquals(x, _prefix) ? y : x; if (IsPrefixMatch(_prefix, testString)) { return 0; } return StringComparer.OrdinalIgnoreCase.Compare(x, y); } } } }