File: System\ServiceModel\Channels\SequenceRangeCollection.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Text;
 
namespace System.ServiceModel.Channels
{
    internal abstract class SequenceRangeCollection
    {
        private static readonly LowerComparer s_lowerComparer = new LowerComparer();
        private static readonly UpperComparer s_upperComparer = new UpperComparer();
 
        public static SequenceRangeCollection Empty { get; } = new EmptyRangeCollection();
 
        public abstract SequenceRange this[int index] { get; }
        public abstract int Count { get; }
        public abstract bool Contains(long number);
        public abstract SequenceRangeCollection MergeWith(long number);
        public abstract SequenceRangeCollection MergeWith(SequenceRange range);
 
        private static SequenceRangeCollection GeneralCreate(SequenceRange[] sortedRanges)
        {
            if (sortedRanges.Length == 0)
            {
                return Empty;
            }
            else if (sortedRanges.Length == 1)
            {
                return new SingleItemRangeCollection(sortedRanges[0]);
            }
            else
            {
                return new MultiItemRangeCollection(sortedRanges);
            }
        }
 
        private static SequenceRangeCollection GeneralMerge(SequenceRange[] sortedRanges, SequenceRange range)
        {
            if (sortedRanges.Length == 0)
            {
                return new SingleItemRangeCollection(range);
            }
 
            int lowerBound;
 
            if (sortedRanges.Length == 1)
            {
                // Avoid performance hit of binary search in single range case
                if (range.Lower == sortedRanges[0].Upper)
                {
                    lowerBound = 0;
                }
                else if (range.Lower < sortedRanges[0].Upper)
                {
                    lowerBound = ~0;
                }
                else
                {
                    lowerBound = ~1;
                }
            }
            else
            {
                lowerBound = Array.BinarySearch(sortedRanges, new SequenceRange(range.Lower), s_upperComparer);
            }
 
            if (lowerBound < 0)
            {
                lowerBound = ~lowerBound;
 
                if ((lowerBound > 0) && (sortedRanges[lowerBound - 1].Upper == range.Lower - 1))
                {
                    lowerBound--;
                }
 
                if (lowerBound == sortedRanges.Length)
                {
                    SequenceRange[] returnedRanges = new SequenceRange[sortedRanges.Length + 1];
                    Array.Copy(sortedRanges, returnedRanges, sortedRanges.Length);
                    returnedRanges[sortedRanges.Length] = range;
                    return GeneralCreate(returnedRanges);
                }
            }
 
            int upperBound;
 
            if (sortedRanges.Length == 1)
            {
                // Avoid performance hit of binary search in single range case
                if (range.Upper == sortedRanges[0].Lower)
                {
                    upperBound = 0;
                }
                else if (range.Upper < sortedRanges[0].Lower)
                {
                    upperBound = ~0;
                }
                else
                {
                    upperBound = ~1;
                }
            }
            else
            {
                upperBound = Array.BinarySearch(sortedRanges, new SequenceRange(range.Upper), s_lowerComparer);
            }
 
            if (upperBound < 0)
            {
                upperBound = ~upperBound;
 
                if (upperBound > 0)
                {
                    if ((upperBound == sortedRanges.Length) || (sortedRanges[upperBound].Lower != range.Upper + 1))
                    {
                        upperBound--;
                    }
                }
                else if (sortedRanges[0].Lower > range.Upper + 1)
                {
                    SequenceRange[] returnedRanges = new SequenceRange[sortedRanges.Length + 1];
                    Array.Copy(sortedRanges, 0, returnedRanges, 1, sortedRanges.Length);
                    returnedRanges[0] = range;
                    return GeneralCreate(returnedRanges);
                }
            }
 
            long newLower = (range.Lower < sortedRanges[lowerBound].Lower) ? range.Lower : sortedRanges[lowerBound].Lower;
            long newUpper = (range.Upper > sortedRanges[upperBound].Upper) ? range.Upper : sortedRanges[upperBound].Upper;
 
            int rangesRemoved = upperBound - lowerBound + 1;
            int rangesRemaining = sortedRanges.Length - rangesRemoved + 1;
            if (rangesRemaining == 1)
            {
                return new SingleItemRangeCollection(newLower, newUpper);
            }
            else
            {
                SequenceRange[] returnedRanges = new SequenceRange[rangesRemaining];
                Array.Copy(sortedRanges, returnedRanges, lowerBound);
                returnedRanges[lowerBound] = new SequenceRange(newLower, newUpper);
                Array.Copy(sortedRanges, upperBound + 1, returnedRanges, lowerBound + 1, sortedRanges.Length - upperBound - 1);
                return GeneralCreate(returnedRanges);
            }
        }
 
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < Count; i++)
            {
                SequenceRange range = this[i];
                if (i > 0)
                {
                    builder.Append(',');
                }
                builder.Append(range.Lower);
                builder.Append('-');
                builder.Append(range.Upper);
            }
 
            return builder.ToString();
        }
 
        private class EmptyRangeCollection : SequenceRangeCollection
        {
            public override SequenceRange this[int index] => throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(index)));
 
            public override int Count => 0;
 
            public override bool Contains(long number) => false;
 
            public override SequenceRangeCollection MergeWith(long number) => new SingleItemRangeCollection(number, number);
 
            public override SequenceRangeCollection MergeWith(SequenceRange range) => new SingleItemRangeCollection(range);
        }
 
        private class MultiItemRangeCollection : SequenceRangeCollection
        {
            private readonly SequenceRange[] _ranges;
 
            public MultiItemRangeCollection(SequenceRange[] sortedRanges)
            {
                _ranges = sortedRanges;
            }
 
            public override SequenceRange this[int index]
            {
                get
                {
                    if (index < 0 || index >= _ranges.Length)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(index), index,
                                                    SRP.Format(SRP.ValueMustBeInRange, 0, _ranges.Length - 1)));
                    return _ranges[index];
                }
            }
 
            public override int Count => _ranges.Length;
 
            public override bool Contains(long number)
            {
                if (_ranges.Length == 0)
                {
                    return false;
                }
                else if (_ranges.Length == 1)
                {
                    return _ranges[0].Contains(number);
                }
 
                SequenceRange searchFor = new SequenceRange(number);
                int searchValue = Array.BinarySearch(_ranges, searchFor, s_lowerComparer);
 
                if (searchValue >= 0)
                {
                    return true;
                }
 
                searchValue = ~searchValue;
 
                if (searchValue == 0)
                {
                    return false;
                }
 
                return (_ranges[searchValue - 1].Upper >= number);
            }
 
            public override SequenceRangeCollection MergeWith(long number) => MergeWith(new SequenceRange(number));
 
            public override SequenceRangeCollection MergeWith(SequenceRange newRange) => GeneralMerge(_ranges, newRange);
        }
 
        private class SingleItemRangeCollection : SequenceRangeCollection
        {
            private SequenceRange _range;
 
            public SingleItemRangeCollection(SequenceRange range)
            {
                _range = range;
            }
 
            public SingleItemRangeCollection(long lower, long upper)
            {
                _range = new SequenceRange(lower, upper);
            }
 
            public override SequenceRange this[int index]
            {
                get
                {
                    if (index != 0)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(index)));
                    return _range;
                }
            }
 
            public override int Count => 1;
 
            public override bool Contains(long number) => _range.Contains(number);
 
            public override SequenceRangeCollection MergeWith(long number)
            {
                if (number == _range.Upper + 1)
                {
                    return new SingleItemRangeCollection(_range.Lower, number);
                }
                else
                {
                    return MergeWith(new SequenceRange(number));
                }
            }
 
            public override SequenceRangeCollection MergeWith(SequenceRange newRange)
            {
                if (newRange.Lower == _range.Upper + 1)
                {
                    return new SingleItemRangeCollection(_range.Lower, newRange.Upper);
                }
                else if (_range.Contains(newRange))
                {
                    return this;
                }
                else if (newRange.Contains(_range))
                {
                    return new SingleItemRangeCollection(newRange);
                }
                else if (newRange.Upper == _range.Lower - 1)
                {
                    return new SingleItemRangeCollection(newRange.Lower, _range.Upper);
                }
                else
                {
                    return GeneralMerge(new SequenceRange[] { _range }, newRange);
                }
            }
        }
 
        private class LowerComparer : IComparer<SequenceRange>
        {
            public int Compare(SequenceRange x, SequenceRange y)
            {
                if (x.Lower < y.Lower)
                {
                    return -1;
                }
                else if (x.Lower > y.Lower)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }
 
        private class UpperComparer : IComparer<SequenceRange>
        {
            public int Compare(SequenceRange x, SequenceRange y)
            {
                if (x.Upper < y.Upper)
                {
                    return -1;
                }
                else if (x.Upper > y.Upper)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }
    }
}