//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] //------------------------------------------------------------------------------ using System.Collections; using System.Text; using System.Xml; using System.Xml.Schema; using System.Xml.XPath; using System.Diagnostics; namespace MS.Internal.Xml.Cache { /// /// The 0th node in each page contains a non-null reference to an XPathNodePageInfo internal class that provides /// information about that node's page. The other fields in the 0th node are undefined and should never /// be used. /// sealed internal class XPathNodePageInfo { private int pageNum; private int nodeCount; private XPathNode[] pagePrev; private XPathNode[] pageNext; /// /// Constructor. /// public XPathNodePageInfo(XPathNode[] pagePrev, int pageNum) { this.pagePrev = pagePrev; this.pageNum = pageNum; this.nodeCount = 1; // Every node page contains PageInfo at 0th position } /// /// Return the sequential page number of the page containing nodes that share this information atom. /// public int PageNumber { get { return this.pageNum; } } /// /// Return the number of nodes allocated in this page. /// public int NodeCount { get { return this.nodeCount; } set { this.nodeCount = value; } } /// /// Return the previous node page in the document. /// public XPathNode[] PreviousPage { get { return this.pagePrev; } } /// /// Return the next node page in the document. /// public XPathNode[] NextPage { get { return this.pageNext; } set { this.pageNext = value; } } } /// /// There is a great deal of redundancy in typical Xml documents. Even in documents with thousands or millions /// of nodes, there are a small number of common names and types. And since nodes are allocated in pages in /// document order, nodes on the same page with the same name and type are likely to have the same sibling and /// parent pages as well. /// Redundant information is shared by creating immutable, atomized objects. This is analogous to the /// string.Intern() operation. If a node's name, type, or parent/sibling pages are modified, then a new /// InfoAtom needs to be obtained, since other nodes may still be referencing the old InfoAtom. /// sealed internal class XPathNodeInfoAtom { private string localName; private string namespaceUri; private string prefix; private string baseUri; private XPathNode[] pageParent; private XPathNode[] pageSibling; private XPathNode[] pageSimilar; private XPathDocument doc; private int lineNumBase; private int linePosBase; private int hashCode; private int localNameHash; private XPathNodeInfoAtom next; private XPathNodePageInfo pageInfo; /// /// Construct information for the 0th node in each page. The only field which is defined is this.pageInfo, /// and it contains information about that page (pageNum, nextPage, etc.). /// public XPathNodeInfoAtom(XPathNodePageInfo pageInfo) { this.pageInfo = pageInfo; } /// /// Construct a new shared information atom. This method should only be used by the XNodeInfoTable. /// public XPathNodeInfoAtom(string localName, string namespaceUri, string prefix, string baseUri, XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar, XPathDocument doc, int lineNumBase, int linePosBase) { Init(localName, namespaceUri, prefix, baseUri, pageParent, pageSibling, pageSimilar, doc, lineNumBase, linePosBase); } /// /// Initialize an existing shared information atom. This method should only be used by the XNodeInfoTable. /// public void Init(string localName, string namespaceUri, string prefix, string baseUri, XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar, XPathDocument doc, int lineNumBase, int linePosBase) { Debug.Assert(localName != null && namespaceUri != null && prefix != null && doc != null); this.localName = localName; this.namespaceUri = namespaceUri; this.prefix = prefix; this.baseUri = baseUri; this.pageParent = pageParent; this.pageSibling = pageSibling; this.pageSimilar = pageSimilar; this.doc = doc; this.lineNumBase = lineNumBase; this.linePosBase = linePosBase; this.next = null; this.pageInfo = null; this.hashCode = 0; this.localNameHash = 0; for (int i = 0; i < this.localName.Length; i++) this.localNameHash += (this.localNameHash << 7) ^ this.localName[i]; } /// /// Returns information about the node page. Only the 0th node on each page has this property defined. /// public XPathNodePageInfo PageInfo { get { return this.pageInfo; } } /// /// Return the local name part of nodes that share this information atom. /// public string LocalName { get { return this.localName; } } /// /// Return the namespace name part of nodes that share this information atom. /// public string NamespaceUri { get { return this.namespaceUri; } } /// /// Return the prefix name part of nodes that share this information atom. /// public string Prefix { get { return this.prefix; } } /// /// Return the base Uri of nodes that share this information atom. /// public string BaseUri { get { return this.baseUri; } } /// /// Return the page containing the next sibling of nodes that share this information atom. /// public XPathNode[] SiblingPage { get { return this.pageSibling; } } /// /// Return the page containing the next element having a name which has same hashcode as this element. /// public XPathNode[] SimilarElementPage { get { return this.pageSimilar; } } /// /// Return the page containing the parent of nodes that share this information atom. /// public XPathNode[] ParentPage { get { return this.pageParent; } } /// /// Return the page containing the owner document of nodes that share this information atom. /// public XPathDocument Document { get { return this.doc; } } /// /// Return the line number to which a line number offset stored in the XPathNode is added. /// public int LineNumberBase { get { return this.lineNumBase; } } /// /// Return the line position to which a line position offset stored in the XPathNode is added. /// public int LinePositionBase { get { return this.linePosBase; } } /// /// Return cached hash code of the local name of nodes which share this information atom. /// public int LocalNameHashCode { get { return this.localNameHash; } } /// /// Link together InfoAtoms that hash to the same hashtable bucket (should only be used by XPathNodeInfoTable) /// public XPathNodeInfoAtom Next { get { return this.next; } set { this.next = value; } } /// /// Return this information atom's hash code, previously computed for performance. /// public override int GetHashCode() { if (this.hashCode == 0) { int hashCode; // Start with local name hashCode = this.localNameHash; // Add page indexes if (this.pageSibling != null) hashCode += (hashCode << 7) ^ this.pageSibling[0].PageInfo.PageNumber; if (this.pageParent != null) hashCode += (hashCode << 7) ^ this.pageParent[0].PageInfo.PageNumber; if (this.pageSimilar != null) hashCode += (hashCode << 7) ^ this.pageSimilar[0].PageInfo.PageNumber; // Save hashcode. Don't save 0, so that it won't ever be recomputed. this.hashCode = ((hashCode == 0) ? 1 : hashCode); } return this.hashCode; } /// /// Return true if this InfoAtom has the same values as another InfoAtom. /// public override bool Equals(object other) { XPathNodeInfoAtom that = other as XPathNodeInfoAtom; Debug.Assert(that != null); Debug.Assert((object) this.doc == (object) that.doc); Debug.Assert(this.pageInfo == null); // Assume that name parts are atomized if (this.GetHashCode() == that.GetHashCode()) { if ((object) this.localName == (object) that.localName && (object) this.pageSibling == (object) that.pageSibling && (object) this.namespaceUri == (object) that.namespaceUri && (object) this.pageParent == (object) that.pageParent && (object) this.pageSimilar == (object) that.pageSimilar && (object) this.prefix == (object) that.prefix && (object) this.baseUri == (object) that.baseUri && this.lineNumBase == that.lineNumBase && this.linePosBase == that.linePosBase) { return true; } } return false; } /// /// Return InfoAtom formatted as a string: /// hash=xxx, {http://my.com}foo:bar, parent=1, sibling=1, lineNum=0, linePos=0 /// public override string ToString() { StringBuilder bldr = new StringBuilder(); bldr.Append("hash="); bldr.Append(GetHashCode()); bldr.Append(", "); if (this.localName.Length != 0) { bldr.Append('{'); bldr.Append(this.namespaceUri); bldr.Append('}'); if (this.prefix.Length != 0) { bldr.Append(this.prefix); bldr.Append(':'); } bldr.Append(this.localName); bldr.Append(", "); } if (this.pageParent != null) { bldr.Append("parent="); bldr.Append(this.pageParent[0].PageInfo.PageNumber); bldr.Append(", "); } if (this.pageSibling != null) { bldr.Append("sibling="); bldr.Append(this.pageSibling[0].PageInfo.PageNumber); bldr.Append(", "); } if (this.pageSimilar != null) { bldr.Append("similar="); bldr.Append(this.pageSimilar[0].PageInfo.PageNumber); bldr.Append(", "); } bldr.Append("lineNum="); bldr.Append(this.lineNumBase); bldr.Append(", "); bldr.Append("linePos="); bldr.Append(this.linePosBase); return bldr.ToString(); } } /// /// An atomization table for XPathNodeInfoAtom. /// sealed internal class XPathNodeInfoTable { private XPathNodeInfoAtom[] hashTable; private int sizeTable; private XPathNodeInfoAtom infoCached; #if DEBUG private const int DefaultTableSize = 2; #else private const int DefaultTableSize = 32; #endif /// /// Constructor. /// public XPathNodeInfoTable() { this.hashTable = new XPathNodeInfoAtom[DefaultTableSize]; this.sizeTable = 0; } /// /// Create a new XNodeInfoAtom and ensure it is atomized in the table. /// public XPathNodeInfoAtom Create(string localName, string namespaceUri, string prefix, string baseUri, XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar, XPathDocument doc, int lineNumBase, int linePosBase) { XPathNodeInfoAtom info; // If this.infoCached already exists, then reuse it; else create new InfoAtom if (this.infoCached == null) { info = new XPathNodeInfoAtom(localName, namespaceUri, prefix, baseUri, pageParent, pageSibling, pageSimilar, doc, lineNumBase, linePosBase); } else { info = this.infoCached; this.infoCached = info.Next; info.Init(localName, namespaceUri, prefix, baseUri, pageParent, pageSibling, pageSimilar, doc, lineNumBase, linePosBase); } return Atomize(info); } /// /// Add a shared information item to the atomization table. If a matching item already exists, then that /// instance is returned. Otherwise, a new item is created. Thus, if itemX and itemY have both been added /// to the same InfoTable: /// 1. itemX.Equals(itemY) != true /// 2. (object) itemX != (object) itemY /// private XPathNodeInfoAtom Atomize(XPathNodeInfoAtom info) { XPathNodeInfoAtom infoNew, infoNext; // Search for existing XNodeInfoAtom in the table infoNew = this.hashTable[info.GetHashCode() & (this.hashTable.Length - 1)]; while (infoNew != null) { if (info.Equals(infoNew)) { // Found existing atom, so return that. Reuse "info". info.Next = this.infoCached; this.infoCached = info; return infoNew; } infoNew = infoNew.Next; } // Expand table and rehash if necessary if (this.sizeTable >= this.hashTable.Length) { XPathNodeInfoAtom[] oldTable = this.hashTable; this.hashTable = new XPathNodeInfoAtom[oldTable.Length * 2]; for (int i = 0; i < oldTable.Length; i++) { infoNew = oldTable[i]; while (infoNew != null) { infoNext = infoNew.Next; AddInfo(infoNew); infoNew = infoNext; } } } // Can't find an existing XNodeInfoAtom, so use the one that was passed in AddInfo(info); return info; } /// /// Add a previously constructed InfoAtom to the table. If a collision occurs, then insert "info" /// as the head of a linked list. /// private void AddInfo(XPathNodeInfoAtom info) { int idx = info.GetHashCode() & (this.hashTable.Length - 1); info.Next = this.hashTable[idx]; this.hashTable[idx] = info; this.sizeTable++; } /// /// Return InfoAtomTable formatted as a string. /// public override string ToString() { StringBuilder bldr = new StringBuilder(); XPathNodeInfoAtom infoAtom; for (int i = 0; i < this.hashTable.Length; i++) { bldr.AppendFormat("{0,4}: ", i); infoAtom = this.hashTable[i]; while (infoAtom != null) { if ((object) infoAtom != (object) this.hashTable[i]) bldr.Append("\n "); bldr.Append(infoAtom); infoAtom = infoAtom.Next; } bldr.Append('\n'); } return bldr.ToString(); } } }