KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > cojen > util > BeanComparator


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

16
17 package org.cojen.util;
18
19 import java.io.Serializable JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Comparator JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24 import java.lang.reflect.Constructor JavaDoc;
25 import java.lang.reflect.Method JavaDoc;
26 import java.lang.reflect.InvocationTargetException JavaDoc;
27 import org.cojen.classfile.ClassFile;
28 import org.cojen.classfile.CodeBuilder;
29 import org.cojen.classfile.Label;
30 import org.cojen.classfile.LocalVariable;
31 import org.cojen.classfile.MethodInfo;
32 import org.cojen.classfile.Modifiers;
33 import org.cojen.classfile.Opcode;
34 import org.cojen.classfile.TypeDesc;
35
36 /**
37  * A highly customizable, high-performance Comparator, designed specifically
38  * for advanced sorting of JavaBeans. BeanComparators contain dynamically
39  * auto-generated code and perform as well as hand written Comparators.
40  * <p>
41  * BeanComparator instances are immutable; order customization methods
42  * return new BeanComparators with refined rules. Calls to customizers can
43  * be chained together to read like a formula. The following example produces
44  * a Comparator that orders Threads by name, thread group name, and reverse
45  * priority.
46  *
47  * <pre>
48  * Comparator c = BeanComparator.forClass(Thread.class)
49  * .orderBy("name")
50  * .orderBy("threadGroup.name")
51  * .orderBy("-priority");
52  * </pre>
53  *
54  * The results of sorting Threads using this Comparator and displaying the
55  * results in a table may look like this:
56  *
57  * <p><table border="2">
58  * <tr><th>name</th><th>threadGroup.name</th><th>priority</th></tr>
59  * <tr><td>daemon</td><td>appGroup</td><td>9</td></tr>
60  * <tr><td>main</td><td>main</td><td>5</td></tr>
61  * <tr><td>main</td><td>secureGroup</td><td>5</td></tr>
62  * <tr><td>sweeper</td><td>main</td><td>1</td></tr>
63  * <tr><td>Thread-0</td><td>main</td><td>5</td></tr>
64  * <tr><td>Thread-1</td><td>main</td><td>5</td></tr>
65  * <tr><td>worker</td><td>appGroup</td><td>8</td></tr>
66  * <tr><td>worker</td><td>appGroup</td><td>5</td></tr>
67  * <tr><td>worker</td><td>secureGroup</td><td>8</td></tr>
68  * <tr><td>worker</td><td>secureGroup</td><td>5</td></tr>
69  * </table><p>
70  *
71  * An equivalent Thread ordering Comparator may be specified as:
72  *
73  * <pre>
74  * Comparator c = BeanComparator.forClass(Thread.class)
75  * .orderBy("name")
76  * .orderBy("threadGroup")
77  * .using(BeanComparator.forClass(ThreadGroup.class).orderBy("name"))
78  * .orderBy("priority")
79  * .reverse();
80  * </pre>
81  *
82  * The current implementation of BeanComparator has been optimized for fast
83  * construction and execution of BeanComparators. For maximum performance,
84  * however, save and re-use BeanComparators wherever possible.
85  * <p>
86  * Even though BeanComparator makes use of auto-generated code, instances are
87  * fully Serializable, as long as all passed in Comparators are also
88  * Serializable.
89  *
90  * @author Brian S O'Neill
91  */

92 public class BeanComparator implements Comparator JavaDoc, Serializable JavaDoc {
93     // Maps Rules to auto-generated Comparators.
94
private static Map JavaDoc cGeneratedComparatorCache;
95
96     static {
97         cGeneratedComparatorCache = new SoftValuedHashMap();
98     }
99
100     /**
101      * Get or create a new BeanComparator for beans of the given type. Without
102      * any {@link #orderBy order-by} properties specified, the returned
103      * BeanComparator can only order against null beans (null is
104      * {@link #nullHigh high} by default), and treats all other comparisons as
105      * equal.
106      */

107     public static BeanComparator forClass(Class JavaDoc clazz) {
108         return new BeanComparator(clazz);
109     }
110
111     /**
112      * Compare two objects for equality.
113      */

114     private static boolean equalTest(Object JavaDoc obj1, Object JavaDoc obj2) {
115         return (obj1 == obj2) ? true :
116             ((obj1 == null || obj2 == null) ? false : obj1.equals(obj2));
117     }
118
119     /**
120      * Compare two object classes for equality.
121      */

122     /*
123     private static boolean equalClassTest(Object obj1, Object obj2) {
124         return (obj1 == obj2) ? true :
125             ((obj1 == null || obj2 == null) ? false :
126              obj1.getClass().equals(obj2.getClass()));
127     }
128     */

129
130     private Class JavaDoc mBeanClass;
131
132     // Maps property names to PropertyDescriptors.
133
private transient Map JavaDoc mProperties;
134
135     private String JavaDoc mOrderByName;
136
137     private Comparator JavaDoc mUsingComparator;
138
139     // bit 0: reverse
140
// bit 1: null low order
141
// bit 2: use String compareTo instead of collator
142
private int mFlags;
143
144     // Used for comparing strings.
145
private Comparator JavaDoc mCollator;
146
147     private BeanComparator mParent;
148
149     // Auto-generated internal Comparator.
150
private transient Comparator JavaDoc mComparator;
151
152     private transient boolean mHasHashCode;
153     private transient int mHashCode;
154
155     private BeanComparator(Class JavaDoc clazz) {
156         mBeanClass = clazz;
157         mCollator = String.CASE_INSENSITIVE_ORDER;
158     }
159
160     private BeanComparator(BeanComparator parent) {
161         mParent = parent;
162         mBeanClass = parent.mBeanClass;
163         mProperties = parent.getProperties();
164         mCollator = parent.mCollator;
165     }
166
167     /**
168      * Add an order-by property to produce a more refined Comparator. If the
169      * property does not return a {@link Comparable} object when
170      * {@link #compare compare} is called on the returned comparator, the
171      * property is ignored. Call {@link #using using} on the returned
172      * BeanComparator to specify a Comparator to use for this property instead.
173      * <p>
174      * The specified propery name may refer to sub-properties using a dot
175      * notation. For example, if the bean being compared contains a property
176      * named "info" of type "Information", and "Information" contains a
177      * property named "text", then ordering by the info text can be specified
178      * by "info.text". Sub-properties of sub-properties may be refered to as
179      * well, a.b.c.d.e etc.
180      * <p>
181      * If property type is a primitive, ordering is the same as for its
182      * Comparable object peer. Primitive booleans are ordered false low, true
183      * high. Floating point primitves are ordered exactly the same way as
184      * {@link Float#compareTo(Float) Float.compareTo} and
185      * {@link Double#compareTo(Double) Double.compareTo}.
186      * <p>
187      * As a convenience, property names may have a '-' or '+' character prefix
188      * to specify sort order. A prefix of '-' indicates that the property
189      * is to be sorted in reverse (descending). By default, properties are
190      * sorted in ascending order, and so a prefix of '+' has no effect.
191      * <p>
192      * Any previously applied {@link #reverse reverse-order}, {@link #nullHigh
193      * null-order} and {@link #caseSensitive case-sensitive} settings are not
194      * carried over, and are reset to the defaults for this order-by property.
195      *
196      * @throws IllegalArgumentException when property doesn't exist or cannot
197      * be read.
198      */

199     public BeanComparator orderBy(String JavaDoc propertyName)
200         throws IllegalArgumentException JavaDoc
201     {
202         int dot = propertyName.indexOf('.');
203         String JavaDoc subName;
204         if (dot < 0) {
205             subName = null;
206         } else {
207             subName = propertyName.substring(dot + 1);
208             propertyName = propertyName.substring(0, dot);
209         }
210
211         boolean reverse = false;
212         if (propertyName.length() > 0) {
213             char prefix = propertyName.charAt(0);
214             switch (prefix) {
215             default:
216                 break;
217             case '-':
218                 reverse = true;
219                 // Fall through
220
case '+':
221                 propertyName = propertyName.substring(1);
222             }
223         }
224
225         BeanProperty prop = (BeanProperty)getProperties().get(propertyName);
226
227         if (prop == null) {
228             throw new IllegalArgumentException JavaDoc
229                 ("Property '" + propertyName + "' doesn't exist in '" +
230                  mBeanClass.getName() + '\'');
231         }
232
233         if (prop.getReadMethod() == null) {
234             throw new IllegalArgumentException JavaDoc
235                 ("Property '" + propertyName + "' cannot be read");
236         }
237
238         if (propertyName.equals(mOrderByName)) {
239             // Make String unique so that properties can be specified in
240
// consecutive order-by calls without being eliminated by
241
// reduceRules. A secondary order-by may wish to further refine an
242
// ambiguous comparison using a Comparator.
243
propertyName = new String JavaDoc(propertyName);
244         }
245
246         BeanComparator bc = new BeanComparator(this);
247         bc.mOrderByName = propertyName;
248
249         if (subName != null) {
250             BeanComparator subOrder = forClass(prop.getType());
251             subOrder.mCollator = mCollator;
252             bc = bc.using(subOrder.orderBy(subName));
253         }
254
255         return reverse ? bc.reverse() : bc;
256     }
257
258     /**
259      * Specifiy a Comparator to use on just the last {@link #orderBy order-by}
260      * property. This is good for comparing properties that are not
261      * {@link Comparable} or for applying special ordering rules for a
262      * property. If no order-by properties have been specified, then Comparator
263      * is applied to the compared beans.
264      * <p>
265      * Any previously applied String {@link #caseSensitive case-sensitive} or
266      * {@link #collate collator} settings are overridden by this Comparator.
267      * If property values being compared are primitive, they are converted to
268      * their object peers before being passed to the Comparator.
269      *
270      * @param c Comparator to use on the last order-by property. Passing null
271      * restores the default comparison for the last order-by property.
272      */

273     public BeanComparator using(Comparator JavaDoc c) {
274         BeanComparator bc = new BeanComparator(this);
275         bc.mOrderByName = mOrderByName;
276         bc.mUsingComparator = c;
277         bc.mFlags = mFlags;
278         return bc;
279     }
280
281     /**
282      * Toggle reverse-order option on just the last {@link #orderBy order-by}
283      * property. By default, order is ascending. If no order-by properties have
284      * been specified, then reverse order is applied to the compared beans.
285      */

286     public BeanComparator reverse() {
287         BeanComparator bc = new BeanComparator(this);
288         bc.mOrderByName = mOrderByName;
289         bc.mUsingComparator = mUsingComparator;
290         bc.mFlags = mFlags ^ 0x01;
291         return bc;
292     }
293
294     /**
295      * Set the order of comparisons against null as being high (the default)
296      * on just the last {@link #orderBy order-by} property. If no order-by
297      * properties have been specified, then null high order is applied to the
298      * compared beans. Null high order is the default for consistency with the
299      * high ordering of {@link Float#NaN NaN} by
300      * {@link Float#compareTo(Float) Float}.
301      * <p>
302      * Calling 'nullHigh, reverse' is equivalent to calling 'reverse, nullLow'.
303      */

304     public BeanComparator nullHigh() {
305         BeanComparator bc = new BeanComparator(this);
306         bc.mOrderByName = mOrderByName;
307         bc.mUsingComparator = mUsingComparator;
308         bc.mFlags = mFlags ^ ((mFlags & 0x01) << 1);
309         return bc;
310     }
311
312     /**
313      * Set the order of comparisons against null as being low
314      * on just the last {@link #orderBy order-by} property. If no order-by
315      * properties have been specified, then null low order is applied to the
316      * compared beans.
317      * <p>
318      * Calling 'reverse, nullLow' is equivalent to calling 'nullHigh, reverse'.
319      */

320     public BeanComparator nullLow() {
321         BeanComparator bc = new BeanComparator(this);
322         bc.mOrderByName = mOrderByName;
323         bc.mUsingComparator = mUsingComparator;
324         bc.mFlags = mFlags ^ ((~mFlags & 0x01) << 1);
325         return bc;
326     }
327
328     /**
329      * Override the collator and compare just the last order-by property using
330      * {@link String#compareTo(String) String.compareTo}, if it is of type
331      * String. If no order-by properties have been specified then this call is
332      * ineffective.
333      * <p>
334      * A {@link #using using} Comparator disables this setting. Passing null to
335      * the using method will re-enable a case-sensitive setting.
336      */

337     public BeanComparator caseSensitive() {
338         if ((mFlags & 0x04) != 0) {
339             // Already case-sensitive.
340
return this;
341         }
342         BeanComparator bc = new BeanComparator(this);
343         bc.mOrderByName = mOrderByName;
344         bc.mUsingComparator = mUsingComparator;
345         bc.mFlags = mFlags | 0x04;
346         return bc;
347     }
348
349     /**
350      * Set a Comparator for ordering Strings, which is passed on to all
351      * BeanComparators derived from this one. By default, String are compared
352      * using {@link String#CASE_INSENSITIVE_ORDER}. Passing null for a collator
353      * will cause all String comparisons to use
354      * {@link String#compareTo(String) String.compareTo}.
355      * <p>
356      * A {@link #using using} Comparator disables this setting. Passing null
357      * to the using method will re-enable a collator.
358      *
359      * @param c Comparator to use for ordering all Strings. Passing null
360      * causes all Strings to be ordered by
361      * {@link String#compareTo(String) String.compareTo}.
362      */

363     public BeanComparator collate(Comparator JavaDoc c) {
364         BeanComparator bc = new BeanComparator(this);
365         bc.mOrderByName = mOrderByName;
366         bc.mUsingComparator = mUsingComparator;
367         bc.mFlags = mFlags & ~0x04;
368         bc.mCollator = c;
369         return bc;
370     }
371
372     public int compare(Object JavaDoc obj1, Object JavaDoc obj2) throws ClassCastException JavaDoc {
373         Comparator JavaDoc c = mComparator;
374         if (c == null) {
375             c = mComparator = generateComparator();
376         }
377         return c.compare(obj1, obj2);
378     }
379
380     public int hashCode() {
381         if (!mHasHashCode) {
382             setHashCode(new Rules(this));
383         }
384         return mHashCode;
385     }
386
387     private void setHashCode(Rules rules) {
388         mHashCode = rules.hashCode();
389         mHasHashCode = true;
390     }
391
392     /**
393      * Compares BeanComparators for equality based on their imposed ordering.
394      * Returns true only if the given object is a BeanComparater and it can be
395      * determined without a doubt that the ordering is identical. Because
396      * equality testing is dependent on the behavior of the equals methods of
397      * any 'using' Comparators and/or collators, false may be returned even
398      * though ordering is in fact identical.
399      */

400     public boolean equals(Object JavaDoc obj) {
401         if (obj instanceof BeanComparator) {
402             BeanComparator bc = (BeanComparator)obj;
403  
404             return mFlags == bc.mFlags &&
405                 equalTest(mBeanClass, bc.mBeanClass) &&
406                 equalTest(mOrderByName, bc.mOrderByName) &&
407                 equalTest(mUsingComparator, bc.mUsingComparator) &&
408                 equalTest(mCollator, bc.mCollator) &&
409                 equalTest(mParent, bc.mParent);
410         } else {
411             return false;
412         }
413     }
414
415     private Map JavaDoc getProperties() {
416         if (mProperties == null) {
417             mProperties = BeanIntrospector.getAllProperties(mBeanClass);
418         }
419         return mProperties;
420     }
421
422     private Comparator JavaDoc generateComparator() {
423         Rules rules = new Rules(this);
424
425         if (!mHasHashCode) {
426             setHashCode(rules);
427         }
428
429         Class JavaDoc clazz;
430
431         synchronized (cGeneratedComparatorCache) {
432             Object JavaDoc c = cGeneratedComparatorCache.get(rules);
433
434             if (c == null) {
435                 clazz = generateComparatorClass(rules);
436                 cGeneratedComparatorCache.put(rules, clazz);
437             } else if (c instanceof Comparator JavaDoc) {
438                 return (Comparator JavaDoc)c;
439             } else {
440                 clazz = (Class JavaDoc)c;
441             }
442
443             BeanComparator[] ruleParts = rules.getRuleParts();
444             Comparator JavaDoc[] collators = new Comparator JavaDoc[ruleParts.length];
445             Comparator JavaDoc[] usingComparators = new Comparator JavaDoc[ruleParts.length];
446             boolean singleton = true;
447
448             for (int i=0; i<ruleParts.length; i++) {
449                 BeanComparator rp = ruleParts[i];
450                 Comparator JavaDoc c2 = rp.mCollator;
451                 if ((collators[i] = c2) != null) {
452                     if (c2 != String.CASE_INSENSITIVE_ORDER) {
453                         singleton = false;
454                     }
455                 }
456                 if ((usingComparators[i] = rp.mUsingComparator) != null) {
457                     singleton = false;
458                 }
459             }
460
461             try {
462                 Constructor JavaDoc ctor = clazz.getDeclaredConstructor
463                     (new Class JavaDoc[] {Comparator JavaDoc[].class, Comparator JavaDoc[].class});
464                 c = (Comparator JavaDoc)ctor.newInstance
465                     (new Object JavaDoc[] {collators, usingComparators});
466             } catch (NoSuchMethodException JavaDoc e) {
467                 throw new InternalError JavaDoc(e.toString());
468             } catch (InstantiationException JavaDoc e) {
469                 throw new InternalError JavaDoc(e.toString());
470             } catch (IllegalAccessException JavaDoc e) {
471                 throw new InternalError JavaDoc(e.toString());
472             } catch (IllegalArgumentException JavaDoc e) {
473                 throw new InternalError JavaDoc(e.toString());
474             } catch (InvocationTargetException JavaDoc e) {
475                 throw new InternalError JavaDoc(e.getTargetException().toString());
476             }
477
478             if (singleton) {
479                 // Can save and re-use instance since it obeys the requirements
480
// for a singleton.
481
cGeneratedComparatorCache.put(rules, c);
482             }
483
484             return (Comparator JavaDoc)c;
485         }
486     }
487
488     private Class JavaDoc generateComparatorClass(Rules rules) {
489         ClassInjector ci = ClassInjector.create
490             (getClass().getName(), mBeanClass.getClassLoader());
491         return ci.defineClass(generateClassFile(ci.getClassName(), rules));
492     }
493
494     private static ClassFile generateClassFile(String JavaDoc className, Rules rules) {
495         ClassFile cf = new ClassFile(className);
496         cf.markSynthetic();
497         cf.setSourceFile(BeanComparator.class.getName());
498         try {
499             cf.setTarget(System.getProperty("java.specification.version"));
500         } catch (Exception JavaDoc e) {
501         }
502
503         cf.addInterface(Comparator JavaDoc.class);
504         cf.addInterface(Serializable JavaDoc.class);
505
506         // Define fields to hold usage comparator and collator.
507
TypeDesc comparatorType = TypeDesc.forClass(Comparator JavaDoc.class);
508         TypeDesc comparatorArrayType = comparatorType.toArrayType();
509         cf.addField(Modifiers.PRIVATE,
510                     "mCollators", comparatorArrayType).markSynthetic();
511         cf.addField(Modifiers.PRIVATE,
512                     "mUsingComparators", comparatorArrayType).markSynthetic();
513
514         // Create constructor to initialize fields.
515
TypeDesc[] paramTypes = {
516             comparatorArrayType, comparatorArrayType
517         };
518         MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC, paramTypes);
519         ctor.markSynthetic();
520         CodeBuilder builder = new CodeBuilder(ctor);
521
522         builder.loadThis();
523         builder.invokeSuperConstructor(null);
524         builder.loadThis();
525         builder.loadLocal(builder.getParameter(0));
526         builder.storeField("mCollators", comparatorArrayType);
527         builder.loadThis();
528         builder.loadLocal(builder.getParameter(1));
529         builder.storeField("mUsingComparators", comparatorArrayType);
530         builder.returnVoid();
531
532         // Create the all-important compare method.
533
Method JavaDoc compareMethod, compareToMethod;
534         try {
535             compareMethod = Comparator JavaDoc.class.getMethod
536                 ("compare", new Class JavaDoc[] {Object JavaDoc.class, Object JavaDoc.class});
537             compareToMethod = Comparable JavaDoc.class.getMethod
538                 ("compareTo", new Class JavaDoc[] {Object JavaDoc.class});
539         } catch (NoSuchMethodException JavaDoc e) {
540             throw new InternalError JavaDoc(e.toString());
541         }
542
543         MethodInfo mi = cf.addMethod(compareMethod);
544         mi.markSynthetic();
545         builder = new CodeBuilder(mi);
546
547         Label endLabel = builder.createLabel();
548         LocalVariable obj1 = builder.getParameter(0);
549         LocalVariable obj2 = builder.getParameter(1);
550
551         // The first rule always applies to the beans directly. All others
552
// apply to properties.
553

554         BeanComparator[] ruleParts = rules.getRuleParts();
555         BeanComparator bc = ruleParts[0];
556
557         if ((bc.mFlags & 0x01) != 0) {
558             // Reverse beans.
559
LocalVariable temp = obj1;
560             obj1 = obj2;
561             obj2 = temp;
562         }
563
564         // Handle the case when obj1 and obj2 are the same (or both null)
565
builder.loadLocal(obj1);
566         builder.loadLocal(obj2);
567         builder.ifEqualBranch(endLabel, true);
568
569         // Do null order checks for beans.
570
boolean nullHigh = (bc.mFlags & 0x02) == 0;
571         Label label = builder.createLabel();
572         builder.loadLocal(obj1);
573         builder.ifNullBranch(label, false);
574         builder.loadConstant(nullHigh ? 1 : -1);
575         builder.returnValue(TypeDesc.INT);
576         label.setLocation();
577         label = builder.createLabel();
578         builder.loadLocal(obj2);
579         builder.ifNullBranch(label, false);
580         builder.loadConstant(nullHigh ? -1 : 1);
581         builder.returnValue(TypeDesc.INT);
582         label.setLocation();
583
584         // Call 'using' Comparator if one is provided.
585
LocalVariable result =
586             builder.createLocalVariable("result", TypeDesc.INT);
587         if (bc.mUsingComparator != null) {
588             builder.loadThis();
589             builder.loadField("mUsingComparators", comparatorArrayType);
590             builder.loadConstant(0);
591             builder.loadFromArray(TypeDesc.forClass(Comparator JavaDoc.class));
592             builder.loadLocal(obj1);
593             builder.loadLocal(obj2);
594             builder.invoke(compareMethod);
595             builder.storeLocal(result);
596             builder.loadLocal(result);
597             label = builder.createLabel();
598             builder.ifZeroComparisonBranch(label, "==");
599             builder.loadLocal(result);
600             builder.returnValue(TypeDesc.INT);
601             label.setLocation();
602         }
603
604         // Cast bean parameters to correct types so that properties may be
605
// accessed.
606
TypeDesc type = TypeDesc.forClass(bc.mBeanClass);
607         builder.loadLocal(obj1);
608         builder.checkCast(type);
609         builder.storeLocal(obj1);
610         builder.loadLocal(obj2);
611         builder.checkCast(type);
612         builder.storeLocal(obj2);
613
614         // Generate code to perform comparisons against each property.
615
for (int i=1; i<ruleParts.length; i++) {
616             bc = ruleParts[i];
617
618             BeanProperty prop =
619                 (BeanProperty)bc.getProperties().get(bc.mOrderByName);
620             Class JavaDoc propertyClass = prop.getType();
621             TypeDesc propertyType = TypeDesc.forClass(propertyClass);
622
623             // Create local variable to hold property values.
624
LocalVariable p1 = builder.createLocalVariable("p1", propertyType);
625             LocalVariable p2 = builder.createLocalVariable("p2", propertyType);
626
627             // Access properties and store in local variables.
628
builder.loadLocal(obj1);
629             builder.invoke(prop.getReadMethod());
630             builder.storeLocal(p1);
631             builder.loadLocal(obj2);
632             builder.invoke(prop.getReadMethod());
633             builder.storeLocal(p2);
634
635             if ((bc.mFlags & 0x01) != 0) {
636                 // Reverse properties.
637
LocalVariable temp = p1;
638                 p1 = p2;
639                 p2 = temp;
640             }
641
642             Label nextLabel = builder.createLabel();
643
644             // Handle the case when p1 and p2 are the same (or both null)
645
if (!propertyClass.isPrimitive()) {
646                 builder.loadLocal(p1);
647                 builder.loadLocal(p2);
648                 builder.ifEqualBranch(nextLabel, true);
649
650                 // Do null order checks for properties.
651
nullHigh = (bc.mFlags & 0x02) == 0;
652                 label = builder.createLabel();
653                 builder.loadLocal(p1);
654                 builder.ifNullBranch(label, false);
655                 builder.loadConstant(nullHigh ? 1 : -1);
656                 builder.returnValue(TypeDesc.INT);
657                 label.setLocation();
658                 label = builder.createLabel();
659                 builder.loadLocal(p2);
660                 builder.ifNullBranch(label, false);
661                 builder.loadConstant(nullHigh ? -1 : 1);
662                 builder.returnValue(TypeDesc.INT);
663                 label.setLocation();
664             }
665
666             // Call 'using' Comparator if one is provided, else assume
667
// Comparable.
668
if (bc.mUsingComparator != null) {
669                 builder.loadThis();
670                 builder.loadField("mUsingComparators", comparatorArrayType);
671                 builder.loadConstant(i);
672                 builder.loadFromArray(TypeDesc.forClass(Comparator JavaDoc.class));
673                 builder.loadLocal(p1);
674                 builder.convert(propertyType, propertyType.toObjectType());
675                 builder.loadLocal(p2);
676                 builder.convert(propertyType, propertyType.toObjectType());
677                 builder.invoke(compareMethod);
678             } else {
679                 // If case-sensitive is off and a collator is provided and
680
// property could be a String, apply collator.
681
if ((bc.mFlags & 0x04) == 0 && bc.mCollator != null &&
682                     propertyClass.isAssignableFrom(String JavaDoc.class)) {
683
684                     Label resultLabel = builder.createLabel();
685
686                     if (!String JavaDoc.class.isAssignableFrom(propertyClass)) {
687                         // Check if both property values are strings at
688
// runtime. If they aren't, cast to Comparable and call
689
// compareTo.
690

691                         TypeDesc stringType = TypeDesc.STRING;
692
693                         builder.loadLocal(p1);
694                         builder.instanceOf(stringType);
695                         Label notString = builder.createLabel();
696                         builder.ifZeroComparisonBranch(notString, "==");
697                         builder.loadLocal(p2);
698                         builder.instanceOf(stringType);
699                         Label isString = builder.createLabel();
700                         builder.ifZeroComparisonBranch(isString, "!=");
701
702                         notString.setLocation();
703                         generateComparableCompareTo
704                             (builder, propertyClass, compareToMethod,
705                              resultLabel, nextLabel, p1, p2);
706
707                         isString.setLocation();
708                     }
709
710                     builder.loadThis();
711                     builder.loadField("mCollators", comparatorArrayType);
712                     builder.loadConstant(i);
713                     builder.loadFromArray(TypeDesc.forClass(Comparator JavaDoc.class));
714                     builder.loadLocal(p1);
715                     builder.loadLocal(p2);
716                     builder.invoke(compareMethod);
717
718                     resultLabel.setLocation();
719                 } else if (propertyClass.isPrimitive()) {
720                     generatePrimitiveComparison(builder, propertyClass, p1,p2);
721                 } else {
722                     // Assume properties are instances of Comparable.
723
generateComparableCompareTo
724                         (builder, propertyClass, compareToMethod,
725                          null, nextLabel, p1, p2);
726                 }
727             }
728
729             if (i < (ruleParts.length - 1)) {
730                 builder.storeLocal(result);
731                 builder.loadLocal(result);
732                 builder.ifZeroComparisonBranch(nextLabel, "==");
733                 builder.loadLocal(result);
734             }
735             builder.returnValue(TypeDesc.INT);
736
737             // The next property comparison will start here.
738
nextLabel.setLocation();
739         }
740
741         endLabel.setLocation();
742         builder.loadConstant(0);
743         builder.returnValue(TypeDesc.INT);
744
745         return cf;
746     }
747
748     private static void generatePrimitiveComparison(CodeBuilder builder,
749                                                     Class JavaDoc type,
750                                                     LocalVariable a,
751                                                     LocalVariable b)
752     {
753         if (type == float.class) {
754             // Comparison is same as for Float.compareTo(Float).
755
Label done = builder.createLabel();
756
757             builder.loadLocal(a);
758             builder.loadLocal(b);
759             builder.math(Opcode.FCMPG);
760             Label label = builder.createLabel();
761             builder.ifZeroComparisonBranch(label, ">=");
762             builder.loadConstant(-1);
763             builder.branch(done);
764
765             label.setLocation();
766             builder.loadLocal(a);
767             builder.loadLocal(b);
768             builder.math(Opcode.FCMPL);
769             label = builder.createLabel();
770             builder.ifZeroComparisonBranch(label, "<=");
771             builder.loadConstant(1);
772             builder.branch(done);
773
774             Method JavaDoc floatToIntBits;
775             try {
776                 floatToIntBits = Float JavaDoc.class.getMethod
777                     ("floatToIntBits", new Class JavaDoc[] {float.class});
778             } catch (NoSuchMethodException JavaDoc e) {
779                 throw new InternalError JavaDoc(e.toString());
780             }
781
782             label.setLocation();
783             builder.loadLocal(a);
784             builder.invoke(floatToIntBits);
785             builder.convert(TypeDesc.INT, TypeDesc.LONG);
786             builder.loadLocal(b);
787             builder.invoke(floatToIntBits);
788             builder.convert(TypeDesc.INT, TypeDesc.LONG);
789             builder.math(Opcode.LCMP);
790
791             done.setLocation();
792         } else if (type == double.class) {
793             // Comparison is same as for Double.compareTo(Double).
794
Label done = builder.createLabel();
795
796             builder.loadLocal(a);
797             builder.loadLocal(b);
798             done = builder.createLabel();
799             builder.math(Opcode.DCMPG);
800             Label label = builder.createLabel();
801             builder.ifZeroComparisonBranch(label, ">=");
802             builder.loadConstant(-1);
803             builder.branch(done);
804
805             label.setLocation();
806             builder.loadLocal(a);
807             builder.loadLocal(b);
808             builder.math(Opcode.DCMPL);
809             label = builder.createLabel();
810             builder.ifZeroComparisonBranch(label, "<=");
811             builder.loadConstant(1);
812             builder.branch(done);
813
814             Method JavaDoc doubleToLongBits;
815             try {
816                 doubleToLongBits = Double JavaDoc.class.getMethod
817                     ("doubleToLongBits", new Class JavaDoc[] {double.class});
818             } catch (NoSuchMethodException JavaDoc e) {
819                 throw new InternalError JavaDoc(e.toString());
820             }
821
822             label.setLocation();
823             builder.loadLocal(a);
824             builder.invoke(doubleToLongBits);
825             builder.loadLocal(b);
826             builder.invoke(doubleToLongBits);
827             builder.math(Opcode.LCMP);
828
829             done.setLocation();
830         } else if (type == long.class) {
831             builder.loadLocal(a);
832             builder.loadLocal(b);
833             builder.math(Opcode.LCMP);
834         } else if (type == int.class) {
835             builder.loadLocal(a);
836             builder.convert(TypeDesc.INT, TypeDesc.LONG);
837             builder.loadLocal(b);
838             builder.convert(TypeDesc.INT, TypeDesc.LONG);
839             builder.math(Opcode.LCMP);
840         } else {
841             builder.loadLocal(a);
842             builder.loadLocal(b);
843             builder.math(Opcode.ISUB);
844         }
845     }
846
847     private static void generateComparableCompareTo(CodeBuilder builder,
848                                                     Class JavaDoc type,
849                                                     Method JavaDoc compareToMethod,
850                                                     Label goodLabel,
851                                                     Label nextLabel,
852                                                     LocalVariable a,
853                                                     LocalVariable b)
854     {
855         if (Comparable JavaDoc.class.isAssignableFrom(type)) {
856             builder.loadLocal(a);
857             builder.loadLocal(b);
858             builder.invoke(compareToMethod);
859             if (goodLabel != null) {
860                 builder.branch(goodLabel);
861             }
862         } else {
863             // Cast each property to Comparable only if needed.
864
TypeDesc comparableType = TypeDesc.forClass(Comparable JavaDoc.class);
865
866             boolean locateGoodLabel = false;
867             if (goodLabel == null) {
868                 goodLabel = builder.createLabel();
869                 locateGoodLabel = true;
870             }
871
872             Label tryStart = builder.createLabel().setLocation();
873             builder.loadLocal(a);
874             builder.checkCast(comparableType);
875             builder.loadLocal(b);
876             builder.checkCast(comparableType);
877             Label tryEnd = builder.createLabel().setLocation();
878             builder.invoke(compareToMethod);
879             builder.branch(goodLabel);
880
881             builder.exceptionHandler(tryStart, tryEnd,
882                                      ClassCastException JavaDoc.class.getName());
883             // One of the properties is not Comparable, so just go to next.
884
// Discard the exception.
885
builder.pop();
886             if (nextLabel == null) {
887                 builder.loadConstant(0);
888             } else {
889                 builder.branch(nextLabel);
890             }
891
892             if (locateGoodLabel) {
893                 goodLabel.setLocation();
894             }
895         }
896     }
897
898     // A key that uniquely describes the rules of a BeanComparator.
899
private static class Rules {
900         private BeanComparator[] mRuleParts;
901         private int mHashCode;
902
903         public Rules(BeanComparator bc) {
904             mRuleParts = reduceRules(bc);
905
906             // Compute hashCode.
907
int hash = 0;
908
909             for (int i = mRuleParts.length - 1; i >= 0; i--) {
910                 bc = mRuleParts[i];
911                 hash = 31 * hash;
912
913                 hash += bc.mFlags << 4;
914
915                 Object JavaDoc obj = bc.mBeanClass;
916                 if (obj != null) {
917                     hash += obj.hashCode();
918                 }
919                 obj = bc.mOrderByName;
920                 if (obj != null) {
921                     hash += obj.hashCode();
922                 }
923                 obj = bc.mUsingComparator;
924                 if (obj != null) {
925                     hash += obj.getClass().hashCode();
926                 }
927                 obj = bc.mCollator;
928                 if (obj != null) {
929                     hash += obj.getClass().hashCode();
930                 }
931             }
932
933             mHashCode = hash;
934         }
935
936         public BeanComparator[] getRuleParts() {
937             return mRuleParts;
938         }
939
940         public int hashCode() {
941             return mHashCode;
942         }
943
944         /**
945          * Equality test determines if rules produce an identical
946          * auto-generated Comparator.
947          */

948         public boolean equals(Object JavaDoc obj) {
949             if (!(obj instanceof Rules)) {
950                 return false;
951             }
952
953             BeanComparator[] ruleParts1 = getRuleParts();
954             BeanComparator[] ruleParts2 = ((Rules)obj).getRuleParts();
955
956             if (ruleParts1.length != ruleParts2.length) {
957                 return false;
958             }
959
960             for (int i=0; i<ruleParts1.length; i++) {
961                 BeanComparator bc1 = ruleParts1[i];
962                 BeanComparator bc2 = ruleParts2[i];
963
964                 if (bc1.mFlags != bc2.mFlags) {
965                     return false;
966                 }
967                 if (!equalTest(bc1.mBeanClass, bc2.mBeanClass)) {
968                     return false;
969                 }
970                 if (!equalTest(bc1.mOrderByName, bc2.mOrderByName)) {
971                     return false;
972                 }
973                 if ((bc1.mUsingComparator == null) !=
974                     (bc2.mUsingComparator == null)) {
975                     return false;
976                 }
977                 if ((bc1.mCollator == null) != (bc2.mCollator == null)) {
978                     return false;
979                 }
980             }
981
982             return true;
983         }
984
985         private BeanComparator[] reduceRules(BeanComparator bc) {
986             // Reduce the ordering rules by returning BeanComparators
987
// that are at the end of the chain or before an order-by rule.
988
List JavaDoc rules = new ArrayList JavaDoc();
989
990             rules.add(bc);
991             String JavaDoc name = bc.mOrderByName;
992
993             while ((bc = bc.mParent) != null) {
994                 // Don't perform string comparison using equals method.
995
if (name != bc.mOrderByName) {
996                     rules.add(bc);
997                     name = bc.mOrderByName;
998                 }
999             }
1000
1001            int size = rules.size();
1002            BeanComparator[] bcs = new BeanComparator[size];
1003            // Reverse rules so that they are in forward order.
1004
for (int i=0; i<size; i++) {
1005                bcs[size - i - 1] = (BeanComparator)rules.get(i);
1006            }
1007
1008            return bcs;
1009        }
1010    }
1011}
1012
Popular Tags