001 package hirondelle.web4j.model; 002 003 import hirondelle.web4j.util.Util; 004 005 import java.lang.reflect.Array; 006 007 /** 008 Collected utilities for overriding {@link Object#toString}, {@link Object#equals}, 009 and {@link Object#hashCode}, and implementing {@link Comparable}. 010 011 <P>All Model Objects should override the above {@link Object} methods. 012 All Model Objects that are being sorted in code should implement {@link Comparable}. 013 014 <P>In general, it is easier to use this class with <em>object</em> fields (<tt>String</tt>, <tt>Date</tt>, 015 <tt>BigDecimal</tt>, and so on), instead of <em>primitive</em> fields (<tt>int</tt>, <tt>boolean</tt>, and so on). 016 017 <P>See below for example implementations of : 018 <ul> 019 <li><a href="#ToString">toString()</a> 020 <li><a href="#HashCode">hashCode()</a> 021 <li><a href="#Equals">equals()</a> 022 <li><a href="#Comparable">compareTo()</a> 023 </ul> 024 025 <a name="ToString"><P><b>toString()</b><br> 026 This class is intended for the most common case, where <tt>toString</tt> is used in 027 an <em>informal</em> manner (usually for logging and stack traces). That is, <span class="highlight"> 028 the caller should not rely on the <tt>toString()</tt> text returned by this class to define program logic.</span> 029 030 <P>Typical example : 031 <PRE> 032 @Override public String toString() { 033 return ModelUtil.toStringFor(this); 034 } 035 </PRE> 036 037 <P>There is one <em>occasional</em> variation, used only when two model objects reference each other. To avoid 038 a problem with cyclic references and infinite looping, implement as : 039 <PRE> 040 @Override public String toString() { 041 return ModelUtil.toStringAvoidCyclicRefs(this, Product.class, "getId"); 042 } 043 </PRE> 044 045 Here, the usual behavior is overridden for any method in 'this' object 046 which returns a <tt>Product</tt> : instead of calling <tt>Product.toString()</tt>, 047 the return value of <tt>Product.getId()</tt> is used instead. 048 049 <a name="HashCode"><P><b>hashCode()</b><br> 050 Example of the simplest style : 051 <pre> 052 @Override public int hashCode() { 053 return ModelUtil.hashFor(getSignificantFields()); 054 } 055 ... 056 private String fName; 057 private Boolean fIsActive; 058 private Object[] getSignificantFields(){ 059 //any primitive fields can be placed in a wrapper Object 060 return new Object[]{fName, fIsActive}; 061 } 062 </pre> 063 064 <P><a name="GetSignificantFields"></a><span class="highlight">Since the {@link Object#equals} and 065 {@link Object#hashCode} methods are so closely related, and should always refer to the same fields, 066 defining a <tt>private</tt> method to return the <tt>Object[]</tt> of significant fields is highly 067 recommended.</span> Such a method would be called by <em>both</em> <tt>equals</tt> and <tt>hashCode</tt>. 068 069 <P>If an object is <a href="http://www.javapractices.com/Topic29.cjp">immutable</a>, 070 then the result may be calculated once, and then cached, as a small performance 071 optimization : 072 <pre> 073 @Override public int hashCode() { 074 if ( fHashCode == 0 ) { 075 fHashCode = ModelUtil.hashFor(getSignificantFields()); 076 } 077 return fHashCode; 078 } 079 ... 080 private String fName; 081 private Boolean fIsActive; 082 private int fHashCode; 083 private Object[] getSignificantFields(){ 084 return new Object[]{fName, fIsActive}; 085 } 086 </pre> 087 088 The most verbose style does not require wrapping primitives in an <tt>Object</tt> array: 089 <pre> 090 @Override public int hashCode(){ 091 int result = ModelUtil.HASH_SEED; 092 //collect the contributions of various fields 093 result = ModelUtil.hash(result, fPrimitive); 094 result = ModelUtil.hash(result, fObject); 095 result = ModelUtil.hash(result, fArray); 096 return result; 097 } 098 </pre> 099 100 <a name="Equals"><P><b>equals()</b><br> 101 Simplest example, in a class called <tt>Visit</tt> (this is the recommended style): 102 <PRE> 103 @Override public boolean equals(Object aThat) { 104 Boolean result = ModelUtil.quickEquals(this, aThat); 105 if ( result == null ){ 106 Visit that = (Visit) aThat; 107 result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); 108 } 109 return result; 110 } 111 ... 112 private final Code fRestaurantCode; 113 private final Date fLunchDate; 114 private final String fMessage; 115 private Object[] getSignificantFields(){ 116 return new Object[] {fRestaurantCode, fLunchDate, fMessage}; 117 } 118 </PRE> 119 120 Second example, in a class called <tt>Member</tt> : 121 <PRE> 122 @Override public boolean equals( Object aThat ) { 123 if ( this == aThat ) return true; 124 if ( !(aThat instanceof Member) ) return false; 125 Member that = (Member)aThat; 126 return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); 127 } 128 ... 129 private final String fName; 130 private final Boolean fIsActive; 131 private final Code fDisposition; 132 private Object[] getSignificantFields(){ 133 return new Object[]{fName, fIsActive, fDisposition}; 134 } 135 </PRE> 136 See note above regarding <a href="#GetSignificantFields">getSignificantFields()</a>. 137 138 <P>More verbose example, in a class called <tt>Planet</tt> : 139 <PRE> 140 @Override public boolean equals(Object aThat){ 141 if ( this == aThat ) return true; 142 if ( !(aThat instanceof Planet) ) return false; 143 Planet that = (Planet)aThat; 144 return 145 EqualsUtil.areEqual(this.fPossiblyNullObject, that.fPossiblyNullObject) && 146 EqualsUtil.areEqual(this.fCollection, that.fCollection) && 147 EqualsUtil.areEqual(this.fPrimitive, that.fPrimitive) && 148 Arrays.equals(this.fArray, that.fArray); //arrays are different! 149 } 150 </PRE> 151 152 <a name="Comparable"><P><b>compareTo()</b><br> 153 The {@link Comparable} interface is distinct, since it is not an overridable method of the 154 {@link Object} class. 155 156 <P>Example use case of using <a href='#comparePossiblyNull(T, T, hirondelle.web4j.model.ModelUtil.NullsGo)'>comparePossiblyNull</a>, 157 (where <tt>EQUAL</tt> takes the value <tt>0</tt>) : 158 <PRE> 159 public int compareTo(Movie aThat) { 160 if ( this == aThat ) return EQUAL; 161 162 int comparison = ModelUtil.comparePossiblyNull(this.fDateViewed, aThat.fDateViewed, NullsGo.LAST); 163 if ( comparison != EQUAL ) return comparison; 164 165 //this field is never null 166 comparison = this.fTitle.compareTo(aThat.fTitle); 167 if ( comparison != EQUAL ) return comparison; 168 169 comparison = ModelUtil.comparePossiblyNull(this.fRating, aThat.fRating, NullsGo.LAST); 170 if ( comparison != EQUAL ) return comparison; 171 172 comparison = ModelUtil.comparePossiblyNull(this.fComment, aThat.fComment, NullsGo.LAST); 173 if ( comparison != EQUAL ) return comparison; 174 175 return EQUAL; 176 } 177 </PRE> 178 179 @author Hirondelle Systems 180 @author with a contribution by an anonymous user of javapractices.com 181 */ 182 public final class ModelUtil { 183 184 // TO STRING // 185 186 /** 187 Implements an override of <tt>Object.toString()</tt> (see class comment). 188 189 <P>Example output format, for an <tt>Rsvp</tt> object with 4 fields : 190 <PRE> 191 hirondelle.fish.main.rsvp.Rsvp { 192 Response: null 193 MemberId: 4 194 MemberName: Tom Thumb 195 VisitId: 13 196 } 197 </PRE> 198 (There is no indentation since it causes problems when there is nesting.) 199 200 <P>The only items which contribute to the result are : 201 <ul> 202 <li>the full class name 203 <li>all no-argument <tt>public</tt> methods which return a value 204 </ul> 205 206 <P>These items are excluded from the result : 207 <ul> 208 <li>methods defined in {@link Object} 209 <li>factory methods which return an object of the native class ("<tt>getInstance()</tt>" methods) 210 </ul> 211 212 <P>Reflection is used to access field values. Items are converted to a <tt>String</tt> simply by calling 213 their <tt>toString method</tt>, with the following exceptions : 214 <ul> 215 <li>for arrays, the {@link Util#getArrayAsString(Object)} is used 216 <li>for methods whose name contains the text <tt>"password"</tt> (case-insensitive), 217 their return values hard-coded to '<tt>****</tt>'. 218 </ul> 219 220 <P>If the method name follows the pattern '<tt>getXXX</tt>', then the word '<tt>get</tt>' 221 is removed from the result. 222 223 <P><span class="highlight">WARNING</span>: If two classes have cyclic references 224 (that is, each has a reference to the other), then infinite looping will result 225 if <em>both</em> call this method! To avoid this problem, use <tt>toStringFor</tt> 226 for one of the classes, and {@link #toStringAvoidCyclicRefs} for the other class. 227 228 @param aObject the object for which a <tt>toString()</tt> result is required. 229 */ 230 public static String toStringFor(Object aObject) { 231 return ToStringUtil.getText(aObject); 232 } 233 234 /** 235 As in {@link #toStringFor}, but avoid problems with cyclic references. 236 237 <P>Cyclic references occur when one Model Object references another, and both Model Objects have 238 their <tt>toString()</tt> methods implemented with this utility class. 239 240 <P>Behaves as in {@link #toStringFor}, with one exception: for methods of <tt>aObject</tt> that 241 return instances of <tt>aSpecialClass</tt>, then call <tt>aMethodName</tt> on such instances, 242 instead of <tt>toString()</tt>. 243 */ 244 public static String toStringAvoidCyclicRefs(Object aObject, Class aSpecialClass, String aMethodName) { 245 return ToStringUtil.getTextAvoidCyclicRefs(aObject, aSpecialClass, aMethodName); 246 } 247 248 // HASH CODE // 249 250 /** 251 Return the hash code in a single step, using all significant fields passed in an {@link Object} sequence parameter. 252 253 <P>(This is the recommended way of implementing <tt>hashCode</tt>.) 254 255 <P>Each element of <tt>aFields</tt> must be an {@link Object}, or an array containing 256 possibly-null <tt>Object</tt>s. These items will each contribute to the 257 result. (It is not a requirement to use <em>all</em> fields related to an object.) 258 259 <P>If the caller is using a <em>primitive</em> field, then it must be converted to a corresponding 260 wrapper object to be included in <tt>aFields</tt>. For example, an <tt>int</tt> field would need 261 conversion to an {@link Integer} before being passed to this method. 262 */ 263 public static final int hashCodeFor(Object... aFields){ 264 int result = HASH_SEED; 265 for(Object field: aFields){ 266 result = hash(result, field); 267 } 268 return result; 269 } 270 271 /** 272 Initial seed value for a <tt>hashCode</tt>. 273 274 Contributions from individual fields are 'added' to this initial value. 275 (Using a non-zero value decreases collisons of <tt>hashCode</tt> values.) 276 */ 277 public static final int HASH_SEED = 23; 278 279 /** Hash code for <tt>boolean</tt> primitives. */ 280 public static int hash( int aSeed, boolean aBoolean ) { 281 return firstTerm( aSeed ) + ( aBoolean ? 1 : 0 ); 282 } 283 284 /** Hash code for <tt>char</tt> primitives. */ 285 public static int hash( int aSeed, char aChar ) { 286 return firstTerm( aSeed ) + aChar; 287 } 288 289 /** 290 Hash code for <tt>int</tt> primitives. 291 <P>Note that <tt>byte</tt> and <tt>short</tt> are also handled by this method, through implicit conversion. 292 */ 293 public static int hash( int aSeed , int aInt ) { 294 return firstTerm( aSeed ) + aInt; 295 } 296 297 /** Hash code for <tt>long</tt> primitives. */ 298 public static int hash( int aSeed , long aLong ) { 299 return firstTerm(aSeed) + (int)( aLong ^ (aLong >>> 32) ); 300 } 301 302 /** Hash code for <tt>float</tt> primitives. */ 303 public static int hash( int aSeed , float aFloat ) { 304 return hash( aSeed, Float.floatToIntBits(aFloat) ); 305 } 306 307 /** Hash code for <tt>double</tt> primitives. */ 308 public static int hash( int aSeed , double aDouble ) { 309 return hash( aSeed, Double.doubleToLongBits(aDouble) ); 310 } 311 312 /** 313 Hash code for an Object. 314 315 <P><tt>aObject</tt> is a possibly-null object field, and possibly an array. 316 317 <P>Arrays can contain possibly-null objects or primitives; no circular references are permitted. 318 */ 319 public static int hash(int aSeed , Object aObject) { 320 int result = aSeed; 321 if (aObject == null) { 322 result = hash(result, 0); 323 } 324 else if ( ! isArray(aObject) ) { 325 //ordinary objects 326 result = hash(result, aObject.hashCode()); 327 } 328 else { 329 int length = Array.getLength(aObject); 330 for ( int idx = 0; idx < length; ++idx ) { 331 Object item = Array.get(aObject, idx); //wraps any primitives! 332 //recursive call - no circular object graphs allowed 333 result = hash(result, item); 334 } 335 } 336 return result; 337 } 338 339 // EQUALS // 340 341 /** 342 Quick checks for <em>possibly</em> determining equality of two objects. 343 344 <P>This method exists to make <tt>equals</tt> implementations read more legibly, 345 and to avoid multiple <tt>return</tt> statements. 346 347 <P><em>It cannot be used by itself to fully implement <tt>equals</tt>. </em> 348 It uses <tt>==</tt> and <tt>instanceof</tt> to determine if equality can be 349 found cheaply, without the need to examine field values in detail. It is 350 <em>always</em> paired with some other method 351 (usually {@link #equalsFor(Object[], Object[])}), as in the following example : 352 <PRE> 353 public boolean equals(Object aThat){ 354 Boolean result = ModelUtil.quickEquals(this, aThat); 355 <b>if ( result == null ){</b> 356 //quick checks not sufficient to determine equality, 357 //so a full field-by-field check is needed : 358 This this = (This) aThat; //will not fail 359 result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); 360 } 361 return result; 362 } 363 </PRE> 364 365 <P>This method is unusual since it returns a <tt>Boolean</tt> that takes 366 <em>3</em> values : <tt>true</tt>, <tt>false</tt>, and <tt>null</tt>. Here, 367 <tt>true</tt> and <tt>false</tt> mean that a simple quick check was able to 368 determine equality. <span class='highlight'>The <tt>null</tt> case means that the 369 quick checks were not able to determine if the objects are equal or not, and that 370 further field-by-field examination is necessary. The caller must always perform a 371 check-for-null on the return value.</span> 372 */ 373 static public Boolean quickEquals(Object aThis, Object aThat){ 374 Boolean result = null; 375 if ( aThis == aThat ) { 376 result = Boolean.TRUE; 377 } 378 else { 379 Class<?> thisClass = aThis.getClass(); 380 if ( ! thisClass.isInstance(aThat) ) { 381 result = Boolean.FALSE; 382 } 383 } 384 return result; 385 } 386 387 /** 388 Return the result of comparing all significant fields. 389 390 <P>Both <tt>Object[]</tt> parameters are the same size. Each includes all fields that have been 391 deemed by the caller to contribute to the <tt>equals</tt> method. <em>None of those fields are 392 array fields.</em> The order is the same in both arrays, in the sense that the Nth item 393 in each array corresponds to the same underlying field. The caller controls the order in which fields are 394 compared simply through the iteration order of these two arguments. 395 396 <P>If a primitive field is significant, then it must be converted to a corresponding 397 wrapper <tt>Object</tt> by the caller. 398 */ 399 static public boolean equalsFor(Object[] aThisSignificantFields, Object[] aThatSignificantFields){ 400 //(varargs can be used for final arg only) 401 if (aThisSignificantFields.length != aThatSignificantFields.length) { 402 throw new IllegalArgumentException( 403 "Array lengths do not match. 'This' length is " + aThisSignificantFields.length + 404 ", while 'That' length is " + aThatSignificantFields.length + "." 405 ); 406 } 407 408 boolean result = true; 409 for(int idx=0; idx < aThisSignificantFields.length; ++idx){ 410 if ( ! areEqual(aThisSignificantFields[idx], aThatSignificantFields[idx]) ){ 411 result = false; 412 break; 413 } 414 } 415 return result; 416 } 417 418 /** Equals for <tt>boolean</tt> fields. */ 419 static public boolean areEqual(boolean aThis, boolean aThat){ 420 return aThis == aThat; 421 } 422 423 /** Equals for <tt>char</tt> fields. */ 424 static public boolean areEqual(char aThis, char aThat){ 425 return aThis == aThat; 426 } 427 428 /** 429 Equals for <tt>long</tt> fields. 430 431 <P>Note that <tt>byte</tt>, <tt>short</tt>, and <tt>int</tt> are handled by this method, through 432 implicit conversion. 433 */ 434 static public boolean areEqual(long aThis, long aThat){ 435 return aThis == aThat; 436 } 437 438 /** Equals for <tt>float</tt> fields. */ 439 static public boolean areEqual(float aThis, float aThat){ 440 return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat); 441 } 442 443 /** Equals for <tt>double</tt> fields. */ 444 static public boolean areEqual(double aThis, double aThat){ 445 return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat); 446 } 447 448 /** 449 Equals for an Object. 450 <P>The objects are possibly-null, and possibly an array. 451 <P>Arrays can contain possibly-null objects or primitives; no circular references are permitted. 452 */ 453 static public boolean areEqual(Object aThis, Object aThat){ 454 if (aThis == aThat){ 455 return true; 456 } 457 if (aThis == null || aThat == null){ 458 return (aThis == null && aThat == null); 459 } 460 461 boolean result = false; 462 if (! isArray(aThis)){ 463 //ordinary objects 464 result = aThis.equals(aThat); 465 } 466 else { 467 //two arrays, with possible sub-structure 468 int length = Array.getLength(aThis); 469 for (int idx = 0; idx < length; ++idx) { 470 Object thisItem = Array.get(aThis, idx); //wraps any primitives! 471 Object thatItem = Array.get(aThat, idx); //wraps any primitives! 472 //recursive call - no circular object graphs allowed 473 result = areEqual(thisItem, thatItem); 474 } 475 } 476 return result; 477 } 478 479 480 //Comparable<T> 481 482 /** 483 Define hows <tt>null</tt> items are treated in a comparison. Controls if <tt>null</tt> 484 items appear first or last. 485 486 <P>See <a href='#comparePossiblyNull(T, T, hirondelle.web4j.model.ModelUtil.NullsGo)'>comparePossiblyNull</a>. 487 */ 488 public enum NullsGo {FIRST,LAST} 489 490 /** 491 Utility for implementing {@link Comparable}. See <a href='#Comparable'>class example</a> 492 for illustration. 493 494 <P>The {@link Comparable} interface specifies that 495 <PRE> 496 blah.compareTo(null) 497 </PRE> should throw a {@link NullPointerException}. You should follow that 498 guideline. Note that this utility method itself 499 accepts nulls <em>without</em> throwing a {@link NullPointerException}. 500 In this way, this method can handle nullable fields just like any other field. 501 502 <P>There are 503 <a href='http://www.javapractices.com/topic/TopicAction.do?Id=207'>special issues</a> 504 for sorting {@link String}s regarding case, {@link java.util.Locale}, 505 and accented characters. 506 507 @param aThis an object that implements {@link Comparable} 508 @param aThat an object of the same type as <tt>aThis</tt> 509 @param aNullsGo defines if <tt>null</tt> items should be placed first or last 510 */ 511 static public <T extends Comparable<T>> int comparePossiblyNull(T aThis, T aThat, NullsGo aNullsGo){ 512 int EQUAL = 0; 513 int BEFORE = -1; 514 int AFTER = 1; 515 int result = EQUAL; 516 517 if(aThis != null && aThat != null){ 518 result = aThis.compareTo(aThat); 519 } 520 else { 521 //at least one reference is null - special handling 522 if(aThis == null && aThat == null) { 523 //not distinguishable, so treat as equal 524 } 525 else if(aThis == null && aThat != null) { 526 result = BEFORE; 527 } 528 else if( aThis != null && aThat == null) { 529 result = AFTER; 530 } 531 if(NullsGo.LAST == aNullsGo){ 532 result = (-1) * result; 533 } 534 } 535 return result; 536 } 537 538 // PRIVATE // 539 540 private ModelUtil(){ 541 //prevent object construction 542 } 543 544 private static final int fODD_PRIME_NUMBER = 37; 545 546 private static int firstTerm( int aSeed ){ 547 return fODD_PRIME_NUMBER * aSeed; 548 } 549 550 private static boolean isArray(Object aObject){ 551 return aObject != null && aObject.getClass().isArray(); 552 } 553 554 }