Mercurial > hg > blitz_stable
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 } |