Package org.cojen.util

Source Code of org.cojen.util.BeanComparator

/*
*  Copyright 2004-2010 Brian S O'Neill
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/

package org.cojen.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.cojen.classfile.CodeBuilder;
import org.cojen.classfile.Label;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.MethodInfo;
import org.cojen.classfile.Modifiers;
import org.cojen.classfile.Opcode;
import org.cojen.classfile.RuntimeClassFile;
import org.cojen.classfile.TypeDesc;

/**
* 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");
* </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
*/
public class BeanComparator<T> implements Comparator<T>, Serializable {
    // Maps Rules to auto-generated Comparators.
    private static Map cGeneratedComparatorCache;

    static {
        cGeneratedComparatorCache = new SoftValuedHashMap();
    }

    /**
     * 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 <T> BeanComparator<T> forClass(Class<T> clazz) {
        return new BeanComparator<T>(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<T> mBeanClass;

    // Maps property names to PropertyDescriptors.
    private transient Map<String, BeanProperty> 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<String> mCollator;

    private BeanComparator<T> mParent;

    // Auto-generated internal Comparator.
    private transient Comparator<T> mComparator;

    private transient boolean mHasHashCode;
    private transient int mHashCode;

    private BeanComparator(Class<T> clazz) {
        mBeanClass = clazz;
        mCollator = String.CASE_INSENSITIVE_ORDER;
    }

    private BeanComparator(BeanComparator<T> 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>
     * As a convenience, property names may have a '-' or '+' character prefix
     * to specify sort order. A prefix of '-' indicates that the property
     * is to be sorted in reverse (descending). By default, properties are
     * sorted in ascending order, and so a prefix of '+' has no effect.
     * <p>
     * Any previously applied {@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<T> 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);
        }

        boolean reverse = false;
        if (propertyName.length() > 0) {
            char prefix = propertyName.charAt(0);
            switch (prefix) {
            default:
                break;
            case '-':
                reverse = true;
                // Fall through
            case '+':
                propertyName = propertyName.substring(1);
            }
        }

        BeanProperty prop = getProperties().get(propertyName);

        if (prop == null) {
            throw new IllegalArgumentException
                ("Property '" + propertyName + "' doesn't exist in '" +
                 mBeanClass.getName() + '\'');
        }

        if (prop.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<T> bc = new BeanComparator<T>(this);
        bc.mOrderByName = propertyName;

        if (subName != null) {
            BeanComparator<?> subOrder = forClass(prop.getType());
            subOrder.mCollator = mCollator;
            bc = bc.using(subOrder.orderBy(subName));
        }

        return reverse ? bc.reverse() : 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 previously applied 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 <S> BeanComparator<T> using(Comparator<S> c) {
        BeanComparator<T> bc = new BeanComparator<T>(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<T> reverse() {
        BeanComparator<T> bc = new BeanComparator<T>(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<T> nullHigh() {
        BeanComparator<T> bc = new BeanComparator<T>(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<T> nullLow() {
        BeanComparator<T> bc = new BeanComparator<T>(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<T> caseSensitive() {
        if ((mFlags & 0x04) != 0) {
            // Already case-sensitive.
            return this;
        }
        BeanComparator<T> bc = new BeanComparator<T>(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<T> collate(Comparator<String> c) {
        BeanComparator<T> bc = new BeanComparator<T>(this);
        bc.mOrderByName = mOrderByName;
        bc.mUsingComparator = mUsingComparator;
        bc.mFlags = mFlags & ~0x04;
        bc.mCollator = c;
        return bc;
    }

    public int compare(T obj1, T obj2) throws ClassCastException {
        Comparator<T> c = mComparator;
        if (c == null) {
            mComparator = c = AccessController.doPrivileged(new PrivilegedAction<Comparator<T>>() {
                public Comparator<T> run() {
                    return 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<String, BeanProperty> getProperties() {
        if (mProperties == null) {
            mProperties = BeanIntrospector.getAllProperties(mBeanClass);
        }
        return mProperties;
    }

    private Comparator<T> 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<T>)c;
        }
    }

    private Class generateComparatorClass(Rules rules) {
        RuntimeClassFile cf = new RuntimeClassFile
            (getClass().getName(), null, mBeanClass.getClassLoader());
        cf.markSynthetic();
        cf.setSourceFile(BeanComparator.class.getName());
        try {
            cf.setTarget(System.getProperty("java.specification.version"));
        } catch (Exception e) {
        }

        cf.addInterface(Comparator.class);
        cf.addInterface(Serializable.class);

        // Define fields to hold usage comparator and collator.
        TypeDesc comparatorType = TypeDesc.forClass(Comparator.class);
        TypeDesc comparatorArrayType = comparatorType.toArrayType();
        cf.addField(Modifiers.PRIVATE,
                    "mCollators", comparatorArrayType).markSynthetic();
        cf.addField(Modifiers.PRIVATE,
                    "mUsingComparators", comparatorArrayType).markSynthetic();

        // Create constructor to initialize fields.
        TypeDesc[] paramTypes = {
            comparatorArrayType, comparatorArrayType
        };
        MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC, paramTypes);
        ctor.markSynthetic();
        CodeBuilder builder = new CodeBuilder(ctor);

        builder.loadThis();
        builder.invokeSuperConstructor(null);
        builder.loadThis();
        builder.loadLocal(builder.getParameter(0));
        builder.storeField("mCollators", comparatorArrayType);
        builder.loadThis();
        builder.loadLocal(builder.getParameter(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.getParameter(0);
        LocalVariable obj2 = builder.getParameter(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(TypeDesc.INT);
        label.setLocation();
        label = builder.createLabel();
        builder.loadLocal(obj2);
        builder.ifNullBranch(label, false);
        builder.loadConstant(nullHigh ? -1 : 1);
        builder.returnValue(TypeDesc.INT);
        label.setLocation();

        // Call 'using' Comparator if one is provided.
        LocalVariable result =
            builder.createLocalVariable("result", TypeDesc.INT);
        if (bc.mUsingComparator != null) {
            builder.loadThis();
            builder.loadField("mUsingComparators", comparatorArrayType);
            builder.loadConstant(0);
            builder.loadFromArray(TypeDesc.forClass(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(TypeDesc.INT);
            label.setLocation();
        }

        // Cast bean parameters to correct types so that properties may be
        // accessed.
        TypeDesc type = TypeDesc.forClass(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];

            BeanProperty prop =
                (BeanProperty)bc.getProperties().get(bc.mOrderByName);
            Class propertyClass = prop.getType();
            TypeDesc propertyType = TypeDesc.forClass(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(prop.getReadMethod());
            builder.storeLocal(p1);
            builder.loadLocal(obj2);
            builder.invoke(prop.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(TypeDesc.INT);
                label.setLocation();
                label = builder.createLabel();
                builder.loadLocal(p2);
                builder.ifNullBranch(label, false);
                builder.loadConstant(nullHigh ? -1 : 1);
                builder.returnValue(TypeDesc.INT);
                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(TypeDesc.forClass(Comparator.class));
                builder.loadLocal(p1);
                builder.convert(propertyType, propertyType.toObjectType());
                builder.loadLocal(p2);
                builder.convert(propertyType, propertyType.toObjectType());
                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.

                        TypeDesc stringType = TypeDesc.STRING;

                        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(TypeDesc.forClass(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(TypeDesc.INT);

            // The next property comparison will start here.
            nextLabel.setLocation();
        }

        endLabel.setLocation();
        builder.loadConstant(0);
        builder.returnValue(TypeDesc.INT);

        return cf.defineClass();
    }

    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(TypeDesc.INT, TypeDesc.LONG);
            builder.loadLocal(b);
            builder.invoke(floatToIntBits);
            builder.convert(TypeDesc.INT, TypeDesc.LONG);
            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(TypeDesc.INT, TypeDesc.LONG);
            builder.loadLocal(b);
            builder.convert(TypeDesc.INT, TypeDesc.LONG);
            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.
            TypeDesc comparableType = TypeDesc.forClass(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<BeanComparator> rules = new ArrayList<BeanComparator>();

            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] = rules.get(i);
            }

            return bcs;
        }
    }
}
TOP

Related Classes of org.cojen.util.BeanComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.