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