Version 4.10.0

hirondelle.web4j.model
Class ModelUtil

Object
  extended by hirondelle.web4j.model.ModelUtil

public final class ModelUtil
extends Object

Collected utilities for overriding Object.toString(), Object.equals(java.lang.Object), and Object.hashCode(), and implementing Comparable.

All Model Objects should override the above Object methods. All Model Objects that are being sorted in code should implement Comparable.

In general, it is easier to use this class with object fields (String, Date, BigDecimal, and so on), instead of primitive fields (int, boolean, and so on).

See below for example implementations of :

toString()
This class is intended for the most common case, where toString is used in an informal manner (usually for logging and stack traces). That is, the caller should not rely on the toString() text returned by this class to define program logic.

Typical example :

  @Override public String toString() {
    return ModelUtil.toStringFor(this);
  }

There is one occasional variation, used only when two model objects reference each other. To avoid a problem with cyclic references and infinite looping, implement as :

 
  @Override public String toString() {
    return ModelUtil.toStringAvoidCyclicRefs(this, Product.class, "getId");
  }
 
Here, the usual behavior is overridden for any method in 'this' object which returns a Product : instead of calling Product.toString(), the return value of Product.getId() is used instead.

hashCode()
Example of the simplest style :

  @Override public int hashCode() {
    return ModelUtil.hashFor(getSignificantFields());
  }
  ...
  private String fName;
  private Boolean fIsActive;
  private Object[] getSignificantFields(){
    //any primitive fields can be placed in a wrapper Object
    return new Object[]{fName, fIsActive};
  }
 

Since the Object.equals(java.lang.Object) and Object.hashCode() methods are so closely related, and should always refer to the same fields, defining a private method to return the Object[] of significant fields is highly recommended. Such a method would be called by both equals and hashCode.

If an object is immutable, then the result may be calculated once, and then cached, as a small performance optimization :

  @Override public int hashCode() {
    if ( fHashCode == 0 ) {
      fHashCode = ModelUtil.hashFor(getSignificantFields());
    }
    return fHashCode;
  }
  ...
  private String fName;
  private Boolean fIsActive;
  private int fHashCode;
  private Object[] getSignificantFields(){
    return new Object[]{fName, fIsActive};
  }
 
The most verbose style does not require wrapping primitives in an Object array:
  @Override public int hashCode(){
    int result = ModelUtil.HASH_SEED;
    //collect the contributions of various fields
    result = ModelUtil.hash(result, fPrimitive);
    result = ModelUtil.hash(result, fObject);
    result = ModelUtil.hash(result, fArray);
    return result;
  }
 

equals()
Simplest example, in a class called Visit (this is the recommended style):

  @Override public boolean equals(Object aThat) {
    Boolean result = ModelUtil.quickEquals(this, aThat);
    if ( result == null ){
      Visit that = (Visit) aThat;
      result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
    }
    return result;
  }
  ...
  private final Code fRestaurantCode;
  private final Date fLunchDate;
  private final String fMessage;
  private Object[] getSignificantFields(){
    return new Object[] {fRestaurantCode, fLunchDate, fMessage};
  }
 
Second example, in a class called Member :
  @Override public boolean equals( Object aThat ) {
    if ( this == aThat ) return true;
    if ( !(aThat instanceof Member) ) return false;
    Member that = (Member)aThat;
    return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
  }
  ...
  private final String fName;
  private final Boolean fIsActive;
  private final Code fDisposition;
  private Object[] getSignificantFields(){
    return new Object[]{fName, fIsActive, fDisposition};
  }
 
See note above regarding
getSignificantFields().

More verbose example, in a class called Planet :

 
  @Override public boolean equals(Object aThat){
    if ( this == aThat ) return true;
    if ( !(aThat instanceof Planet) ) return false;
    Planet that = (Planet)aThat;
    return 
      EqualsUtil.areEqual(this.fPossiblyNullObject, that.fPossiblyNullObject) &&
      EqualsUtil.areEqual(this.fCollection, that.fCollection) &&
      EqualsUtil.areEqual(this.fPrimitive, that.fPrimitive) &&
      Arrays.equals(this.fArray, that.fArray); //arrays are different!
  }
 

compareTo()
The
Comparable interface is distinct, since it is not an overridable method of the Object class.

Example use case of using comparePossiblyNull, (where EQUAL takes the value 0) :

  public int compareTo(Movie aThat) {
    if ( this == aThat ) return EQUAL;

    int comparison = ModelUtil.comparePossiblyNull(this.fDateViewed, aThat.fDateViewed, NullsGo.LAST);
    if ( comparison != EQUAL ) return comparison;

    //this field is never null
    comparison = this.fTitle.compareTo(aThat.fTitle);
    if ( comparison != EQUAL ) return comparison;

    comparison = ModelUtil.comparePossiblyNull(this.fRating, aThat.fRating, NullsGo.LAST);
    if ( comparison != EQUAL ) return comparison;

    comparison = ModelUtil.comparePossiblyNull(this.fComment, aThat.fComment, NullsGo.LAST);
    if ( comparison != EQUAL ) return comparison;

    return EQUAL;
  }
 

Author:
Hirondelle Systems, with a contribution by an anonymous user of javapractices.com

Nested Class Summary
static class ModelUtil.NullsGo
          Define hows null items are treated in a comparison.
 
Field Summary
static int HASH_SEED
          Initial seed value for a hashCode.
 
Method Summary
static boolean areEqual(boolean aThis, boolean aThat)
          Equals for boolean fields.
static boolean areEqual(char aThis, char aThat)
          Equals for char fields.
static boolean areEqual(double aThis, double aThat)
          Equals for double fields.
static boolean areEqual(float aThis, float aThat)
          Equals for float fields.
static boolean areEqual(long aThis, long aThat)
          Equals for long fields.
static boolean areEqual(Object aThis, Object aThat)
          Equals for an Object.
static
<T extends Comparable<T>>
int
comparePossiblyNull(T aThis, T aThat, ModelUtil.NullsGo aNullsGo)
          Utility for implementing Comparable.
static boolean equalsFor(Object[] aThisSignificantFields, Object[] aThatSignificantFields)
          Return the result of comparing all significant fields.
static int hash(int aSeed, boolean aBoolean)
          Hash code for boolean primitives.
static int hash(int aSeed, char aChar)
          Hash code for char primitives.
static int hash(int aSeed, double aDouble)
          Hash code for double primitives.
static int hash(int aSeed, float aFloat)
          Hash code for float primitives.
static int hash(int aSeed, int aInt)
          Hash code for int primitives.
static int hash(int aSeed, long aLong)
          Hash code for long primitives.
static int hash(int aSeed, Object aObject)
          Hash code for an Object.
static int hashCodeFor(Object... aFields)
          Return the hash code in a single step, using all significant fields passed in an Object sequence parameter.
static Boolean quickEquals(Object aThis, Object aThat)
          Quick checks for possibly determining equality of two objects.
static String toStringAvoidCyclicRefs(Object aObject, Class aSpecialClass, String aMethodName)
          As in toStringFor(java.lang.Object), but avoid problems with cyclic references.
static String toStringFor(Object aObject)
          Implements an override of Object.toString() (see class comment).
 
Methods inherited from class Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

HASH_SEED

public static final int HASH_SEED
Initial seed value for a hashCode. Contributions from individual fields are 'added' to this initial value. (Using a non-zero value decreases collisons of hashCode values.)

See Also:
Constant Field Values
Method Detail

toStringFor

public static String toStringFor(Object aObject)
Implements an override of Object.toString() (see class comment).

Example output format, for an Rsvp object with 4 fields :

  hirondelle.fish.main.rsvp.Rsvp {
  Response: null
  MemberId: 4
  MemberName: Tom Thumb
  VisitId: 13
  }
   
(There is no indentation since it causes problems when there is nesting.)

The only items which contribute to the result are :

These items are excluded from the result :

Reflection is used to access field values. Items are converted to a String simply by calling their toString method, with the following exceptions :

If the method name follows the pattern 'getXXX', then the word 'get' is removed from the result.

WARNING: If two classes have cyclic references (that is, each has a reference to the other), then infinite looping will result if both call this method! To avoid this problem, use toStringFor for one of the classes, and toStringAvoidCyclicRefs(java.lang.Object, java.lang.Class, java.lang.String) for the other class.

Parameters:
aObject - the object for which a toString() result is required.

toStringAvoidCyclicRefs

public static String toStringAvoidCyclicRefs(Object aObject,
                                             Class aSpecialClass,
                                             String aMethodName)
As in toStringFor(java.lang.Object), but avoid problems with cyclic references.

Cyclic references occur when one Model Object references another, and both Model Objects have their toString() methods implemented with this utility class.

Behaves as in toStringFor(java.lang.Object), with one exception: for methods of aObject that return instances of aSpecialClass, then call aMethodName on such instances, instead of toString().


hashCodeFor

public static final int hashCodeFor(Object... aFields)
Return the hash code in a single step, using all significant fields passed in an Object sequence parameter.

(This is the recommended way of implementing hashCode.)

Each element of aFields must be an Object, or an array containing possibly-null Objects. These items will each contribute to the result. (It is not a requirement to use all fields related to an object.)

If the caller is using a primitive field, then it must be converted to a corresponding wrapper object to be included in aFields. For example, an int field would need conversion to an Integer before being passed to this method.


hash

public static int hash(int aSeed,
                       boolean aBoolean)
Hash code for boolean primitives.


hash

public static int hash(int aSeed,
                       char aChar)
Hash code for char primitives.


hash

public static int hash(int aSeed,
                       int aInt)
Hash code for int primitives.

Note that byte and short are also handled by this method, through implicit conversion.


hash

public static int hash(int aSeed,
                       long aLong)
Hash code for long primitives.


hash

public static int hash(int aSeed,
                       float aFloat)
Hash code for float primitives.


hash

public static int hash(int aSeed,
                       double aDouble)
Hash code for double primitives.


hash

public static int hash(int aSeed,
                       Object aObject)
Hash code for an Object.

aObject is a possibly-null object field, and possibly an array.

Arrays can contain possibly-null objects or primitives; no circular references are permitted.


quickEquals

public static Boolean quickEquals(Object aThis,
                                  Object aThat)
Quick checks for possibly determining equality of two objects.

This method exists to make equals implementations read more legibly, and to avoid multiple return statements.

It cannot be used by itself to fully implement equals. It uses == and instanceof to determine if equality can be found cheaply, without the need to examine field values in detail. It is always paired with some other method (usually equalsFor(Object[], Object[])), as in the following example :

   public boolean equals(Object aThat){
     Boolean result = ModelUtil.quickEquals(this, aThat);
     if ( result == null ){
       //quick checks not sufficient to determine equality,
       //so a full field-by-field check is needed :
       This this = (This) aThat; //will not fail 
       result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
     }
     return result;
   }
   

This method is unusual since it returns a Boolean that takes 3 values : true, false, and null. Here, true and false mean that a simple quick check was able to determine equality. The null case means that the quick checks were not able to determine if the objects are equal or not, and that further field-by-field examination is necessary. The caller must always perform a check-for-null on the return value.


equalsFor

public static boolean equalsFor(Object[] aThisSignificantFields,
                                Object[] aThatSignificantFields)
Return the result of comparing all significant fields.

Both Object[] parameters are the same size. Each includes all fields that have been deemed by the caller to contribute to the equals method. None of those fields are array fields. The order is the same in both arrays, in the sense that the Nth item in each array corresponds to the same underlying field. The caller controls the order in which fields are compared simply through the iteration order of these two arguments.

If a primitive field is significant, then it must be converted to a corresponding wrapper Object by the caller.


areEqual

public static boolean areEqual(boolean aThis,
                               boolean aThat)
Equals for boolean fields.


areEqual

public static boolean areEqual(char aThis,
                               char aThat)
Equals for char fields.


areEqual

public static boolean areEqual(long aThis,
                               long aThat)
Equals for long fields.

Note that byte, short, and int are handled by this method, through implicit conversion.


areEqual

public static boolean areEqual(float aThis,
                               float aThat)
Equals for float fields.


areEqual

public static boolean areEqual(double aThis,
                               double aThat)
Equals for double fields.


areEqual

public static boolean areEqual(Object aThis,
                               Object aThat)
Equals for an Object.

The objects are possibly-null, and possibly an array.

Arrays can contain possibly-null objects or primitives; no circular references are permitted.


comparePossiblyNull

public static <T extends Comparable<T>> int comparePossiblyNull(T aThis,
                                                                T aThat,
                                                                ModelUtil.NullsGo aNullsGo)
Utility for implementing Comparable. See class example for illustration.

The Comparable interface specifies that

   blah.compareTo(null)
   
should throw a NullPointerException. You should follow that guideline. Note that this utility method itself accepts nulls without throwing a NullPointerException. In this way, this method can handle nullable fields just like any other field.

There are special issues for sorting Strings regarding case, Locale, and accented characters.

Parameters:
aThis - an object that implements Comparable
aThat - an object of the same type as aThis
aNullsGo - defines if null items should be placed first or last

Version 4.10.0

Copyright Hirondelle Systems. Published October 19, 2013 - User Guide - All Docs.