| File: System\Xml\Schema\ConstraintStruct.cs | Web Access |
| Project: src\runtime\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.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using System.Xml.XPath; using MS.Internal.Xml.XPath; namespace System.Xml.Schema { internal sealed class ConstraintStruct { // for each constraint internal CompiledIdentityConstraint constraint; // pointer to constraint internal SelectorActiveAxis axisSelector; internal ArrayList axisFields; // Add tableDim * LocatedActiveAxis in a loop internal Hashtable? qualifiedTable; // Checking confliction internal Hashtable? keyrefTable; // several keyref tables having connections to this one is possible private readonly int _tableDim; // dimension of table = numbers of fields; internal int TableDim { get { return _tableDim; } } internal ConstraintStruct(CompiledIdentityConstraint constraint) { this.constraint = constraint; _tableDim = constraint.Fields.Length; this.axisFields = new ArrayList(); // empty fields this.axisSelector = new SelectorActiveAxis(constraint.Selector, this); if (this.constraint.Role != CompiledIdentityConstraint.ConstraintRole.Keyref) { this.qualifiedTable = new Hashtable(); } } } // ActiveAxis plus the location plus the state of matching in the constraint table : only for field internal sealed class LocatedActiveAxis : ActiveAxis { private readonly int _column; // the column in the table (the field sequence) internal bool isMatched; // if it's matched, then fill value in the validator later internal KeySequence Ks; // associated with a keysequence it will fills in internal int Column { get { return _column; } } internal LocatedActiveAxis(Asttree astfield, KeySequence ks, int column) : base(astfield) { this.Ks = ks; _column = column; this.isMatched = false; } internal void Reactivate(KeySequence ks) { Reactivate(); this.Ks = ks; } } // exist for optimization purpose // ActiveAxis plus // 1. overload endelement function from parent to return result // 2. combine locatedactiveaxis and keysequence more closely // 3. enable locatedactiveaxis reusing (the most important optimization point) // 4. enable ks adding to hashtable right after moving out selector node (to enable 3) // 5. will modify locatedactiveaxis class accordingly // 6. taking care of updating ConstraintStruct.axisFields // 7. remove constraintTable from ConstraintStruct // 8. still need centralized locatedactiveaxis for movetoattribute purpose internal sealed class SelectorActiveAxis : ActiveAxis { private readonly ConstraintStruct _cs; // pointer of constraintstruct, to enable 6 private readonly ArrayList _KSs; // stack of KSStruct, will not become less private int _KSpointer; // indicate current stack top (next available element); public int lastDepth { get { return (_KSpointer == 0) ? -1 : ((KSStruct)_KSs[_KSpointer - 1]!).depth; } } public SelectorActiveAxis(Asttree axisTree, ConstraintStruct cs) : base(axisTree) { _KSs = new ArrayList(); _cs = cs; } public override bool EndElement(string localname, string? URN) { base.EndElement(localname, URN); if (_KSpointer > 0 && this.CurrentDepth == lastDepth) { return true; // next step PopPS, and insert into hash } return false; } // update constraintStruct.axisFields as well, if it's new LocatedActiveAxis public int PushKS(int errline, int errcol) { // new KeySequence each time KeySequence ks = new KeySequence(_cs.TableDim, errline, errcol); // needs to clear KSStruct before using KSStruct kss; if (_KSpointer < _KSs.Count) { // reuse, clear up KSs.KSpointer kss = (KSStruct)_KSs[_KSpointer]!; kss.ks = ks; // reactivate LocatedActiveAxis for (int i = 0; i < _cs.TableDim; i++) { kss.fields[i].Reactivate(ks); // reassociate key sequence } } else { // "==", new kss = new KSStruct(ks, _cs.TableDim); for (int i = 0; i < _cs.TableDim; i++) { kss.fields[i] = new LocatedActiveAxis(_cs.constraint.Fields[i], ks, i); _cs.axisFields.Add(kss.fields[i]); // new, add to axisFields } _KSs.Add(kss); } kss.depth = this.CurrentDepth - 1; return (_KSpointer++); } public KeySequence PopKS() { return ((KSStruct)_KSs[--_KSpointer]!).ks; } } internal sealed class KSStruct { public int depth; // depth of selector when it matches public KeySequence ks; // ks of selector when it matches and assigned -- needs to new each time public LocatedActiveAxis[] fields; // array of fields activeaxis when it matches and assigned public KSStruct(KeySequence ks, int dim) { this.ks = ks; this.fields = new LocatedActiveAxis[dim]; } } internal sealed class TypedObject { private sealed class DecimalStruct { private bool _isDecimal; // rare case it will be used... private readonly decimal[] _dvalue; // to accelerate equals operation. array <-> list public bool IsDecimal { get { return _isDecimal; } set { _isDecimal = value; } } public decimal[] Dvalue { get { return _dvalue; } } public DecimalStruct() { _dvalue = new decimal[1]; } //list public DecimalStruct(int dim) { _dvalue = new decimal[dim]; } } private DecimalStruct? _dstruct; private object? _ovalue; private readonly string _svalue; // only for output private XmlSchemaDatatype _xsdtype; private readonly int _dim = 1; private readonly bool _isList; public int Dim { get { return _dim; } } public bool IsList { get { return _isList; } } public bool IsDecimal { get { Debug.Assert(_dstruct != null); return _dstruct.IsDecimal; } } public decimal[] Dvalue { get { Debug.Assert(_dstruct != null); return _dstruct.Dvalue; } } public object? Value { get { return _ovalue; } set { _ovalue = value; } } public XmlSchemaDatatype Type { get { return _xsdtype; } set { _xsdtype = value; } } public TypedObject(object? obj, string svalue, XmlSchemaDatatype xsdtype) { _ovalue = obj; _svalue = svalue; _xsdtype = xsdtype; if (xsdtype.Variety == XmlSchemaDatatypeVariety.List || xsdtype is Datatype_base64Binary || xsdtype is Datatype_hexBinary) { _isList = true; _dim = ((Array)obj!).Length; } } public override string ToString() { // only for exception return _svalue; } public void SetDecimal() { if (_dstruct != null) { return; } // Debug.Assert(!this.IsDecimal); switch (_xsdtype.TypeCode) { case XmlTypeCode.Byte: case XmlTypeCode.UnsignedByte: case XmlTypeCode.Short: case XmlTypeCode.UnsignedShort: case XmlTypeCode.Int: case XmlTypeCode.UnsignedInt: case XmlTypeCode.Long: case XmlTypeCode.UnsignedLong: case XmlTypeCode.Decimal: case XmlTypeCode.Integer: case XmlTypeCode.PositiveInteger: case XmlTypeCode.NonNegativeInteger: case XmlTypeCode.NegativeInteger: case XmlTypeCode.NonPositiveInteger: if (_isList) { _dstruct = new DecimalStruct(_dim); for (int i = 0; i < _dim; i++) { _dstruct.Dvalue[i] = Convert.ToDecimal(((Array)_ovalue!).GetValue(i), NumberFormatInfo.InvariantInfo); } } else { //not list _dstruct = new DecimalStruct(); //possibility of list of length 1. _dstruct.Dvalue[0] = Convert.ToDecimal(_ovalue, NumberFormatInfo.InvariantInfo); } _dstruct.IsDecimal = true; break; default: if (_isList) { _dstruct = new DecimalStruct(_dim); } else { _dstruct = new DecimalStruct(); } break; } } private bool ListDValueEquals(TypedObject other) { for (int i = 0; i < this.Dim; i++) { if (this.Dvalue[i] != other.Dvalue[i]) { return false; } } return true; } public bool Equals(TypedObject other) { // ? one is list with one member, another is not list -- still might be equal if (this.Dim != other.Dim) { return false; } if (this.Type != other.Type) { //Check if types are comparable if (!(this.Type.IsComparable(other.Type))) { return false; } other.SetDecimal(); // can't use cast and other.Type.IsEqual (value1, value2) this.SetDecimal(); if (this.IsDecimal && other.IsDecimal) { //Both are decimal / derived types return this.ListDValueEquals(other); } } // not-Decimal derivation or type equal if (this.IsList) { if (other.IsList) { //Both are lists and values are XmlAtomicValue[] or clrvalue[]. So use Datatype_List.Compare return this.Type.Compare(this.Value!, other.Value!) == 0; } else { //this is a list and other is a single value Array? arr1 = this.Value as System.Array; XmlAtomicValue[]? atomicValues1 = arr1 as XmlAtomicValue[]; if (atomicValues1 != null) { // this is a list of union return atomicValues1.Length == 1 && atomicValues1.GetValue(0)!.Equals(other.Value); } else { Debug.Assert(arr1 != null); return arr1.Length == 1 && arr1.GetValue(0)!.Equals(other.Value); } } } else if (other.IsList) { Array? arr2 = other.Value as System.Array; XmlAtomicValue[]? atomicValues2 = arr2 as XmlAtomicValue[]; if (atomicValues2 != null) { // other is a list of union return atomicValues2.Length == 1 && atomicValues2.GetValue(0)!.Equals(this.Value); } else { Debug.Assert(arr2 != null); return arr2.Length == 1 && arr2.GetValue(0)!.Equals(this.Value); } } else { //Both are not lists return this.Value!.Equals(other.Value); } } } internal sealed class KeySequence { private readonly TypedObject[] _ks; private readonly int _dim; private int _hashcode = -1; private readonly int _posline, _poscol; // for error reporting internal KeySequence(int dim, int line, int col) { Debug.Assert(dim > 0); _dim = dim; _ks = new TypedObject[dim]; _posline = line; _poscol = col; } public int PosLine { get { return _posline; } } public int PosCol { get { return _poscol; } } public object this[int index] { get { object result = _ks[index]; return result; } set { _ks[index] = (TypedObject)value; } } // return true if no null field internal bool IsQualified() { for (int i = 0; i < _ks.Length; ++i) { if ((_ks[i] == null) || (_ks[i].Value == null)) return false; } return true; } // it's not directly suit for hashtable, because it's always calculating address public override int GetHashCode() { if (_hashcode != -1) { return _hashcode; } _hashcode = 0; // indicate it's changed. even the calculated hashcode below is 0 for (int i = 0; i < _ks.Length; i++) { // extract its primitive value to calculate hashcode // decimal is handled differently to enable among different CLR types _ks[i].SetDecimal(); if (_ks[i].IsDecimal) { for (int j = 0; j < _ks[i].Dim; j++) { _hashcode += _ks[i].Dvalue[j].GetHashCode(); } } // BUGBUG: will need to change below parts, using canonical presentation. else { if (_ks[i].Value is Array arr) { XmlAtomicValue[]? atomicValues = arr as XmlAtomicValue[]; if (atomicValues != null) { for (int j = 0; j < atomicValues.Length; j++) { _hashcode += ((XmlAtomicValue)atomicValues.GetValue(j)!).TypedValue.GetHashCode(); } } else { for (int j = 0; j < ((Array)_ks[i].Value!).Length; j++) { _hashcode += ((Array)_ks[i].Value!).GetValue(j)!.GetHashCode(); } } } else { //not a list _hashcode += _ks[i].Value!.GetHashCode(); } } } return _hashcode; } // considering about derived type public override bool Equals([NotNullWhen(true)] object? other) { // each key sequence member can have different type if (other is KeySequence keySequence) { for (int i = 0; i < _ks.Length; i++) { if (!_ks[i].Equals(keySequence._ks[i])) { return false; } } return true; } else { Debug.Fail($"{nameof(other)} is not of type {nameof(KeySequence)}"); return false; } } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(_ks[0].ToString()); for (int i = 1; i < _ks.Length; i++) { sb.Append(' '); sb.Append(_ks[i].ToString()); } return sb.ToString(); } } }