1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: NodeSorter.java 468645 2006-10-28 06:57:24Z minchau $
20  */
21 package org.apache.xalan.transformer;
22 
23 import java.text.CollationKey;
24 import java.util.Vector;
25 
26 import javax.xml.transform.TransformerException;
27 
28 import org.apache.xml.dtm.DTM;
29 import org.apache.xml.dtm.DTMIterator;
30 import org.apache.xpath.XPathContext;
31 import org.apache.xpath.objects.XNodeSet;
32 import org.apache.xpath.objects.XObject;
33 
34 /**
35  * This class can sort vectors of DOM nodes according to a select pattern.
36  * @xsl.usage internal
37  */
38 public class NodeSorter
39 {
40 
41   /** Current XPath context           */
42   XPathContext m_execContext;
43 
44   /** Vector of NodeSortKeys          */
45   Vector m_keys;  // vector of NodeSortKeys
46 
47 //  /**
48 //   * TODO: Adjust this for locale.
49 //   */
50 //  NumberFormat m_formatter = NumberFormat.getNumberInstance();
51 
52   /**
53    * Construct a NodeSorter, passing in the XSL TransformerFactory
54    * so it can know how to get the node data according to
55    * the proper whitespace rules.
56    *
57    * @param p Xpath context to use
58    */
NodeSorter(XPathContext p)59   public NodeSorter(XPathContext p)
60   {
61     m_execContext = p;
62   }
63 
64   /**
65    * Given a vector of nodes, sort each node according to
66    * the criteria in the keys.
67    * @param v an vector of Nodes.
68    * @param keys a vector of NodeSortKeys.
69    * @param support XPath context to use
70    *
71    * @throws javax.xml.transform.TransformerException
72    */
sort(DTMIterator v, Vector keys, XPathContext support)73   public void sort(DTMIterator v, Vector keys, XPathContext support)
74           throws javax.xml.transform.TransformerException
75   {
76 
77     m_keys = keys;
78 
79     // QuickSort2(v, 0, v.size() - 1 );
80     int n = v.getLength();
81 
82     // %OPT% Change mergesort to just take a DTMIterator?
83     // We would also have to adapt DTMIterator to have the function
84     // of NodeCompareElem.
85 
86     // Create a vector of node compare elements
87     // based on the input vector of nodes
88     Vector nodes = new Vector();
89 
90     for (int i = 0; i < n; i++)
91     {
92       NodeCompareElem elem = new NodeCompareElem(v.item(i));
93 
94       nodes.addElement(elem);
95     }
96 
97     Vector scratchVector = new Vector();
98 
99     mergesort(nodes, scratchVector, 0, n - 1, support);
100 
101     // return sorted vector of nodes
102     for (int i = 0; i < n; i++)
103     {
104       v.setItem(((NodeCompareElem) nodes.elementAt(i)).m_node, i);
105     }
106     v.setCurrentPos(0);
107 
108     // old code...
109     //NodeVector scratchVector = new NodeVector(n);
110     //mergesort(v, scratchVector, 0, n - 1, support);
111   }
112 
113   /**
114    * Return the results of a compare of two nodes.
115    * TODO: Optimize compare -- cache the getStringExpr results, key by m_selectPat + hash of node.
116    *
117    * @param n1 First node to use in compare
118    * @param n2 Second node to use in compare
119    * @param kIndex Index of NodeSortKey to use for sort
120    * @param support XPath context to use
121    *
122    * @return The results of the compare of the two nodes.
123    *
124    * @throws TransformerException
125    */
compare( NodeCompareElem n1, NodeCompareElem n2, int kIndex, XPathContext support)126   int compare(
127           NodeCompareElem n1, NodeCompareElem n2, int kIndex, XPathContext support)
128             throws TransformerException
129   {
130 
131     int result = 0;
132     NodeSortKey k = (NodeSortKey) m_keys.elementAt(kIndex);
133 
134     if (k.m_treatAsNumbers)
135     {
136       double n1Num, n2Num;
137 
138       if (kIndex == 0)
139       {
140         n1Num = ((Double) n1.m_key1Value).doubleValue();
141         n2Num = ((Double) n2.m_key1Value).doubleValue();
142       }
143       else if (kIndex == 1)
144       {
145         n1Num = ((Double) n1.m_key2Value).doubleValue();
146         n2Num = ((Double) n2.m_key2Value).doubleValue();
147       }
148 
149       /* Leave this in case we decide to use an array later
150       if (kIndex < maxkey)
151       {
152       double n1Num = (double)n1.m_keyValue[kIndex];
153       double n2Num = (double)n2.m_keyValue[kIndex];
154       }*/
155       else
156       {
157 
158         // Get values dynamically
159         XObject r1 = k.m_selectPat.execute(m_execContext, n1.m_node,
160                                            k.m_namespaceContext);
161         XObject r2 = k.m_selectPat.execute(m_execContext, n2.m_node,
162                                            k.m_namespaceContext);
163         n1Num = r1.num();
164 
165         // Can't use NaN for compare. They are never equal. Use zero instead.
166         // That way we can keep elements in document order.
167         //n1Num = Double.isNaN(d) ? 0.0 : d;
168         n2Num = r2.num();
169         //n2Num = Double.isNaN(d) ? 0.0 : d;
170       }
171 
172       if ((n1Num == n2Num) && ((kIndex + 1) < m_keys.size()))
173       {
174         result = compare(n1, n2, kIndex + 1, support);
175       }
176       else
177       {
178         double diff;
179         if (Double.isNaN(n1Num))
180         {
181           if (Double.isNaN(n2Num))
182             diff = 0.0;
183           else
184             diff = -1;
185         }
186         else if (Double.isNaN(n2Num))
187            diff = 1;
188         else
189           diff = n1Num - n2Num;
190 
191         // process order parameter
192         result = (int) ((diff < 0.0)
193                         ? (k.m_descending ? 1 : -1)
194                         : (diff > 0.0) ? (k.m_descending ? -1 : 1) : 0);
195       }
196     }  // end treat as numbers
197     else
198     {
199       CollationKey n1String, n2String;
200 
201       if (kIndex == 0)
202       {
203         n1String = (CollationKey) n1.m_key1Value;
204         n2String = (CollationKey) n2.m_key1Value;
205       }
206       else if (kIndex == 1)
207       {
208         n1String = (CollationKey) n1.m_key2Value;
209         n2String = (CollationKey) n2.m_key2Value;
210       }
211 
212       /* Leave this in case we decide to use an array later
213       if (kIndex < maxkey)
214       {
215         String n1String = (String)n1.m_keyValue[kIndex];
216         String n2String = (String)n2.m_keyValue[kIndex];
217       }*/
218       else
219       {
220 
221         // Get values dynamically
222         XObject r1 = k.m_selectPat.execute(m_execContext, n1.m_node,
223                                            k.m_namespaceContext);
224         XObject r2 = k.m_selectPat.execute(m_execContext, n2.m_node,
225                                            k.m_namespaceContext);
226 
227         n1String = k.m_col.getCollationKey(r1.str());
228         n2String = k.m_col.getCollationKey(r2.str());
229       }
230 
231       // Use collation keys for faster compare, but note that whitespaces
232       // etc... are treated differently from if we were comparing Strings.
233       result = n1String.compareTo(n2String);
234 
235       //Process caseOrder parameter
236       if (k.m_caseOrderUpper)
237       {
238         String tempN1 = n1String.getSourceString().toLowerCase();
239         String tempN2 = n2String.getSourceString().toLowerCase();
240 
241         if (tempN1.equals(tempN2))
242         {
243 
244           //java defaults to upper case is greater.
245           result = result == 0 ? 0 : -result;
246         }
247       }
248 
249       //Process order parameter
250       if (k.m_descending)
251       {
252         result = -result;
253       }
254     }  //end else
255 
256     if (0 == result)
257     {
258       if ((kIndex + 1) < m_keys.size())
259       {
260         result = compare(n1, n2, kIndex + 1, support);
261       }
262     }
263 
264     if (0 == result)
265     {
266 
267       // I shouldn't have to do this except that there seems to
268       // be a glitch in the mergesort
269       // if(r1.getType() == r1.CLASS_NODESET)
270       // {
271       DTM dtm = support.getDTM(n1.m_node); // %OPT%
272       result = dtm.isNodeAfter(n1.m_node, n2.m_node) ? -1 : 1;
273 
274       // }
275     }
276 
277     return result;
278   }
279 
280   /**
281    * This implements a standard Mergesort, as described in
282    * Robert Sedgewick's Algorithms book.  This is a better
283    * sort for our purpose than the Quicksort because it
284    * maintains the original document order of the input if
285    * the order isn't changed by the sort.
286    *
287    * @param a First vector of nodes to compare
288    * @param b Second vector of  nodes to compare
289    * @param l Left boundary of  partition
290    * @param r Right boundary of  partition
291    * @param support XPath context to use
292    *
293    * @throws TransformerException
294    */
mergesort(Vector a, Vector b, int l, int r, XPathContext support)295   void mergesort(Vector a, Vector b, int l, int r, XPathContext support)
296           throws TransformerException
297   {
298 
299     if ((r - l) > 0)
300     {
301       int m = (r + l) / 2;
302 
303       mergesort(a, b, l, m, support);
304       mergesort(a, b, m + 1, r, support);
305 
306       int i, j, k;
307 
308       for (i = m; i >= l; i--)
309       {
310 
311         // b[i] = a[i];
312         // Use insert if we need to increment vector size.
313         if (i >= b.size())
314           b.insertElementAt(a.elementAt(i), i);
315         else
316           b.setElementAt(a.elementAt(i), i);
317       }
318 
319       i = l;
320 
321       for (j = (m + 1); j <= r; j++)
322       {
323 
324         // b[r+m+1-j] = a[j];
325         if (r + m + 1 - j >= b.size())
326           b.insertElementAt(a.elementAt(j), r + m + 1 - j);
327         else
328           b.setElementAt(a.elementAt(j), r + m + 1 - j);
329       }
330 
331       j = r;
332 
333       int compVal;
334 
335       for (k = l; k <= r; k++)
336       {
337 
338         // if(b[i] < b[j])
339         if (i == j)
340           compVal = -1;
341         else
342           compVal = compare((NodeCompareElem) b.elementAt(i),
343                             (NodeCompareElem) b.elementAt(j), 0, support);
344 
345         if (compVal < 0)
346         {
347 
348           // a[k]=b[i];
349           a.setElementAt(b.elementAt(i), k);
350 
351           i++;
352         }
353         else if (compVal > 0)
354         {
355 
356           // a[k]=b[j];
357           a.setElementAt(b.elementAt(j), k);
358 
359           j--;
360         }
361       }
362     }
363   }
364 
365   /**
366    * This is a generic version of C.A.R Hoare's Quick Sort
367    * algorithm.  This will handle arrays that are already
368    * sorted, and arrays with duplicate keys.<BR>
369    *
370    * If you think of a one dimensional array as going from
371    * the lowest index on the left to the highest index on the right
372    * then the parameters to this function are lowest index or
373    * left and highest index or right.  The first time you call
374    * this function it will be with the parameters 0, a.length - 1.
375    *
376    * @param v       a vector of integers
377    * @param lo0     left boundary of array partition
378    * @param hi0     right boundary of array partition
379    *
380    */
381 
382   /*  private void QuickSort2(Vector v, int lo0, int hi0, XPathContext support)
383       throws javax.xml.transform.TransformerException,
384              java.net.MalformedURLException,
385              java.io.FileNotFoundException,
386              java.io.IOException
387     {
388       int lo = lo0;
389       int hi = hi0;
390 
391       if ( hi0 > lo0)
392       {
393         // Arbitrarily establishing partition element as the midpoint of
394         // the array.
395         Node midNode = (Node)v.elementAt( ( lo0 + hi0 ) / 2 );
396 
397         // loop through the array until indices cross
398         while( lo <= hi )
399         {
400           // find the first element that is greater than or equal to
401           // the partition element starting from the left Index.
402           while( (lo < hi0) && (compare((Node)v.elementAt(lo), midNode, 0, support) < 0) )
403           {
404             ++lo;
405           } // end while
406 
407           // find an element that is smaller than or equal to
408           // the partition element starting from the right Index.
409           while( (hi > lo0) && (compare((Node)v.elementAt(hi), midNode, 0, support) > 0) )
410           {
411             --hi;
412           }
413 
414           // if the indexes have not crossed, swap
415           if( lo <= hi )
416           {
417             swap(v, lo, hi);
418             ++lo;
419             --hi;
420           }
421         }
422 
423         // If the right index has not reached the left side of array
424         // must now sort the left partition.
425         if( lo0 < hi )
426         {
427           QuickSort2( v, lo0, hi, support );
428         }
429 
430         // If the left index has not reached the right side of array
431         // must now sort the right partition.
432         if( lo < hi0 )
433         {
434           QuickSort2( v, lo, hi0, support );
435         }
436       }
437     } // end QuickSort2  */
438 
439 //  /**
440 //   * Simple function to swap two elements in
441 //   * a vector.
442 //   *
443 //   * @param v Vector of nodes to swap
444 //   * @param i Index of first node to swap
445 //   * @param i Index of second node to swap
446 //   */
447 //  private void swap(Vector v, int i, int j)
448 //  {
449 //
450 //    int node = (Node) v.elementAt(i);
451 //
452 //    v.setElementAt(v.elementAt(j), i);
453 //    v.setElementAt(node, j);
454 //  }
455 
456   /**
457    * This class holds the value(s) from executing the given
458    * node against the sort key(s).
459    * @xsl.usage internal
460    */
461   class NodeCompareElem
462   {
463 
464     /** Current node          */
465     int m_node;
466 
467     /** This maxkey value was chosen arbitrarily. We are assuming that the
468     // maxkey + 1 keys will only hit fairly rarely and therefore, we
469     // will get the node values for those keys dynamically.
470     */
471     int maxkey = 2;
472 
473     // Keep this in case we decide to use an array. Right now
474     // using two variables is cheaper.
475     //Object[] m_KeyValue = new Object[2];
476 
477     /** Value from first sort key           */
478     Object m_key1Value;
479 
480     /** Value from second sort key            */
481     Object m_key2Value;
482 
483     /**
484      * Constructor NodeCompareElem
485      *
486      *
487      * @param node Current node
488      *
489      * @throws javax.xml.transform.TransformerException
490      */
NodeCompareElem(int node)491     NodeCompareElem(int node) throws javax.xml.transform.TransformerException
492     {
493       m_node = node;
494 
495       if (!m_keys.isEmpty())
496       {
497         NodeSortKey k1 = (NodeSortKey) m_keys.elementAt(0);
498         XObject r = k1.m_selectPat.execute(m_execContext, node,
499                                            k1.m_namespaceContext);
500 
501         double d;
502 
503         if (k1.m_treatAsNumbers)
504         {
505           d = r.num();
506 
507           // Can't use NaN for compare. They are never equal. Use zero instead.
508           m_key1Value = new Double(d);
509         }
510         else
511         {
512           m_key1Value = k1.m_col.getCollationKey(r.str());
513         }
514 
515         if (r.getType() == XObject.CLASS_NODESET)
516         {
517           // %REVIEW%
518           DTMIterator ni = ((XNodeSet)r).iterRaw();
519           int current = ni.getCurrentNode();
520           if(DTM.NULL == current)
521             current = ni.nextNode();
522 
523           // if (ni instanceof ContextNodeList) // %REVIEW%
524           // tryNextKey = (DTM.NULL != current);
525 
526           // else abdicate... should never happen, but... -sb
527         }
528 
529         if (m_keys.size() > 1)
530         {
531           NodeSortKey k2 = (NodeSortKey) m_keys.elementAt(1);
532 
533           XObject r2 = k2.m_selectPat.execute(m_execContext, node,
534                                               k2.m_namespaceContext);
535 
536           if (k2.m_treatAsNumbers) {
537             d = r2.num();
538             m_key2Value = new Double(d);
539           } else {
540             m_key2Value = k2.m_col.getCollationKey(r2.str());
541           }
542         }
543 
544         /* Leave this in case we decide to use an array later
545         while (kIndex <= m_keys.size() && kIndex < maxkey)
546         {
547           NodeSortKey k = (NodeSortKey)m_keys.elementAt(kIndex);
548           XObject r = k.m_selectPat.execute(m_execContext, node, k.m_namespaceContext);
549           if(k.m_treatAsNumbers)
550             m_KeyValue[kIndex] = r.num();
551           else
552             m_KeyValue[kIndex] = r.str();
553         } */
554       }  // end if not empty
555     }
556   }  // end NodeCompareElem class
557 }
558