File: System\Xml\Xsl\Runtime\XmlSortKey.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Globalization;
 
namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// Base internal class for all sort keys.
    /// Inherits from IComparable, so that Array.Sort can perform comparison.
    /// </summary>
    internal abstract class XmlSortKey : IComparable
    {
        private int _priority;           // Original input ordering used to ensure that sort is stable
        private XmlSortKey? _nextKey;     // Next sort key if there are multiple keys (null otherwise)
 
        /// <summary>
        /// Get or set this key's index, relative to other keys involved in a sort.  This priority will
        /// break ties.  If the priority is not set, then the sort will not be stable.
        /// </summary>
        public int Priority
        {
            //get { return this.priority; }
            set
            {
                // All linked keys have same priority
                XmlSortKey? key = this;
 
                while (key != null)
                {
                    key._priority = value;
                    key = key._nextKey;
                }
            }
        }
 
        /// <summary>
        /// Sometimes a key is composed of multiple parts.  For example: (LastName, FirstName).  Multi-part
        /// keys are linked together in a list.  This method recursively adds a new key part to the end of the list.
        /// Returns the first (primary) key in the list.
        /// </summary>
        public XmlSortKey AddSortKey(XmlSortKey sortKey)
        {
            if (_nextKey != null)
            {
                // Add to end of list--this is not it
                _nextKey.AddSortKey(sortKey);
            }
            else
            {
                // This is the end of the list
                _nextKey = sortKey;
            }
 
            return this;
        }
 
        /// <summary>
        /// When two keys are compared and found to be equal, the tie must be broken.  If there is a secondary key,
        /// then use that to break the tie.  Otherwise, use the input ordering to break the tie.  Since every key
        /// has a unique index, this is guaranteed to always break the tie.
        /// </summary>
        protected int BreakSortingTie(XmlSortKey that)
        {
            if (_nextKey != null)
            {
                // There are multiple keys, so break tie using next key
                Debug.Assert(_nextKey != null && that._nextKey != null);
                return _nextKey.CompareTo(that._nextKey);
            }
 
            Debug.Assert(_priority != that._priority);
            return (_priority < that._priority) ? -1 : 1;
        }
 
        /// <summary>
        /// Compare a non-empty key (this) to an empty key (obj).  The empty sequence always sorts either before all
        /// other values, or after all other values.
        /// </summary>
        protected int CompareToEmpty(object? obj)
        {
            XmlEmptySortKey? that = obj as XmlEmptySortKey;
            Debug.Assert(that != null && !(this is XmlEmptySortKey));
            return that.IsEmptyGreatest ? -1 : 1;
        }
 
        /// <summary>
        /// Base internal class is abstract and doesn't actually implement CompareTo; derived classes must do this.
        /// </summary>
        public abstract int CompareTo(object? that);
    }
 
 
    /// <summary>
    /// Sort key for the empty sequence.  Empty sequence always compares sorts either before all other values,
    /// or after all other values.
    /// </summary>
    internal sealed class XmlEmptySortKey : XmlSortKey
    {
        private readonly bool _isEmptyGreatest;
 
        public XmlEmptySortKey(XmlCollation collation)
        {
            // Greatest, Ascending: isEmptyGreatest = true
            // Greatest, Descending: isEmptyGreatest = false
            // Least, Ascending: isEmptyGreatest = false
            // Least, Descending: isEmptyGreatest = true
            _isEmptyGreatest = collation.EmptyGreatest != collation.DescendingOrder;
        }
 
        public bool IsEmptyGreatest
        {
            get { return _isEmptyGreatest; }
        }
 
        public override int CompareTo(object? obj)
        {
            XmlEmptySortKey? that = obj as XmlEmptySortKey;
 
            if (that == null)
            {
                // Empty compared to non-empty
                Debug.Assert(obj is XmlSortKey);
                return -(obj as XmlSortKey)!.CompareTo(this);
            }
 
            // Empty compared to empty
            return BreakSortingTie(that);
        }
    }
 
 
    /// <summary>
    /// Sort key for xs:decimal values.
    /// </summary>
    internal sealed class XmlDecimalSortKey : XmlSortKey
    {
        private readonly decimal _decVal;
 
        public XmlDecimalSortKey(decimal value, XmlCollation collation)
        {
            // Invert decimal if sorting in descending order
            _decVal = collation.DescendingOrder ? -value : value;
        }
 
        public override int CompareTo(object? obj)
        {
            XmlDecimalSortKey? that = obj as XmlDecimalSortKey;
            int cmp;
 
            if (that == null)
                return CompareToEmpty(obj);
 
            cmp = decimal.Compare(_decVal, that._decVal);
            if (cmp == 0)
                return BreakSortingTie(that);
 
            return cmp;
        }
    }
 
 
    /// <summary>
    /// Sort key for xs:integer values.
    /// </summary>
    internal class XmlIntegerSortKey : XmlSortKey
    {
        private readonly long _longVal;
 
        public XmlIntegerSortKey(long value, XmlCollation collation)
        {
            // Invert long if sorting in descending order
            _longVal = collation.DescendingOrder ? ~value : value;
        }
 
        public override int CompareTo(object? obj)
        {
            XmlIntegerSortKey? that = obj as XmlIntegerSortKey;
 
            if (that == null)
                return CompareToEmpty(obj);
 
            if (_longVal == that._longVal)
                return BreakSortingTie(that);
 
            return (_longVal < that._longVal) ? -1 : 1;
        }
    }
 
 
    /// <summary>
    /// Sort key for xs:int values.
    /// </summary>
    internal sealed class XmlIntSortKey : XmlSortKey
    {
        private readonly int _intVal;
 
        public XmlIntSortKey(int value, XmlCollation collation)
        {
            // Invert integer if sorting in descending order
            _intVal = collation.DescendingOrder ? ~value : value;
        }
 
        public override int CompareTo(object? obj)
        {
            XmlIntSortKey? that = obj as XmlIntSortKey;
 
            if (that == null)
                return CompareToEmpty(obj);
 
            if (_intVal == that._intVal)
                return BreakSortingTie(that);
 
            return (_intVal < that._intVal) ? -1 : 1;
        }
    }
 
    /// <summary>
    /// Sort key for xs:string values.  Strings are sorted according to a byte-wise sort key calculated by caller.
    /// </summary>
    internal sealed class XmlStringSortKey : XmlSortKey
    {
        private readonly SortKey? _sortKey;
        private readonly byte[]? _sortKeyBytes;
        private readonly bool _descendingOrder;
 
        public XmlStringSortKey(SortKey sortKey, bool descendingOrder)
        {
            _sortKey = sortKey;
            _descendingOrder = descendingOrder;
        }
 
        public XmlStringSortKey(byte[] sortKey, bool descendingOrder)
        {
            _sortKeyBytes = sortKey;
            _descendingOrder = descendingOrder;
        }
 
        public override int CompareTo(object? obj)
        {
            XmlStringSortKey? that = obj as XmlStringSortKey;
            int idx, cntCmp, result;
 
            if (that == null)
                return CompareToEmpty(obj);
 
            // Compare either using SortKey.Compare or byte arrays
            if (_sortKey != null)
            {
                Debug.Assert(that._sortKey != null, "Both keys must have non-null sortKey field");
                result = SortKey.Compare(_sortKey, that._sortKey);
            }
            else
            {
                Debug.Assert(_sortKeyBytes != null && that._sortKeyBytes != null, "Both keys must have non-null sortKeyBytes field");
 
                cntCmp = (_sortKeyBytes.Length < that._sortKeyBytes.Length) ? _sortKeyBytes.Length : that._sortKeyBytes.Length;
                for (idx = 0; idx < cntCmp; idx++)
                {
                    if (_sortKeyBytes[idx] < that._sortKeyBytes[idx])
                    {
                        result = -1;
                        goto Done;
                    }
 
                    if (_sortKeyBytes[idx] > that._sortKeyBytes[idx])
                    {
                        result = 1;
                        goto Done;
                    }
                }
 
                // So far, keys are equal, so now test length of each key
                if (_sortKeyBytes.Length < that._sortKeyBytes.Length)
                    result = -1;
                else if (_sortKeyBytes.Length > that._sortKeyBytes.Length)
                    result = 1;
                else
                    result = 0;
            }
 
        Done:
            // Use document order to break sorting tie
            if (result == 0)
                return BreakSortingTie(that);
 
            return _descendingOrder ? -result : result;
        }
    }
 
    /// <summary>
    /// Sort key for Double values.
    /// </summary>
    internal sealed class XmlDoubleSortKey : XmlSortKey
    {
        private readonly double _dblVal;
        private readonly bool _isNaN;
 
        public XmlDoubleSortKey(double value, XmlCollation collation)
        {
            if (double.IsNaN(value))
            {
                // Treat NaN as if it were the empty sequence
                _isNaN = true;
 
                // Greatest, Ascending: isEmptyGreatest = true
                // Greatest, Descending: isEmptyGreatest = false
                // Least, Ascending: isEmptyGreatest = false
                // Least, Descending: isEmptyGreatest = true
                _dblVal = (collation.EmptyGreatest != collation.DescendingOrder) ? double.PositiveInfinity : double.NegativeInfinity;
            }
            else
            {
                _dblVal = collation.DescendingOrder ? -value : value;
            }
        }
 
        public override int CompareTo(object? obj)
        {
            XmlDoubleSortKey? that = obj as XmlDoubleSortKey;
 
            if (that == null)
            {
                // Compare to empty sequence
                if (_isNaN)
                    return BreakSortingTie((obj as XmlSortKey)!);
 
                return CompareToEmpty(obj);
            }
 
            if (_dblVal == that._dblVal)
            {
                if (_isNaN)
                {
                    // NaN sorts equal to NaN
                    if (that._isNaN)
                        return BreakSortingTie(that);
 
                    // NaN sorts before or after all non-NaN values
                    Debug.Assert(_dblVal == double.NegativeInfinity || _dblVal == double.PositiveInfinity);
                    return (_dblVal == double.NegativeInfinity) ? -1 : 1;
                }
                else if (that._isNaN)
                {
                    // NaN sorts before or after all non-NaN values
                    Debug.Assert(that._dblVal == double.NegativeInfinity || that._dblVal == double.PositiveInfinity);
                    return (that._dblVal == double.NegativeInfinity) ? 1 : -1;
                }
 
                return BreakSortingTie(that);
            }
 
            return (_dblVal < that._dblVal) ? -1 : 1;
        }
    }
 
 
    /// <summary>
    /// Sort key for DateTime values (just convert DateTime to ticks and use Long sort key).
    /// </summary>
    internal sealed class XmlDateTimeSortKey : XmlIntegerSortKey
    {
        public XmlDateTimeSortKey(DateTime value, XmlCollation collation) : base(value.Ticks, collation)
        {
        }
    }
}