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      &#064;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      &#064;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      &#064;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      &#064;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      &#064;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      &#064;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      &#064;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      &#064;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    }