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 */
21package org.apache.xalan.transformer;
22
23import java.text.CollationKey;
24import java.util.Vector;
25
26import javax.xml.transform.TransformerException;
27
28import org.apache.xml.dtm.DTM;
29import org.apache.xml.dtm.DTMIterator;
30import org.apache.xpath.XPathContext;
31import org.apache.xpath.objects.XNodeSet;
32import 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 */
38public 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   */
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   */
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   */
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   */
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     */
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