1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xalan.transformer;
  58. import java.util.Vector;
  59. import java.text.NumberFormat;
  60. import java.text.CollationKey;
  61. //import org.w3c.dom.Node;
  62. //import org.w3c.dom.traversal.NodeIterator;
  63. import org.apache.xml.dtm.DTM;
  64. import org.apache.xml.dtm.DTMIterator;
  65. import org.apache.xpath.axes.ContextNodeList;
  66. import org.apache.xpath.XPathContext;
  67. import org.apache.xpath.NodeSetDTM;
  68. import org.apache.xpath.objects.XObject;
  69. import org.apache.xpath.objects.XNodeSet;
  70. import org.apache.xml.utils.NodeVector;
  71. import javax.xml.transform.TransformerException;
  72. /**
  73. * <meta name="usage" content="internal"/>
  74. * This class can sort vectors of DOM nodes according to a select pattern.
  75. */
  76. public class NodeSorter
  77. {
  78. /** Current XPath context */
  79. XPathContext m_execContext;
  80. /** Vector of NodeSortKeys */
  81. Vector m_keys; // vector of NodeSortKeys
  82. // /**
  83. // * TODO: Adjust this for locale.
  84. // */
  85. // NumberFormat m_formatter = NumberFormat.getNumberInstance();
  86. /**
  87. * Construct a NodeSorter, passing in the XSL TransformerFactory
  88. * so it can know how to get the node data according to
  89. * the proper whitespace rules.
  90. *
  91. * @param p Xpath context to use
  92. */
  93. public NodeSorter(XPathContext p)
  94. {
  95. m_execContext = p;
  96. }
  97. /**
  98. * Given a vector of nodes, sort each node according to
  99. * the criteria in the keys.
  100. * @param v an vector of Nodes.
  101. * @param keys a vector of NodeSortKeys.
  102. * @param support XPath context to use
  103. *
  104. * @throws javax.xml.transform.TransformerException
  105. */
  106. public void sort(DTMIterator v, Vector keys, XPathContext support)
  107. throws javax.xml.transform.TransformerException
  108. {
  109. m_keys = keys;
  110. // QuickSort2(v, 0, v.size() - 1 );
  111. int n = v.getLength();
  112. // %OPT% Change mergesort to just take a DTMIterator?
  113. // We would also have to adapt DTMIterator to have the function
  114. // of NodeCompareElem.
  115. // Create a vector of node compare elements
  116. // based on the input vector of nodes
  117. Vector nodes = new Vector();
  118. for (int i = 0; i < n; i++)
  119. {
  120. NodeCompareElem elem = new NodeCompareElem(v.item(i));
  121. nodes.addElement(elem);
  122. }
  123. Vector scratchVector = new Vector();
  124. mergesort(nodes, scratchVector, 0, n - 1, support);
  125. // return sorted vector of nodes
  126. for (int i = 0; i < n; i++)
  127. {
  128. v.setItem(((NodeCompareElem) nodes.elementAt(i)).m_node, i);
  129. }
  130. v.setCurrentPos(0);
  131. // old code...
  132. //NodeVector scratchVector = new NodeVector(n);
  133. //mergesort(v, scratchVector, 0, n - 1, support);
  134. }
  135. /**
  136. * Return the results of a compare of two nodes.
  137. * TODO: Optimize compare -- cache the getStringExpr results, key by m_selectPat + hash of node.
  138. *
  139. * @param n1 First node to use in compare
  140. * @param n2 Second node to use in compare
  141. * @param kIndex Index of NodeSortKey to use for sort
  142. * @param support XPath context to use
  143. *
  144. * @return The results of the compare of the two nodes.
  145. *
  146. * @throws TransformerException
  147. */
  148. int compare(
  149. NodeCompareElem n1, NodeCompareElem n2, int kIndex, XPathContext support)
  150. throws TransformerException
  151. {
  152. int result = 0;
  153. NodeSortKey k = (NodeSortKey) m_keys.elementAt(kIndex);
  154. if (k.m_treatAsNumbers)
  155. {
  156. double n1Num, n2Num;
  157. if (kIndex == 0)
  158. {
  159. n1Num = ((Double) n1.m_key1Value).doubleValue();
  160. n2Num = ((Double) n2.m_key1Value).doubleValue();
  161. }
  162. else if (kIndex == 1)
  163. {
  164. n1Num = ((Double) n1.m_key2Value).doubleValue();
  165. n2Num = ((Double) n2.m_key2Value).doubleValue();
  166. }
  167. /* Leave this in case we decide to use an array later
  168. if (kIndex < maxkey)
  169. {
  170. double n1Num = (double)n1.m_keyValue[kIndex];
  171. double n2Num = (double)n2.m_keyValue[kIndex];
  172. }*/
  173. else
  174. {
  175. // Get values dynamically
  176. XObject r1 = k.m_selectPat.execute(m_execContext, n1.m_node,
  177. k.m_namespaceContext);
  178. XObject r2 = k.m_selectPat.execute(m_execContext, n2.m_node,
  179. k.m_namespaceContext);
  180. n1Num = r1.num();
  181. // Can't use NaN for compare. They are never equal. Use zero instead.
  182. // That way we can keep elements in document order.
  183. //n1Num = Double.isNaN(d) ? 0.0 : d;
  184. n2Num = r2.num();
  185. //n2Num = Double.isNaN(d) ? 0.0 : d;
  186. }
  187. if ((n1Num == n2Num) && ((kIndex + 1) < m_keys.size()))
  188. {
  189. result = compare(n1, n2, kIndex + 1, support);
  190. }
  191. else
  192. {
  193. double diff;
  194. if (Double.isNaN(n1Num))
  195. {
  196. if (Double.isNaN(n2Num))
  197. diff = 0.0;
  198. else
  199. diff = -1;
  200. }
  201. else if (Double.isNaN(n2Num))
  202. diff = 1;
  203. else
  204. diff = n1Num - n2Num;
  205. // process order parameter
  206. result = (int) ((diff < 0.0)
  207. ? (k.m_descending ? 1 : -1)
  208. : (diff > 0.0) ? (k.m_descending ? -1 : 1) : 0);
  209. }
  210. } // end treat as numbers
  211. else
  212. {
  213. CollationKey n1String, n2String;
  214. if (kIndex == 0)
  215. {
  216. n1String = (CollationKey) n1.m_key1Value;
  217. n2String = (CollationKey) n2.m_key1Value;
  218. }
  219. else if (kIndex == 1)
  220. {
  221. n1String = (CollationKey) n1.m_key2Value;
  222. n2String = (CollationKey) n2.m_key2Value;
  223. }
  224. /* Leave this in case we decide to use an array later
  225. if (kIndex < maxkey)
  226. {
  227. String n1String = (String)n1.m_keyValue[kIndex];
  228. String n2String = (String)n2.m_keyValue[kIndex];
  229. }*/
  230. else
  231. {
  232. // Get values dynamically
  233. XObject r1 = k.m_selectPat.execute(m_execContext, n1.m_node,
  234. k.m_namespaceContext);
  235. XObject r2 = k.m_selectPat.execute(m_execContext, n2.m_node,
  236. k.m_namespaceContext);
  237. n1String = k.m_col.getCollationKey(r1.str());
  238. n2String = k.m_col.getCollationKey(r2.str());
  239. }
  240. // Use collation keys for faster compare, but note that whitespaces
  241. // etc... are treated differently from if we were comparing Strings.
  242. result = n1String.compareTo(n2String);
  243. //Process caseOrder parameter
  244. if (k.m_caseOrderUpper)
  245. {
  246. String tempN1 = n1String.getSourceString().toLowerCase();
  247. String tempN2 = n2String.getSourceString().toLowerCase();
  248. if (tempN1.equals(tempN2))
  249. {
  250. //java defaults to upper case is greater.
  251. result = result == 0 ? 0 : -result;
  252. }
  253. }
  254. //Process order parameter
  255. if (k.m_descending)
  256. {
  257. result = -result;
  258. }
  259. } //end else
  260. if (0 == result)
  261. {
  262. if ((kIndex + 1) < m_keys.size())
  263. {
  264. result = compare(n1, n2, kIndex + 1, support);
  265. }
  266. }
  267. if (0 == result)
  268. {
  269. // I shouldn't have to do this except that there seems to
  270. // be a glitch in the mergesort
  271. // if(r1.getType() == r1.CLASS_NODESET)
  272. // {
  273. DTM dtm = support.getDTM(n1.m_node); // %OPT%
  274. result = dtm.isNodeAfter(n1.m_node, n2.m_node) ? -1 : 1;
  275. // }
  276. }
  277. return result;
  278. }
  279. /**
  280. * This implements a standard Mergesort, as described in
  281. * Robert Sedgewick's Algorithms book. This is a better
  282. * sort for our purpose than the Quicksort because it
  283. * maintains the original document order of the input if
  284. * the order isn't changed by the sort.
  285. *
  286. * @param a First vector of nodes to compare
  287. * @param b Second vector of nodes to compare
  288. * @param l Left boundary of partition
  289. * @param r Right boundary of partition
  290. * @param support XPath context to use
  291. *
  292. * @throws TransformerException
  293. */
  294. void mergesort(Vector a, Vector b, int l, int r, XPathContext support)
  295. throws TransformerException
  296. {
  297. if ((r - l) > 0)
  298. {
  299. int m = (r + l) / 2;
  300. mergesort(a, b, l, m, support);
  301. mergesort(a, b, m + 1, r, support);
  302. int i, j, k;
  303. for (i = m; i >= l; i--)
  304. {
  305. // b[i] = a[i];
  306. // Use insert if we need to increment vector size.
  307. if (i >= b.size())
  308. b.insertElementAt(a.elementAt(i), i);
  309. else
  310. b.setElementAt(a.elementAt(i), i);
  311. }
  312. i = l;
  313. for (j = (m + 1); j <= r; j++)
  314. {
  315. // b[r+m+1-j] = a[j];
  316. if (r + m + 1 - j >= b.size())
  317. b.insertElementAt(a.elementAt(j), r + m + 1 - j);
  318. else
  319. b.setElementAt(a.elementAt(j), r + m + 1 - j);
  320. }
  321. j = r;
  322. int compVal;
  323. for (k = l; k <= r; k++)
  324. {
  325. // if(b[i] < b[j])
  326. if (i == j)
  327. compVal = -1;
  328. else
  329. compVal = compare((NodeCompareElem) b.elementAt(i),
  330. (NodeCompareElem) b.elementAt(j), 0, support);
  331. if (compVal < 0)
  332. {
  333. // a[k]=b[i];
  334. a.setElementAt(b.elementAt(i), k);
  335. i++;
  336. }
  337. else if (compVal > 0)
  338. {
  339. // a[k]=b[j];
  340. a.setElementAt(b.elementAt(j), k);
  341. j--;
  342. }
  343. }
  344. }
  345. }
  346. /**
  347. * This is a generic version of C.A.R Hoare's Quick Sort
  348. * algorithm. This will handle arrays that are already
  349. * sorted, and arrays with duplicate keys.<BR>
  350. *
  351. * If you think of a one dimensional array as going from
  352. * the lowest index on the left to the highest index on the right
  353. * then the parameters to this function are lowest index or
  354. * left and highest index or right. The first time you call
  355. * this function it will be with the parameters 0, a.length - 1.
  356. *
  357. * @param v a vector of integers
  358. * @param lo0 left boundary of array partition
  359. * @param hi0 right boundary of array partition
  360. *
  361. */
  362. /* private void QuickSort2(Vector v, int lo0, int hi0, XPathContext support)
  363. throws javax.xml.transform.TransformerException,
  364. java.net.MalformedURLException,
  365. java.io.FileNotFoundException,
  366. java.io.IOException
  367. {
  368. int lo = lo0;
  369. int hi = hi0;
  370. if ( hi0 > lo0)
  371. {
  372. // Arbitrarily establishing partition element as the midpoint of
  373. // the array.
  374. Node midNode = (Node)v.elementAt( ( lo0 + hi0 ) / 2 );
  375. // loop through the array until indices cross
  376. while( lo <= hi )
  377. {
  378. // find the first element that is greater than or equal to
  379. // the partition element starting from the left Index.
  380. while( (lo < hi0) && (compare((Node)v.elementAt(lo), midNode, 0, support) < 0) )
  381. {
  382. ++lo;
  383. } // end while
  384. // find an element that is smaller than or equal to
  385. // the partition element starting from the right Index.
  386. while( (hi > lo0) && (compare((Node)v.elementAt(hi), midNode, 0, support) > 0) )
  387. {
  388. --hi;
  389. }
  390. // if the indexes have not crossed, swap
  391. if( lo <= hi )
  392. {
  393. swap(v, lo, hi);
  394. ++lo;
  395. --hi;
  396. }
  397. }
  398. // If the right index has not reached the left side of array
  399. // must now sort the left partition.
  400. if( lo0 < hi )
  401. {
  402. QuickSort2( v, lo0, hi, support );
  403. }
  404. // If the left index has not reached the right side of array
  405. // must now sort the right partition.
  406. if( lo < hi0 )
  407. {
  408. QuickSort2( v, lo, hi0, support );
  409. }
  410. }
  411. } // end QuickSort2 */
  412. // /**
  413. // * Simple function to swap two elements in
  414. // * a vector.
  415. // *
  416. // * @param v Vector of nodes to swap
  417. // * @param i Index of first node to swap
  418. // * @param i Index of second node to swap
  419. // */
  420. // private void swap(Vector v, int i, int j)
  421. // {
  422. //
  423. // int node = (Node) v.elementAt(i);
  424. //
  425. // v.setElementAt(v.elementAt(j), i);
  426. // v.setElementAt(node, j);
  427. // }
  428. /**
  429. * <meta name="usage" content="internal"/>
  430. * This class holds the value(s) from executing the given
  431. * node against the sort key(s).
  432. */
  433. class NodeCompareElem
  434. {
  435. /** Current node */
  436. int m_node;
  437. /** This maxkey value was chosen arbitrarily. We are assuming that the
  438. // maxkey + 1 keys will only hit fairly rarely and therefore, we
  439. // will get the node values for those keys dynamically.
  440. */
  441. int maxkey = 2;
  442. // Keep this in case we decide to use an array. Right now
  443. // using two variables is cheaper.
  444. //Object[] m_KeyValue = new Object[2];
  445. /** Value from first sort key */
  446. Object m_key1Value;
  447. /** Value from second sort key */
  448. Object m_key2Value;
  449. /**
  450. * Constructor NodeCompareElem
  451. *
  452. *
  453. * @param node Current node
  454. *
  455. * @throws javax.xml.transform.TransformerException
  456. */
  457. NodeCompareElem(int node) throws javax.xml.transform.TransformerException
  458. {
  459. boolean tryNextKey = true;
  460. m_node = node;
  461. if (!m_keys.isEmpty())
  462. {
  463. NodeSortKey k1 = (NodeSortKey) m_keys.elementAt(0);
  464. XObject r = k1.m_selectPat.execute(m_execContext, node,
  465. k1.m_namespaceContext);
  466. if (r == null)
  467. tryNextKey = false;
  468. double d;
  469. if (k1.m_treatAsNumbers)
  470. {
  471. d = r.num();
  472. // Can't use NaN for compare. They are never equal. Use zero instead.
  473. m_key1Value = new Double(d);
  474. }
  475. else
  476. {
  477. m_key1Value = k1.m_col.getCollationKey(r.str());
  478. }
  479. if (r.getType() == XObject.CLASS_NODESET)
  480. {
  481. // %REVIEW%
  482. DTMIterator ni = ((XNodeSet)r).iterRaw();
  483. int current = ni.getCurrentNode();
  484. if(DTM.NULL == current)
  485. current = ni.nextNode();
  486. // if (ni instanceof ContextNodeList) // %REVIEW%
  487. tryNextKey = (DTM.NULL != current);
  488. // else abdicate... should never happen, but... -sb
  489. }
  490. if (m_keys.size() > 1)
  491. {
  492. NodeSortKey k2 = (NodeSortKey) m_keys.elementAt(1);
  493. if (!tryNextKey)
  494. {
  495. if (k2.m_treatAsNumbers)
  496. m_key2Value = new Double(0.0);
  497. else
  498. m_key2Value = k2.m_col.getCollationKey("");
  499. }
  500. else
  501. {
  502. XObject r2 = k2.m_selectPat.execute(m_execContext, node,
  503. k2.m_namespaceContext);
  504. if (k2.m_treatAsNumbers)
  505. {
  506. d = r2.num();
  507. m_key2Value = new Double(d);
  508. }
  509. else
  510. m_key2Value = k2.m_col.getCollationKey(r2.str());
  511. }
  512. }
  513. /* Leave this in case we decide to use an array later
  514. while (kIndex <= m_keys.size() && kIndex < maxkey)
  515. {
  516. NodeSortKey k = (NodeSortKey)m_keys.elementAt(kIndex);
  517. XObject r = k.m_selectPat.execute(m_execContext, node, k.m_namespaceContext);
  518. if(k.m_treatAsNumbers)
  519. m_KeyValue[kIndex] = r.num();
  520. else
  521. m_KeyValue[kIndex] = r.str();
  522. } */
  523. } // end if not empty
  524. }
  525. } // end NodeCompareElem class
  526. }