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 }