001: package net.sf.saxon.sort;
002:
003: import net.sf.saxon.type.Type;
004: import net.sf.saxon.type.TypeHierarchy;
005: import net.sf.saxon.value.*;
006: import net.sf.saxon.expr.XPathContext;
007: import net.sf.saxon.Configuration;
008:
009: import java.text.CollationKey;
010: import java.text.Collator;
011: import java.util.Comparator;
012:
013: /**
014: * A Comparator used for comparing atomic values of arbitrary item types. It encapsulates
015: * a Collator that is used when the values to be compared are strings. It also supports
016: * a separate method for testing equality of items, which can be used for data types that
017: * are not ordered.
018: *
019: * The AtomicSortComparer is identical to the AtomicComparer except for its handling
020: * of NaN: it treats NaN values as lower than any other value, and NaNs as equal to
021: * each other.
022: *
023: * @author Michael H. Kay
024: *
025: */
026:
027: public class AtomicSortComparer implements Comparator,
028: java.io.Serializable {
029:
030: private Comparator collator;
031: private XPathContext conversionContext;
032:
033: public AtomicSortComparer(Comparator collator,
034: XPathContext conversion) {
035: this .collator = collator;
036: if (collator == null) {
037: this .collator = CodepointCollator.getInstance();
038: }
039: this .conversionContext = conversion;
040: }
041:
042: /**
043: * Compare two AtomicValue objects according to the rules for their data type. UntypedAtomic
044: * values are compared as if they were strings; if different semantics are wanted, the conversion
045: * must be done by the caller.
046: * @param a the first object to be compared. It is intended that this should be an instance
047: * of AtomicValue, though this restriction is not enforced. If it is a StringValue, the
048: * collator is used to compare the values, otherwise the value must implement the java.util.Comparable
049: * interface.
050: * @param b the second object to be compared. This must be comparable with the first object: for
051: * example, if one is a string, they must both be strings.
052: * @return <0 if a<b, 0 if a=b, >0 if a>b
053: * @throws ClassCastException if the objects are not comparable
054: */
055:
056: public int compare(Object a, Object b) {
057:
058: // System.err.println("Comparing " + a.getClass() + "(" + a + ") with " + b.getClass() + "(" + b + ") using " + collator);
059: if (a instanceof AtomicValue
060: && !((AtomicValue) a).hasBuiltInType()) {
061: a = ((AtomicValue) a).getPrimitiveValue();
062: }
063: if (b instanceof AtomicValue
064: && !((AtomicValue) b).hasBuiltInType()) {
065: b = ((AtomicValue) b).getPrimitiveValue();
066: }
067: if (a instanceof UntypedAtomicValue) {
068: return ((UntypedAtomicValue) a).compareTo(b, collator,
069: conversionContext);
070: } else if (b instanceof UntypedAtomicValue) {
071: return -((UntypedAtomicValue) b).compareTo(a, collator,
072: conversionContext);
073: } else if (a instanceof DoubleValue
074: && Double.isNaN(((DoubleValue) a).getDoubleValue())) {
075: if (b instanceof DoubleValue
076: && Double.isNaN(((DoubleValue) b).getDoubleValue())) {
077: return 0;
078: } else {
079: return -1;
080: }
081: } else if (b instanceof DoubleValue
082: && Double.isNaN(((DoubleValue) b).getDoubleValue())) {
083: return +1;
084: } else if (a instanceof CalendarValue
085: && b instanceof CalendarValue) {
086: return ((CalendarValue) a).compareTo((CalendarValue) b,
087: conversionContext);
088: } else if (a instanceof Comparable) {
089: return ((Comparable) a).compareTo(b);
090: } else if (a instanceof StringValue && b instanceof StringValue) {
091: return collator.compare(((StringValue) a).getStringValue(),
092: ((StringValue) b).getStringValue());
093: } else if (a instanceof AtomicValue && b instanceof AtomicValue) {
094: throw new ClassCastException("Objects are not comparable ("
095: + ((AtomicValue) a).getItemType(null) + ", "
096: + ((AtomicValue) b).getItemType(null) + ')');
097: } else {
098: throw new ClassCastException("Objects are not comparable ("
099: + a.getClass() + ", " + b.getClass() + ')');
100: }
101: }
102:
103: /**
104: * Get a comparison key for an object. This must satisfy the rule that if two objects are equal,
105: * then their comparison keys are equal, and vice versa. There is no requirement that the
106: * comparison keys should reflect the ordering of the underlying objects.
107: */
108:
109: public ComparisonKey getComparisonKey(AtomicValue a) {
110: AtomicValue prim = a.getPrimitiveValue();
111: final Configuration config = conversionContext
112: .getConfiguration();
113: final TypeHierarchy th = config.getNamePool()
114: .getTypeHierarchy();
115: if (prim instanceof NumericValue) {
116: if (((NumericValue) prim).isNaN()) {
117: // Deal with NaN specially. For this function, NaN is considered equal to itself
118: return new ComparisonKey(Type.NUMBER, NaN);
119: } else {
120: return new ComparisonKey(Type.NUMBER, prim);
121: }
122: } else if (prim instanceof StringValue) {
123: if (collator instanceof Collator) {
124: return new ComparisonKey(Type.STRING,
125: ((Collator) collator)
126: .getCollationKey(((StringValue) prim)
127: .getStringValue()));
128: } else {
129: return new ComparisonKey(Type.STRING, prim);
130: }
131: } else if (prim instanceof CalendarValue) {
132: CalendarValue cv = (CalendarValue) prim;
133: if (cv.hasTimezone()) {
134: return new ComparisonKey(prim.getItemType(th)
135: .getPrimitiveType(), prim);
136: } else {
137: cv = cv.copy();
138: cv.setTimezoneInMinutes(config.getImplicitTimezone());
139: return new ComparisonKey(cv.getItemType(th)
140: .getPrimitiveType(), cv);
141: }
142: } else {
143: return new ComparisonKey(prim.getItemType(th)
144: .getPrimitiveType(), prim);
145: }
146: }
147:
148: private static StringValue NaN = new StringValue("NaN");
149:
150: /**
151: * Inner class: an object used as a comparison key. Two XPath atomic values are equal if and only if their
152: * comparison keys are equal.
153: */
154:
155: public static class ComparisonKey {
156: int category;
157: Object value;
158:
159: /**
160: * Create a comparison key for a value in a particular category. The "category" here represents a
161: * set of primitive types that allow mutual comparison (so all numeric values are in the same category).
162: * @param category the category
163: * @param value the value within the category
164: */
165:
166: public ComparisonKey(int category, AtomicValue value) {
167: this .category = category;
168: this .value = value;
169: }
170:
171: /**
172: * Create a comparison key for strings using a particular collation. In this case the value compared is
173: * the collation key provided by the collator
174: * @param category Always Type.STRING
175: * @param value The collation key
176: */
177: public ComparisonKey(int category, CollationKey value) {
178: this .category = category;
179: this .value = value;
180: }
181:
182: /**
183: * Test if two comparison keys are equal
184: * @param other the other comparison key
185: * @return true if they are equal
186: * @throws ClassCastException if the other object is not a ComparisonKey
187: */
188: public boolean equals(Object other) {
189: if (other instanceof ComparisonKey) {
190: ComparisonKey otherKey = (ComparisonKey) other;
191: return this .category == otherKey.category
192: && this .value.equals(otherKey.value);
193: } else {
194: throw new ClassCastException(
195: "Cannot compare a ComparisonKey to an object of a different class");
196: }
197: }
198:
199: /**
200: * Get a hashcode for a comparison key. If two comparison keys are equal, they must have the same hash code.
201: * @return the hash code.
202: */
203: public int hashCode() {
204: return value.hashCode() ^ category;
205: }
206:
207: }
208:
209: }
210:
211: //
212: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
213: // you may not use this file except in compliance with the License. You may obtain a copy of the
214: // License at http://www.mozilla.org/MPL/
215: //
216: // Software distributed under the License is distributed on an "AS IS" basis,
217: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
218: // See the License for the specific language governing rights and limitations under the License.
219: //
220: // The Original Code is: all this file.
221: //
222: // The Initial Developer of the Original Code is Michael H. Kay
223: //
224: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
225: //
226: // Contributor(s): none
227: //
|