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

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
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: 1.1 $-->, <!--$$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 clazz) {
140 return new BeanComparator(clazz);
141 }
142
143 /**
144 * Compare two objects for equality.
145 */
146 private static boolean equalTest(Object obj1, Object 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 mBeanClass;
163
164 // Maps property names to PropertyDescriptors.
165 private transient Map mProperties;
166
167 private String 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 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 propertyName)
227 throws IllegalArgumentException
228 {
229 int dot = propertyName.indexOf('.');
230 String 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
244 ("Property '" + propertyName + "' doesn't exist in '" +
245 mBeanClass.getName() + '\'');
246 }
247
248 if (desc.getPropertyType() == null) {
249 throw new IllegalArgumentException
250 ("Property '" + propertyName + "' is only an indexed type");
251 }
252
253 if (desc.getReadMethod() == null) {
254 throw new IllegalArgumentException
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(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 obj1, Object obj2) throws ClassCastException {
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 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(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 clazz;
457
458 synchronized (cGeneratedComparatorCache) {
459 Object 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)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[] {Comparator[].class, Comparator[].class});
493 c = (Comparator)ctor.newInstance
494 (new Object[] {collators, usingComparators});
495 }
496 catch (NoSuchMethodException e) {
497 throw new InternalError(e.toString());
498 }
499 catch (InstantiationException e) {
500 throw new InternalError(e.toString());
501 }
502 catch (IllegalAccessException e) {
503 throw new InternalError(e.toString());
504 }
505 catch (IllegalArgumentException e) {
506 throw new InternalError(e.toString());
507 }
508 catch (InvocationTargetException e) {
509 throw new InternalError(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 generateComparatorClass(Rules rules) {
523 ClassInjector injector =
524 ClassInjector.getInstance(mBeanClass.getClassLoader());
525
526 int id = rules.hashCode();
527
528 String baseName = this.getClass().getName() + '$';
529 String className = baseName;
530 try {
531 while (true) {
532 className = baseName + (id & 0xffffffffL);
533 try {
534 injector.loadClass(className);
535 }
536 catch (LinkageError e) {
537 }
538 id++;
539 }
540 }
541 catch (ClassNotFoundException 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(e.toString());
568 }
569
570 try {
571 return injector.loadClass(cf.getClassName());
572 }
573 catch (ClassNotFoundException e) {
574 throw new InternalError(e.toString());
575 }
576 }
577
578 private static ClassFile generateClassFile(String 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[] {Object.class, Object.class});
622 compareToMethod = Comparable.class.getMethod
623 ("compareTo", new Class[] {Object.class});
624 }
625 catch (NoSuchMethodException e) {
626 throw new InternalError(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 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.class)) {
768
769 Label resultLabel = builder.createLabel();
770
771 if (!String.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.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 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.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 objectType;
859
860 if (type == int.class) {
861 objectType = Integer.class;
862 }
863 else if (type == long.class) {
864 objectType = Long.class;
865 }
866 else if (type == float.class) {
867 objectType = Float.class;
868 }
869 else if (type == double.class) {
870 objectType = Double.class;
871 }
872 else if (type == char.class) {
873 objectType = Character.class;
874 }
875 else if (type == byte.class) {
876 objectType = Byte.class;
877 }
878 else if (type == short.class) {
879 objectType = Short.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 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.class.getMethod
924 ("floatToIntBits", new Class[] {float.class});
925 }
926 catch (NoSuchMethodException e) {
927 throw new InternalError(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.class.getMethod
966 ("doubleToLongBits", new Class[] {double.class});
967 }
968 catch (NoSuchMethodException e) {
969 throw new InternalError(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 type,
1002 Method compareToMethod,
1003 Label goodLabel,
1004 Label nextLabel,
1005 LocalVariable a,
1006 LocalVariable b)
1007 {
1008 if (Comparable.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.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.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 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 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 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 }