0001: /*
0002: * Copyright 2004 Brian S O'Neill
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.cojen.util;
0018:
0019: import java.io.Serializable;
0020: import java.util.ArrayList;
0021: import java.util.Comparator;
0022: import java.util.List;
0023: import java.util.Map;
0024: import java.lang.reflect.Constructor;
0025: import java.lang.reflect.Method;
0026: import java.lang.reflect.InvocationTargetException;
0027: import org.cojen.classfile.ClassFile;
0028: import org.cojen.classfile.CodeBuilder;
0029: import org.cojen.classfile.Label;
0030: import org.cojen.classfile.LocalVariable;
0031: import org.cojen.classfile.MethodInfo;
0032: import org.cojen.classfile.Modifiers;
0033: import org.cojen.classfile.Opcode;
0034: import org.cojen.classfile.TypeDesc;
0035:
0036: /**
0037: * A highly customizable, high-performance Comparator, designed specifically
0038: * for advanced sorting of JavaBeans. BeanComparators contain dynamically
0039: * auto-generated code and perform as well as hand written Comparators.
0040: * <p>
0041: * BeanComparator instances are immutable; order customization methods
0042: * return new BeanComparators with refined rules. Calls to customizers can
0043: * be chained together to read like a formula. The following example produces
0044: * a Comparator that orders Threads by name, thread group name, and reverse
0045: * priority.
0046: *
0047: * <pre>
0048: * Comparator c = BeanComparator.forClass(Thread.class)
0049: * .orderBy("name")
0050: * .orderBy("threadGroup.name")
0051: * .orderBy("-priority");
0052: * </pre>
0053: *
0054: * The results of sorting Threads using this Comparator and displaying the
0055: * results in a table may look like this:
0056: *
0057: * <p><table border="2">
0058: * <tr><th>name</th><th>threadGroup.name</th><th>priority</th></tr>
0059: * <tr><td>daemon</td><td>appGroup</td><td>9</td></tr>
0060: * <tr><td>main</td><td>main</td><td>5</td></tr>
0061: * <tr><td>main</td><td>secureGroup</td><td>5</td></tr>
0062: * <tr><td>sweeper</td><td>main</td><td>1</td></tr>
0063: * <tr><td>Thread-0</td><td>main</td><td>5</td></tr>
0064: * <tr><td>Thread-1</td><td>main</td><td>5</td></tr>
0065: * <tr><td>worker</td><td>appGroup</td><td>8</td></tr>
0066: * <tr><td>worker</td><td>appGroup</td><td>5</td></tr>
0067: * <tr><td>worker</td><td>secureGroup</td><td>8</td></tr>
0068: * <tr><td>worker</td><td>secureGroup</td><td>5</td></tr>
0069: * </table><p>
0070: *
0071: * An equivalent Thread ordering Comparator may be specified as:
0072: *
0073: * <pre>
0074: * Comparator c = BeanComparator.forClass(Thread.class)
0075: * .orderBy("name")
0076: * .orderBy("threadGroup")
0077: * .using(BeanComparator.forClass(ThreadGroup.class).orderBy("name"))
0078: * .orderBy("priority")
0079: * .reverse();
0080: * </pre>
0081: *
0082: * The current implementation of BeanComparator has been optimized for fast
0083: * construction and execution of BeanComparators. For maximum performance,
0084: * however, save and re-use BeanComparators wherever possible.
0085: * <p>
0086: * Even though BeanComparator makes use of auto-generated code, instances are
0087: * fully Serializable, as long as all passed in Comparators are also
0088: * Serializable.
0089: *
0090: * @author Brian S O'Neill
0091: */
0092: public class BeanComparator implements Comparator, Serializable {
0093: // Maps Rules to auto-generated Comparators.
0094: private static Map cGeneratedComparatorCache;
0095:
0096: static {
0097: cGeneratedComparatorCache = new SoftValuedHashMap();
0098: }
0099:
0100: /**
0101: * Get or create a new BeanComparator for beans of the given type. Without
0102: * any {@link #orderBy order-by} properties specified, the returned
0103: * BeanComparator can only order against null beans (null is
0104: * {@link #nullHigh high} by default), and treats all other comparisons as
0105: * equal.
0106: */
0107: public static BeanComparator forClass(Class clazz) {
0108: return new BeanComparator(clazz);
0109: }
0110:
0111: /**
0112: * Compare two objects for equality.
0113: */
0114: private static boolean equalTest(Object obj1, Object obj2) {
0115: return (obj1 == obj2) ? true
0116: : ((obj1 == null || obj2 == null) ? false : obj1
0117: .equals(obj2));
0118: }
0119:
0120: /**
0121: * Compare two object classes for equality.
0122: */
0123: /*
0124: private static boolean equalClassTest(Object obj1, Object obj2) {
0125: return (obj1 == obj2) ? true :
0126: ((obj1 == null || obj2 == null) ? false :
0127: obj1.getClass().equals(obj2.getClass()));
0128: }
0129: */
0130:
0131: private Class mBeanClass;
0132:
0133: // Maps property names to PropertyDescriptors.
0134: private transient Map mProperties;
0135:
0136: private String mOrderByName;
0137:
0138: private Comparator mUsingComparator;
0139:
0140: // bit 0: reverse
0141: // bit 1: null low order
0142: // bit 2: use String compareTo instead of collator
0143: private int mFlags;
0144:
0145: // Used for comparing strings.
0146: private Comparator mCollator;
0147:
0148: private BeanComparator mParent;
0149:
0150: // Auto-generated internal Comparator.
0151: private transient Comparator mComparator;
0152:
0153: private transient boolean mHasHashCode;
0154: private transient int mHashCode;
0155:
0156: private BeanComparator(Class clazz) {
0157: mBeanClass = clazz;
0158: mCollator = String.CASE_INSENSITIVE_ORDER;
0159: }
0160:
0161: private BeanComparator(BeanComparator parent) {
0162: mParent = parent;
0163: mBeanClass = parent.mBeanClass;
0164: mProperties = parent.getProperties();
0165: mCollator = parent.mCollator;
0166: }
0167:
0168: /**
0169: * Add an order-by property to produce a more refined Comparator. If the
0170: * property does not return a {@link Comparable} object when
0171: * {@link #compare compare} is called on the returned comparator, the
0172: * property is ignored. Call {@link #using using} on the returned
0173: * BeanComparator to specify a Comparator to use for this property instead.
0174: * <p>
0175: * The specified propery name may refer to sub-properties using a dot
0176: * notation. For example, if the bean being compared contains a property
0177: * named "info" of type "Information", and "Information" contains a
0178: * property named "text", then ordering by the info text can be specified
0179: * by "info.text". Sub-properties of sub-properties may be refered to as
0180: * well, a.b.c.d.e etc.
0181: * <p>
0182: * If property type is a primitive, ordering is the same as for its
0183: * Comparable object peer. Primitive booleans are ordered false low, true
0184: * high. Floating point primitves are ordered exactly the same way as
0185: * {@link Float#compareTo(Float) Float.compareTo} and
0186: * {@link Double#compareTo(Double) Double.compareTo}.
0187: * <p>
0188: * As a convenience, property names may have a '-' or '+' character prefix
0189: * to specify sort order. A prefix of '-' indicates that the property
0190: * is to be sorted in reverse (descending). By default, properties are
0191: * sorted in ascending order, and so a prefix of '+' has no effect.
0192: * <p>
0193: * Any previously applied {@link #reverse reverse-order}, {@link #nullHigh
0194: * null-order} and {@link #caseSensitive case-sensitive} settings are not
0195: * carried over, and are reset to the defaults for this order-by property.
0196: *
0197: * @throws IllegalArgumentException when property doesn't exist or cannot
0198: * be read.
0199: */
0200: public BeanComparator orderBy(String propertyName)
0201: throws IllegalArgumentException {
0202: int dot = propertyName.indexOf('.');
0203: String subName;
0204: if (dot < 0) {
0205: subName = null;
0206: } else {
0207: subName = propertyName.substring(dot + 1);
0208: propertyName = propertyName.substring(0, dot);
0209: }
0210:
0211: boolean reverse = false;
0212: if (propertyName.length() > 0) {
0213: char prefix = propertyName.charAt(0);
0214: switch (prefix) {
0215: default:
0216: break;
0217: case '-':
0218: reverse = true;
0219: // Fall through
0220: case '+':
0221: propertyName = propertyName.substring(1);
0222: }
0223: }
0224:
0225: BeanProperty prop = (BeanProperty) getProperties().get(
0226: propertyName);
0227:
0228: if (prop == null) {
0229: throw new IllegalArgumentException("Property '"
0230: + propertyName + "' doesn't exist in '"
0231: + mBeanClass.getName() + '\'');
0232: }
0233:
0234: if (prop.getReadMethod() == null) {
0235: throw new IllegalArgumentException("Property '"
0236: + propertyName + "' cannot be read");
0237: }
0238:
0239: if (propertyName.equals(mOrderByName)) {
0240: // Make String unique so that properties can be specified in
0241: // consecutive order-by calls without being eliminated by
0242: // reduceRules. A secondary order-by may wish to further refine an
0243: // ambiguous comparison using a Comparator.
0244: propertyName = new String(propertyName);
0245: }
0246:
0247: BeanComparator bc = new BeanComparator(this );
0248: bc.mOrderByName = propertyName;
0249:
0250: if (subName != null) {
0251: BeanComparator subOrder = forClass(prop.getType());
0252: subOrder.mCollator = mCollator;
0253: bc = bc.using(subOrder.orderBy(subName));
0254: }
0255:
0256: return reverse ? bc.reverse() : bc;
0257: }
0258:
0259: /**
0260: * Specifiy a Comparator to use on just the last {@link #orderBy order-by}
0261: * property. This is good for comparing properties that are not
0262: * {@link Comparable} or for applying special ordering rules for a
0263: * property. If no order-by properties have been specified, then Comparator
0264: * is applied to the compared beans.
0265: * <p>
0266: * Any previously applied String {@link #caseSensitive case-sensitive} or
0267: * {@link #collate collator} settings are overridden by this Comparator.
0268: * If property values being compared are primitive, they are converted to
0269: * their object peers before being passed to the Comparator.
0270: *
0271: * @param c Comparator to use on the last order-by property. Passing null
0272: * restores the default comparison for the last order-by property.
0273: */
0274: public BeanComparator using(Comparator c) {
0275: BeanComparator bc = new BeanComparator(this );
0276: bc.mOrderByName = mOrderByName;
0277: bc.mUsingComparator = c;
0278: bc.mFlags = mFlags;
0279: return bc;
0280: }
0281:
0282: /**
0283: * Toggle reverse-order option on just the last {@link #orderBy order-by}
0284: * property. By default, order is ascending. If no order-by properties have
0285: * been specified, then reverse order is applied to the compared beans.
0286: */
0287: public BeanComparator reverse() {
0288: BeanComparator bc = new BeanComparator(this );
0289: bc.mOrderByName = mOrderByName;
0290: bc.mUsingComparator = mUsingComparator;
0291: bc.mFlags = mFlags ^ 0x01;
0292: return bc;
0293: }
0294:
0295: /**
0296: * Set the order of comparisons against null as being high (the default)
0297: * on just the last {@link #orderBy order-by} property. If no order-by
0298: * properties have been specified, then null high order is applied to the
0299: * compared beans. Null high order is the default for consistency with the
0300: * high ordering of {@link Float#NaN NaN} by
0301: * {@link Float#compareTo(Float) Float}.
0302: * <p>
0303: * Calling 'nullHigh, reverse' is equivalent to calling 'reverse, nullLow'.
0304: */
0305: public BeanComparator nullHigh() {
0306: BeanComparator bc = new BeanComparator(this );
0307: bc.mOrderByName = mOrderByName;
0308: bc.mUsingComparator = mUsingComparator;
0309: bc.mFlags = mFlags ^ ((mFlags & 0x01) << 1);
0310: return bc;
0311: }
0312:
0313: /**
0314: * Set the order of comparisons against null as being low
0315: * on just the last {@link #orderBy order-by} property. If no order-by
0316: * properties have been specified, then null low order is applied to the
0317: * compared beans.
0318: * <p>
0319: * Calling 'reverse, nullLow' is equivalent to calling 'nullHigh, reverse'.
0320: */
0321: public BeanComparator nullLow() {
0322: BeanComparator bc = new BeanComparator(this );
0323: bc.mOrderByName = mOrderByName;
0324: bc.mUsingComparator = mUsingComparator;
0325: bc.mFlags = mFlags ^ ((~mFlags & 0x01) << 1);
0326: return bc;
0327: }
0328:
0329: /**
0330: * Override the collator and compare just the last order-by property using
0331: * {@link String#compareTo(String) String.compareTo}, if it is of type
0332: * String. If no order-by properties have been specified then this call is
0333: * ineffective.
0334: * <p>
0335: * A {@link #using using} Comparator disables this setting. Passing null to
0336: * the using method will re-enable a case-sensitive setting.
0337: */
0338: public BeanComparator caseSensitive() {
0339: if ((mFlags & 0x04) != 0) {
0340: // Already case-sensitive.
0341: return this ;
0342: }
0343: BeanComparator bc = new BeanComparator(this );
0344: bc.mOrderByName = mOrderByName;
0345: bc.mUsingComparator = mUsingComparator;
0346: bc.mFlags = mFlags | 0x04;
0347: return bc;
0348: }
0349:
0350: /**
0351: * Set a Comparator for ordering Strings, which is passed on to all
0352: * BeanComparators derived from this one. By default, String are compared
0353: * using {@link String#CASE_INSENSITIVE_ORDER}. Passing null for a collator
0354: * will cause all String comparisons to use
0355: * {@link String#compareTo(String) String.compareTo}.
0356: * <p>
0357: * A {@link #using using} Comparator disables this setting. Passing null
0358: * to the using method will re-enable a collator.
0359: *
0360: * @param c Comparator to use for ordering all Strings. Passing null
0361: * causes all Strings to be ordered by
0362: * {@link String#compareTo(String) String.compareTo}.
0363: */
0364: public BeanComparator collate(Comparator c) {
0365: BeanComparator bc = new BeanComparator(this );
0366: bc.mOrderByName = mOrderByName;
0367: bc.mUsingComparator = mUsingComparator;
0368: bc.mFlags = mFlags & ~0x04;
0369: bc.mCollator = c;
0370: return bc;
0371: }
0372:
0373: public int compare(Object obj1, Object obj2)
0374: throws ClassCastException {
0375: Comparator c = mComparator;
0376: if (c == null) {
0377: c = mComparator = generateComparator();
0378: }
0379: return c.compare(obj1, obj2);
0380: }
0381:
0382: public int hashCode() {
0383: if (!mHasHashCode) {
0384: setHashCode(new Rules(this ));
0385: }
0386: return mHashCode;
0387: }
0388:
0389: private void setHashCode(Rules rules) {
0390: mHashCode = rules.hashCode();
0391: mHasHashCode = true;
0392: }
0393:
0394: /**
0395: * Compares BeanComparators for equality based on their imposed ordering.
0396: * Returns true only if the given object is a BeanComparater and it can be
0397: * determined without a doubt that the ordering is identical. Because
0398: * equality testing is dependent on the behavior of the equals methods of
0399: * any 'using' Comparators and/or collators, false may be returned even
0400: * though ordering is in fact identical.
0401: */
0402: public boolean equals(Object obj) {
0403: if (obj instanceof BeanComparator) {
0404: BeanComparator bc = (BeanComparator) obj;
0405:
0406: return mFlags == bc.mFlags
0407: && equalTest(mBeanClass, bc.mBeanClass)
0408: && equalTest(mOrderByName, bc.mOrderByName)
0409: && equalTest(mUsingComparator, bc.mUsingComparator)
0410: && equalTest(mCollator, bc.mCollator)
0411: && equalTest(mParent, bc.mParent);
0412: } else {
0413: return false;
0414: }
0415: }
0416:
0417: private Map getProperties() {
0418: if (mProperties == null) {
0419: mProperties = BeanIntrospector.getAllProperties(mBeanClass);
0420: }
0421: return mProperties;
0422: }
0423:
0424: private Comparator generateComparator() {
0425: Rules rules = new Rules(this );
0426:
0427: if (!mHasHashCode) {
0428: setHashCode(rules);
0429: }
0430:
0431: Class clazz;
0432:
0433: synchronized (cGeneratedComparatorCache) {
0434: Object c = cGeneratedComparatorCache.get(rules);
0435:
0436: if (c == null) {
0437: clazz = generateComparatorClass(rules);
0438: cGeneratedComparatorCache.put(rules, clazz);
0439: } else if (c instanceof Comparator) {
0440: return (Comparator) c;
0441: } else {
0442: clazz = (Class) c;
0443: }
0444:
0445: BeanComparator[] ruleParts = rules.getRuleParts();
0446: Comparator[] collators = new Comparator[ruleParts.length];
0447: Comparator[] usingComparators = new Comparator[ruleParts.length];
0448: boolean singleton = true;
0449:
0450: for (int i = 0; i < ruleParts.length; i++) {
0451: BeanComparator rp = ruleParts[i];
0452: Comparator c2 = rp.mCollator;
0453: if ((collators[i] = c2) != null) {
0454: if (c2 != String.CASE_INSENSITIVE_ORDER) {
0455: singleton = false;
0456: }
0457: }
0458: if ((usingComparators[i] = rp.mUsingComparator) != null) {
0459: singleton = false;
0460: }
0461: }
0462:
0463: try {
0464: Constructor ctor = clazz
0465: .getDeclaredConstructor(new Class[] {
0466: Comparator[].class, Comparator[].class });
0467: c = (Comparator) ctor.newInstance(new Object[] {
0468: collators, usingComparators });
0469: } catch (NoSuchMethodException e) {
0470: throw new InternalError(e.toString());
0471: } catch (InstantiationException e) {
0472: throw new InternalError(e.toString());
0473: } catch (IllegalAccessException e) {
0474: throw new InternalError(e.toString());
0475: } catch (IllegalArgumentException e) {
0476: throw new InternalError(e.toString());
0477: } catch (InvocationTargetException e) {
0478: throw new InternalError(e.getTargetException()
0479: .toString());
0480: }
0481:
0482: if (singleton) {
0483: // Can save and re-use instance since it obeys the requirements
0484: // for a singleton.
0485: cGeneratedComparatorCache.put(rules, c);
0486: }
0487:
0488: return (Comparator) c;
0489: }
0490: }
0491:
0492: private Class generateComparatorClass(Rules rules) {
0493: ClassInjector ci = ClassInjector.create(getClass().getName(),
0494: mBeanClass.getClassLoader());
0495: return ci.defineClass(generateClassFile(ci.getClassName(),
0496: rules));
0497: }
0498:
0499: private static ClassFile generateClassFile(String className,
0500: Rules rules) {
0501: ClassFile cf = new ClassFile(className);
0502: cf.markSynthetic();
0503: cf.setSourceFile(BeanComparator.class.getName());
0504: try {
0505: cf.setTarget(System
0506: .getProperty("java.specification.version"));
0507: } catch (Exception e) {
0508: }
0509:
0510: cf.addInterface(Comparator.class);
0511: cf.addInterface(Serializable.class);
0512:
0513: // Define fields to hold usage comparator and collator.
0514: TypeDesc comparatorType = TypeDesc.forClass(Comparator.class);
0515: TypeDesc comparatorArrayType = comparatorType.toArrayType();
0516: cf.addField(Modifiers.PRIVATE, "mCollators",
0517: comparatorArrayType).markSynthetic();
0518: cf.addField(Modifiers.PRIVATE, "mUsingComparators",
0519: comparatorArrayType).markSynthetic();
0520:
0521: // Create constructor to initialize fields.
0522: TypeDesc[] paramTypes = { comparatorArrayType,
0523: comparatorArrayType };
0524: MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC,
0525: paramTypes);
0526: ctor.markSynthetic();
0527: CodeBuilder builder = new CodeBuilder(ctor);
0528:
0529: builder.loadThis();
0530: builder.invokeSuperConstructor(null);
0531: builder.loadThis();
0532: builder.loadLocal(builder.getParameter(0));
0533: builder.storeField("mCollators", comparatorArrayType);
0534: builder.loadThis();
0535: builder.loadLocal(builder.getParameter(1));
0536: builder.storeField("mUsingComparators", comparatorArrayType);
0537: builder.returnVoid();
0538:
0539: // Create the all-important compare method.
0540: Method compareMethod, compareToMethod;
0541: try {
0542: compareMethod = Comparator.class.getMethod("compare",
0543: new Class[] { Object.class, Object.class });
0544: compareToMethod = Comparable.class.getMethod("compareTo",
0545: new Class[] { Object.class });
0546: } catch (NoSuchMethodException e) {
0547: throw new InternalError(e.toString());
0548: }
0549:
0550: MethodInfo mi = cf.addMethod(compareMethod);
0551: mi.markSynthetic();
0552: builder = new CodeBuilder(mi);
0553:
0554: Label endLabel = builder.createLabel();
0555: LocalVariable obj1 = builder.getParameter(0);
0556: LocalVariable obj2 = builder.getParameter(1);
0557:
0558: // The first rule always applies to the beans directly. All others
0559: // apply to properties.
0560:
0561: BeanComparator[] ruleParts = rules.getRuleParts();
0562: BeanComparator bc = ruleParts[0];
0563:
0564: if ((bc.mFlags & 0x01) != 0) {
0565: // Reverse beans.
0566: LocalVariable temp = obj1;
0567: obj1 = obj2;
0568: obj2 = temp;
0569: }
0570:
0571: // Handle the case when obj1 and obj2 are the same (or both null)
0572: builder.loadLocal(obj1);
0573: builder.loadLocal(obj2);
0574: builder.ifEqualBranch(endLabel, true);
0575:
0576: // Do null order checks for beans.
0577: boolean nullHigh = (bc.mFlags & 0x02) == 0;
0578: Label label = builder.createLabel();
0579: builder.loadLocal(obj1);
0580: builder.ifNullBranch(label, false);
0581: builder.loadConstant(nullHigh ? 1 : -1);
0582: builder.returnValue(TypeDesc.INT);
0583: label.setLocation();
0584: label = builder.createLabel();
0585: builder.loadLocal(obj2);
0586: builder.ifNullBranch(label, false);
0587: builder.loadConstant(nullHigh ? -1 : 1);
0588: builder.returnValue(TypeDesc.INT);
0589: label.setLocation();
0590:
0591: // Call 'using' Comparator if one is provided.
0592: LocalVariable result = builder.createLocalVariable("result",
0593: TypeDesc.INT);
0594: if (bc.mUsingComparator != null) {
0595: builder.loadThis();
0596: builder.loadField("mUsingComparators", comparatorArrayType);
0597: builder.loadConstant(0);
0598: builder.loadFromArray(TypeDesc.forClass(Comparator.class));
0599: builder.loadLocal(obj1);
0600: builder.loadLocal(obj2);
0601: builder.invoke(compareMethod);
0602: builder.storeLocal(result);
0603: builder.loadLocal(result);
0604: label = builder.createLabel();
0605: builder.ifZeroComparisonBranch(label, "==");
0606: builder.loadLocal(result);
0607: builder.returnValue(TypeDesc.INT);
0608: label.setLocation();
0609: }
0610:
0611: // Cast bean parameters to correct types so that properties may be
0612: // accessed.
0613: TypeDesc type = TypeDesc.forClass(bc.mBeanClass);
0614: builder.loadLocal(obj1);
0615: builder.checkCast(type);
0616: builder.storeLocal(obj1);
0617: builder.loadLocal(obj2);
0618: builder.checkCast(type);
0619: builder.storeLocal(obj2);
0620:
0621: // Generate code to perform comparisons against each property.
0622: for (int i = 1; i < ruleParts.length; i++) {
0623: bc = ruleParts[i];
0624:
0625: BeanProperty prop = (BeanProperty) bc.getProperties().get(
0626: bc.mOrderByName);
0627: Class propertyClass = prop.getType();
0628: TypeDesc propertyType = TypeDesc.forClass(propertyClass);
0629:
0630: // Create local variable to hold property values.
0631: LocalVariable p1 = builder.createLocalVariable("p1",
0632: propertyType);
0633: LocalVariable p2 = builder.createLocalVariable("p2",
0634: propertyType);
0635:
0636: // Access properties and store in local variables.
0637: builder.loadLocal(obj1);
0638: builder.invoke(prop.getReadMethod());
0639: builder.storeLocal(p1);
0640: builder.loadLocal(obj2);
0641: builder.invoke(prop.getReadMethod());
0642: builder.storeLocal(p2);
0643:
0644: if ((bc.mFlags & 0x01) != 0) {
0645: // Reverse properties.
0646: LocalVariable temp = p1;
0647: p1 = p2;
0648: p2 = temp;
0649: }
0650:
0651: Label nextLabel = builder.createLabel();
0652:
0653: // Handle the case when p1 and p2 are the same (or both null)
0654: if (!propertyClass.isPrimitive()) {
0655: builder.loadLocal(p1);
0656: builder.loadLocal(p2);
0657: builder.ifEqualBranch(nextLabel, true);
0658:
0659: // Do null order checks for properties.
0660: nullHigh = (bc.mFlags & 0x02) == 0;
0661: label = builder.createLabel();
0662: builder.loadLocal(p1);
0663: builder.ifNullBranch(label, false);
0664: builder.loadConstant(nullHigh ? 1 : -1);
0665: builder.returnValue(TypeDesc.INT);
0666: label.setLocation();
0667: label = builder.createLabel();
0668: builder.loadLocal(p2);
0669: builder.ifNullBranch(label, false);
0670: builder.loadConstant(nullHigh ? -1 : 1);
0671: builder.returnValue(TypeDesc.INT);
0672: label.setLocation();
0673: }
0674:
0675: // Call 'using' Comparator if one is provided, else assume
0676: // Comparable.
0677: if (bc.mUsingComparator != null) {
0678: builder.loadThis();
0679: builder.loadField("mUsingComparators",
0680: comparatorArrayType);
0681: builder.loadConstant(i);
0682: builder.loadFromArray(TypeDesc
0683: .forClass(Comparator.class));
0684: builder.loadLocal(p1);
0685: builder.convert(propertyType, propertyType
0686: .toObjectType());
0687: builder.loadLocal(p2);
0688: builder.convert(propertyType, propertyType
0689: .toObjectType());
0690: builder.invoke(compareMethod);
0691: } else {
0692: // If case-sensitive is off and a collator is provided and
0693: // property could be a String, apply collator.
0694: if ((bc.mFlags & 0x04) == 0 && bc.mCollator != null
0695: && propertyClass.isAssignableFrom(String.class)) {
0696:
0697: Label resultLabel = builder.createLabel();
0698:
0699: if (!String.class.isAssignableFrom(propertyClass)) {
0700: // Check if both property values are strings at
0701: // runtime. If they aren't, cast to Comparable and call
0702: // compareTo.
0703:
0704: TypeDesc stringType = TypeDesc.STRING;
0705:
0706: builder.loadLocal(p1);
0707: builder.instanceOf(stringType);
0708: Label notString = builder.createLabel();
0709: builder.ifZeroComparisonBranch(notString, "==");
0710: builder.loadLocal(p2);
0711: builder.instanceOf(stringType);
0712: Label isString = builder.createLabel();
0713: builder.ifZeroComparisonBranch(isString, "!=");
0714:
0715: notString.setLocation();
0716: generateComparableCompareTo(builder,
0717: propertyClass, compareToMethod,
0718: resultLabel, nextLabel, p1, p2);
0719:
0720: isString.setLocation();
0721: }
0722:
0723: builder.loadThis();
0724: builder
0725: .loadField("mCollators",
0726: comparatorArrayType);
0727: builder.loadConstant(i);
0728: builder.loadFromArray(TypeDesc
0729: .forClass(Comparator.class));
0730: builder.loadLocal(p1);
0731: builder.loadLocal(p2);
0732: builder.invoke(compareMethod);
0733:
0734: resultLabel.setLocation();
0735: } else if (propertyClass.isPrimitive()) {
0736: generatePrimitiveComparison(builder, propertyClass,
0737: p1, p2);
0738: } else {
0739: // Assume properties are instances of Comparable.
0740: generateComparableCompareTo(builder, propertyClass,
0741: compareToMethod, null, nextLabel, p1, p2);
0742: }
0743: }
0744:
0745: if (i < (ruleParts.length - 1)) {
0746: builder.storeLocal(result);
0747: builder.loadLocal(result);
0748: builder.ifZeroComparisonBranch(nextLabel, "==");
0749: builder.loadLocal(result);
0750: }
0751: builder.returnValue(TypeDesc.INT);
0752:
0753: // The next property comparison will start here.
0754: nextLabel.setLocation();
0755: }
0756:
0757: endLabel.setLocation();
0758: builder.loadConstant(0);
0759: builder.returnValue(TypeDesc.INT);
0760:
0761: return cf;
0762: }
0763:
0764: private static void generatePrimitiveComparison(
0765: CodeBuilder builder, Class type, LocalVariable a,
0766: LocalVariable b) {
0767: if (type == float.class) {
0768: // Comparison is same as for Float.compareTo(Float).
0769: Label done = builder.createLabel();
0770:
0771: builder.loadLocal(a);
0772: builder.loadLocal(b);
0773: builder.math(Opcode.FCMPG);
0774: Label label = builder.createLabel();
0775: builder.ifZeroComparisonBranch(label, ">=");
0776: builder.loadConstant(-1);
0777: builder.branch(done);
0778:
0779: label.setLocation();
0780: builder.loadLocal(a);
0781: builder.loadLocal(b);
0782: builder.math(Opcode.FCMPL);
0783: label = builder.createLabel();
0784: builder.ifZeroComparisonBranch(label, "<=");
0785: builder.loadConstant(1);
0786: builder.branch(done);
0787:
0788: Method floatToIntBits;
0789: try {
0790: floatToIntBits = Float.class.getMethod(
0791: "floatToIntBits", new Class[] { float.class });
0792: } catch (NoSuchMethodException e) {
0793: throw new InternalError(e.toString());
0794: }
0795:
0796: label.setLocation();
0797: builder.loadLocal(a);
0798: builder.invoke(floatToIntBits);
0799: builder.convert(TypeDesc.INT, TypeDesc.LONG);
0800: builder.loadLocal(b);
0801: builder.invoke(floatToIntBits);
0802: builder.convert(TypeDesc.INT, TypeDesc.LONG);
0803: builder.math(Opcode.LCMP);
0804:
0805: done.setLocation();
0806: } else if (type == double.class) {
0807: // Comparison is same as for Double.compareTo(Double).
0808: Label done = builder.createLabel();
0809:
0810: builder.loadLocal(a);
0811: builder.loadLocal(b);
0812: done = builder.createLabel();
0813: builder.math(Opcode.DCMPG);
0814: Label label = builder.createLabel();
0815: builder.ifZeroComparisonBranch(label, ">=");
0816: builder.loadConstant(-1);
0817: builder.branch(done);
0818:
0819: label.setLocation();
0820: builder.loadLocal(a);
0821: builder.loadLocal(b);
0822: builder.math(Opcode.DCMPL);
0823: label = builder.createLabel();
0824: builder.ifZeroComparisonBranch(label, "<=");
0825: builder.loadConstant(1);
0826: builder.branch(done);
0827:
0828: Method doubleToLongBits;
0829: try {
0830: doubleToLongBits = Double.class.getMethod(
0831: "doubleToLongBits",
0832: new Class[] { double.class });
0833: } catch (NoSuchMethodException e) {
0834: throw new InternalError(e.toString());
0835: }
0836:
0837: label.setLocation();
0838: builder.loadLocal(a);
0839: builder.invoke(doubleToLongBits);
0840: builder.loadLocal(b);
0841: builder.invoke(doubleToLongBits);
0842: builder.math(Opcode.LCMP);
0843:
0844: done.setLocation();
0845: } else if (type == long.class) {
0846: builder.loadLocal(a);
0847: builder.loadLocal(b);
0848: builder.math(Opcode.LCMP);
0849: } else if (type == int.class) {
0850: builder.loadLocal(a);
0851: builder.convert(TypeDesc.INT, TypeDesc.LONG);
0852: builder.loadLocal(b);
0853: builder.convert(TypeDesc.INT, TypeDesc.LONG);
0854: builder.math(Opcode.LCMP);
0855: } else {
0856: builder.loadLocal(a);
0857: builder.loadLocal(b);
0858: builder.math(Opcode.ISUB);
0859: }
0860: }
0861:
0862: private static void generateComparableCompareTo(
0863: CodeBuilder builder, Class type, Method compareToMethod,
0864: Label goodLabel, Label nextLabel, LocalVariable a,
0865: LocalVariable b) {
0866: if (Comparable.class.isAssignableFrom(type)) {
0867: builder.loadLocal(a);
0868: builder.loadLocal(b);
0869: builder.invoke(compareToMethod);
0870: if (goodLabel != null) {
0871: builder.branch(goodLabel);
0872: }
0873: } else {
0874: // Cast each property to Comparable only if needed.
0875: TypeDesc comparableType = TypeDesc
0876: .forClass(Comparable.class);
0877:
0878: boolean locateGoodLabel = false;
0879: if (goodLabel == null) {
0880: goodLabel = builder.createLabel();
0881: locateGoodLabel = true;
0882: }
0883:
0884: Label tryStart = builder.createLabel().setLocation();
0885: builder.loadLocal(a);
0886: builder.checkCast(comparableType);
0887: builder.loadLocal(b);
0888: builder.checkCast(comparableType);
0889: Label tryEnd = builder.createLabel().setLocation();
0890: builder.invoke(compareToMethod);
0891: builder.branch(goodLabel);
0892:
0893: builder.exceptionHandler(tryStart, tryEnd,
0894: ClassCastException.class.getName());
0895: // One of the properties is not Comparable, so just go to next.
0896: // Discard the exception.
0897: builder.pop();
0898: if (nextLabel == null) {
0899: builder.loadConstant(0);
0900: } else {
0901: builder.branch(nextLabel);
0902: }
0903:
0904: if (locateGoodLabel) {
0905: goodLabel.setLocation();
0906: }
0907: }
0908: }
0909:
0910: // A key that uniquely describes the rules of a BeanComparator.
0911: private static class Rules {
0912: private BeanComparator[] mRuleParts;
0913: private int mHashCode;
0914:
0915: public Rules(BeanComparator bc) {
0916: mRuleParts = reduceRules(bc);
0917:
0918: // Compute hashCode.
0919: int hash = 0;
0920:
0921: for (int i = mRuleParts.length - 1; i >= 0; i--) {
0922: bc = mRuleParts[i];
0923: hash = 31 * hash;
0924:
0925: hash += bc.mFlags << 4;
0926:
0927: Object obj = bc.mBeanClass;
0928: if (obj != null) {
0929: hash += obj.hashCode();
0930: }
0931: obj = bc.mOrderByName;
0932: if (obj != null) {
0933: hash += obj.hashCode();
0934: }
0935: obj = bc.mUsingComparator;
0936: if (obj != null) {
0937: hash += obj.getClass().hashCode();
0938: }
0939: obj = bc.mCollator;
0940: if (obj != null) {
0941: hash += obj.getClass().hashCode();
0942: }
0943: }
0944:
0945: mHashCode = hash;
0946: }
0947:
0948: public BeanComparator[] getRuleParts() {
0949: return mRuleParts;
0950: }
0951:
0952: public int hashCode() {
0953: return mHashCode;
0954: }
0955:
0956: /**
0957: * Equality test determines if rules produce an identical
0958: * auto-generated Comparator.
0959: */
0960: public boolean equals(Object obj) {
0961: if (!(obj instanceof Rules)) {
0962: return false;
0963: }
0964:
0965: BeanComparator[] ruleParts1 = getRuleParts();
0966: BeanComparator[] ruleParts2 = ((Rules) obj).getRuleParts();
0967:
0968: if (ruleParts1.length != ruleParts2.length) {
0969: return false;
0970: }
0971:
0972: for (int i = 0; i < ruleParts1.length; i++) {
0973: BeanComparator bc1 = ruleParts1[i];
0974: BeanComparator bc2 = ruleParts2[i];
0975:
0976: if (bc1.mFlags != bc2.mFlags) {
0977: return false;
0978: }
0979: if (!equalTest(bc1.mBeanClass, bc2.mBeanClass)) {
0980: return false;
0981: }
0982: if (!equalTest(bc1.mOrderByName, bc2.mOrderByName)) {
0983: return false;
0984: }
0985: if ((bc1.mUsingComparator == null) != (bc2.mUsingComparator == null)) {
0986: return false;
0987: }
0988: if ((bc1.mCollator == null) != (bc2.mCollator == null)) {
0989: return false;
0990: }
0991: }
0992:
0993: return true;
0994: }
0995:
0996: private BeanComparator[] reduceRules(BeanComparator bc) {
0997: // Reduce the ordering rules by returning BeanComparators
0998: // that are at the end of the chain or before an order-by rule.
0999: List rules = new ArrayList();
1000:
1001: rules.add(bc);
1002: String name = bc.mOrderByName;
1003:
1004: while ((bc = bc.mParent) != null) {
1005: // Don't perform string comparison using equals method.
1006: if (name != bc.mOrderByName) {
1007: rules.add(bc);
1008: name = bc.mOrderByName;
1009: }
1010: }
1011:
1012: int size = rules.size();
1013: BeanComparator[] bcs = new BeanComparator[size];
1014: // Reverse rules so that they are in forward order.
1015: for (int i = 0; i < size; i++) {
1016: bcs[size - i - 1] = (BeanComparator) rules.get(i);
1017: }
1018:
1019: return bcs;
1020: }
1021: }
1022: }
|