![JAR search and dependency download from the Maven repository](/logo.png)
org.intelligentsia.hirondelle.date4j.ModelUtil Maven / Gradle / Ivy
Show all versions of date4j Show documentation
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.intelligentsia.hirondelle.date4j;
import java.lang.reflect.Array;
/**
Collected utilities for overriding {@link Object#toString}, {@link Object#equals},
and {@link Object#hashCode}, and implementing {@link Comparable}.
All Model Objects should override the above {@link Object} methods.
All Model Objects that are being sorted in code should implement {@link 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 {@link Object#equals} and
{@link 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:
@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 {@link Comparable} interface is distinct, since it is not an overridable method of the
{@link 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
@author with a contribution by an anonymous user of javapractices.com
*/
final class ModelUtil {
// TO STRING //
/**
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 :
- the full class name
- all no-argument public methods which return a value
These items are excluded from the result :
- methods defined in {@link Object}
- factory methods which return an object of the native class ("getInstance()" methods)
Reflection is used to access field values. Items are converted to a String simply by calling
their toString method, with the following exceptions :
- for arrays, the {@link Util#getArrayAsString(Object)} is used
- for methods whose name contains the text "password" (case-insensitive),
their return values hard-coded to '****'.
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 {@link #toStringAvoidCyclicRefs} for the other class.
@param aObject the object for which a toString() result is required.
*/
static String toStringFor(Object aObject) {
return ToStringUtil.getText(aObject);
}
/**
As in {@link #toStringFor}, 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 {@link #toStringFor}, with one exception: for methods of aObject that
return instances of aSpecialClass, then call aMethodName on such instances,
instead of toString().
*/
static String toStringAvoidCyclicRefs(Object aObject, Class> aSpecialClass, String aMethodName) {
return ToStringUtil.getTextAvoidCyclicRefs(aObject, aSpecialClass, aMethodName);
}
// HASH CODE //
/**
Return the hash code in a single step, using all significant fields passed in an {@link Object} sequence parameter.
(This is the recommended way of implementing hashCode.)
Each element of aFields must be an {@link 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 {@link Integer} before being passed to this method.
*/
static final int hashCodeFor(Object... aFields){
int result = HASH_SEED;
for(Object field: aFields){
result = hash(result, field);
}
return result;
}
/**
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.)
*/
static final int HASH_SEED = 23;
/** Hash code for boolean primitives. */
static int hash( int aSeed, boolean aBoolean ) {
return firstTerm( aSeed ) + ( aBoolean ? 1 : 0 );
}
/** Hash code for char primitives. */
static int hash( int aSeed, char aChar ) {
return firstTerm( aSeed ) + aChar;
}
/**
Hash code for int primitives.
Note that byte and short are also handled by this method, through implicit conversion.
*/
static int hash( int aSeed , int aInt ) {
return firstTerm( aSeed ) + aInt;
}
/** Hash code for long primitives. */
static int hash( int aSeed , long aLong ) {
return firstTerm(aSeed) + (int)( aLong ^ (aLong >>> 32) );
}
/** Hash code for float primitives. */
static int hash( int aSeed , float aFloat ) {
return hash( aSeed, Float.floatToIntBits(aFloat) );
}
/** Hash code for double primitives. */
static int hash( int aSeed , double aDouble ) {
return hash( aSeed, Double.doubleToLongBits(aDouble) );
}
/**
Hash code for an Object.
aObject is a possibly-null object field, and possibly an array.
If aObject is an array, then each element may be a primitive
or a possibly-null object.
*/
static int hash( int aSeed , Object aObject ) {
int result = aSeed;
if ( aObject == null) {
result = hash(result, 0);
}
else if ( ! isArray(aObject) ) {
result = hash(result, aObject.hashCode());
}
else {
int length = Array.getLength(aObject);
for ( int idx = 0; idx < length; ++idx ) {
Object item = Array.get(aObject, idx);
//recursive call!
result = hash(result, item);
}
}
return result;
}
// EQUALS //
/**
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 {@link #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.
*/
static Boolean quickEquals(Object aThis, Object aThat){
Boolean result = null;
if ( aThis == aThat ) {
result = Boolean.TRUE;
}
else {
Class> thisClass = aThis.getClass();
if ( ! thisClass.isInstance(aThat) ) {
result = Boolean.FALSE;
}
}
return result;
}
/**
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.
*/
static boolean equalsFor(Object[] aThisSignificantFields, Object[] aThatSignificantFields){
//(varargs can be used for final arg only)
if (aThisSignificantFields.length != aThatSignificantFields.length) {
throw new IllegalArgumentException(
"Array lengths do not match. 'This' length is " + aThisSignificantFields.length +
", while 'That' length is " + aThatSignificantFields.length + "."
);
}
boolean result = true;
for(int idx=0; idx < aThisSignificantFields.length; ++idx){
if ( ! areEqual(aThisSignificantFields[idx], aThatSignificantFields[idx]) ){
result = false;
break;
}
}
return result;
}
/** Equals for boolean fields. */
static boolean areEqual(boolean aThis, boolean aThat){
return aThis == aThat;
}
/** Equals for char fields. */
static boolean areEqual(char aThis, char aThat){
return aThis == aThat;
}
/**
Equals for long fields.
Note that byte, short, and int are handled by this method, through
implicit conversion.
*/
static boolean areEqual(long aThis, long aThat){
return aThis == aThat;
}
/** Equals for float fields. */
static boolean areEqual(float aThis, float aThat){
return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat);
}
/** Equals for double fields. */
static boolean areEqual(double aThis, double aThat){
return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat);
}
/**
Equals for possibly-null object field.
Does not include arrays. (This restriction will likely be removed in a future version.)
*/
static boolean areEqual(Object aThis, Object aThat){
if (isArray(aThis) || isArray(aThat)){
throw new IllegalArgumentException("This method does not currently support arrays.");
}
return aThis == null ? aThat == null : aThis.equals(aThat);
}
//Comparable
/**
Define hows null items are treated in a comparison. Controls if null
items appear first or last.
See comparePossiblyNull.
*/
enum NullsGo {FIRST,LAST}
/**
Utility for implementing {@link Comparable}. See class example
for illustration.
The {@link Comparable} interface specifies that
blah.compareTo(null)
should throw a {@link NullPointerException}. You should follow that
guideline. Note that this utility method itself
accepts nulls without throwing a {@link NullPointerException}.
In this way, this method can handle nullable fields just like any other field.
There are
special issues
for sorting {@link String}s regarding case, {@link java.util.Locale},
and accented characters.
@param aThis an object that implements {@link Comparable}
@param aThat an object of the same type as aThis
@param aNullsGo defines if null items should be placed first or last
*/
static > int comparePossiblyNull(T aThis, T aThat, NullsGo aNullsGo){
int EQUAL = 0;
int BEFORE = -1;
int AFTER = 1;
int result = EQUAL;
if(aThis != null && aThat != null){
result = aThis.compareTo(aThat);
}
else {
//at least one reference is null - special handling
if(aThis == null && aThat == null) {
//not distinguishable, so treat as equal
}
else if(aThis == null && aThat != null) {
result = BEFORE;
}
else if( aThis != null && aThat == null) {
result = AFTER;
}
if(NullsGo.LAST == aNullsGo){
result = (-1) * result;
}
}
return result;
}
// PRIVATE //
private ModelUtil(){
//prevent object construction
}
private static final int fODD_PRIME_NUMBER = 37;
private static int firstTerm( int aSeed ){
return fODD_PRIME_NUMBER * aSeed;
}
private static boolean isArray(Object aObject){
return aObject != null && aObject.getClass().isArray();
}
}