XPathNodeInfoAtom.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathNodeInfoAtom.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. //------------------------------------------------------------------------------
  7. using System.Collections;
  8. using System.Text;
  9. using System.Xml;
  10. using System.Xml.Schema;
  11. using System.Xml.XPath;
  12. using System.Diagnostics;
  13. namespace MS.Internal.Xml.Cache {
  14. /// <summary>
  15. /// The 0th node in each page contains a non-null reference to an XPathNodePageInfo internal class that provides
  16. /// information about that node's page. The other fields in the 0th node are undefined and should never
  17. /// be used.
  18. /// </summary>
  19. sealed internal class XPathNodePageInfo {
  20. private int pageNum;
  21. private int nodeCount;
  22. private XPathNode[] pagePrev;
  23. private XPathNode[] pageNext;
  24. /// <summary>
  25. /// Constructor.
  26. /// </summary>
  27. public XPathNodePageInfo(XPathNode[] pagePrev, int pageNum) {
  28. this.pagePrev = pagePrev;
  29. this.pageNum = pageNum;
  30. this.nodeCount = 1; // Every node page contains PageInfo at 0th position
  31. }
  32. /// <summary>
  33. /// Return the sequential page number of the page containing nodes that share this information atom.
  34. /// </summary>
  35. public int PageNumber {
  36. get { return this.pageNum; }
  37. }
  38. /// <summary>
  39. /// Return the number of nodes allocated in this page.
  40. /// </summary>
  41. public int NodeCount {
  42. get { return this.nodeCount; }
  43. set { this.nodeCount = value; }
  44. }
  45. /// <summary>
  46. /// Return the previous node page in the document.
  47. /// </summary>
  48. public XPathNode[] PreviousPage {
  49. get { return this.pagePrev; }
  50. }
  51. /// <summary>
  52. /// Return the next node page in the document.
  53. /// </summary>
  54. public XPathNode[] NextPage {
  55. get { return this.pageNext; }
  56. set { this.pageNext = value; }
  57. }
  58. }
  59. /// <summary>
  60. /// There is a great deal of redundancy in typical Xml documents. Even in documents with thousands or millions
  61. /// of nodes, there are a small number of common names and types. And since nodes are allocated in pages in
  62. /// document order, nodes on the same page with the same name and type are likely to have the same sibling and
  63. /// parent pages as well.
  64. /// Redundant information is shared by creating immutable, atomized objects. This is analogous to the
  65. /// string.Intern() operation. If a node's name, type, or parent/sibling pages are modified, then a new
  66. /// InfoAtom needs to be obtained, since other nodes may still be referencing the old InfoAtom.
  67. /// </summary>
  68. sealed internal class XPathNodeInfoAtom {
  69. private string localName;
  70. private string namespaceUri;
  71. private string prefix;
  72. private string baseUri;
  73. private XPathNode[] pageParent;
  74. private XPathNode[] pageSibling;
  75. private XPathNode[] pageSimilar;
  76. private XPathDocument doc;
  77. private int lineNumBase;
  78. private int linePosBase;
  79. private int hashCode;
  80. private int localNameHash;
  81. private XPathNodeInfoAtom next;
  82. private XPathNodePageInfo pageInfo;
  83. /// <summary>
  84. /// Construct information for the 0th node in each page. The only field which is defined is this.pageInfo,
  85. /// and it contains information about that page (pageNum, nextPage, etc.).
  86. /// </summary>
  87. public XPathNodeInfoAtom(XPathNodePageInfo pageInfo) {
  88. this.pageInfo = pageInfo;
  89. }
  90. /// <summary>
  91. /// Construct a new shared information atom. This method should only be used by the XNodeInfoTable.
  92. /// </summary>
  93. public XPathNodeInfoAtom(string localName, string namespaceUri, string prefix, string baseUri,
  94. XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar,
  95. XPathDocument doc, int lineNumBase, int linePosBase) {
  96. Init(localName, namespaceUri, prefix, baseUri, pageParent, pageSibling, pageSimilar, doc, lineNumBase, linePosBase);
  97. }
  98. /// <summary>
  99. /// Initialize an existing shared information atom. This method should only be used by the XNodeInfoTable.
  100. /// </summary>
  101. public void Init(string localName, string namespaceUri, string prefix, string baseUri,
  102. XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar,
  103. XPathDocument doc, int lineNumBase, int linePosBase) {
  104. Debug.Assert(localName != null && namespaceUri != null && prefix != null && doc != null);
  105. this.localName = localName;
  106. this.namespaceUri = namespaceUri;
  107. this.prefix = prefix;
  108. this.baseUri = baseUri;
  109. this.pageParent = pageParent;
  110. this.pageSibling = pageSibling;
  111. this.pageSimilar = pageSimilar;
  112. this.doc = doc;
  113. this.lineNumBase = lineNumBase;
  114. this.linePosBase = linePosBase;
  115. this.next = null;
  116. this.pageInfo = null;
  117. this.hashCode = 0;
  118. this.localNameHash = 0;
  119. for (int i = 0; i < this.localName.Length; i++)
  120. this.localNameHash += (this.localNameHash << 7) ^ this.localName[i];
  121. }
  122. /// <summary>
  123. /// Returns information about the node page. Only the 0th node on each page has this property defined.
  124. /// </summary>
  125. public XPathNodePageInfo PageInfo {
  126. get { return this.pageInfo; }
  127. }
  128. /// <summary>
  129. /// Return the local name part of nodes that share this information atom.
  130. /// </summary>
  131. public string LocalName {
  132. get { return this.localName; }
  133. }
  134. /// <summary>
  135. /// Return the namespace name part of nodes that share this information atom.
  136. /// </summary>
  137. public string NamespaceUri {
  138. get { return this.namespaceUri; }
  139. }
  140. /// <summary>
  141. /// Return the prefix name part of nodes that share this information atom.
  142. /// </summary>
  143. public string Prefix {
  144. get { return this.prefix; }
  145. }
  146. /// <summary>
  147. /// Return the base Uri of nodes that share this information atom.
  148. /// </summary>
  149. public string BaseUri {
  150. get { return this.baseUri; }
  151. }
  152. /// <summary>
  153. /// Return the page containing the next sibling of nodes that share this information atom.
  154. /// </summary>
  155. public XPathNode[] SiblingPage {
  156. get { return this.pageSibling; }
  157. }
  158. /// <summary>
  159. /// Return the page containing the next element having a name which has same hashcode as this element.
  160. /// </summary>
  161. public XPathNode[] SimilarElementPage {
  162. get { return this.pageSimilar; }
  163. }
  164. /// <summary>
  165. /// Return the page containing the parent of nodes that share this information atom.
  166. /// </summary>
  167. public XPathNode[] ParentPage {
  168. get { return this.pageParent; }
  169. }
  170. /// <summary>
  171. /// Return the page containing the owner document of nodes that share this information atom.
  172. /// </summary>
  173. public XPathDocument Document {
  174. get { return this.doc; }
  175. }
  176. /// <summary>
  177. /// Return the line number to which a line number offset stored in the XPathNode is added.
  178. /// </summary>
  179. public int LineNumberBase {
  180. get { return this.lineNumBase; }
  181. }
  182. /// <summary>
  183. /// Return the line position to which a line position offset stored in the XPathNode is added.
  184. /// </summary>
  185. public int LinePositionBase {
  186. get { return this.linePosBase; }
  187. }
  188. /// <summary>
  189. /// Return cached hash code of the local name of nodes which share this information atom.
  190. /// </summary>
  191. public int LocalNameHashCode {
  192. get { return this.localNameHash; }
  193. }
  194. /// <summary>
  195. /// Link together InfoAtoms that hash to the same hashtable bucket (should only be used by XPathNodeInfoTable)
  196. /// </summary>
  197. public XPathNodeInfoAtom Next {
  198. get { return this.next; }
  199. set { this.next = value; }
  200. }
  201. /// <summary>
  202. /// Return this information atom's hash code, previously computed for performance.
  203. /// </summary>
  204. public override int GetHashCode() {
  205. if (this.hashCode == 0) {
  206. int hashCode;
  207. // Start with local name
  208. hashCode = this.localNameHash;
  209. // Add page indexes
  210. if (this.pageSibling != null)
  211. hashCode += (hashCode << 7) ^ this.pageSibling[0].PageInfo.PageNumber;
  212. if (this.pageParent != null)
  213. hashCode += (hashCode << 7) ^ this.pageParent[0].PageInfo.PageNumber;
  214. if (this.pageSimilar != null)
  215. hashCode += (hashCode << 7) ^ this.pageSimilar[0].PageInfo.PageNumber;
  216. // Save hashcode. Don't save 0, so that it won't ever be recomputed.
  217. this.hashCode = ((hashCode == 0) ? 1 : hashCode);
  218. }
  219. return this.hashCode;
  220. }
  221. /// <summary>
  222. /// Return true if this InfoAtom has the same values as another InfoAtom.
  223. /// </summary>
  224. public override bool Equals(object other) {
  225. XPathNodeInfoAtom that = other as XPathNodeInfoAtom;
  226. Debug.Assert(that != null);
  227. Debug.Assert((object) this.doc == (object) that.doc);
  228. Debug.Assert(this.pageInfo == null);
  229. // Assume that name parts are atomized
  230. if (this.GetHashCode() == that.GetHashCode()) {
  231. if ((object) this.localName == (object) that.localName &&
  232. (object) this.pageSibling == (object) that.pageSibling &&
  233. (object) this.namespaceUri == (object) that.namespaceUri &&
  234. (object) this.pageParent == (object) that.pageParent &&
  235. (object) this.pageSimilar == (object) that.pageSimilar &&
  236. (object) this.prefix == (object) that.prefix &&
  237. (object) this.baseUri == (object) that.baseUri &&
  238. this.lineNumBase == that.lineNumBase &&
  239. this.linePosBase == that.linePosBase) {
  240. return true;
  241. }
  242. }
  243. return false;
  244. }
  245. /// <summary>
  246. /// Return InfoAtom formatted as a string:
  247. /// hash=xxx, {http://my.com}foo:bar, parent=1, sibling=1, lineNum=0, linePos=0
  248. /// </summary>
  249. public override string ToString() {
  250. StringBuilder bldr = new StringBuilder();
  251. bldr.Append("hash=");
  252. bldr.Append(GetHashCode());
  253. bldr.Append(", ");
  254. if (this.localName.Length != 0) {
  255. bldr.Append('{');
  256. bldr.Append(this.namespaceUri);
  257. bldr.Append('}');
  258. if (this.prefix.Length != 0) {
  259. bldr.Append(this.prefix);
  260. bldr.Append(':');
  261. }
  262. bldr.Append(this.localName);
  263. bldr.Append(", ");
  264. }
  265. if (this.pageParent != null) {
  266. bldr.Append("parent=");
  267. bldr.Append(this.pageParent[0].PageInfo.PageNumber);
  268. bldr.Append(", ");
  269. }
  270. if (this.pageSibling != null) {
  271. bldr.Append("sibling=");
  272. bldr.Append(this.pageSibling[0].PageInfo.PageNumber);
  273. bldr.Append(", ");
  274. }
  275. if (this.pageSimilar != null) {
  276. bldr.Append("similar=");
  277. bldr.Append(this.pageSimilar[0].PageInfo.PageNumber);
  278. bldr.Append(", ");
  279. }
  280. bldr.Append("lineNum=");
  281. bldr.Append(this.lineNumBase);
  282. bldr.Append(", ");
  283. bldr.Append("linePos=");
  284. bldr.Append(this.linePosBase);
  285. return bldr.ToString();
  286. }
  287. }
  288. /// <summary>
  289. /// An atomization table for XPathNodeInfoAtom.
  290. /// </summary>
  291. sealed internal class XPathNodeInfoTable {
  292. private XPathNodeInfoAtom[] hashTable;
  293. private int sizeTable;
  294. private XPathNodeInfoAtom infoCached;
  295. #if DEBUG
  296. private const int DefaultTableSize = 2;
  297. #else
  298. private const int DefaultTableSize = 32;
  299. #endif
  300. /// <summary>
  301. /// Constructor.
  302. /// </summary>
  303. public XPathNodeInfoTable() {
  304. this.hashTable = new XPathNodeInfoAtom[DefaultTableSize];
  305. this.sizeTable = 0;
  306. }
  307. /// <summary>
  308. /// Create a new XNodeInfoAtom and ensure it is atomized in the table.
  309. /// </summary>
  310. public XPathNodeInfoAtom Create(string localName, string namespaceUri, string prefix, string baseUri,
  311. XPathNode[] pageParent, XPathNode[] pageSibling, XPathNode[] pageSimilar,
  312. XPathDocument doc, int lineNumBase, int linePosBase) {
  313. XPathNodeInfoAtom info;
  314. // If this.infoCached already exists, then reuse it; else create new InfoAtom
  315. if (this.infoCached == null) {
  316. info = new XPathNodeInfoAtom(localName, namespaceUri, prefix, baseUri,
  317. pageParent, pageSibling, pageSimilar,
  318. doc, lineNumBase, linePosBase);
  319. }
  320. else {
  321. info = this.infoCached;
  322. this.infoCached = info.Next;
  323. info.Init(localName, namespaceUri, prefix, baseUri,
  324. pageParent, pageSibling, pageSimilar,
  325. doc, lineNumBase, linePosBase);
  326. }
  327. return Atomize(info);
  328. }
  329. /// <summary>
  330. /// Add a shared information item to the atomization table. If a matching item already exists, then that
  331. /// instance is returned. Otherwise, a new item is created. Thus, if itemX and itemY have both been added
  332. /// to the same InfoTable:
  333. /// 1. itemX.Equals(itemY) != true
  334. /// 2. (object) itemX != (object) itemY
  335. /// </summary>
  336. private XPathNodeInfoAtom Atomize(XPathNodeInfoAtom info) {
  337. XPathNodeInfoAtom infoNew, infoNext;
  338. // Search for existing XNodeInfoAtom in the table
  339. infoNew = this.hashTable[info.GetHashCode() & (this.hashTable.Length - 1)];
  340. while (infoNew != null) {
  341. if (info.Equals(infoNew)) {
  342. // Found existing atom, so return that. Reuse "info".
  343. info.Next = this.infoCached;
  344. this.infoCached = info;
  345. return infoNew;
  346. }
  347. infoNew = infoNew.Next;
  348. }
  349. // Expand table and rehash if necessary
  350. if (this.sizeTable >= this.hashTable.Length) {
  351. XPathNodeInfoAtom[] oldTable = this.hashTable;
  352. this.hashTable = new XPathNodeInfoAtom[oldTable.Length * 2];
  353. for (int i = 0; i < oldTable.Length; i++) {
  354. infoNew = oldTable[i];
  355. while (infoNew != null) {
  356. infoNext = infoNew.Next;
  357. AddInfo(infoNew);
  358. infoNew = infoNext;
  359. }
  360. }
  361. }
  362. // Can't find an existing XNodeInfoAtom, so use the one that was passed in
  363. AddInfo(info);
  364. return info;
  365. }
  366. /// <summary>
  367. /// Add a previously constructed InfoAtom to the table. If a collision occurs, then insert "info"
  368. /// as the head of a linked list.
  369. /// </summary>
  370. private void AddInfo(XPathNodeInfoAtom info) {
  371. int idx = info.GetHashCode() & (this.hashTable.Length - 1);
  372. info.Next = this.hashTable[idx];
  373. this.hashTable[idx] = info;
  374. this.sizeTable++;
  375. }
  376. /// <summary>
  377. /// Return InfoAtomTable formatted as a string.
  378. /// </summary>
  379. public override string ToString() {
  380. StringBuilder bldr = new StringBuilder();
  381. XPathNodeInfoAtom infoAtom;
  382. for (int i = 0; i < this.hashTable.Length; i++) {
  383. bldr.AppendFormat("{0,4}: ", i);
  384. infoAtom = this.hashTable[i];
  385. while (infoAtom != null) {
  386. if ((object) infoAtom != (object) this.hashTable[i])
  387. bldr.Append("\n ");
  388. bldr.Append(infoAtom);
  389. infoAtom = infoAtom.Next;
  390. }
  391. bldr.Append('\n');
  392. }
  393. return bldr.ToString();
  394. }
  395. }
  396. }