package javolution.util; import javolution.lang.Configurable; import javolution.text.Text; //import javolution.xml.XMLSerializable; import j2me.lang.CharSequence; import j2me.lang.Comparable; import j2me.util.Comparator; /** *

This class represents a comparator to be used for equality as well as * for ordering; instances of this class provide a hashcode function * consistent with equal (if two objects {@link #areEqual * are equal}, they have the same {@link #hashCodeOf hashcode}), * equality with null values is supported.

* *

{@link FastComparator} can be employed with {@link FastMap} (e.g. custom * key comparators for identity maps, value retrieval using keys of a * different class that the map keys) or with {@link FastCollection} * classes.

* * @author Jean-Marie Dautelle * @version 4.2, December 18, 2006 */ public abstract class FastComparator/**/implements Comparator/**/ /*,XMLSerializable*/ { /** * Indicates if the system hash code should be rehashed * (see * Javolution Configuration for details). */ public static final Configurable/**/ REHASH_SYSTEM_HASHCODE = new Configurable(new Boolean(isPoorSystemHash())) { protected void notifyChange() { _Rehash = ((Boolean)get()).booleanValue(); } }; static boolean _Rehash = ((Boolean)REHASH_SYSTEM_HASHCODE.get()).booleanValue(); private static boolean isPoorSystemHash() { boolean[] dist = new boolean[64]; // Length power of 2. for (int i = 0; i < dist.length; i++) { dist[new Object().hashCode() & (dist.length - 1)] = true; } int occupied = 0; for (int i = 0; i < dist.length;) { occupied += dist[i++] ? 1 : 0; // Count occupied slots. } return occupied < (dist.length >> 2); // Less than 16 slots on 64. } /** * Holds the default object comparator; rehash is performed if the * system hash code (platform dependent) is not evenly distributed. * @see * Javolution Configuration */ public static final FastComparator/**/ DEFAULT = new Default/**/(); static final class Default/**/ extends FastComparator/**/ { public int hashCodeOf(Object/*{T}*/ obj) { return (_Rehash ? REHASH.hashCodeOf(obj) : obj.hashCode()); } public boolean areEqual(Object/*{T}*/ o1, Object/*{T}*/ o2) { return (o1 == null) ? (o2 == null) : (o1 == o2) || o1.equals(o2); } public int compare(Object/*{T}*/ o1, Object/*{T}*/ o2) { return ((Comparable) o1).compareTo(o2); } public String toString() { return "Default"; } }; /** * Holds the direct object comparator; no rehash is performed. * Two objects o1 and o2 are considered {@link #areEqual equal} if and * only if o1.equals(o2). The {@link #compare} method * throws {@link ClassCastException} if the specified objects are not * {@link Comparable}. */ public static final FastComparator/**/ DIRECT = new Direct/**/(); static final class Direct/**/ extends FastComparator/**/ { public int hashCodeOf(Object/*{T}*/ obj) { return obj.hashCode(); } public boolean areEqual(Object/*{T}*/ o1, Object/*{T}*/ o2) { return (o1 == null) ? (o2 == null) : (o1 == o2) || o1.equals(o2); } public int compare(Object/*{T}*/ o1, Object/*{T}*/ o2) { return ((Comparable) o1).compareTo(o2); } public String toString() { return "Direct"; } }; /** * Holds the comparator for objects with uneven hash distribution; objects * hashcodes are rehashed. Two objects o1 and o2 are considered * {@link #areEqual equal} if and only if o1.equals(o2). * The {@link #compare} method throws {@link ClassCastException} if the * specified objects are not {@link Comparable}. */ public static final FastComparator/**/ REHASH = new Rehash/**/(); static final class Rehash/**/ extends FastComparator/**/ { public int hashCodeOf(Object/*{T}*/ obj) { // Formula identical java.util.HashMap to ensures // similar behavior for ill-conditioned hashcode keys. int h = obj.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); return h ^ (h >>> 10); } public boolean areEqual(Object/*{T}*/ o1, Object/*{T}*/ o2) { return (o1 == null) ? (o2 == null) : (o1 == o2) || o1.equals(o2); } public int compare(Object/*{T}*/ o1, Object/*{T}*/ o2) { return ((Comparable) o1).compareTo(o2); } public String toString() { return "Rehash"; } }; /** * Holds a fast comparator for java.lang.String. Hashcodes * are calculated by taking a sample of few characters instead of * the whole string. */ public static final FastComparator/**/ STRING = new StringComparator(); static final class StringComparator extends FastComparator { public int hashCodeOf(Object obj) { final String str = (String)obj; final int length = str.length(); if (length == 0) return 0; return str.charAt(0) + str.charAt(length - 1) * 31 + str.charAt(length >> 1) * 1009 + str.charAt(length >> 2) * 27583 + str.charAt(length - 1 - (length >> 2)) * 73408859; } public boolean areEqual(Object o1, Object o2) { return (o1 == null) ? (o2 == null) : (o1 == o2) || o1.equals(o2); } public int compare(Object o1, Object o2) { return ((String) o1).compareTo((String)o2); } public String toString() { return "String"; } }; /** * Holds the identity comparator; poorly distributed system hashcodes are * rehashed. Two objects o1 and o2 are considered {@link #areEqual equal} * if and only if (o1 == o2). The {@link #compare} method * throws {@link ClassCastException} if the specified objects are not * {@link Comparable}. */ public static final FastComparator/**/ IDENTITY = new Identity(); static final class Identity extends FastComparator { public int hashCodeOf(Object obj) { int h = System.identityHashCode(obj); if (!_Rehash) return h; h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); return h ^ (h >>> 10); } public boolean areEqual(Object o1, Object o2) { return o1 == o2; } public int compare(Object o1, Object o2) { return ((Comparable) o1).compareTo(o2); } public String toString() { return "identity"; } }; /** * Holds a lexicographic comparator for any {@link CharSequence} or * {@link String} instances. * Two objects are considered {@link #areEqual equal} if and only if they * represents the same character sequence). The hashcode is calculated * using the following formula (same as for java.lang.String): * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] */ public static final FastComparator/**/ LEXICAL = new Lexical(); static final class Lexical extends FastComparator { public int hashCodeOf(Object obj) { if ((obj instanceof String) || (obj instanceof Text)) return obj.hashCode(); CharSequence chars = (CharSequence) obj; int h = 0; final int length = chars.length(); for (int i = 0; i < length;) { h = 31 * h + chars.charAt(i++); } return h; } public boolean areEqual(Object o1, Object o2) { if ((o1 instanceof String) && (o2 instanceof String)) return o1.equals(o2); if ((o1 instanceof CharSequence) && (o2 instanceof String)) { final CharSequence csq = (CharSequence) o1; final String str = (String) o2; final int length = str.length(); if (csq.length() != length) return false; for (int i = 0; i < length;) { if (str.charAt(i) != csq.charAt(i++)) return false; } return true; } if ((o1 instanceof String) && (o2 instanceof CharSequence)) { final CharSequence csq = (CharSequence) o2; final String str = (String) o1; final int length = str.length(); if (csq.length() != length) return false; for (int i = 0; i < length;) { if (str.charAt(i) != csq.charAt(i++)) return false; } return true; } if ((o1 == null) || (o2 == null)) return o1 == o2; final CharSequence csq1 = (CharSequence) o1; final CharSequence csq2 = (CharSequence) o2; final int length = csq1.length(); if (csq2.length() != length) return false; for (int i = 0; i < length;) { if (csq1.charAt(i) != csq2.charAt(i++)) return false; } return true; } public int compare(Object left, Object right) { if (left instanceof String) { if (right instanceof String) return ((String) left).compareTo((String) right); // Right must be a CharSequence. String seq1 = (String) left; CharSequence seq2 = (CharSequence) right; int i = 0; int n = Math.min(seq1.length(), seq2.length()); while (n-- != 0) { char c1 = seq1.charAt(i); char c2 = seq2.charAt(i++); if (c1 != c2) { return c1 - c2; } } return seq1.length() - seq2.length(); } if (right instanceof String) return -compare(right, left); // Both are CharSequence. CharSequence seq1 = (CharSequence) left; CharSequence seq2 = (CharSequence) right; int i = 0; int n = Math.min(seq1.length(), seq2.length()); while (n-- != 0) { char c1 = seq1.charAt(i); char c2 = seq2.charAt(i++); if (c1 != c2) { return c1 - c2; } } return seq1.length() - seq2.length(); } public String toString() { return "lexical"; } }; /** * Returns the hash code for the specified object (consistent with * {@link #areEqual}). Two objects considered {@link #areEqual equal} have * the same hash code. * * @param obj the object to return the hashcode for. * @return the hashcode for the specified object. * @throws NullPointerException if the specified object is * null. */ public abstract int hashCodeOf(Object/*{T}*/obj); /** * Indicates if the specified objects can be considered equal. * * @param o1 the first object (or null). * @param o2 the second object (or null). * @return true if both objects are considered equal; * false otherwise. */ public abstract boolean areEqual(Object/*{T}*/o1, Object/*{T}*/o2); /** * Compares the specified objects for order. Returns a negative integer, * zero, or a positive integer as the first argument is less than, equal to, * or greater than the second. * * @param o1 the first object. * @param o2 the second object. * @return a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second. * @throws NullPointerException if any of the specified object is * null. */ public abstract int compare(Object/*{T}*/o1, Object/*{T}*/o2); }