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