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: NodeSortRecord.java 468651 2006-10-28 07:04:25Z minchau $
020     */
021    
022    package org.apache.xalan.xsltc.dom;
023    
024    import java.text.CollationKey;
025    import java.text.Collator;
026    import java.util.Locale;
027    
028    import org.apache.xalan.xsltc.CollatorFactory;
029    import org.apache.xalan.xsltc.DOM;
030    import org.apache.xalan.xsltc.TransletException;
031    import org.apache.xalan.xsltc.runtime.AbstractTranslet;
032    import org.apache.xml.utils.StringComparable;
033    
034    /**
035     * Base class for sort records containing application specific sort keys 
036     */
037    public abstract class NodeSortRecord {
038        public static final int COMPARE_STRING     = 0;
039        public static final int COMPARE_NUMERIC    = 1;
040    
041        public static final int COMPARE_ASCENDING  = 0;
042        public static final int COMPARE_DESCENDING = 1;
043    
044        /**
045         * A reference to a collator. May be updated by subclass if the stylesheet
046         * specifies a different language (will be updated iff _locale is updated).
047         * @deprecated This field continues to exist for binary compatibility.
048         *             New code should not refer to it.
049         */
050        private static final Collator DEFAULT_COLLATOR = Collator.getInstance();
051    
052        /**
053         * A reference to the first Collator
054         * @deprecated This field continues to exist for binary compatibility.
055         *             New code should not refer to it.
056         */
057        protected Collator _collator = DEFAULT_COLLATOR;
058        protected Collator[] _collators;
059    
060        /**
061         * A locale field that might be set by an instance of a subclass.
062         * @deprecated This field continues to exist for binary compatibility.
063         *             New code should not refer to it.
064         */
065        protected Locale _locale;
066    
067        protected CollatorFactory _collatorFactory;
068    
069        protected SortSettings _settings;
070    
071        private DOM    _dom = null;
072        private int    _node;           // The position in the current iterator
073        private int    _last = 0;       // Number of nodes in the current iterator
074        private int    _scanned = 0;    // Number of key levels extracted from DOM
075    
076        private Object[] _values; // Contains Comparable  objects
077    
078        /**
079         * This constructor is run by a call to ClassLoader in the
080         * makeNodeSortRecord method in the NodeSortRecordFactory class. Since we
081         * cannot pass any parameters to the constructor in that case we just set
082         * the default values here and wait for new values through initialize().
083         */ 
084        public NodeSortRecord(int node) {
085            _node = node;
086        }
087    
088        public NodeSortRecord() {
089            this(0);
090        }
091    
092        /**
093         * This method allows the caller to set the values that could not be passed
094         * to the default constructor.
095         */
096        public final void initialize(int node, int last, DOM dom,
097             SortSettings settings)
098            throws TransletException
099        {
100            _dom = dom;
101            _node = node;
102            _last = last;
103            _settings = settings;
104    
105            int levels = settings.getSortOrders().length;
106            _values = new Object[levels];
107      
108            // -- W. Eliot Kimber (eliot@isogen.com)
109            String colFactClassname = 
110                System.getProperty("org.apache.xalan.xsltc.COLLATOR_FACTORY");
111    
112            if (colFactClassname != null) {
113                try {
114                    Object candObj = ObjectFactory.findProviderClass(
115                        colFactClassname, ObjectFactory.findClassLoader(), true);
116                    _collatorFactory = (CollatorFactory)candObj;
117                } catch (ClassNotFoundException e) {
118                    throw new TransletException(e);
119                }
120                Locale[] locales = settings.getLocales();
121                _collators = new Collator[levels];
122                for (int i = 0; i < levels; i++){
123                    _collators[i] = _collatorFactory.getCollator(locales[i]);
124                }
125                _collator = _collators[0];
126            } else {
127                _collators = settings.getCollators();
128                _collator = _collators[0];
129            }
130        }
131    
132        /**
133         * Returns the node for this sort object
134         */
135        public final int getNode() {
136            return _node;
137        }
138    
139        /**
140         *
141         */
142        public final int compareDocOrder(NodeSortRecord other) {
143            return _node - other._node;
144        }
145    
146        /**
147         * Get the string or numeric value of a specific level key for this sort
148         * element. The value is extracted from the DOM if it is not already in
149         * our sort key vector.
150         */
151        private final Comparable stringValue(int level) {
152            // Get value from our array if possible
153            if (_scanned <= level) {
154                AbstractTranslet translet = _settings.getTranslet();
155                Locale[] locales = _settings.getLocales();
156                String[] caseOrder = _settings.getCaseOrders();
157    
158                // Get value from DOM if accessed for the first time
159                final String str = extractValueFromDOM(_dom, _node, level,
160                                                       translet, _last);
161                final Comparable key =
162                    StringComparable.getComparator(str, locales[level],
163                                                   _collators[level],
164                                                   caseOrder[level]);
165                _values[_scanned++] = key;
166                return(key);
167            }
168            return((Comparable)_values[level]);
169      }
170        
171        private final Double numericValue(int level) {
172            // Get value from our vector if possible
173            if (_scanned <= level) {
174                AbstractTranslet translet = _settings.getTranslet();
175    
176                // Get value from DOM if accessed for the first time
177                final String str = extractValueFromDOM(_dom, _node, level,
178                                                       translet, _last);
179                Double num;
180                try {
181                    num = new Double(str);
182                }
183                // Treat number as NaN if it cannot be parsed as a double
184                catch (NumberFormatException e) {
185                    num = new Double(Double.NEGATIVE_INFINITY);
186                }
187                _values[_scanned++] = num;
188                return(num);
189            }
190            return((Double)_values[level]);
191        }
192    
193        /**
194         * Compare this sort element to another. The first level is checked first,
195         * and we proceed to the next level only if the first level keys are
196         * identical (and so the key values may not even be extracted from the DOM)
197         *
198         * !!!!MUST OPTIMISE - THIS IS REALLY, REALLY SLOW!!!!
199         */
200        public int compareTo(NodeSortRecord other) {
201            int cmp, level;
202            int[] sortOrder = _settings.getSortOrders();
203            int levels = _settings.getSortOrders().length;
204            int[] compareTypes = _settings.getTypes();
205    
206            for (level = 0; level < levels; level++) {
207                // Compare the two nodes either as numeric or text values
208                if (compareTypes[level] == COMPARE_NUMERIC) {
209                    final Double our = numericValue(level);
210                    final Double their = other.numericValue(level);
211                    cmp = our.compareTo(their);
212                }
213                else {
214                    final Comparable our = stringValue(level);
215                    final Comparable their = other.stringValue(level);
216                    cmp = our.compareTo(their);
217                }
218                
219                // Return inverse compare value if inverse sort order
220                if (cmp != 0) {
221                    return sortOrder[level] == COMPARE_DESCENDING ? 0 - cmp : cmp;
222                }
223            }
224            // Compare based on document order if all sort keys are equal
225            return(_node - other._node);
226        }
227    
228        /**
229         * Returns the array of Collators used for text comparisons in this object.
230         * May be overridden by inheriting classes
231         */
232        public Collator[] getCollator() {
233            return _collators;
234        }
235    
236        /**
237         * Extract the sort value for a level of this key.
238         */
239        public abstract String extractValueFromDOM(DOM dom, int current, int level,
240                                                   AbstractTranslet translet,
241                                                   int last);
242    
243    }