File: ModelBinding\PrefixContainer.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
namespace System.Web.ModelBinding {
    using System;
    using System.Collections.Generic;
    using System.Linq;
 
    /// <summary>
    /// 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.
    /// </summary>
    internal sealed class PrefixContainer {
 
        private readonly string[] _sortedValues;
 
        internal PrefixContainer(IEnumerable<string> 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<String> {
            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);
            }
        }
 
    }
}