KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > go > trove > util > BeanComparator


1 /* ====================================================================
2  * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
3  * ====================================================================
4  * The Tea Software License, Version 1.1
5  *
6  * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Walt Disney Internet Group (http://opensource.go.com/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact opensource@dig.com.
31  *
32  * 5. Products derived from this software may not be called "Tea",
33  * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34  * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35  * written permission of the Walt Disney Internet Group.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  * ====================================================================
49  *
50  * For more information about Tea, please see http://opensource.go.com/.
51  */

52
53 package com.go.trove.util;
54
55 import java.beans.*;
56 import java.io.*;
57 import java.util.*;
58 import java.lang.reflect.*;
59 import com.go.trove.classfile.*;
60
61 /******************************************************************************
62  * A highly customizable, high-performance Comparator, designed specifically
63  * for advanced sorting of JavaBeans. BeanComparators contain dynamically
64  * auto-generated code and perform as well as hand written Comparators.
65  * <p>
66  * BeanComparator instances are immutable; order customization methods
67  * return new BeanComparators with refined rules. Calls to customizers can
68  * be chained together to read like a formula. The following example produces
69  * a Comparator that orders Threads by name, thread group name, and reverse
70  * priority.
71  *
72  * <pre>
73  * Comparator c = BeanComparator.forClass(Thread.class)
74  * .orderBy("name")
75  * .orderBy("threadGroup.name")
76  * .orderBy("priority")
77  * .reverse();
78  * </pre>
79  *
80  * The results of sorting Threads using this Comparator and displaying the
81  * results in a table may look like this:
82  *
83  * <p><table border="2">
84  * <tr><th>name</th><th>threadGroup.name</th><th>priority</th></tr>
85  * <tr><td>daemon</td><td>appGroup</td><td>9</td></tr>
86  * <tr><td>main</td><td>main</td><td>5</td></tr>
87  * <tr><td>main</td><td>secureGroup</td><td>5</td></tr>
88  * <tr><td>sweeper</td><td>main</td><td>1</td></tr>
89  * <tr><td>Thread-0</td><td>main</td><td>5</td></tr>
90  * <tr><td>Thread-1</td><td>main</td><td>5</td></tr>
91  * <tr><td>worker</td><td>appGroup</td><td>8</td></tr>
92  * <tr><td>worker</td><td>appGroup</td><td>5</td></tr>
93  * <tr><td>worker</td><td>secureGroup</td><td>8</td></tr>
94  * <tr><td>worker</td><td>secureGroup</td><td>5</td></tr>
95  * </table><p>
96  *
97  * An equivalent Thread ordering Comparator may be specified as:
98  *
99  * <pre>
100  * Comparator c = BeanComparator.forClass(Thread.class)
101  * .orderBy("name")
102  * .orderBy("threadGroup")
103  * .using(BeanComparator.forClass(ThreadGroup.class).orderBy("name"))
104  * .orderBy("priority")
105  * .reverse();
106  * </pre>
107  *
108  * The current implementation of BeanComparator has been optimized for fast
109  * construction and execution of BeanComparators. For maximum performance,
110  * however, save and re-use BeanComparators wherever possible.
111  * <p>
112  * Even though BeanComparator makes use of auto-generated code, instances are
113  * fully Serializable, as long as all passed in Comparators are also
114  * Serializable.
115  *
116  * @author Brian S O'Neill
117  * @version
118  * <!--$$Revision:--> 18 <!-- $-->, <!--$$JustDate:--> 00/12/18 <!-- $-->
119  * @see java.beans.Introspector
120  * @see CompleteIntrospector
121  * @see java.util.Collections.sort
122  * @see java.util.Arrays.sort
123  */

124 public class BeanComparator implements Comparator, Serializable {
125     // Maps Rules to auto-generated Comparators.
126
private static Map cGeneratedComparatorCache;
127
128     static {
129         cGeneratedComparatorCache = new SoftHashMap();
130     }
131
132     /**
133      * Get or create a new BeanComparator for beans of the given type. Without
134      * any {@link #orderBy order-by} properties specified, the returned
135      * BeanComparator can only order against null beans (null is
136      * {@link #nullHigh high} by default), and treats all other comparisons as
137      * equal.
138      */

139     public static BeanComparator forClass(Class JavaDoc clazz) {
140         return new BeanComparator(clazz);
141     }
142
143     /**
144      * Compare two objects for equality.
145      */

146     private static boolean equalTest(Object JavaDoc obj1, Object JavaDoc obj2) {
147         return (obj1 == obj2) ? true :
148             ((obj1 == null || obj2 == null) ? false : obj1.equals(obj2));
149     }
150
151     /**
152      * Compare two object classes for equality.
153      */

154     /*
155     private static boolean equalClassTest(Object obj1, Object obj2) {
156         return (obj1 == obj2) ? true :
157             ((obj1 == null || obj2 == null) ? false :
158              obj1.getClass().equals(obj2.getClass()));
159     }
160     */

161
162     private Class JavaDoc mBeanClass;
163
164     // Maps property names to PropertyDescriptors.
165
private transient Map mProperties;
166
167     private String JavaDoc mOrderByName;
168
169     private Comparator mUsingComparator;
170
171     // bit 0: reverse
172
// bit 1: null low order
173
// bit 2: use String compareTo instead of collator
174
private int mFlags;
175
176     // Used for comparing strings.
177
private Comparator mCollator;
178
179     private BeanComparator mParent;
180
181     // Auto-generated internal Comparator.
182
private transient Comparator mComparator;
183
184     private transient boolean mHasHashCode;
185     private transient int mHashCode;
186
187     private BeanComparator(Class JavaDoc clazz) {
188         mBeanClass = clazz;
189         mCollator = String.CASE_INSENSITIVE_ORDER;
190     }
191
192     private BeanComparator(BeanComparator parent) {
193         mParent = parent;
194         mBeanClass = parent.mBeanClass;
195         mProperties = parent.getProperties();
196         mCollator = parent.mCollator;
197     }
198
199     /**
200      * Add an order-by property to produce a more refined Comparator. If the
201      * property does not return a {@link Comparable} object when
202      * {@link #compare compare} is called on the returned comparator, the
203      * property is ignored. Call {@link #using using} on the returned
204      * BeanComparator to specify a Comparator to use for this property instead.
205      * <p>
206      * The specified propery name may refer to sub-properties using a dot
207      * notation. For example, if the bean being compared contains a property
208      * named "info" of type "Information", and "Information" contains a
209      * property named "text", then ordering by the info text can be specified
210      * by "info.text". Sub-properties of sub-properties may be refered to as
211      * well, a.b.c.d.e etc.
212      * <p>
213      * If property type is a primitive, ordering is the same as for its
214      * Comparable object peer. Primitive booleans are ordered false low, true
215      * high. Floating point primitves are ordered exactly the same way as
216      * {@link Float#compareTo(Float) Float.compareTo} and
217      * {@link Double#compareTo(Double) Double.compareTo}.
218      * <p>
219      * Any {@link #reverse reverse-order}, {@link #nullHigh null-order} and
220      * {@link #caseSensitive case-sensitive} settings are not carried over,
221      * and are reset to the defaults for this order-by property.
222      *
223      * @throws IllegalArgumentException when property doesn't exist or cannot
224      * be read.
225      */

226     public BeanComparator orderBy(String JavaDoc propertyName)
227         throws IllegalArgumentException JavaDoc
228     {
229         int dot = propertyName.indexOf('.');
230         String JavaDoc subName;
231         if (dot < 0) {
232             subName = null;
233         }
234         else {
235             subName = propertyName.substring(dot + 1);
236             propertyName = propertyName.substring(0, dot);
237         }
238
239         PropertyDescriptor desc =
240             (PropertyDescriptor)getProperties().get(propertyName);
241
242         if (desc == null) {
243             throw new IllegalArgumentException JavaDoc
244                 ("Property '" + propertyName + "' doesn't exist in '" +
245                  mBeanClass.getName() + '\'');
246         }
247
248         if (desc.getPropertyType() == null) {
249             throw new IllegalArgumentException JavaDoc
250                 ("Property '" + propertyName + "' is only an indexed type");
251         }
252
253         if (desc.getReadMethod() == null) {
254             throw new IllegalArgumentException JavaDoc
255                 ("Property '" + propertyName + "' cannot be read");
256         }
257
258         if (propertyName.equals(mOrderByName)) {
259             // Make String unique so that properties can be specified in
260
// consecutive order-by calls without being eliminated by
261
// reduceRules. A secondary order-by may wish to further refine an
262
// ambiguous comparison using a Comparator.
263
propertyName = new String JavaDoc(propertyName);
264         }
265
266         BeanComparator bc = new BeanComparator(this);
267         bc.mOrderByName = propertyName;
268
269         if (subName != null) {
270             BeanComparator subOrder = forClass(desc.getPropertyType());
271             subOrder.mCollator = mCollator;
272             bc = bc.using(subOrder.orderBy(subName));
273         }
274
275         return bc;
276     }
277     
278     /**
279      * Specifiy a Comparator to use on just the last {@link #orderBy order-by}
280      * property. This is good for comparing properties that are not
281      * {@link Comparable} or for applying special ordering rules for a
282      * property. If no order-by properties have been specified, then Comparator
283      * is applied to the compared beans.
284      * <p>
285      * Any String {@link #caseSensitive case-sensitive} or
286      * {@link #collate collator} settings are overridden by this Comparator.
287      * If property values being compared are primitive, they are converted to
288      * their object peers before being passed to the Comparator.
289      *
290      * @param c Comparator to use on the last order-by property. Passing null
291      * restores the default comparison for the last order-by property.
292      */

293     public BeanComparator using(Comparator c) {
294         BeanComparator bc = new BeanComparator(this);
295         bc.mOrderByName = mOrderByName;
296         bc.mUsingComparator = c;
297         bc.mFlags = mFlags;
298         return bc;
299     }
300
301     /**
302      * Toggle reverse-order option on just the last {@link #orderBy order-by}
303      * property. By default, order is ascending. If no order-by properties have
304      * been specified, then reverse order is applied to the compared beans.
305      */

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

324     public BeanComparator nullHigh() {
325         BeanComparator bc = new BeanComparator(this);
326         bc.mOrderByName = mOrderByName;
327         bc.mUsingComparator = mUsingComparator;
328         bc.mFlags = mFlags ^ ((mFlags & 0x01) << 1);
329         return bc;
330     }
331
332     /**
333      * Set the order of comparisons against null as being low
334      * on just the last {@link #orderBy order-by} property. If no order-by
335      * properties have been specified, then null low order is applied to the
336      * compared beans.
337      * <p>
338      * Calling 'reverse, nullLow' is equivalent to calling 'nullHigh, reverse'.
339      */

340     public BeanComparator nullLow() {
341         BeanComparator bc = new BeanComparator(this);
342         bc.mOrderByName = mOrderByName;
343         bc.mUsingComparator = mUsingComparator;
344         bc.mFlags = mFlags ^ ((~mFlags & 0x01) << 1);
345         return bc;
346     }
347
348     /**
349      * Override the collator and compare just the last order-by property using
350      * {@link String#compareTo(String) String.compareTo}, if it is of type
351      * String. If no order-by properties have been specified then this call is
352      * ineffective.
353      * <p>
354      * A {@link #using using} Comparator disables this setting. Passing null to
355      * the using method will re-enable a case-sensitive setting.
356      */

357     public BeanComparator caseSensitive() {
358         if ((mFlags & 0x04) != 0) {
359             // Already case-sensitive.
360
return this;
361         }
362         BeanComparator bc = new BeanComparator(this);
363         bc.mOrderByName = mOrderByName;
364         bc.mUsingComparator = mUsingComparator;
365         bc.mFlags = mFlags | 0x04;
366         return bc;
367     }
368
369     /**
370      * Set a Comparator for ordering Strings, which is passed on to all
371      * BeanComparators derived from this one. By default, String are compared
372      * using {@link String#CASE_INSENSITIVE_ORDER}. Passing null for a collator
373      * will cause all String comparisons to use
374      * {@link String#compareTo(String) String.compareTo}.
375      * <p>
376      * A {@link #using using} Comparator disables this setting. Passing null
377      * to the using method will re-enable a collator.
378      *
379      * @param c Comparator to use for ordering all Strings. Passing null
380      * causes all Strings to be ordered by
381      * {@link String#compareTo(String) String.compareTo}.
382      */

383     public BeanComparator collate(Comparator c) {
384         BeanComparator bc = new BeanComparator(this);
385         bc.mOrderByName = mOrderByName;
386         bc.mUsingComparator = mUsingComparator;
387         bc.mFlags = mFlags & ~0x04;
388         bc.mCollator = c;
389         return bc;
390     }
391
392     public int compare(Object JavaDoc obj1, Object JavaDoc obj2) throws ClassCastException JavaDoc {
393         Comparator c = mComparator;
394         if (c == null) {
395             c = mComparator = generateComparator();
396         }
397         return c.compare(obj1, obj2);
398     }
399
400     public int hashCode() {
401         if (!mHasHashCode) {
402             setHashCode(new Rules(this));
403         }
404         return mHashCode;
405     }
406
407     private void setHashCode(Rules rules) {
408         mHashCode = rules.hashCode();
409         mHasHashCode = true;
410     }
411
412     /**
413      * Compares BeanComparators for equality based on their imposed ordering.
414      * Returns true only if the given object is a BeanComparater and it can be
415      * determined without a doubt that the ordering is identical. Because
416      * equality testing is dependent on the behavior of the equals methods of
417      * any 'using' Comparators and/or collators, false may be returned even
418      * though ordering is in fact identical.
419      */

420     public boolean equals(Object JavaDoc obj) {
421         if (obj instanceof BeanComparator) {
422             BeanComparator bc = (BeanComparator)obj;
423          
424             return mFlags == bc.mFlags &&
425                 equalTest(mBeanClass, bc.mBeanClass) &&
426                 equalTest(mOrderByName, bc.mOrderByName) &&
427                 equalTest(mUsingComparator, bc.mUsingComparator) &&
428                 equalTest(mCollator, bc.mCollator) &&
429                 equalTest(mParent, bc.mParent);
430         }
431         else {
432             return false;
433         }
434     }
435     
436     private Map getProperties() {
437         if (mProperties == null) {
438             try {
439                 mProperties =
440                     CompleteIntrospector.getAllProperties(mBeanClass);
441             }
442             catch (IntrospectionException e) {
443                 throw new RuntimeException JavaDoc(e.toString());
444             }
445         }
446         return mProperties;
447     }
448
449     private Comparator generateComparator() {
450         Rules rules = new Rules(this);
451
452         if (!mHasHashCode) {
453             setHashCode(rules);
454         }
455
456         Class JavaDoc clazz;
457
458         synchronized (cGeneratedComparatorCache) {
459             Object JavaDoc c = cGeneratedComparatorCache.get(rules);
460             
461             if (c == null) {
462                 clazz = generateComparatorClass(rules);
463                 cGeneratedComparatorCache.put(rules, clazz);
464             }
465             else if (c instanceof Comparator) {
466                 return (Comparator)c;
467             }
468             else {
469                 clazz = (Class JavaDoc)c;
470             }
471
472             BeanComparator[] ruleParts = rules.getRuleParts();
473             Comparator[] collators = new Comparator[ruleParts.length];
474             Comparator[] usingComparators = new Comparator[ruleParts.length];
475             boolean singleton = true;
476
477             for (int i=0; i<ruleParts.length; i++) {
478                 BeanComparator rp = ruleParts[i];
479                 Comparator c2 = rp.mCollator;
480                 if ((collators[i] = c2) != null) {
481                     if (c2 != String.CASE_INSENSITIVE_ORDER) {
482                         singleton = false;
483                     }
484                 }
485                 if ((usingComparators[i] = rp.mUsingComparator) != null) {
486                     singleton = false;
487                 }
488             }
489             
490             try {
491                 Constructor ctor = clazz.getDeclaredConstructor
492                     (new Class JavaDoc[] {Comparator[].class, Comparator[].class});
493                 c = (Comparator)ctor.newInstance
494                     (new Object JavaDoc[] {collators, usingComparators});
495             }
496             catch (NoSuchMethodException JavaDoc e) {
497                 throw new InternalError JavaDoc(e.toString());
498             }
499             catch (InstantiationException JavaDoc e) {
500                 throw new InternalError JavaDoc(e.toString());
501             }
502             catch (IllegalAccessException JavaDoc e) {
503                 throw new InternalError JavaDoc(e.toString());
504             }
505             catch (IllegalArgumentException JavaDoc e) {
506                 throw new InternalError JavaDoc(e.toString());
507             }
508             catch (InvocationTargetException e) {
509                 throw new InternalError JavaDoc(e.getTargetException().toString());
510             }
511             
512             if (singleton) {
513                 // Can save and re-use instance since it obeys the requirements
514
// for a singleton.
515
cGeneratedComparatorCache.put(rules, c);
516             }
517
518             return (Comparator)c;
519         }
520     }
521
522     private Class JavaDoc generateComparatorClass(Rules rules) {
523         ClassInjector injector =
524             ClassInjector.getInstance(mBeanClass.getClassLoader());
525         
526         int id = rules.hashCode();
527             
528         String JavaDoc baseName = this.getClass().getName() + '$';
529         String JavaDoc className = baseName;
530         try {
531             while (true) {
532                 className = baseName + (id & 0xffffffffL);
533                 try {
534                     injector.loadClass(className);
535                 }
536                 catch (LinkageError JavaDoc e) {
537                 }
538                 id++;
539             }
540         }
541         catch (ClassNotFoundException JavaDoc e) {
542         }
543         
544         ClassFile cf = generateClassFile(className, rules);
545             
546         /*
547         try {
548             String name = cf.getClassName();
549             name = name.substring(name.lastIndexOf('.') + 1) + ".class";
550             System.out.println(name);
551             java.io.FileOutputStream out =
552                 new java.io.FileOutputStream(name);
553             cf.writeTo(out);
554             out.close();
555         }
556         catch (Exception e) {
557             e.printStackTrace();
558         }
559         */

560         
561         try {
562             OutputStream stream = injector.getStream(cf.getClassName());
563             cf.writeTo(stream);
564             stream.close();
565         }
566         catch (IOException e) {
567             throw new InternalError JavaDoc(e.toString());
568         }
569         
570         try {
571             return injector.loadClass(cf.getClassName());
572         }
573         catch (ClassNotFoundException JavaDoc e) {
574             throw new InternalError JavaDoc(e.toString());
575         }
576     }
577
578     private static ClassFile generateClassFile(String JavaDoc className, Rules rules) {
579         ClassFile cf = new ClassFile(className);
580         cf.markSynthetic();
581
582         cf.addInterface(Comparator.class);
583         cf.addInterface(Serializable.class);
584
585         AccessFlags privateAccess = new AccessFlags();
586         privateAccess.setPrivate(true);
587         AccessFlags publicAccess = new AccessFlags();
588         publicAccess.setPublic(true);
589
590         // Define fields to hold usage comparator and collator.
591
TypeDescriptor comparatorType = new TypeDescriptor(Comparator.class);
592         TypeDescriptor comparatorArrayType =
593             new TypeDescriptor(comparatorType, 1);
594         cf.addField(privateAccess,
595                     "mCollators", comparatorArrayType).markSynthetic();
596         cf.addField(privateAccess,
597                     "mUsingComparators", comparatorArrayType).markSynthetic();
598
599         // Create constructor to initialize fields.
600
TypeDescriptor[] paramTypes = {
601             comparatorArrayType, comparatorArrayType
602         };
603         MethodInfo ctor = cf.addConstructor(publicAccess, paramTypes);
604         ctor.markSynthetic();
605         CodeBuilder builder = new CodeBuilder(ctor);
606
607         builder.loadThis();
608         builder.invokeSuperConstructor(null);
609         builder.loadThis();
610         builder.loadLocal(builder.getParameters()[0]);
611         builder.storeField("mCollators", comparatorArrayType);
612         builder.loadThis();
613         builder.loadLocal(builder.getParameters()[1]);
614         builder.storeField("mUsingComparators", comparatorArrayType);
615         builder.returnVoid();
616
617         // Create the all-important compare method.
618
Method compareMethod, compareToMethod;
619         try {
620             compareMethod = Comparator.class.getMethod
621                 ("compare", new Class JavaDoc[] {Object JavaDoc.class, Object JavaDoc.class});
622             compareToMethod = Comparable JavaDoc.class.getMethod
623                 ("compareTo", new Class JavaDoc[] {Object JavaDoc.class});
624         }
625         catch (NoSuchMethodException JavaDoc e) {
626             throw new InternalError JavaDoc(e.toString());
627         }
628
629         MethodInfo mi = cf.addMethod(compareMethod);
630         mi.markSynthetic();
631         builder = new CodeBuilder(mi);
632
633         Label endLabel = builder.createLabel();
634         LocalVariable obj1 = builder.getParameters()[0];
635         LocalVariable obj2 = builder.getParameters()[1];
636
637         // The first rule always applies to the beans directly. All others
638
// apply to properties.
639

640         BeanComparator[] ruleParts = rules.getRuleParts();
641         BeanComparator bc = ruleParts[0];
642
643         if ((bc.mFlags & 0x01) != 0) {
644             // Reverse beans.
645
LocalVariable temp = obj1;
646             obj1 = obj2;
647             obj2 = temp;
648         }
649
650         // Handle the case when obj1 and obj2 are the same (or both null)
651
builder.loadLocal(obj1);
652         builder.loadLocal(obj2);
653         builder.ifEqualBranch(endLabel, true);
654
655         // Do null order checks for beans.
656
boolean nullHigh = (bc.mFlags & 0x02) == 0;
657         Label label = builder.createLabel();
658         builder.loadLocal(obj1);
659         builder.ifNullBranch(label, false);
660         builder.loadConstant(nullHigh ? 1 : -1);
661         builder.returnValue(int.class);
662         label.setLocation();
663         label = builder.createLabel();
664         builder.loadLocal(obj2);
665         builder.ifNullBranch(label, false);
666         builder.loadConstant(nullHigh ? -1 : 1);
667         builder.returnValue(int.class);
668         label.setLocation();
669
670         // Call 'using' Comparator if one is provided.
671
LocalVariable result = builder.createLocalVariable
672             ("result", new TypeDescriptor(int.class));
673         if (bc.mUsingComparator != null) {
674             builder.loadThis();
675             builder.loadField("mUsingComparators", comparatorArrayType);
676             builder.loadConstant(0);
677             builder.loadFromArray(Comparator.class);
678             builder.loadLocal(obj1);
679             builder.loadLocal(obj2);
680             builder.invoke(compareMethod);
681             builder.storeLocal(result);
682             builder.loadLocal(result);
683             label = builder.createLabel();
684             builder.ifZeroComparisonBranch(label, "==");
685             builder.loadLocal(result);
686             builder.returnValue(int.class);
687             label.setLocation();
688         }
689
690         // Cast bean parameters to correct types so that properties may be
691
// accessed.
692
TypeDescriptor type = new TypeDescriptor(bc.mBeanClass);
693         builder.loadLocal(obj1);
694         builder.checkCast(type);
695         builder.storeLocal(obj1);
696         builder.loadLocal(obj2);
697         builder.checkCast(type);
698         builder.storeLocal(obj2);
699
700         // Generate code to perform comparisons against each property.
701
for (int i=1; i<ruleParts.length; i++) {
702             bc = ruleParts[i];
703
704             PropertyDescriptor desc =
705                 (PropertyDescriptor)bc.getProperties().get(bc.mOrderByName);
706             Class JavaDoc propertyClass = desc.getPropertyType();
707             TypeDescriptor propertyType = new TypeDescriptor(propertyClass);
708             
709             // Create local variable to hold property values.
710
LocalVariable p1 = builder.createLocalVariable("p1", propertyType);
711             LocalVariable p2 = builder.createLocalVariable("p2", propertyType);
712
713             // Access properties and store in local variables.
714
builder.loadLocal(obj1);
715             builder.invoke(desc.getReadMethod());
716             builder.storeLocal(p1);
717             builder.loadLocal(obj2);
718             builder.invoke(desc.getReadMethod());
719             builder.storeLocal(p2);
720
721             if ((bc.mFlags & 0x01) != 0) {
722                 // Reverse properties.
723
LocalVariable temp = p1;
724                 p1 = p2;
725                 p2 = temp;
726             }
727
728             Label nextLabel = builder.createLabel();
729
730             // Handle the case when p1 and p2 are the same (or both null)
731
if (!propertyClass.isPrimitive()) {
732                 builder.loadLocal(p1);
733                 builder.loadLocal(p2);
734                 builder.ifEqualBranch(nextLabel, true);
735
736                 // Do null order checks for properties.
737
nullHigh = (bc.mFlags & 0x02) == 0;
738                 label = builder.createLabel();
739                 builder.loadLocal(p1);
740                 builder.ifNullBranch(label, false);
741                 builder.loadConstant(nullHigh ? 1 : -1);
742                 builder.returnValue(int.class);
743                 label.setLocation();
744                 label = builder.createLabel();
745                 builder.loadLocal(p2);
746                 builder.ifNullBranch(label, false);
747                 builder.loadConstant(nullHigh ? -1 : 1);
748                 builder.returnValue(int.class);
749                 label.setLocation();
750             }
751
752             // Call 'using' Comparator if one is provided, else assume
753
// Comparable.
754
if (bc.mUsingComparator != null) {
755                 builder.loadThis();
756                 builder.loadField("mUsingComparators", comparatorArrayType);
757                 builder.loadConstant(i);
758                 builder.loadFromArray(Comparator.class);
759                 loadAsObject(builder, propertyClass, p1);
760                 loadAsObject(builder, propertyClass, p2);
761                 builder.invoke(compareMethod);
762             }
763             else {
764                 // If case-sensitive is off and a collator is provided and
765
// property could be a String, apply collator.
766
if ((bc.mFlags & 0x04) == 0 && bc.mCollator != null &&
767                     propertyClass.isAssignableFrom(String JavaDoc.class)) {
768
769                     Label resultLabel = builder.createLabel();
770
771                     if (!String JavaDoc.class.isAssignableFrom(propertyClass)) {
772                         // Check if both property values are strings at
773
// runtime. If they aren't, cast to Comparable and call
774
// compareTo.
775

776                         TypeDescriptor stringType =
777                             new TypeDescriptor(String JavaDoc.class);
778
779                         builder.loadLocal(p1);
780                         builder.instanceOf(stringType);
781                         Label notString = builder.createLabel();
782                         builder.ifZeroComparisonBranch(notString, "==");
783                         builder.loadLocal(p2);
784                         builder.instanceOf(stringType);
785                         Label isString = builder.createLabel();
786                         builder.ifZeroComparisonBranch(isString, "!=");
787
788                         notString.setLocation();
789                         generateComparableCompareTo
790                             (builder, propertyClass, compareToMethod,
791                              resultLabel, nextLabel, p1, p2);
792
793                         isString.setLocation();
794                     }
795
796                     builder.loadThis();
797                     builder.loadField("mCollators", comparatorArrayType);
798                     builder.loadConstant(i);
799                     builder.loadFromArray(Comparator.class);
800                     builder.loadLocal(p1);
801                     builder.loadLocal(p2);
802                     builder.invoke(compareMethod);
803
804                     resultLabel.setLocation();
805                 }
806                 else if (propertyClass.isPrimitive()) {
807                     generatePrimitiveComparison(builder, propertyClass, p1,p2);
808                 }
809                 else {
810                     // Assume properties are instances of Comparable.
811
generateComparableCompareTo
812                         (builder, propertyClass, compareToMethod,
813                          null, nextLabel, p1, p2);
814                 }
815             }
816
817             if (i < (ruleParts.length - 1)) {
818                 builder.storeLocal(result);
819                 builder.loadLocal(result);
820                 builder.ifZeroComparisonBranch(nextLabel, "==");
821                 builder.loadLocal(result);
822             }
823             builder.returnValue(int.class);
824
825             // The next property comparison will start here.
826
nextLabel.setLocation();
827         }
828
829         endLabel.setLocation();
830         builder.loadConstant(0);
831         builder.returnValue(int.class);
832
833         return cf;
834     }
835
836     private static void loadAsObject(CodeBuilder builder,
837                                      Class JavaDoc type, LocalVariable v)
838     {
839         if (!type.isPrimitive()) {
840             builder.loadLocal(v);
841             return;
842         }
843
844         if (type == boolean.class) {
845             TypeDescriptor td = new TypeDescriptor(Boolean JavaDoc.class);
846             Label falseLabel = builder.createLabel();
847             Label endLabel = builder.createLabel();
848             builder.loadLocal(v);
849             builder.ifZeroComparisonBranch(falseLabel, "==");
850             builder.loadStaticField("java.lang.Boolean", "TRUE", td);
851             builder.branch(endLabel);
852             falseLabel.setLocation();
853             builder.loadStaticField("java.lang.Boolean", "FALSE", td);
854             endLabel.setLocation();
855             return;
856         }
857
858         Class JavaDoc objectType;
859
860         if (type == int.class) {
861             objectType = Integer JavaDoc.class;
862         }
863         else if (type == long.class) {
864             objectType = Long JavaDoc.class;
865         }
866         else if (type == float.class) {
867             objectType = Float JavaDoc.class;
868         }
869         else if (type == double.class) {
870             objectType = Double JavaDoc.class;
871         }
872         else if (type == char.class) {
873             objectType = Character JavaDoc.class;
874         }
875         else if (type == byte.class) {
876             objectType = Byte JavaDoc.class;
877         }
878         else if (type == short.class) {
879             objectType = Short JavaDoc.class;
880         }
881         else {
882             objectType = type;
883         }
884
885         TypeDescriptor[] params = {
886             new TypeDescriptor(type)
887         };
888
889         builder.newObject(new TypeDescriptor(objectType));
890         builder.dup();
891         builder.loadLocal(v);
892         builder.invokeConstructor(objectType.getName(), params);
893     }
894
895     private static void generatePrimitiveComparison(CodeBuilder builder,
896                                                     Class JavaDoc type,
897                                                     LocalVariable a,
898                                                     LocalVariable b)
899     {
900         if (type == float.class) {
901             // Comparison is same as for Float.compareTo(Float).
902
Label done = builder.createLabel();
903
904             builder.loadLocal(a);
905             builder.loadLocal(b);
906             builder.math(Opcode.FCMPG);
907             Label label = builder.createLabel();
908             builder.ifZeroComparisonBranch(label, ">=");
909             builder.loadConstant(-1);
910             builder.branch(done);
911
912             label.setLocation();
913             builder.loadLocal(a);
914             builder.loadLocal(b);
915             builder.math(Opcode.FCMPL);
916             label = builder.createLabel();
917             builder.ifZeroComparisonBranch(label, "<=");
918             builder.loadConstant(1);
919             builder.branch(done);
920
921             Method floatToIntBits;
922             try {
923                 floatToIntBits = Float JavaDoc.class.getMethod
924                     ("floatToIntBits", new Class JavaDoc[] {float.class});
925             }
926             catch (NoSuchMethodException JavaDoc e) {
927                 throw new InternalError JavaDoc(e.toString());
928             }
929
930             label.setLocation();
931             builder.loadLocal(a);
932             builder.invoke(floatToIntBits);
933             builder.convert(int.class, long.class);
934             builder.loadLocal(b);
935             builder.invoke(floatToIntBits);
936             builder.convert(int.class, long.class);
937             builder.math(Opcode.LCMP);
938
939             done.setLocation();
940         }
941         else if (type == double.class) {
942             // Comparison is same as for Double.compareTo(Double).
943
Label done = builder.createLabel();
944
945             builder.loadLocal(a);
946             builder.loadLocal(b);
947             done = builder.createLabel();
948             builder.math(Opcode.DCMPG);
949             Label label = builder.createLabel();
950             builder.ifZeroComparisonBranch(label, ">=");
951             builder.loadConstant(-1);
952             builder.branch(done);
953
954             label.setLocation();
955             builder.loadLocal(a);
956             builder.loadLocal(b);
957             builder.math(Opcode.DCMPL);
958             label = builder.createLabel();
959             builder.ifZeroComparisonBranch(label, "<=");
960             builder.loadConstant(1);
961             builder.branch(done);
962
963             Method doubleToLongBits;
964             try {
965                 doubleToLongBits = Double JavaDoc.class.getMethod
966                     ("doubleToLongBits", new Class JavaDoc[] {double.class});
967             }
968             catch (NoSuchMethodException JavaDoc e) {
969                 throw new InternalError JavaDoc(e.toString());
970             }
971
972             label.setLocation();
973             builder.loadLocal(a);
974             builder.invoke(doubleToLongBits);
975             builder.loadLocal(b);
976             builder.invoke(doubleToLongBits);
977             builder.math(Opcode.LCMP);
978
979             done.setLocation();
980         }
981         else if (type == long.class) {
982             builder.loadLocal(a);
983             builder.loadLocal(b);
984             builder.math(Opcode.LCMP);
985         }
986         else if (type == int.class) {
987             builder.loadLocal(a);
988             builder.convert(int.class, long.class);
989             builder.loadLocal(b);
990             builder.convert(int.class, long.class);
991             builder.math(Opcode.LCMP);
992         }
993         else {
994             builder.loadLocal(a);
995             builder.loadLocal(b);
996             builder.math(Opcode.ISUB);
997         }
998     }
999
1000    private static void generateComparableCompareTo(CodeBuilder builder,
1001                                                    Class JavaDoc type,
1002                                                    Method compareToMethod,
1003                                                    Label goodLabel,
1004                                                    Label nextLabel,
1005                                                    LocalVariable a,
1006                                                    LocalVariable b)
1007    {
1008        if (Comparable JavaDoc.class.isAssignableFrom(type)) {
1009            builder.loadLocal(a);
1010            builder.loadLocal(b);
1011            builder.invoke(compareToMethod);
1012            if (goodLabel != null) {
1013                builder.branch(goodLabel);
1014            }
1015        }
1016        else {
1017            // Cast each property to Comparable only if needed.
1018
TypeDescriptor comparableType =
1019                new TypeDescriptor(Comparable JavaDoc.class);
1020
1021            boolean locateGoodLabel = false;
1022            if (goodLabel == null) {
1023                goodLabel = builder.createLabel();
1024                locateGoodLabel = true;
1025            }
1026
1027            Label tryStart = builder.createLabel().setLocation();
1028            builder.loadLocal(a);
1029            builder.checkCast(comparableType);
1030            builder.loadLocal(b);
1031            builder.checkCast(comparableType);
1032            Label tryEnd = builder.createLabel().setLocation();
1033            builder.invoke(compareToMethod);
1034            builder.branch(goodLabel);
1035
1036            builder.exceptionHandler(tryStart, tryEnd,
1037                                     ClassCastException JavaDoc.class.getName());
1038            // One of the properties is not Comparable, so just go to next.
1039
// Discard the exception.
1040
builder.pop();
1041            if (nextLabel == null) {
1042                builder.loadConstant(0);
1043            }
1044            else {
1045                builder.branch(nextLabel);
1046            }
1047
1048            if (locateGoodLabel) {
1049                goodLabel.setLocation();
1050            }
1051        }
1052    }
1053
1054    // A key that uniquely describes the rules of a BeanComparator.
1055
private static class Rules {
1056        private BeanComparator[] mRuleParts;
1057        private int mHashCode;
1058
1059        public Rules(BeanComparator bc) {
1060            mRuleParts = reduceRules(bc);
1061
1062            // Compute hashCode.
1063
int hash = 0;
1064
1065            for (int i = mRuleParts.length - 1; i >= 0; i--) {
1066                bc = mRuleParts[i];
1067                hash = 31 * hash;
1068
1069                hash += bc.mFlags << 4;
1070
1071                Object JavaDoc obj = bc.mBeanClass;
1072                if (obj != null) {
1073                    hash += obj.hashCode();
1074                }
1075                obj = bc.mOrderByName;
1076                if (obj != null) {
1077                    hash += obj.hashCode();
1078                }
1079                obj = bc.mUsingComparator;
1080                if (obj != null) {
1081                    hash += obj.getClass().hashCode();
1082                }
1083                obj = bc.mCollator;
1084                if (obj != null) {
1085                    hash += obj.getClass().hashCode();
1086                }
1087            }
1088
1089            mHashCode = hash;
1090        }
1091
1092        public BeanComparator[] getRuleParts() {
1093            return mRuleParts;
1094        }
1095
1096        public int hashCode() {
1097            return mHashCode;
1098        }
1099
1100        /**
1101         * Equality test determines if rules produce an identical
1102         * auto-generated Comparator.
1103         */

1104        public boolean equals(Object JavaDoc obj) {
1105            if (!(obj instanceof Rules)) {
1106                return false;
1107            }
1108
1109            BeanComparator[] ruleParts1 = getRuleParts();
1110            BeanComparator[] ruleParts2 = ((Rules)obj).getRuleParts();
1111
1112            if (ruleParts1.length != ruleParts2.length) {
1113                return false;
1114            }
1115            
1116            for (int i=0; i<ruleParts1.length; i++) {
1117                BeanComparator bc1 = ruleParts1[i];
1118                BeanComparator bc2 = ruleParts2[i];
1119
1120                if (bc1.mFlags != bc2.mFlags) {
1121                    return false;
1122                }
1123                if (!equalTest(bc1.mBeanClass, bc2.mBeanClass)) {
1124                    return false;
1125                }
1126                if (!equalTest(bc1.mOrderByName, bc2.mOrderByName)) {
1127                    return false;
1128                }
1129                if ((bc1.mUsingComparator == null) !=
1130                    (bc2.mUsingComparator == null)) {
1131                    return false;
1132                }
1133                if ((bc1.mCollator == null) != (bc2.mCollator == null)) {
1134                    return false;
1135                }
1136            }
1137
1138            return true;
1139        }
1140    
1141        private BeanComparator[] reduceRules(BeanComparator bc) {
1142            // Reduce the ordering rules by returning BeanComparators
1143
// that are at the end of the chain or before an order-by rule.
1144
List rules = new ArrayList();
1145            
1146            rules.add(bc);
1147            String JavaDoc name = bc.mOrderByName;
1148
1149            while ((bc = bc.mParent) != null) {
1150                // Don't perform string comparison using equals method.
1151
if (name != bc.mOrderByName) {
1152                    rules.add(bc);
1153                    name = bc.mOrderByName;
1154                }
1155            }
1156            
1157            int size = rules.size();
1158            BeanComparator[] bcs = new BeanComparator[size];
1159            // Reverse rules so that they are in forward order.
1160
for (int i=0; i<size; i++) {
1161                bcs[size - i - 1] = (BeanComparator)rules.get(i);
1162            }
1163            
1164            return bcs;
1165        }
1166    }
1167}
1168
Popular Tags