001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: NodeSorter.java 468645 2006-10-28 06:57:24Z minchau $
020     */
021    package org.apache.xalan.transformer;
022    
023    import java.text.CollationKey;
024    import java.util.Vector;
025    
026    import javax.xml.transform.TransformerException;
027    
028    import org.apache.xml.dtm.DTM;
029    import org.apache.xml.dtm.DTMIterator;
030    import org.apache.xpath.XPathContext;
031    import org.apache.xpath.objects.XNodeSet;
032    import org.apache.xpath.objects.XObject;
033    
034    /**
035     * This class can sort vectors of DOM nodes according to a select pattern.
036     * @xsl.usage internal
037     */
038    public class NodeSorter
039    {
040    
041      /** Current XPath context           */
042      XPathContext m_execContext;
043    
044      /** Vector of NodeSortKeys          */
045      Vector m_keys;  // vector of NodeSortKeys
046    
047    //  /**
048    //   * TODO: Adjust this for locale.
049    //   */
050    //  NumberFormat m_formatter = NumberFormat.getNumberInstance();
051    
052      /**
053       * Construct a NodeSorter, passing in the XSL TransformerFactory
054       * so it can know how to get the node data according to
055       * the proper whitespace rules.
056       *
057       * @param p Xpath context to use
058       */
059      public NodeSorter(XPathContext p)
060      {
061        m_execContext = p;
062      }
063    
064      /**
065       * Given a vector of nodes, sort each node according to
066       * the criteria in the keys.
067       * @param v an vector of Nodes.
068       * @param keys a vector of NodeSortKeys.
069       * @param support XPath context to use
070       *
071       * @throws javax.xml.transform.TransformerException
072       */
073      public void sort(DTMIterator v, Vector keys, XPathContext support)
074              throws javax.xml.transform.TransformerException
075      {
076    
077        m_keys = keys;
078    
079        // QuickSort2(v, 0, v.size() - 1 );
080        int n = v.getLength();
081    
082        // %OPT% Change mergesort to just take a DTMIterator?
083        // We would also have to adapt DTMIterator to have the function 
084        // of NodeCompareElem.
085        
086        // Create a vector of node compare elements
087        // based on the input vector of nodes
088        Vector nodes = new Vector();
089    
090        for (int i = 0; i < n; i++)
091        {
092          NodeCompareElem elem = new NodeCompareElem(v.item(i));
093    
094          nodes.addElement(elem);
095        }
096    
097        Vector scratchVector = new Vector();
098    
099        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    }