diff src/com/go/trove/util/BeanComparator.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/go/trove/util/BeanComparator.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,1167 @@
+/* ====================================================================
+ * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
+ * ====================================================================
+ * The Tea Software License, Version 1.1
+ *
+ * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Walt Disney Internet Group (http://opensource.go.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact opensource@dig.com.
+ *
+ * 5. Products derived from this software may not be called "Tea",
+ *    "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
+ *    "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
+ *    written permission of the Walt Disney Internet Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * For more information about Tea, please see http://opensource.go.com/.
+ */
+
+package com.go.trove.util;
+
+import java.beans.*;
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+import com.go.trove.classfile.*;
+
+/******************************************************************************
+ * A highly customizable, high-performance Comparator, designed specifically
+ * for advanced sorting of JavaBeans. BeanComparators contain dynamically
+ * auto-generated code and perform as well as hand written Comparators.
+ * <p>
+ * BeanComparator instances are immutable; order customization methods
+ * return new BeanComparators with refined rules. Calls to customizers can
+ * be chained together to read like a formula. The following example produces
+ * a Comparator that orders Threads by name, thread group name, and reverse
+ * priority.
+ *
+ * <pre>
+ * Comparator c = BeanComparator.forClass(Thread.class)
+ *     .orderBy("name")
+ *     .orderBy("threadGroup.name")
+ *     .orderBy("priority")
+ *     .reverse();
+ * </pre>
+ *
+ * The results of sorting Threads using this Comparator and displaying the
+ * results in a table may look like this:
+ *
+ * <p><table border="2">
+ * <tr><th>name</th><th>threadGroup.name</th><th>priority</th></tr>
+ * <tr><td>daemon</td><td>appGroup</td><td>9</td></tr>
+ * <tr><td>main</td><td>main</td><td>5</td></tr>
+ * <tr><td>main</td><td>secureGroup</td><td>5</td></tr>
+ * <tr><td>sweeper</td><td>main</td><td>1</td></tr>
+ * <tr><td>Thread-0</td><td>main</td><td>5</td></tr>
+ * <tr><td>Thread-1</td><td>main</td><td>5</td></tr>
+ * <tr><td>worker</td><td>appGroup</td><td>8</td></tr>
+ * <tr><td>worker</td><td>appGroup</td><td>5</td></tr>
+ * <tr><td>worker</td><td>secureGroup</td><td>8</td></tr>
+ * <tr><td>worker</td><td>secureGroup</td><td>5</td></tr>
+ * </table><p>
+ *
+ * An equivalent Thread ordering Comparator may be specified as:
+ *
+ * <pre>
+ * Comparator c = BeanComparator.forClass(Thread.class)
+ *     .orderBy("name")
+ *     .orderBy("threadGroup")
+ *     .using(BeanComparator.forClass(ThreadGroup.class).orderBy("name"))
+ *     .orderBy("priority")
+ *     .reverse();
+ * </pre>
+ *
+ * The current implementation of BeanComparator has been optimized for fast
+ * construction and execution of BeanComparators. For maximum performance,
+ * however, save and re-use BeanComparators wherever possible.
+ * <p>
+ * Even though BeanComparator makes use of auto-generated code, instances are
+ * fully Serializable, as long as all passed in Comparators are also
+ * Serializable.
+ *
+ * @author Brian S O'Neill
+ * @version
+ * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 00/12/18 <!-- $-->
+ * @see java.beans.Introspector
+ * @see CompleteIntrospector
+ * @see java.util.Collections.sort
+ * @see java.util.Arrays.sort
+ */
+public class BeanComparator implements Comparator, Serializable {
+    // Maps Rules to auto-generated Comparators.
+    private static Map cGeneratedComparatorCache;
+
+    static {
+        cGeneratedComparatorCache = new SoftHashMap();
+    }
+
+    /**
+     * Get or create a new BeanComparator for beans of the given type. Without
+     * any {@link #orderBy order-by} properties specified, the returned
+     * BeanComparator can only order against null beans (null is 
+     * {@link #nullHigh high} by default), and treats all other comparisons as
+     * equal.
+     */
+    public static BeanComparator forClass(Class clazz) {
+        return new BeanComparator(clazz);
+    }
+
+    /**
+     * Compare two objects for equality.
+     */
+    private static boolean equalTest(Object obj1, Object obj2) {
+        return (obj1 == obj2) ? true :
+            ((obj1 == null || obj2 == null) ? false : obj1.equals(obj2));
+    }
+
+    /**
+     * Compare two object classes for equality.
+     */
+    /*
+    private static boolean equalClassTest(Object obj1, Object obj2) {
+        return (obj1 == obj2) ? true :
+            ((obj1 == null || obj2 == null) ? false :
+             obj1.getClass().equals(obj2.getClass()));
+    }
+    */
+
+    private Class mBeanClass;
+
+    // Maps property names to PropertyDescriptors.
+    private transient Map mProperties;
+
+    private String mOrderByName;
+
+    private Comparator mUsingComparator;
+
+    // bit 0: reverse
+    // bit 1: null low order
+    // bit 2: use String compareTo instead of collator
+    private int mFlags;
+
+    // Used for comparing strings.
+    private Comparator mCollator;
+
+    private BeanComparator mParent;
+
+    // Auto-generated internal Comparator.
+    private transient Comparator mComparator;
+
+    private transient boolean mHasHashCode;
+    private transient int mHashCode;
+
+    private BeanComparator(Class clazz) {
+        mBeanClass = clazz;
+        mCollator = String.CASE_INSENSITIVE_ORDER;
+    }
+
+    private BeanComparator(BeanComparator parent) {
+        mParent = parent;
+        mBeanClass = parent.mBeanClass;
+        mProperties = parent.getProperties();
+        mCollator = parent.mCollator;
+    }
+
+    /**
+     * Add an order-by property to produce a more refined Comparator. If the
+     * property does not return a {@link Comparable} object when
+     * {@link #compare compare} is called on the returned comparator, the
+     * property is ignored. Call {@link #using using} on the returned
+     * BeanComparator to specify a Comparator to use for this property instead.
+     * <p>
+     * The specified propery name may refer to sub-properties using a dot
+     * notation. For example, if the bean being compared contains a property
+     * named "info" of type "Information", and "Information" contains a
+     * property named "text", then ordering by the info text can be specified
+     * by "info.text". Sub-properties of sub-properties may be refered to as
+     * well, a.b.c.d.e etc.
+     * <p>
+     * If property type is a primitive, ordering is the same as for its
+     * Comparable object peer. Primitive booleans are ordered false low, true
+     * high. Floating point primitves are ordered exactly the same way as
+     * {@link Float#compareTo(Float) Float.compareTo} and
+     * {@link Double#compareTo(Double) Double.compareTo}.
+     * <p>
+     * Any {@link #reverse reverse-order}, {@link #nullHigh null-order} and 
+     * {@link #caseSensitive case-sensitive} settings are not carried over,
+     * and are reset to the defaults for this order-by property.
+     *
+     * @throws IllegalArgumentException when property doesn't exist or cannot
+     * be read.
+     */
+    public BeanComparator orderBy(String propertyName)
+        throws IllegalArgumentException
+    {
+        int dot = propertyName.indexOf('.');
+        String subName;
+        if (dot < 0) {
+            subName = null;
+        }
+        else {
+            subName = propertyName.substring(dot + 1);
+            propertyName = propertyName.substring(0, dot);
+        }
+
+        PropertyDescriptor desc =
+            (PropertyDescriptor)getProperties().get(propertyName);
+
+        if (desc == null) {
+            throw new IllegalArgumentException
+                ("Property '" + propertyName + "' doesn't exist in '" +
+                 mBeanClass.getName() + '\'');
+        }
+
+        if (desc.getPropertyType() == null) {
+            throw new IllegalArgumentException
+                ("Property '" + propertyName + "' is only an indexed type");
+        }
+
+        if (desc.getReadMethod() == null) {
+            throw new IllegalArgumentException
+                ("Property '" + propertyName + "' cannot be read");
+        }
+
+        if (propertyName.equals(mOrderByName)) {
+            // Make String unique so that properties can be specified in
+            // consecutive order-by calls without being eliminated by
+            // reduceRules. A secondary order-by may wish to further refine an
+            // ambiguous comparison using a Comparator.
+            propertyName = new String(propertyName);
+        }
+
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = propertyName;
+
+        if (subName != null) {
+            BeanComparator subOrder = forClass(desc.getPropertyType());
+            subOrder.mCollator = mCollator;
+            bc = bc.using(subOrder.orderBy(subName));
+        }
+
+        return bc;
+    }
+    
+    /**
+     * Specifiy a Comparator to use on just the last {@link #orderBy order-by}
+     * property. This is good for comparing properties that are not
+     * {@link Comparable} or for applying special ordering rules for a
+     * property. If no order-by properties have been specified, then Comparator
+     * is applied to the compared beans.
+     * <p>
+     * Any String {@link #caseSensitive case-sensitive} or 
+     * {@link #collate collator} settings are overridden by this Comparator.
+     * If property values being compared are primitive, they are converted to
+     * their object peers before being passed to the Comparator.
+     *
+     * @param c Comparator to use on the last order-by property. Passing null
+     * restores the default comparison for the last order-by property.
+     */
+    public BeanComparator using(Comparator c) {
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = c;
+        bc.mFlags = mFlags;
+        return bc;
+    }
+
+    /**
+     * Toggle reverse-order option on just the last {@link #orderBy order-by}
+     * property. By default, order is ascending. If no order-by properties have
+     * been specified, then reverse order is applied to the compared beans.
+     */
+    public BeanComparator reverse() {
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = mUsingComparator;
+        bc.mFlags = mFlags ^ 0x01;
+        return bc;
+    }
+
+    /**
+     * Set the order of comparisons against null as being high (the default)
+     * on just the last {@link #orderBy order-by} property. If no order-by
+     * properties have been specified, then null high order is applied to the
+     * compared beans. Null high order is the default for consistency with the
+     * high ordering of {@link Float#NaN NaN} by
+     * {@link Float#compareTo(Float) Float}.
+     * <p>
+     * Calling 'nullHigh, reverse' is equivalent to calling 'reverse, nullLow'.
+     */
+    public BeanComparator nullHigh() {
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = mUsingComparator;
+        bc.mFlags = mFlags ^ ((mFlags & 0x01) << 1);
+        return bc;
+    }
+
+    /**
+     * Set the order of comparisons against null as being low
+     * on just the last {@link #orderBy order-by} property. If no order-by
+     * properties have been specified, then null low order is applied to the
+     * compared beans.
+     * <p>
+     * Calling 'reverse, nullLow' is equivalent to calling 'nullHigh, reverse'.
+     */
+    public BeanComparator nullLow() {
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = mUsingComparator;
+        bc.mFlags = mFlags ^ ((~mFlags & 0x01) << 1);
+        return bc;
+    }
+
+    /**
+     * Override the collator and compare just the last order-by property using
+     * {@link String#compareTo(String) String.compareTo}, if it is of type
+     * String. If no order-by properties have been specified then this call is
+     * ineffective.
+     * <p>
+     * A {@link #using using} Comparator disables this setting. Passing null to
+     * the using method will re-enable a case-sensitive setting.
+     */
+    public BeanComparator caseSensitive() {
+        if ((mFlags & 0x04) != 0) {
+            // Already case-sensitive.
+            return this;
+        }
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = mUsingComparator;
+        bc.mFlags = mFlags | 0x04;
+        return bc;
+    }
+
+    /**
+     * Set a Comparator for ordering Strings, which is passed on to all
+     * BeanComparators derived from this one. By default, String are compared
+     * using {@link String#CASE_INSENSITIVE_ORDER}. Passing null for a collator
+     * will cause all String comparisons to use
+     * {@link String#compareTo(String) String.compareTo}.
+     * <p>
+     * A {@link #using using} Comparator disables this setting. Passing null
+     * to the using method will re-enable a collator.
+     *
+     * @param c Comparator to use for ordering all Strings. Passing null
+     * causes all Strings to be ordered by
+     * {@link String#compareTo(String) String.compareTo}.
+     */
+    public BeanComparator collate(Comparator c) {
+        BeanComparator bc = new BeanComparator(this);
+        bc.mOrderByName = mOrderByName;
+        bc.mUsingComparator = mUsingComparator;
+        bc.mFlags = mFlags & ~0x04;
+        bc.mCollator = c;
+        return bc;
+    }
+
+    public int compare(Object obj1, Object obj2) throws ClassCastException {
+        Comparator c = mComparator;
+        if (c == null) {
+            c = mComparator = generateComparator();
+        }
+        return c.compare(obj1, obj2);
+    }
+
+    public int hashCode() {
+        if (!mHasHashCode) {
+            setHashCode(new Rules(this));
+        }
+        return mHashCode;
+    }
+
+    private void setHashCode(Rules rules) {
+        mHashCode = rules.hashCode();
+        mHasHashCode = true;
+    }
+
+    /**
+     * Compares BeanComparators for equality based on their imposed ordering.
+     * Returns true only if the given object is a BeanComparater and it can be
+     * determined without a doubt that the ordering is identical. Because
+     * equality testing is dependent on the behavior of the equals methods of
+     * any 'using' Comparators and/or collators, false may be returned even
+     * though ordering is in fact identical.
+     */
+    public boolean equals(Object obj) {
+        if (obj instanceof BeanComparator) {
+            BeanComparator bc = (BeanComparator)obj;
+         
+            return mFlags == bc.mFlags &&
+                equalTest(mBeanClass, bc.mBeanClass) &&
+                equalTest(mOrderByName, bc.mOrderByName) &&
+                equalTest(mUsingComparator, bc.mUsingComparator) &&
+                equalTest(mCollator, bc.mCollator) &&
+                equalTest(mParent, bc.mParent);
+        }
+        else {
+            return false;
+        }
+    }
+    
+    private Map getProperties() {
+        if (mProperties == null) {
+            try {
+                mProperties =
+                    CompleteIntrospector.getAllProperties(mBeanClass);
+            }
+            catch (IntrospectionException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+        return mProperties;
+    }
+
+    private Comparator generateComparator() {
+        Rules rules = new Rules(this);
+
+        if (!mHasHashCode) {
+            setHashCode(rules);
+        }
+
+        Class clazz;
+
+        synchronized (cGeneratedComparatorCache) {
+            Object c = cGeneratedComparatorCache.get(rules);
+            
+            if (c == null) {
+                clazz = generateComparatorClass(rules);
+                cGeneratedComparatorCache.put(rules, clazz);
+            }
+            else if (c instanceof Comparator) {
+                return (Comparator)c;
+            }
+            else {
+                clazz = (Class)c;
+            }
+
+            BeanComparator[] ruleParts = rules.getRuleParts();
+            Comparator[] collators = new Comparator[ruleParts.length];
+            Comparator[] usingComparators = new Comparator[ruleParts.length];
+            boolean singleton = true;
+
+            for (int i=0; i<ruleParts.length; i++) {
+                BeanComparator rp = ruleParts[i];
+                Comparator c2 = rp.mCollator;
+                if ((collators[i] = c2) != null) {
+                    if (c2 != String.CASE_INSENSITIVE_ORDER) {
+                        singleton = false;
+                    }
+                }
+                if ((usingComparators[i] = rp.mUsingComparator) != null) {
+                    singleton = false;
+                }
+            }
+            
+            try {
+                Constructor ctor = clazz.getDeclaredConstructor
+                    (new Class[] {Comparator[].class, Comparator[].class});
+                c = (Comparator)ctor.newInstance
+                    (new Object[] {collators, usingComparators});
+            }
+            catch (NoSuchMethodException e) {
+                throw new InternalError(e.toString());
+            }
+            catch (InstantiationException e) {
+                throw new InternalError(e.toString());
+            }
+            catch (IllegalAccessException e) {
+                throw new InternalError(e.toString());
+            }
+            catch (IllegalArgumentException e) {
+                throw new InternalError(e.toString());
+            }
+            catch (InvocationTargetException e) {
+                throw new InternalError(e.getTargetException().toString());
+            }
+            
+            if (singleton) {
+                // Can save and re-use instance since it obeys the requirements
+                // for a singleton.
+                cGeneratedComparatorCache.put(rules, c);
+            }
+
+            return (Comparator)c;
+        }
+    }
+
+    private Class generateComparatorClass(Rules rules) {
+        ClassInjector injector =
+            ClassInjector.getInstance(mBeanClass.getClassLoader());
+        
+        int id = rules.hashCode();
+            
+        String baseName = this.getClass().getName() + '$';
+        String className = baseName;
+        try {
+            while (true) {
+                className = baseName + (id & 0xffffffffL);
+                try {
+                    injector.loadClass(className);
+                }
+                catch (LinkageError e) {
+                }
+                id++;
+            }
+        }
+        catch (ClassNotFoundException e) {
+        }
+        
+        ClassFile cf = generateClassFile(className, rules);
+            
+        /*
+        try {
+            String name = cf.getClassName();
+            name = name.substring(name.lastIndexOf('.') + 1) + ".class";
+            System.out.println(name);
+            java.io.FileOutputStream out =
+                new java.io.FileOutputStream(name);
+            cf.writeTo(out);
+            out.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+        */
+        
+        try {
+            OutputStream stream = injector.getStream(cf.getClassName());
+            cf.writeTo(stream);
+            stream.close();
+        }
+        catch (IOException e) {
+            throw new InternalError(e.toString());
+        }
+        
+        try {
+            return injector.loadClass(cf.getClassName());
+        }
+        catch (ClassNotFoundException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    private static ClassFile generateClassFile(String className, Rules rules) {
+        ClassFile cf = new ClassFile(className);
+        cf.markSynthetic();
+
+        cf.addInterface(Comparator.class);
+        cf.addInterface(Serializable.class);
+
+        AccessFlags privateAccess = new AccessFlags();
+        privateAccess.setPrivate(true);
+        AccessFlags publicAccess = new AccessFlags();
+        publicAccess.setPublic(true);
+
+        // Define fields to hold usage comparator and collator.
+        TypeDescriptor comparatorType = new TypeDescriptor(Comparator.class);
+        TypeDescriptor comparatorArrayType =
+            new TypeDescriptor(comparatorType, 1);
+        cf.addField(privateAccess, 
+                    "mCollators", comparatorArrayType).markSynthetic();
+        cf.addField(privateAccess, 
+                    "mUsingComparators", comparatorArrayType).markSynthetic();
+
+        // Create constructor to initialize fields.
+        TypeDescriptor[] paramTypes = {
+            comparatorArrayType, comparatorArrayType
+        };
+        MethodInfo ctor = cf.addConstructor(publicAccess, paramTypes);
+        ctor.markSynthetic();
+        CodeBuilder builder = new CodeBuilder(ctor);
+
+        builder.loadThis();
+        builder.invokeSuperConstructor(null);
+        builder.loadThis();
+        builder.loadLocal(builder.getParameters()[0]);
+        builder.storeField("mCollators", comparatorArrayType);
+        builder.loadThis();
+        builder.loadLocal(builder.getParameters()[1]);
+        builder.storeField("mUsingComparators", comparatorArrayType);
+        builder.returnVoid();
+
+        // Create the all-important compare method.
+        Method compareMethod, compareToMethod;
+        try {
+            compareMethod = Comparator.class.getMethod
+                ("compare", new Class[] {Object.class, Object.class});
+            compareToMethod = Comparable.class.getMethod
+                ("compareTo", new Class[] {Object.class});
+        }
+        catch (NoSuchMethodException e) {
+            throw new InternalError(e.toString());
+        }
+
+        MethodInfo mi = cf.addMethod(compareMethod);
+        mi.markSynthetic();
+        builder = new CodeBuilder(mi);
+
+        Label endLabel = builder.createLabel();
+        LocalVariable obj1 = builder.getParameters()[0];
+        LocalVariable obj2 = builder.getParameters()[1];
+
+        // The first rule always applies to the beans directly. All others
+        // apply to properties.
+
+        BeanComparator[] ruleParts = rules.getRuleParts();
+        BeanComparator bc = ruleParts[0];
+
+        if ((bc.mFlags & 0x01) != 0) {
+            // Reverse beans.
+            LocalVariable temp = obj1;
+            obj1 = obj2;
+            obj2 = temp;
+        }
+
+        // Handle the case when obj1 and obj2 are the same (or both null)
+        builder.loadLocal(obj1);
+        builder.loadLocal(obj2);
+        builder.ifEqualBranch(endLabel, true);
+
+        // Do null order checks for beans.
+        boolean nullHigh = (bc.mFlags & 0x02) == 0;
+        Label label = builder.createLabel();
+        builder.loadLocal(obj1);
+        builder.ifNullBranch(label, false);
+        builder.loadConstant(nullHigh ? 1 : -1);
+        builder.returnValue(int.class);
+        label.setLocation();
+        label = builder.createLabel();
+        builder.loadLocal(obj2);
+        builder.ifNullBranch(label, false);
+        builder.loadConstant(nullHigh ? -1 : 1);
+        builder.returnValue(int.class);
+        label.setLocation();
+
+        // Call 'using' Comparator if one is provided.
+        LocalVariable result = builder.createLocalVariable
+            ("result", new TypeDescriptor(int.class));
+        if (bc.mUsingComparator != null) {
+            builder.loadThis();
+            builder.loadField("mUsingComparators", comparatorArrayType);
+            builder.loadConstant(0);
+            builder.loadFromArray(Comparator.class);
+            builder.loadLocal(obj1);
+            builder.loadLocal(obj2);
+            builder.invoke(compareMethod);
+            builder.storeLocal(result);
+            builder.loadLocal(result);
+            label = builder.createLabel();
+            builder.ifZeroComparisonBranch(label, "==");
+            builder.loadLocal(result);
+            builder.returnValue(int.class);
+            label.setLocation();
+        }
+
+        // Cast bean parameters to correct types so that properties may be
+        // accessed.
+        TypeDescriptor type = new TypeDescriptor(bc.mBeanClass);
+        builder.loadLocal(obj1);
+        builder.checkCast(type);
+        builder.storeLocal(obj1);
+        builder.loadLocal(obj2);
+        builder.checkCast(type);
+        builder.storeLocal(obj2);
+
+        // Generate code to perform comparisons against each property.
+        for (int i=1; i<ruleParts.length; i++) {
+            bc = ruleParts[i];
+
+            PropertyDescriptor desc = 
+                (PropertyDescriptor)bc.getProperties().get(bc.mOrderByName);
+            Class propertyClass = desc.getPropertyType();
+            TypeDescriptor propertyType = new TypeDescriptor(propertyClass);
+            
+            // Create local variable to hold property values.
+            LocalVariable p1 = builder.createLocalVariable("p1", propertyType);
+            LocalVariable p2 = builder.createLocalVariable("p2", propertyType);
+
+            // Access properties and store in local variables.
+            builder.loadLocal(obj1);
+            builder.invoke(desc.getReadMethod());
+            builder.storeLocal(p1);
+            builder.loadLocal(obj2);
+            builder.invoke(desc.getReadMethod());
+            builder.storeLocal(p2);
+
+            if ((bc.mFlags & 0x01) != 0) {
+                // Reverse properties.
+                LocalVariable temp = p1;
+                p1 = p2;
+                p2 = temp;
+            }
+
+            Label nextLabel = builder.createLabel();
+
+            // Handle the case when p1 and p2 are the same (or both null)
+            if (!propertyClass.isPrimitive()) {
+                builder.loadLocal(p1);
+                builder.loadLocal(p2);
+                builder.ifEqualBranch(nextLabel, true);
+
+                // Do null order checks for properties.
+                nullHigh = (bc.mFlags & 0x02) == 0;
+                label = builder.createLabel();
+                builder.loadLocal(p1);
+                builder.ifNullBranch(label, false);
+                builder.loadConstant(nullHigh ? 1 : -1);
+                builder.returnValue(int.class);
+                label.setLocation();
+                label = builder.createLabel();
+                builder.loadLocal(p2);
+                builder.ifNullBranch(label, false);
+                builder.loadConstant(nullHigh ? -1 : 1);
+                builder.returnValue(int.class);
+                label.setLocation();
+            }
+
+            // Call 'using' Comparator if one is provided, else assume
+            // Comparable.
+            if (bc.mUsingComparator != null) {
+                builder.loadThis();
+                builder.loadField("mUsingComparators", comparatorArrayType);
+                builder.loadConstant(i);
+                builder.loadFromArray(Comparator.class);
+                loadAsObject(builder, propertyClass, p1);
+                loadAsObject(builder, propertyClass, p2);
+                builder.invoke(compareMethod);
+            }
+            else {
+                // If case-sensitive is off and a collator is provided and
+                // property could be a String, apply collator.
+                if ((bc.mFlags & 0x04) == 0 && bc.mCollator != null &&
+                    propertyClass.isAssignableFrom(String.class)) {
+
+                    Label resultLabel = builder.createLabel();
+
+                    if (!String.class.isAssignableFrom(propertyClass)) {
+                        // Check if both property values are strings at
+                        // runtime. If they aren't, cast to Comparable and call
+                        // compareTo.
+
+                        TypeDescriptor stringType =
+                            new TypeDescriptor(String.class);
+
+                        builder.loadLocal(p1);
+                        builder.instanceOf(stringType);
+                        Label notString = builder.createLabel();
+                        builder.ifZeroComparisonBranch(notString, "==");
+                        builder.loadLocal(p2);
+                        builder.instanceOf(stringType);
+                        Label isString = builder.createLabel();
+                        builder.ifZeroComparisonBranch(isString, "!=");
+
+                        notString.setLocation();
+                        generateComparableCompareTo
+                            (builder, propertyClass, compareToMethod,
+                             resultLabel, nextLabel, p1, p2);
+
+                        isString.setLocation();
+                    }
+
+                    builder.loadThis();
+                    builder.loadField("mCollators", comparatorArrayType);
+                    builder.loadConstant(i);
+                    builder.loadFromArray(Comparator.class);
+                    builder.loadLocal(p1);
+                    builder.loadLocal(p2);
+                    builder.invoke(compareMethod);
+
+                    resultLabel.setLocation();
+                }
+                else if (propertyClass.isPrimitive()) {
+                    generatePrimitiveComparison(builder, propertyClass, p1,p2);
+                }
+                else {
+                    // Assume properties are instances of Comparable.
+                    generateComparableCompareTo
+                        (builder, propertyClass, compareToMethod,
+                         null, nextLabel, p1, p2);
+                }
+            }
+
+            if (i < (ruleParts.length - 1)) {
+                builder.storeLocal(result);
+                builder.loadLocal(result);
+                builder.ifZeroComparisonBranch(nextLabel, "==");
+                builder.loadLocal(result);
+            }
+            builder.returnValue(int.class);
+
+            // The next property comparison will start here.
+            nextLabel.setLocation();
+        }
+
+        endLabel.setLocation();
+        builder.loadConstant(0);
+        builder.returnValue(int.class);
+
+        return cf;
+    }
+
+    private static void loadAsObject(CodeBuilder builder,
+                                     Class type, LocalVariable v)
+    {
+        if (!type.isPrimitive()) {
+            builder.loadLocal(v);
+            return;
+        }
+
+        if (type == boolean.class) {
+            TypeDescriptor td = new TypeDescriptor(Boolean.class);
+            Label falseLabel = builder.createLabel();
+            Label endLabel = builder.createLabel();
+            builder.loadLocal(v);
+            builder.ifZeroComparisonBranch(falseLabel, "==");
+            builder.loadStaticField("java.lang.Boolean", "TRUE", td);
+            builder.branch(endLabel);
+            falseLabel.setLocation();
+            builder.loadStaticField("java.lang.Boolean", "FALSE", td);
+            endLabel.setLocation();
+            return;
+        }
+
+        Class objectType;
+
+        if (type == int.class) {
+            objectType = Integer.class;
+        }
+        else if (type == long.class) {
+            objectType = Long.class;
+        }
+        else if (type == float.class) {
+            objectType = Float.class;
+        }
+        else if (type == double.class) {
+            objectType = Double.class;
+        }
+        else if (type == char.class) {
+            objectType = Character.class;
+        }
+        else if (type == byte.class) {
+            objectType = Byte.class;
+        }
+        else if (type == short.class) {
+            objectType = Short.class;
+        }
+        else {
+            objectType = type;
+        }
+
+        TypeDescriptor[] params = {
+            new TypeDescriptor(type)
+        };
+
+        builder.newObject(new TypeDescriptor(objectType));
+        builder.dup();
+        builder.loadLocal(v);
+        builder.invokeConstructor(objectType.getName(), params);
+    }
+
+    private static void generatePrimitiveComparison(CodeBuilder builder,
+                                                    Class type,
+                                                    LocalVariable a,
+                                                    LocalVariable b)
+    {
+        if (type == float.class) {
+            // Comparison is same as for Float.compareTo(Float).
+            Label done = builder.createLabel();
+
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.math(Opcode.FCMPG);
+            Label label = builder.createLabel();
+            builder.ifZeroComparisonBranch(label, ">=");
+            builder.loadConstant(-1);
+            builder.branch(done);
+
+            label.setLocation();
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.math(Opcode.FCMPL);
+            label = builder.createLabel();
+            builder.ifZeroComparisonBranch(label, "<=");
+            builder.loadConstant(1);
+            builder.branch(done);
+
+            Method floatToIntBits;
+            try {
+                floatToIntBits = Float.class.getMethod
+                    ("floatToIntBits", new Class[] {float.class});
+            }
+            catch (NoSuchMethodException e) {
+                throw new InternalError(e.toString());
+            }
+
+            label.setLocation();
+            builder.loadLocal(a);
+            builder.invoke(floatToIntBits);
+            builder.convert(int.class, long.class);
+            builder.loadLocal(b);
+            builder.invoke(floatToIntBits);
+            builder.convert(int.class, long.class);
+            builder.math(Opcode.LCMP);
+
+            done.setLocation();
+        }
+        else if (type == double.class) {
+            // Comparison is same as for Double.compareTo(Double).
+            Label done = builder.createLabel();
+
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            done = builder.createLabel();
+            builder.math(Opcode.DCMPG);
+            Label label = builder.createLabel();
+            builder.ifZeroComparisonBranch(label, ">=");
+            builder.loadConstant(-1);
+            builder.branch(done);
+
+            label.setLocation();
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.math(Opcode.DCMPL);
+            label = builder.createLabel();
+            builder.ifZeroComparisonBranch(label, "<=");
+            builder.loadConstant(1);
+            builder.branch(done);
+
+            Method doubleToLongBits;
+            try {
+                doubleToLongBits = Double.class.getMethod
+                    ("doubleToLongBits", new Class[] {double.class});
+            }
+            catch (NoSuchMethodException e) {
+                throw new InternalError(e.toString());
+            }
+
+            label.setLocation();
+            builder.loadLocal(a);
+            builder.invoke(doubleToLongBits);
+            builder.loadLocal(b);
+            builder.invoke(doubleToLongBits);
+            builder.math(Opcode.LCMP);
+
+            done.setLocation();
+        }
+        else if (type == long.class) {
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.math(Opcode.LCMP);
+        }
+        else if (type == int.class) {
+            builder.loadLocal(a);
+            builder.convert(int.class, long.class);
+            builder.loadLocal(b);
+            builder.convert(int.class, long.class);
+            builder.math(Opcode.LCMP);
+        }
+        else {
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.math(Opcode.ISUB);
+        }
+    }
+
+    private static void generateComparableCompareTo(CodeBuilder builder,
+                                                    Class type,
+                                                    Method compareToMethod,
+                                                    Label goodLabel,
+                                                    Label nextLabel,
+                                                    LocalVariable a,
+                                                    LocalVariable b)
+    {
+        if (Comparable.class.isAssignableFrom(type)) {
+            builder.loadLocal(a);
+            builder.loadLocal(b);
+            builder.invoke(compareToMethod);
+            if (goodLabel != null) {
+                builder.branch(goodLabel);
+            }
+        }
+        else {
+            // Cast each property to Comparable only if needed.
+            TypeDescriptor comparableType =
+                new TypeDescriptor(Comparable.class);
+
+            boolean locateGoodLabel = false;
+            if (goodLabel == null) {
+                goodLabel = builder.createLabel();
+                locateGoodLabel = true;
+            }
+
+            Label tryStart = builder.createLabel().setLocation();
+            builder.loadLocal(a);
+            builder.checkCast(comparableType);
+            builder.loadLocal(b);
+            builder.checkCast(comparableType);
+            Label tryEnd = builder.createLabel().setLocation();
+            builder.invoke(compareToMethod);
+            builder.branch(goodLabel);
+
+            builder.exceptionHandler(tryStart, tryEnd,
+                                     ClassCastException.class.getName());
+            // One of the properties is not Comparable, so just go to next.
+            // Discard the exception.
+            builder.pop();
+            if (nextLabel == null) {
+                builder.loadConstant(0);
+            }
+            else {
+                builder.branch(nextLabel);
+            }
+
+            if (locateGoodLabel) {
+                goodLabel.setLocation();
+            }
+        }
+    }
+
+    // A key that uniquely describes the rules of a BeanComparator.
+    private static class Rules {
+        private BeanComparator[] mRuleParts;
+        private int mHashCode;
+
+        public Rules(BeanComparator bc) {
+            mRuleParts = reduceRules(bc);
+
+            // Compute hashCode.
+            int hash = 0;
+
+            for (int i = mRuleParts.length - 1; i >= 0; i--) {
+                bc = mRuleParts[i];
+                hash = 31 * hash;
+
+                hash += bc.mFlags << 4;
+
+                Object obj = bc.mBeanClass;
+                if (obj != null) {
+                    hash += obj.hashCode();
+                }
+                obj = bc.mOrderByName;
+                if (obj != null) {
+                    hash += obj.hashCode();
+                }
+                obj = bc.mUsingComparator;
+                if (obj != null) {
+                    hash += obj.getClass().hashCode();
+                }
+                obj = bc.mCollator;
+                if (obj != null) {
+                    hash += obj.getClass().hashCode();
+                }
+            }
+
+            mHashCode = hash;
+        }
+
+        public BeanComparator[] getRuleParts() {
+            return mRuleParts;
+        }
+
+        public int hashCode() {
+            return mHashCode;
+        }
+
+        /**
+         * Equality test determines if rules produce an identical
+         * auto-generated Comparator.
+         */
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Rules)) {
+                return false;
+            }
+
+            BeanComparator[] ruleParts1 = getRuleParts();
+            BeanComparator[] ruleParts2 = ((Rules)obj).getRuleParts();
+
+            if (ruleParts1.length != ruleParts2.length) {
+                return false;
+            }
+            
+            for (int i=0; i<ruleParts1.length; i++) {
+                BeanComparator bc1 = ruleParts1[i];
+                BeanComparator bc2 = ruleParts2[i];
+
+                if (bc1.mFlags != bc2.mFlags) {
+                    return false;
+                }
+                if (!equalTest(bc1.mBeanClass, bc2.mBeanClass)) {
+                    return false;
+                }
+                if (!equalTest(bc1.mOrderByName, bc2.mOrderByName)) {
+                    return false;
+                }
+                if ((bc1.mUsingComparator == null) !=
+                    (bc2.mUsingComparator == null)) {
+                    return false;
+                }
+                if ((bc1.mCollator == null) != (bc2.mCollator == null)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    
+        private BeanComparator[] reduceRules(BeanComparator bc) {
+            // Reduce the ordering rules by returning BeanComparators
+            // that are at the end of the chain or before an order-by rule.
+            List rules = new ArrayList();
+            
+            rules.add(bc);
+            String name = bc.mOrderByName;
+
+            while ((bc = bc.mParent) != null) {
+                // Don't perform string comparison using equals method.
+                if (name != bc.mOrderByName) {
+                    rules.add(bc);
+                    name = bc.mOrderByName;
+                }
+            }
+            
+            int size = rules.size();
+            BeanComparator[] bcs = new BeanComparator[size];
+            // Reverse rules so that they are in forward order.
+            for (int i=0; i<size; i++) {
+                bcs[size - i - 1] = (BeanComparator)rules.get(i);
+            }
+            
+            return bcs;
+        }
+    }
+}